Testing and Debugging MEF, avoiding misconceptions – Part 3

2010/10/29

Testing and Debugging MEF, avoiding misconceptions – Part 3

MEf, Import, Export, Compose, extension, extensibilitythis 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
  1. [Export]
  2. public class MyPlugin
  3. {
  4.     public MyPlugin()
  5.     {   // the logger import doesn't satisfied yet
  6.         // the next line will result with NullReferenceException
  7.         Logger.Write("MyPlugin initialized");
  8.     }
  9.  
  10.     [Import]
  11.     public ILogger Logger { get; set; }
  12. }

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
  1. [Export]
  2. public class MyPlugin
  3. {
  4.     // cross import constructor can lead to deadlock an rejection
  5.     [ImportingConstructor]
  6.     public MyPlugin(ILogger logger)
  7.     {  
  8.         logger.Write("MyPlugin initialized");
  9.     }
  10. }

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
  1. [Export]
  2. public class MyPlugin: IPartImportsSatisfiedNotification
  3. {
  4.     public void OnImportsSatisfied()
  5.     {
  6.         // happens just after the imports satisfaction
  7.         Logger.Write("MyPlugin initialized");
  8.     }
  9.  
  10.     [Import]
  11.     public ILogger Logger { get; set; }
  12. }

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
  1. [TestMethod]
  2. public void GetInstanceFromContainerTest()
  3. {
  4.     var catalog = new AssemblyCatalog(typeof(ILogger).Assembly);
  5.     var container = new CompositionContainer(catalog);
  6.  
  7.     var plugin = new MyPlugin(); ;
  8.     container.ComposeParts(plugin);
  9.  
  10.     Assert.IsNotNull(plugin.Logger);
  11. }
  12.  
  13. public interface ILogger
  14. {
  15.     void Write(string content);
  16. }
  17.  
  18. [Export(typeof(ILogger))]
  19. public class MyLogger:ILogger
  20. {
  21.     public void Write(string content)
  22.     {
  23.         Debug.WriteLine(content);
  24.     }
  25. }
  26.  
  27. [Export]
  28. public class MyPlugin
  29. {
  30.     [Import]
  31.     public ILogger Logger { get; set; }
  32. }

  • other solution is getting our instance dynamically from
    the container (for example by using GetExportedValue)
GetExportedValue
  1. [TestMethod]
  2. public void GetInstanceFromContainerTest()
  3. {
  4.     var catalog = new AssemblyCatalog(typeof(ILogger).Assembly);
  5.     var container = new CompositionContainer(catalog);
  6.     container.Compose(new CompositionBatch());
  7.  
  8.     MyPlugin plugin = container.GetExportedValue<MyPlugin>();
  9.  
  10.     Assert.IsNotNull(plugin);
  11.     Assert.IsNotNull(plugin.Logger);
  12. }
  13.  
  14. public interface ILogger
  15. {
  16.     void Write(string content);
  17. }
  18.  
  19. [Export(typeof(ILogger))]
  20. public class MyLogger:ILogger
  21. {
  22.     public void Write(string content)
  23.     {
  24.         Debug.WriteLine(content);
  25.     }
  26. }
  27.  
  28. [Export]
  29. public class MyPlugin
  30. {
  31.     [Import]
  32.     public ILogger Logger { get; set; }
  33. }

  • 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
  1. public class MyPlugin
  2. {
  3.     public MyPlugin()
  4.     {
  5.         CompositionInitializer.SatisfyImports(this);
  6.     }
  7.  
  8.     [Import]
  9.     public ILogger Logger { get; set; }
  10. }

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
  1. [Export]
  2. public class MyLogger:ILogger {
  3.     …
  4. }

it is equivalent to:

Code Snippet
  1. [Export(typeof(MyLogger))]
  2. public class MyLogger:ILogger {
  3.     …
  4. }

while what you may really wanted is:

Code Snippet
  1. [Export(typeof(ILogger))]
  2. public class MyLogger:ILogger {
  3.     …
  4. }

the following import won’t get the first 2 snippet because of the explicit contract comparison:

Code Snippet
  1. [Import]
  2. 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:

 

kick it on DotNetKicks.com Shout it

Digg This
Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>