Testing and Debugging MEF, avoiding misconceptions - Part 3
Testing and Debugging MEF, avoiding misconceptions - Part 3
this is the 3rd post of this series and it will
discuss common misconceptions which may lead unexpected behavior and long
debugging nights.
Initialization issues
the first misconception occurs when developer are trying to access
imported property at construction time.
NullReferenceException
- [Export]
- public class MyPlugin
- {
- public MyPlugin()
- { // the logger import doesn't satisfied yet
- // the next line will result with NullReferenceException
- Logger.Write("MyPlugin initialized");
- }
-
- [Import]
- public ILogger Logger { get; set; }
- }
MyPlugin instantiation occur when the MEF container composed,
the composition process will create an instance and only then it will try
to satisfy its imports. which mean that at the construction time the imports does not yet satisfied.
the above code will result with NullReferenceException.
What can we do about it?
we can have a few solutions solving this issue:
- we can use ImportingConstructor (but we should use it carefully because
cross import constructor may lead to deadlock and rejection).
ImportingConstructor
- [Export]
- public class MyPlugin
- {
- // cross import constructor can lead to deadlock an rejection
- [ImportingConstructor]
- public MyPlugin(ILogger logger)
- {
- logger.Write("MyPlugin initialized");
- }
- }
this way the logger will be instantiate before the instantiation of MyPlugin.
- we can implement IPartImportsSatisfiedNotification (which is my favorite technique because there is no dead lock risk).
this way we can initialize our component right after all imports has been satisfied.
IPartImportsSatisfiedNotificat
- [Export]
- public class MyPlugin: IPartImportsSatisfiedNotification
- {
- public void OnImportsSatisfied()
- {
- // happens just after the imports satisfaction
- Logger.Write("MyPlugin initialized");
- }
-
- [Import]
- public ILogger Logger { get; set; }
- }
MEF infrastructure will call OnImportsSatisfied right after it done with the composition process.
None composed instance issue
developers are used to instantiate their own type using the new keyword (var plugin = new MyPlugin()).
doing so wouldn't satisfy any of the imported properties (because the type doesn't took part in any composition), because MEF does not aware of the instantiation.
How can we do it right?
- we can introduce the instance to the composition by using ComposePart.
the compose part will satisfy any of the instance imports.
ComposeParts
- [TestMethod]
- public void GetInstanceFromContainerTest()
- {
- var catalog = new AssemblyCatalog(typeof(ILogger).Assembly);
- var container = new CompositionContainer(catalog);
-
- var plugin = new MyPlugin(); ;
- container.ComposeParts(plugin);
-
- Assert.IsNotNull(plugin.Logger);
- }
-
- public interface ILogger
- {
- void Write(string content);
- }
-
- [Export(typeof(ILogger))]
- public class MyLogger:ILogger
- {
- public void Write(string content)
- {
- Debug.WriteLine(content);
- }
- }
-
- [Export]
- public class MyPlugin
- {
- [Import]
- public ILogger Logger { get; set; }
- }
- other solution is getting our instance dynamically from
the container (for example by using GetExportedValue)
GetExportedValue
- [TestMethod]
- public void GetInstanceFromContainerTest()
- {
- var catalog = new AssemblyCatalog(typeof(ILogger).Assembly);
- var container = new CompositionContainer(catalog);
- container.Compose(new CompositionBatch());
-
- MyPlugin plugin = container.GetExportedValue<MyPlugin>();
-
- Assert.IsNotNull(plugin);
- Assert.IsNotNull(plugin.Logger);
- }
-
- public interface ILogger
- {
- void Write(string content);
- }
-
- [Export(typeof(ILogger))]
- public class MyLogger:ILogger
- {
- public void Write(string content)
- {
- Debug.WriteLine(content);
- }
- }
-
- [Export]
- public class MyPlugin
- {
- [Import]
- public ILogger Logger { get; set; }
- }
- using Silverlight we can initialize our container
by using CompositionHost.Initialize(catalog). and we can add the
following line to the constructor:
CompositionInitializer.SatisfyImports(this), which mean that
each time we are creating new instance it will try to satisfy it's own imports.
(be aware that you must use CompositionHost.Initialize(catalog) before using
CompositionInitializer.SatisfyImports(this))
SatisfyImports
- public class MyPlugin
- {
- public MyPlugin()
- {
- CompositionInitializer.SatisfyImports(this);
- }
-
- [Import]
- public ILogger Logger { get; set; }
- }
if you want to use the same technique for none Silverlight components,
you can download the code for System.ComponentModel.Composition.Initialization.dll
which hold CompositionHost.Initialize and CompositionInitializer.SatisfyImports
from here.
Exporting the wrong type issue
last issue is to avoid exporting or importing the wrong type.
MEF does not goes through the inheritance list,
MEF contract are explicit and doesn't aware of the inheritance chain.
the following code snippet export the MyLogger (not the ILogger).
Code Snippet
- [Export]
- public class MyLogger:ILogger {
- ...
- }
it is equivalent to:
Code Snippet
- [Export(typeof(MyLogger))]
- public class MyLogger:ILogger {
- ...
- }
while what you may really wanted is:
Code Snippet
- [Export(typeof(ILogger))]
- public class MyLogger:ILogger {
- ...
- }
the following import won't get the first 2 snippet because of the explicit contract comparison:
Code Snippet
- [Import]
- public ILogger Logger { get; set; }
Summary
MEF instantiation occurs at runtime and sometime it is not
so easy or straight forward for debug.
avoiding common misconceptions may save us long debugging hours.
Read more
I strongly recommend the following post which discuss other points of failure: