Saturday, January 14, 2012

MEF: Instantiating parts via meta data query

I quite like MEF; something about the simplicity of its approach appeals, certainly when compared to MAF.

Recently, I needed to be able to instantiate a part from an [ImportMany] collection, where the collection in question was part of a factory; that is, all parts were lazily held in a central location (the factory). The clumsy way of doing this would be to get the type of a part, and use Activator to instantiate it. But a slightly more elegant (to my mind) mechanism is to use meta data to locate a part, and use the composition container to create as necessary.

Thus the extensions shown below.Given a MEF container, you can ask it to instantiate a part or parts based on a Func that will be used to select eligible parts based on the decomposed meta data held by MEF (looking at the implementation now, I'd posit that I could optimise the LINQ).

 public static class MEFExtensions {  
    public static IEnumerable<TValue> CreateParts<TValue>(  
          this CompositionContainer container,  
          Func<IDictionary<string, object>, bool> selector) {     
       return container.Catalog.Parts.SelectMany(p =>   
          p.ExportDefinitions.Select(d =>   
             new Tuple<ComposablePartDefinition, ExportDefinition>(p, d))).     
          Where(tup => tup.Item2.ContractName == typeof(TValue).FullName &&  
                        selector(tup.Item2.Metadata)).     
          Select(t => (TValue)t.Item1.CreatePart().GetExportedValue(t.Item2));     
    }  
    public static TValue CreatePart<TValue>(     
          this CompositionContainer container,  
          Func<IDictionary<string, object>, bool> selector) {     
       return CreateParts<TValue>(container, selector).FirstOrDefault();  
    }  
 }  

As decomposed meta data in MEF is typed as an IDictionary, this next set of extensions can be used if a straight (and simple) comparison of dictionary associations is required. IOW, a selector might look similar to:

dict => dict.EqualKeysAndValues(externallySuppliedMetaData)

 public static class IDictionaryExtensions {  
    public static bool EqualKeysAndValues<TKey, TValue>(  
         this IDictionary<TKey, TValue> dict, IDictionary<TKey, TValue> rhs) {     
       return EqualKeysAndValues(dict, rhs, Enumerable.Empty<TKey>());  
    }  
    public static bool EqualKeysAndValues<TKey, TValue>(  
         this IDictionary<TKey, TValue> dict  
         IDictionary<TKey, TValue> rhs,   
         IEnumerable<TKey> exclusions) {  
       IDictionary<TKey, TValue> comp =   
          new Dictionary<TKey, TValue>().AddRange(  
               dict.Where(kvp => !exclusions.Contains(kvp.Key)));  
       return comp.Count() == rhs.Count() &&   
             !comp.Keys.Except(rhs.Keys).Any() &&   
          comp.All(kvp => rhs[kvp.Key].Equals(kvp.Value));  
    }  
    public static IDictionary<TKey, TValue> AddRange<TKey, TValue>(  
        this IDictionary<TKey, TValue> dict,   
        IEnumerable<KeyValuePair<TKey, TValue>> pairs) {  
       pairs.ToList().ForEach(kvp => dict[kvp.Key] = kvp.Value);  
       return dict;  
    }  
 }  

No comments: