Anyone who has played with or used MEF has probably written code in their App.xaml.cs file that looks something like this:
catalog.Catalogs.Add(new DirectoryCatalog(”.”));
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
_container = new CompositionContainer(catalog);
try
{
_container.ComposeParts(this);
var a = this.VoidObjects;
}
catch (CompositionException compositionException)
{
MessageBox.Show(compositionException.ToString());
return false;
}
While I see code like that shown above in lots of applications that use MEF it always brings two questions to my mind:
1.) Why isn’t the composition container disposed of after composition is complete?
2.) What could we do if we made the composition container available to more components throughout an applications lifecycle instead of just at start-up?
The answer to (1.) remains, “I don’t know”. When MEF composition is baked so directly into the OnStartUp method there is no way to access it from anywhere else in the application so you might as well dispose of it right after composition is complete since you aren’t going to (to be able to) use it. So why wait until application shutdown to dispose of it? I have no idea.
Having said all that I would like to move onto the second question which inspires a much more interesting and useful discussion because there are lots of advantages to being able to trigger MEF composition from (almost)anywhere in the application. Let’s consider a particular concrete example.Let’s say that you have a menu option that should create and display a new instance of a plug-in every time it’s clicked. Then we can simply use a factory pattern as previously discussed here and everything is great, right? Well - yes and no. Everything is great until an additional dependency is created in our factory after we have finished writing the factory method. Unless you remember to append the factory method of your class to give the new factory instance a reference to the new dependency you will eventually run into a null reference exception when the instance of the class created by the factory tries to call a method on that dependency.
The factory pattern as described above is brittle yet is used by lots of people and has cost at least two teams I have worked with entire DAYS to find and fix. I call it brittle because every time a new dependency is added our memories (and/or knowledge) are not always going to be good enough to remember, “Oh yeah, now I need to go change the factory method so that my instances all come out right”. Furthermore, our class’s new dependencies might have additional nested dependencies of its own that we would have to know how to new-up. That’s not even the worst of it. Now, whenever the dependencies of our dependencies changes we will probably need to go back and change our factory method even more. DI without MEF can get very messy very quickly if we are not careful.
Instead of worrying about all of that we’d like to be able to add a MEF import statement and have the factory method continue to work without relying on our (at least my) shotty memories about what other things need to be changed. In-fact, we’d like there to be no other things that need to be changed when adding a new import, right?
Thanks to MEF, we can do exactly that if we can de-couple composition from the application start-up. My approach has been to move the composition code from the application start-up to its own CompositionService class in the SoapBox.Core.Contracts project. I then create a CompositionServiceLocator class (also in the SoapBox.Core.Contracts project) that can be used by any plug-in to gain read-only access to the application-level composition container and compose any object at any time.
More concretely, I moved the typical composition code that lives directly in-side the OnStartup method to its own class that looks like this:
{
private static object[] _compositionRoot;
private static CompositionContainer _container;
public bool Compose(params object[] o)
{
if (_compositionRoot == null)
{
_compositionRoot = o;
}
if (_container == null)
{
var catalog = GetCompositionCatalog();
_container = new CompositionContainer(catalog);
}
try
{
_container.ComposeParts(o);
}
catch (CompositionException compositionException)
{
Console.WriteLine(compositionException.Message);
return false;
}
return true;
}
private ComposablePartCatalog GetCompositionCatalog()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(”.”));
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetEntryAssembly()));
return catalog;
}
public void DisposeOfContainer()
{
_container.Dispose();
}
}
I then created this seperate class through which other application components can gain access to the composition class.
{
static ICompositionService _defaultInstance = null;
public static ICompositionService DefaultInstance
{
get
{
if (_defaultInstance == null)
{
_defaultInstance = new DesktopCompositionService();
}
return _defaultInstance;
}
}
}
Let’s see how this simple change of moving the composition code from the app.xaml.cs file into its’ own more accessible class has improve the plight of our factory method. Before, it may have looked like this (or worse):
{
var newWidget = new Widget();
newWidget.Log = this.Log;
newWidget.Dal = this.Dal;
newWidget.MiscDependency =
this.MiscDependency.CreateNewInstance(new DependencyOfOtherDependency());
//.
//.
//.
//plus maybe more stuff that is brittle
//plus all the configuration once everything is connected
//and maybe not everything should be a shared instance.
//ect.
//.
//.
//.
return newWidget;
}
and as we discussed above, it was brittle, hard to maintain, and error-prone. Now, with our new CompositionService and CompositionServiceLocator our factory method simply looks like this:
{
var newWidget = new Widget();
CompositionServiceLocator.DefaultInstance.Compose(newWidget);
return newWidget;
}
There, isn’t that better? Now, we have effectively off-loaded all of the work done by our factory method to a single-line call to the CompositionService which in-turn makes MEF do all the “factory-ing” for us.
This approach effectively changes a factory method into a composition of an object. Now this approach has worked nicely in the custom tailored case shown above but composition is typically only part of what a true factory does. Though there are other steps we can take to get MEF to help us with the configuration aspect of a factory method I think we have already come a long way with a minimal amount of effort.
-
celebrateswe liked this
-
letti-mathews reblogged this from beachfrontcoding
-
beachfrontcoding posted this