MEF Part 3
MEF has an awesome facility that gives you a lot of power over your exportinG and importing that I am going to describe to you in this post.
I first started using MEF I faced a problem early on with importing many objects of a shared class or interface. In some cases I wanted to control which objects I loaded based on some attribute. My first implementation was very naive. I created read only properties on my interfaces (only getters, no setters). This allowed my objects to expose to the importing container various information or metadata about themselves. I then created factory methods which used some LINQ sugar do return objects based on metadata filtering.
This approach, while it worked, was flawed for many reasons. The least of which is that I would have to load each object and examine it's property before I can determine it was the object I needed or wanted in that situation. If the objects are small and lightweight this isn't too much of an issue. While it might be a candidate for optimization later it probably is fine in those situations. Imagine, however, your objects open database connections, file streams, sockets, etc... In those cases it would be prohibitively expensive to create all those objects just to look at a static string exposed by the object.Along comes the Lazy class. This class provides an extension to Lazy for object metadata. You can use it to "peek" at the metadata of the exported types without actually loading the type. Let's look at some code.
public interface IMessageSender { void Send(string message); } [Export(typeof(IMessageSender))] [ExportMetadata("transport", "smtp")] public class EmailSender : IMessageSender { public void Send(string message) { } }
So, in this example I created an interface called IMessageSender. I created an implementation class called EmailSender. I'm exporting the type IMessageSender (again, by contract is more flexible than by type). I'm also adding some metadata to the export. In this case I'm adding a key called "transport" with a value of "smtp". Since the default implementation of the attribute class exposes a Dictionary we have to declare our import like so:
[ImportMany] IEnumerable<IMessageSender> _senders;
Now we can use a little LINQ sugar to find our objects:
var smtpSender = _senders .Where((lazy) => lazy.Metadata.ContainsKey("transport") && ((string)lazy.Metadata["transport"] == "smtp")) .Select(lazy => lazy.Value).FirstOrDefault();
And there you have it! Now that is some sweet goodness. Next part I'll show how you can extend ExportAttribute to provide your own contract for Metadata and not have to search for a key in a dictionary and convert the key to a particular type and hope the conversion succeeds without losing data.