MEF Tip: Initializing Objects

December 5, 2016

no comments

I like MEF. Maybe it’s because of the default way MEF binds – using custom attributes; I like the declarative nature of attributes. Anyway, MEF exports declare their dependencies through the Import attribute. Here is a simple class export that has some imports:

[Export]
class AppManager {

    [Import]
    ILogger _logger;

    [Import]
    IFileService _fileService;

    public AppManager() {
        _logger.LogMessage("AppManager created");
    }

    public void DoWork() {
        var filename = _fileService.GetFileForOpen();
        if (filename == null)
            return;

        _logger.LogMessage("Opening file...");

        var lines = File.ReadAllLines(filename);
        _logger.LogMessage("Reading done.");

        Console.WriteLine($"Read {lines.Length} lines of text.");
    }
}

The AppManager class has two dependencies – an ILogger implementation and an IFileService implementation. Let’s build a MEF container in the Main method and ask it to generate an instance, followed by an invocation of the DoWork method (assume all MEF parts needed are in the current assembly):

static void Main(string[] args) {
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    var manager = container.GetExportedValue<AppManager>();
    manager.DoWork();
}

 

Can you guess what’s the logger’s output from the above?

If you guessed “Exception”, you are correct. The problem is the AppManager constructor using the ILogger interface. Unfortunately, it’s null, since imports are satisfied following object creation – and the object is not yet fully constructed while the constructor is executing.

One obvious way to solve this is to include a new method, say Init, leaving the constructor mostly empty. This Init method would be call after the GetExportedValue call. The problem here is that this could should never be forgotten, and in some scenarios such as generating objects with LINQ would be highly inconvenient to call.

MEF provides two ways to address this. The first option is to use the ImportingConstructor attribute to tell MEF that certain arguments are needed in the constructor, like so:

[ImportingConstructor]
public AppManager(ILogger logger) {
    logger.LogMessage("AppManager created");
}

MEF provides the requested dependencies in the constructor, allowing proper initialization. It’s also possible to set the _logger member right in the constructor and remove the Import attribute over that member.

There is a second option (and that’s the promised tip from the title) – using the IPartImportsSatisfiedNotification interface. If a MEF part implements this interface, it’s called by MEF once the object is fully initialized from MEF’s perspective – it has all its imports satisfied. There’s just one method to implement – OnImportsSatisfied, which can be thought of as a constructor: it’s called automatically, and all initialization code can go there. The added bonus this method has over using ImportingConstructor is that it’s a true method, thus allowing using await calls inside it (something a constructor can’t do). This is the final result:

[Export]
class AppManager : IPartImportsSatisfiedNotification {

    [Import]
    ILogger _logger;

    [Import]
    IFileService _fileService;

    [ImportingConstructor]
    public AppManager(ILogger logger) {
    }

    public void DoWork() {
        var filename = _fileService.GetFileForOpen();
        if (filename == null)
            return;

        _logger.LogMessage("Opening file...");

        var lines = File.ReadAllLines(filename);
        _logger.LogMessage("Reading done.");

        Console.WriteLine($"Read {lines.Length} lines of text.");
    }

    public void OnImportsSatisfied() {
        _logger.LogMessage("AppManager created");
    }
}

 

The complete sample code can be downloaded here.

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=""> <s> <strike> <strong>

*