MEF is a component of .NET Framework 4.0, to create lightweight, extensible applications. It avoids hard dependencies and lets the application developer discover and use extensions without any configuration required.
Why MEF?
Imagine a situation where an application is using several smaller components. The application is responsible for creating and running those components.
One possible solution could be to include all the components as source code in the application. However, you cannot add new components without modifying the source code.
Another solution could be to provide an interface for decoupling between applications and components. So the component can implement the interface and interact with the application. However, this approach has a drawback. As the application cannot discover components by itself, it must be explicitly told which components are required and loaded.
Here MEF comes into the picture. MEF provides a way to discover components via composition. A MEF component specifies both its dependencies (known as imports) and what capabilities (known as exports) it makes available.
Let's understand it with the help of an example. Suppose you are making a simple calculator application that currently supports addition and subtraction.
Creating a composition container and catalog
The composition container keeps track of which components are available for composition and what are their dependencies. It provides a way by which an application can get the instance of components to be composed. We need to include System.ComponentModel.Composition in reference.
// An aggregate catalog that combines multiple catalogs
var catalog = new AggregateCatalog();
// Adds all the parts found in the same assembly as the current class
catalog.Catalogs.Add(new AssemblyCatalog(typeof(this).Assembly));
// If parts are placed at some other location then adds that directory path
// catalog.Catalogs.Add(new DirectoryCatalog(componentsDirectoryPath));
// Create the CompositionContainer with the parts in the catalog
CompositionContainer _container = new CompositionContainer(catalog);
// Fill the imports of this object
try
{
this._container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
Console.WriteLine(compositionException.ToString());
}
catalog.Catalogs.Add(new AssemblyCatalog(typeof(this).Assembly));
This adds the components from the current assembly. To add the components from some specified folder location, add a directory catalog.
catalog.Catalogs.Add(new DirectoryCatalog(componentssDirectoryPath));
Where componentsDirectoryPath is the path of the directory where components can be found.
Import and Exports
Define an interface like it.
[Import(typeof(IOperation))]
public interface IOperation
{
string Operate(int leftOperand, int rightOperand);
}
This interface has an attribute Import. This ImportAttribute defines that the type IOperation needs to be imported. So we implement the IOperation class, and above the class, we use ExportAttribute indicating that it has the capabilities of IOperation. Also, it contains an ExportMetadata attribute indicating that depending on the metadata Symbol the operation is performed.
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add : IOperation
{
// Implementation of IOperation
}
Now we will do lazy initialization for getting objects like this:[ImportMany] IEnumerable<Lazy<IOperation, IMetadata>> operations; Lazy initialization is used so that only operations that are needed are initialized. Based on the metadata it initializes what operation is to be performed. It contains ImportMany attributes because IOperation can be filled by many exports like add, subtract, etc. Lazy InitializationAs we see we declare an IEnumerable for lazy initialization. Now how does it work? When we run the application, it initializes the catalog and creates a container. Based on the Import attribute it finds the components which can be filled for it. Here we have only one class Add to fill it. So operations will contain only a single Lazy<IOperation, IOperationData> object, and that object will be initialized when it is accessed the first time. So based on the operation we will call the Operate function.
foreach (Lazy<IOperation, IOperationData> i in operations)
{
if (i.Metadata.Symbol.Equals(operation))
result = i.Value.Operate(left, right).ToString();
}
I have a sample project to illustrate it. It contains 2 operations Add and Subtract in the assembly and Multiplication in a separate DLL, which can be found in the "<current executing assembly path>\Extensions" folder.