Effective MEF (Managed Extensibility Framework)4 Comments
I’ve been working heavily with Managed Extensibility Framework (MEF) as part of Prism v4. Through this effort (and by regularly pestering Glenn Block with questions), I’ve compiled some guidelines that I find useful.
I’ve based my guidelines on a style I first saw in ‘Effective C++’ by Scott Meyers. I like his use of the word “judiciously”. :-)
A couple of to know before we get started:
This post isn’t a getting started guide for MEF, go to the MEF codeplex site instead.
If you use Prism, you may notice that the Prism library code violates almost every guideline I have listed below. Prism is “container agnostic” and there are some additional complexities and compromises in order to be able to support multiple dependency injection containers. I guess Prism is the exception that proves the rule. Working to make Prism support MEF certainly made me familiar with MEF in a hurry.
#1 Prefer new() within assemblies
Ultimately, MEF uses reflection to discover attributed types. It also tracks containers, catalogs, and exports; and must walk the tree of imports to satisfy them. Overuse of MEF complicates design and slows performance.
The new() operator is the right choice for strong cohesion. If you still need logic for which types to create, use design patterns such as the abstract factory pattern.
Clarification: If you have a Customer class that uses an AccountId class where no one will ever replace the implementation of AccountId, then just use new(). There’s no need to use dependency injection here.
#2 Prefer a mocking framework for unit testing
Yes, MEF can make unit testing easier by allowing run-time replacement. Your program pays the cost of MEF even when you are not running tests. Mocking frameworks can inject mock implementations only during testing, leaving your code optimal for the real world.
#3 Prefer MEF’s declarative attributes to imperative programming
MEF has a large collection of classes and rich services. With the exception of setting up the CompositionContainer and some advanced scenarios, you should avoid calling them. You won’t get the benefits of static analysis tools, you’ll spend a lot of time to understand which to use when, and your code will be significantly more complex to manage.
MEF’s declarative attributes allow you to build your classes much like you do today. When you ask the composition container for an instance of your class, MEF handles the walking of the exports to satisfy the imports you need.
#4 Prefer Export interfaces (avoid exporting concrete types)
In MEF an export is identified through a contract name. When just [Export] is specified, the contract name is the namespace qualified type name. Exporting and importing concrete types reduces MEF to a slower version of new().
The whole point of managed extensibility is being able to discover and replace implementation at run time, so always prefer to define and export an interface. As well, moving your interfaces into a separate component goes a long way to being able to quickly change an assembly reference or deploy a different DLL to replace dependencies at run time.
#5 Use strongly-typed metadata to prevent unnecessary instantiation.
Although MEF provides name/value pair metadata, its real power comes with defining a strongly-typed export attribute. This way you get strongly-typed metadata that can be used before the type is instantiated. It also allows [ImportMany] with Lazy<T, M> to filter to only the types that have that metadata.
If you call GetType() on a Lazy<T>.Value it is very easy to end up instantiating the type before you need it. However, you may sometimes need to know the concrete type of the export. By putting the concrete type name in the export metadata, you can inspect an export’s type without instantiating it.
#6 Use Lazy<T> when importing types
Lazy<T> has the same annoying value de-reference as null-able types, but is well worth it not only instantiate a type with someone actually needs it.
Using Lazy<T> during imports provides great benefits:
- If the type isn’t used at run-time, it is never instantiated and its imports are not satisfied. This improves performance and decreases the memory footprint.
- Import metadata can be used to determine what to do with the type without instantiating it.
- Lazy<T, M> (where M stands for metadata) can be used to filter out types that don’t have certain attributes.
- The longer a program waits before instantiating a type, the more likely the imports it requires can be satisfied. This is especially true to Silverlight programs broken multiple XAP files that download in the background.
#7 Judiciously restrict recomposition
MEF provides two very attractive ways to restrict recomposition: The [ImportingConstructor] attribute and setting ‘AllowRecomposition=false’ when using the [Import] attribute.
It often feels like a good design choice to use these because they help ensure a class is initialized properly. However, they have a very high impact to composition:
When you restrict recomposition of type ‘B’ within type ‘A’, recomposing ‘B’ isn’t just restricted within ‘A’ – you can no longer recompose ‘B’ within that container. This is a core feature and tenant of MEF called “stable composition” – if the recomposition requirements cannot be satisfied, composition is rejected and the container is unchanged.
#8 Allow for late composition when possible
The [Import] and even [ImportingConstructor] attributes have an option you can set to ‘AllowDefault=true’. This allows the import to be set to Default(T) (usually null for reference types) if the import cannot be satisfied.
If your type can be written such that an import is not required immediately, then setting ‘AllowDefault=true’ can make your class much more forgiving when an import is satisfied at a later time during recomposition. Of course, you will need to add proper null checking before trying to use that import.
Note: Make sure when implementing IPartImportsSatisfiedNotification.OnImportsSatisfied that you code it to be called multiple times (as recomposition can occur multiple times).
#9 Don’t register the CompositionContainer with itself
Many dependency injection containers register themselves. It is tempting to do the same with MEF, but if you use the declarative attributes, you shouldn’t need access to the container.
Registering the composition container with itself causes some circular references that make it harder for the container to be disposed correctly and may artificially extend the lifetime of the part exports in the catalog. MEF’s container holds exports, and singleton exports hold references to types that have been instantiated.
If you do need access to the container, use a singleton pattern. If you are using Silverlight, reference System.ComponentModel.Composition.Initialization and use the CompositionInitializer to bootstrap MEF or get imports satisfied on a newly constructed type.
#10 Learn to debug composition errors
I will always prefer compile-time errors to run-time errors (a la Scott Meyers). If you use MEF, you are making an important trade-off: you dramatically increasing the chance of run-time errors to gain discovery and extensibility.
Using MEF can entail some deeply frustrating debug sessions when composition mysteriously fails. MEF is run-time extensibility system and is lacking in static analysis tools. Learning a few techniques can get to the root cause of the composition error quicker.
- Implement IPartImportsSatisfiedNotification on your type to be able to set breakpoints during the composition process.
- Slow down and read the composition exception detail carefully. It looks like a bunch of repetitive cascading errors –and it is – but don’t ignore it. The cause of the composition error is often right in front of you.
- With the .NET 4.0 RTM, Silverlight classes cannot compose on private fields. You have to mark them ‘public’ to have imports work successfully.
- Make sure to set ‘CopyLocal=false’ for shared assembly references. ComposablePart has no notion of identity and if an assembly exports a singleton and then the DownloadCatalog or DirectoryCatalog import that DLL again composition will fail. This is especially important for Silverlight applications with multiple XAPs.
- Build your application to recover from composition exceptions. MEF provides stable composition so that the container is still OK to use because composition is rejected when imports can’t be properly satisfied.
- Follow the KISS principle. If your types have so many dependencies that you can’t keep them straight, the problem may be your design. Visual Studio has some nice analysis tools for cyclic complexity and maintainability indices where you can check how well your code is re-factored.
- Bad case: When things get tough, build a skeleton sample application that contains the types in the composition order you want, and debug it piecemeal there.
- Worst case: Unfortunately, sometimes I have to put Debug.WriteLine in the constructors of types, or start replacing [Import] with new().
I really like MEF and have gotten pretty good at using it “judiciously”. There are some incredibly clever things you can do with MEF and it can seriously change your thinking on solving some problems. MEF is still a baby (although a darn cute one), and as it matures I think it will be an incredible tool that will have radically altered how programs are written and how they can adapt at run-time.
Give MEF a try!
Leave a comment
Your email address will not be published.