MEF or Managed Extensibility Framework and Lazy<T> – Being Lazy with MEF, Custom Export Attributes etc


ANOOP MADHUSUDANAN

Vote on HN

Overview

The objective of this post is to brief how you can leverage the lazy initialization support available in MEF. We’ll examine MEF and Lazy, and then we’ll see how to use them together.

 

Preface About MEF

I hope you are already using Managed Extensibility Framework to build beautiful software. If you are not yet there, that is a crime, and I highly recommend you to read my introductory post on MEF. Let us start with another very basic MEF example. Let us get back our Zoo.

image 

We are composing our Zoo with a couple of Animals here.

    //Abstract animal interface
    interface IAnimal { void Eat(); }

    //Concrete animal 1
    [Export(typeof(IAnimal))]
    class Lion : IAnimal
    {
        public Lion() { Console.WriteLine("Grr.. Lion got created"); }
        public void Eat() { Console.WriteLine("Grr.. Lion eating meat"); }
    }

    //Concrete animal 2
    [Export(typeof(IAnimal))]
    class Rabbit : IAnimal
    {
        public Rabbit() { Console.WriteLine("Crrr.. Rabbit got created"); }
        public void Eat() { Console.WriteLine("Crrr.. Rabbit eating carrot"); }
    }
    
    //Our Zoo. MEF will inject animals to this zoo later, at the time of composition
    class Zoo
    {
        [ImportMany(typeof(IAnimal))]
        public IEnumerable<IAnimal> Animals { get; set; }
    }

    //Let us construct our zoo and animals
    class Program
    {
        static void Main(string[] args)
        {
            //Let us create a catalog and a container
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);
            var container = new CompositionContainer(catalog);

            //Compose the zoo.
            var zoo = new Zoo();
            container.ComposeParts(zoo);

            //Let's feed our animals
            foreach (var animal in zoo.Animals)
                animal.Eat();

        }
    }

As you remember, a catalog is a sack full of types (cookies) to export. The container will fetch these exported types from the catalog, to instantiate and import them where ever applicable - based on the matching contracts specified in the Export and Import attributes. (Note to self - Shh!! don’t mention anything about ExportProvider and ExportDefinition now). In this case, we are using a type as the contract (IAnimal) for our Export and Import. And if you run that app, you’ll see.. hm.. a cute black screen. More importantly, you’ll see that MEF has created an instance of Lion and Rabbit at the time of composition – i.e, when we call container.ComposeParts. See the messages we are writing from the constructor.

image 

Note – If you want to be more realistic or if you hate animals in general, my advice is to think about your Business class instead of the Zoo, and think about some validation rules instead of those Animals. So that, you can plug in new validation rules with out affecting your entire system.

Preface About Lazy<T>

Oh yea, though MEF is seductive, let us stop thinking about that for a moment. Let us be a bit Lazy. You might have already heard about the Lazy<T> class in .NET 4.0. Lazy class allow you to support for lazy initialization. I.e, you can use Lazy<T> to defer the creation of a large or resource-intensive object, till you really need that.

Here is a quick example.

 

            //Let us be lazy about creating a lion
            Lazy<Lion> lazyLion = new Lazy<Lion>();

            //Do something else..

            //Actual object will be created here
            var lion = lazyLion.Value;
   

The interesting point to note is, you can defer the creation of your lion, till you really need it in your flow, probably inside an if block or so. Another interesting aspect of Lazy<T> is, you can specify your own factory method to create your object, via the Lazy<T> constructor. See this example.

            //Let us be lazy about creating our animal
            Lazy<IAnimal> lazyAnimal = new Lazy<IAnimal>(()=>new Lion());

            //Actual object will be created here
            var animal = lazyAnimal.Value;

Being Lazy with MEF

Now, let us see how to combine MEF and Lazy together. All right, you don’t really need to do anything special there – MEF already has some great support for Lazy. MEF can wire up your exports directly to a Lazy at the time of import. Now, let us see how to do that. We just need to make two modifications to our zoo example. Time for a KG Excercise - Spot the difference of this code example with the very first example.

 //Our zoo
    class Zoo
    {
        //** MODIFICATION 1 -You need to directly import to Lazy<IAnimal>
        [ImportMany(typeof(IAnimal))]
        public IEnumerable<Lazy<IAnimal>> Animals { get; set; }
    }

    //Abstract animal interface
    interface IAnimal { void Eat(); }

    //Concrete animal 1
    [Export(typeof(IAnimal))]
    class Lion : IAnimal
    {
        public Lion() { Console.WriteLine("Grr.. Lion got created"); }
        public void Eat() { Console.WriteLine("Grr.. Lion eating meat"); }
    }

    //Concrete animal 2
    [Export(typeof(IAnimal))]
    class Rabbit : IAnimal
    {
        public Rabbit() { Console.WriteLine("Crrr.. Rabbit got created"); }
        public void Eat() { Console.WriteLine("Crrr.. Rabbit eating carrot"); }
    }



    //Let us construct our zoo and animals
    class Program
    {
        static void Main(string[] args)
        {
            //Let us create a catalog and a container
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);
            var container = new CompositionContainer(catalog);

            //Compose the zoo.
            var zoo = new Zoo();
            container.ComposeParts(zoo);

            //** MODIFICATION 2 - An instance will be created only when you access it
            //Use the Value property of the Lazy object to initialize and access the actual value
            foreach (var animal in zoo.Animals) 
                animal.Value.Eat();
        }
    }

And now, to understand how this changed the flow, let us run the application.

image

You’ll see that our animals are getting created only when we consume them – and not at the time of composing the zoo as we did earlier. 

Dealing with Metadata when MEF goes Lazy

MEF allows you to export Metadata along with the types you export. For example, assume that you want to specify whether your exported animals eat meat or not, so that some one can decide what to give them as food. One way is to use the ExportMetaData attribute with your exported types, as shown below.

   //Concrete animal 1
    [Export(typeof(IAnimal))]
    [ExportMetadata("EatMeat",true)]
    class Lion : IAnimal
    {
        public Lion() { Console.WriteLine("Grr.. Lion got created"); }
        public void Eat() { Console.WriteLine("Grr.. Lion eating meat"); }
    }

    //Concrete animal 2
    [Export(typeof(IAnimal))]
    [ExportMetadata("EatMeat", false)]
    class Rabbit : IAnimal
    {
        public Rabbit() { Console.WriteLine("Crrr.. Rabbit got created"); }
        public void Eat() { Console.WriteLine("Crrr.. Rabbit eating carrot"); }
    }
That looks a bit clumsy, because of those strings. Right? A better way is to create your own custom Export attribute that includes the metadata information as well. That is pretty simple. The code below is equivalent to what we have just done above. You see that we are creating a custom export attribute named ExportAnimal.
       //Our custom metadata attribute to exp animals
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    class ExportAnimal : ExportAttribute
    {
        //Pass the contract type to the base
        public ExportAnimal() : base(typeof(IAnimal)) { }
        //Additional metadata info
        public bool EatMeat { get; set; }
    }

    //Concrete animal 1
    [ExportAnimal(EatMeat=true)]
    class Lion : IAnimal
    {
        public Lion() { Console.WriteLine("Grr.. Lion got created"); }
        public void Eat() { Console.WriteLine("Grr.. Lion eating meat"); }
    }

    //Concrete animal 2
    [ExportAnimal(EatMeat = false)]
    class Rabbit : IAnimal
    {
        public Rabbit() { Console.WriteLine("Crrr.. Rabbit got created"); }
        public void Eat() { Console.WriteLine("Crrr.. Rabbit eating carrot"); }
    }
Now let us come to the real question. How to import the metadata information in a Lazy way? Fortunately, MEF has an overload of Lazy, Lazy that supports importing Metadata information. So, all we need to do is create a metadata import interface that matches our export definition, and use it. Like this.
   //An interface to import animal metadata
   //This should match the metadata we've in our custom export definition
    public interface IAnimalMetadata
    {
        bool EatMeat { get; }
    }

    //Our zoo
    class Zoo
    {
        //Directly import to Lazy<IAnimal,IAnimalMetadata>
        [ImportMany(typeof(IAnimal))]
        public IEnumerable<Lazy<IAnimal, IAnimalMetadata>> Animals { get; set; }
    }

So, here is the final piece that includes the above fragments. You can see that we are exporting our animals using a custom export attribute, and importing them in a lazy way along with the metadata. Then, Have a look at the Main method. You'll find that we are using the metadata to feed only the animals that eat meat.

In other words, the Lazy implementation provides you the luxury of initializing only the animals that you want to feed, at the time of feeding them - and not all the animals.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;

namespace MefLazy
{

    #region Contracts

    //Abstract animal interface
    interface IAnimal { void Eat(); }

    #endregion

    #region Export Related

    //Our custom metadata attribute to exp animals
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    class ExportAnimal : ExportAttribute
    {
        //Pass the contract type to the base
        public ExportAnimal() : base(typeof(IAnimal)) { }
        //Additional metadata info
        public bool EatMeat { get; set; }
    }

    //Concrete animal 1
    [ExportAnimal(EatMeat = true)]
    class Lion : IAnimal
    {
        public Lion() { Console.WriteLine("Grr.. Lion got created"); }
        public void Eat() { Console.WriteLine("Grr.. Lion eating meat"); }
    }

    //Concrete animal 2
    [ExportAnimal(EatMeat = false)]
    class Rabbit : IAnimal
    {
        public Rabbit() { Console.WriteLine("Crrr.. Rabbit got created"); }
        public void Eat() { Console.WriteLine("Crrr.. Rabbit eating carrot"); }
    }

    #endregion

    #region Import Related

    //An interface to import animal metadata
    public interface IAnimalMetadata
    {
        bool EatMeat { get; }
    }

    //Our zoo
    class Zoo
    {
        //Directly import to Lazy<IAnimal,IAnimalMetadata>
        [ImportMany(typeof(IAnimal))]
        public IEnumerable<Lazy<IAnimal, IAnimalMetadata>> Animals { get; set; }
    }

    #endregion

    #region Main

    //Let us construct our zoo and animals
    class Program
    {
        static void Main(string[] args)
        {
            //Let us create a catalog and a container
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);
            var container = new CompositionContainer(catalog);

            //Compose the zoo.
            var zoo = new Zoo();
            container.ComposeParts(zoo);

            //An instance will be created only when you access it
            //Use the Value property of the Lazy object to initialize and access the actual value
            //Let us feed only animals eating meat
            foreach (var animal in zoo.Animals) 
                if (animal.Metadata.EatMeat)
                    animal.Value.Eat();
        }
    }

    #endregion
}

Conclusion

I encourage you to apply these concepts to more practical scenarios. Like, instead of a Zoo, think about a RuleProcessor that runs a set of rules on a business object - and instead of an Animal, think about a Rule that can validate or process the business object. I’ll give some more practical examples pretty soon :). For now, the objective was to introduce Lazy concepts and MEF, and I hope you enjoyed the read. And you can download the final version of the code from the above link. Keep in touch, Follow me on twitter

Also, read my last post 4 .NET 4.0 Libraries You should know about

Happy Coding!!

© 2012. All Rights Reserved. Amazedsaint.com