Der Zugriff auf Metadaten erfolgt über die Klasse Lazy<T, TMetadata>. So steht es in der Dokumentation zum Managed Extensibility Framework (MEF). Doch es geht auch ohne Lazy<T, TMetadata>. Was dabei zu beachten ist und welche Möglichkeiten sich dadurch ergeben, zeigt das folgende Beispiel.
Metadaten werden mit Hilfe des Attributes ExportMetadata bekannt gegeben. Dazu wird jedes Element der Metadaten durch ein Name-Werte-Paar beschrieben. Der Name ist immer vom Typ String, während der Wert vom Typ Object ist. Wie der Zugriff auf Metadaten i.d.R. erfolgt, wird im Artikel MEF Teil 2 – Metadaten und Erstellungsrichtlinien beschrieben.
Im Folgenden Beispiel sind die Parts wie folgt implementiert:
[ExportMetadata("Name", "Mercedes")] [ExportMetadata("Price", (uint)48000)] [Export(typeof(ICarContract))] public class Mercedes : ICarContract { private static int counter; public Mercedes() { Console.WriteLine("constructor Mercedes: {0}", ++counter); } public string GetName() { return "Mercedes"; } }
Die Schnittstelle ICarContract dient ebenfalls zur Definition des Contracts:
public interface ICarContract { string GetName(); }
Das Auslesen der Metadaten kann jetzt recht einfach über den Container erfolgen.
class Program { [ImportMany(typeof(ICarContract))] private List<ICarContract> CarParts = new List<ICarContract>(); static void Main(string[] args) { new Program().Run(); } void Run() { var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); container.ComposeParts(this); foreach (ComposablePartDefinition partDefinition in container.Catalog.Parts) { foreach (ExportDefinition exportDefinition in partDefinition.ExportDefinitions) { Console.WriteLine("Metadaten:"); IDictionary<String, Object> metadata = exportDefinition.Metadata; Console.WriteLine("Name: {0}", metadata["Name"].ToString()); Console.WriteLine("Price: {0}\n", metadata["Price"].ToString()); } } container.Dispose(); } }
Wer sich über die Bedeutung der einzelnen Klassen noch nicht ganz im Klaren ist, wird vielleicht hier fündig: MEF Teil 8 – Eigenen ExportProvider erstellen.
Das obige Beispiel hat nur einen großen Haken. Mir ist es nicht gelungen, die Metadaten den einzelnen Objekten aus der Liste CarParts zuzuordnen. Was nützen die Metadaten, wenn diese dem Export nicht zugeordnet werden können?
Um dieses Problem zu lösen, musste ich das Programm so abändern, dass das Erzeugen der einzelnen Car-Objekte nicht von MEF ausgeführt wird, sondern ‘von Hand’.
class Program { // [ImportMany(typeof(ICarContract))] private List<ICarContract> CarParts = new List<ICarContract>(); static void Main(string[] args) { new Program().Run(); } void Run() { var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); // container.ComposeParts(this); foreach (ComposablePartDefinition partDefinition in container.Catalog.Parts) { ComposablePart part = partDefinition.CreatePart(); foreach (ExportDefinition exportDefinition in partDefinition.ExportDefinitions) { Console.WriteLine("Create new instance"); ICarContract car = (ICarContract)part.GetExportedValue(exportDefinition); Console.WriteLine("Metadaten:"); IDictionary<String, Object> metadata = exportDefinition.Metadata; Console.WriteLine("Name: {0}", metadata["Name"].ToString()); Console.WriteLine("Price: {0}\n", metadata["Price"].ToString()); CarParts.Add(car); } } Console.WriteLine("\nCarParts:"); foreach (ICarContract carParts in CarParts) Console.WriteLine(carParts.GetName()); container.Dispose(); } }
Da die Objekte selber erzeugt werden, kann auf das Attribut Import in Zeile 3 verzichtet werden. Ebenfalls nicht mehr notwendig ist der Aufruf der Methode ComposeParts() in Zeile 15. Der Aufruf würde so oder so nichts bewirken, da es im Programm keinen passenden Import mehr gibt. Ohne Import gibt es nichts zu composen.
Das Erzeugen der Car-Objekte erfolgt mit Hilfe der Methode GetExportValue() der Klasse ComposablePart. In Zeile 19 wird das notwendige Objekt erzeugt und in Zeile 23 das gewünschte Car-Objekt instanziiert. Die dann folgenden Metadaten gehören somit zwangsläufig zu dem zuvor erzeugten Objekt. Damit unsere Liste CarParts nicht leer bleibt, wird in Zeile 28 das Objekt der Liste hinzugefügt.
Auch wenn das manuelle Anlegen der Objekte einen der größten Vorteile von MEF untergräbt, so ergeben sich doch einige neue Möglichkeiten. Die Metadaten könnten z.B. direkt ausgewertet werden, um so die erzeugten Objekte unterschiedlichen Listen zuzuordnen.
if ((uint)metadata["Price"] > 50000) ExpensiveCarParts.Add(car); else CarParts.Add(car);
Fazit:
So richtig kommt bei diesem Ansatz keine Freude auf. Die Nachteile überwiegen klar die Vorteile. Das Angenehme bei MEF ist eben das automatische Composen, also das Zuordnen der Exports zu den Imports. Auch ist der typsichere Zugriff auf die Metadaten nicht mehr möglich. Sollen die Exports an Hand der Metadaten gefiltert oder sortiert werden, so bietet Lazy<T, TMetadata> deutlich mehr Komfort. Ein Beispiel mit LINQ ist in MEF Teil 2 – Metadaten und Erstellungsrichtlinien zu finden.