MEF 2.0 - mini series: part 6 (Composition scoping and lifetime management)
MEF 2.0 - mini series: part 6
(Composition scoping and lifetime management)
this is the 6th post in the MEF 2.0 mini series.
you can see the following TOC for other posts in this series.
in this post I will cover a new concept of scoping and part lifetime management, which is a great improvement over MEF 1.

MEF 1 was coming with a fairly naïve lifetime management.
part's lifetime could be either shared or non-shared (you could also apply 'any' but eventually 'any' will be created as shared or non-shared).
shared is a singleton instantiation, while non-shared will create a new instance each time.
MEF 1's instantiation model doesn't support a complex scenario where some dependency's lifetime should be dictate by the lifetime of other unites.
you can conceder a UI window that is having plug-ins that should be dispose while the UI window is closing.
for example consider you have the following components (parts) dependency flow:

the application can have multiple processes.
each process is having some plugins, but each view should have different instantiation of the plugins.
until now you can argue that a non-shared instantiation will do the job.
the next dependency level (DAL) should be shared under the boundary of a process but shouldn't be shared across processes.
this one can neither handle by the share nor by non-shared instantiation.
single instance of DAL should be created under each view scope.
MEF 2 added a scoped lifetime management.
I will use the attribute based model for this sample, but it will work in the same way in the fluent model.
Code Snippet
- [Export]
- public class Application
- {
- [Import]
- private ExportFactory<ProcessA> ProcAFactory { get; set;}
- [Import]
- private ExportFactory<ProcessB> ProcBFactory { get; set;}
- }
-
- [Export]
- public class ProcessA
- {
- [Import]
- public PluginA PlugA { get; private set; }
- [Import]
- public PluginB PlugB { get; private set; }
- }
-
- [Export]
- public class ProcessB
- {
- [Import]
- public PluginA PlugA { get; private set; }
- [Import]
- public PluginB PlugB { get; private set; }
- }
-
- [Export]
- public class PluginA
- {
- [Import]
- public DAL Dal { get; private set; }
- }
-
- [Export]
- public class PluginB
- {
- [Import]
- public DAL Dal { get; private set; }
- }
-
- [Export]
- public class DAL
- {
- }
at line 4-7 you can see a new type of importing target called ExportFactory<T> this type will initialize the scope.
It is having a CreateExport method that return a ExportLifetimeContext<T> can control the part life time.
the full implementation of the Application class is:
Code Snippet
- [Export]
- public class Application
- {
- [Import]
- private ExportFactory<ProcessA> ProcAFactory { get; set;}
- [Import]
- private ExportFactory<ProcessB> ProcBFactory { get; set;}
-
- public void WriteLayoutA()
- {
- using (ExportLifetimeContext<ProcessA> lifeOfA = ProcAFactory.CreateExport())
- {
- ProcessA a = lifeOfA.Value;
- Console.WriteLine("Proc A");
- Console.WriteLine("\tPlug A: {0}", a.PlugA.GetHashCode());
- Console.WriteLine("\t\tDal: {0}", a.PlugA.Dal.GetHashCode());
- Console.WriteLine("\tPlug B: {0}", a.PlugB.GetHashCode());
- Console.WriteLine("\t\tDal: {0}", a.PlugB.Dal.GetHashCode());
- }
- }
-
- public void WriteLayoutB()
- {
- using (ExportLifetimeContext<ProcessB> lifeOfB = ProcBFactory.CreateExport())
- {
- ProcessBb = lifeOfB.Value;
- Console.WriteLine("Proc B");
- Console.WriteLine("\tPlug A: {0}", b.PlugA.GetHashCode());
- Console.WriteLine("\t\tDal: {0}", b.PlugA.Dal.GetHashCode());
- Console.WriteLine("\tPlug B: {0}", b.PlugB.GetHashCode());
- Console.WriteLine("\t\tDal: {0}", b.PlugB.Dal.GetHashCode());
- }
- }
- }
you can see the usage of ExportFactory<T> at lines 11 and 24.
having a lifetime handled part is the first step for the scoping.
now we can define which part will be managed by the scope.
for each scope we except to find a single instantiation of each scoped part.
as you can see in the diagram below, the Plugins and the DAL should be instantiate once for each scope.

in order to define what's goes within the scope we should use the CompositionScopeDefinition as shown in the following code snippet:
Code Snippet
- static void Main(string[] args)
- {
- var scopeDependentCatalog = new TypeCatalog(
- typeof(ProcessA),
- typeof(ProcessB),
- typeof(PluginA),
- typeof(PluginB),
- typeof(DAL));
- var scopeDefDependent = new CompositionScopeDefinition(scopeDependentCatalog, null);
-
- var appCatalog = new TypeCatalog(typeof(Application));
- var scopeDefRoot = new CompositionScopeDefinition(appCatalog, new[] { scopeDefDependent });
-
-
- var container = new CompositionContainer(scopeDefRoot);
-
- var app = container.GetExportedValue<Application>();
-
- app.WriteLayoutA();
- Console.WriteLine("------------------------------------");
- app.WriteLayoutB();
- }
you can see the scope dependent definition at line 9
and the hierarchic between the scope and the application at line 12.
as you may notice it is possible to have deeper hierarchic, but this will be shown in future posts.
the output is:

remember that we was printing the part's hash code.
you can easily see that each process is having different plugin and DAL's instantiations, but even those the DAL was consumed by both plugins it has a single instantiation per scope.
Summary
scoping is a very powerful instantiation model which can solve some of the real-life scenario which wasn't fall into the shared or non-shared models.
in future post I will present a deeper hierarchic and the catalog filtering capability.