Im 1. Teil ging es um die Grundlagen, die Imports und Exports. Der 2. Teil schließt an den ersten an und zeigt weitere Leistungsmerkmale des Managed Extensibility Framework (MEF). Schwerpunkt sind dieses Mal die Metadaten und die Erstellungsrichtlinien.
Metadaten
Exports können über Metadaten weitere Informationen bereitstellen. Diese Informationen werden über die Klasse Lazy<> abgefragt, ohne dass von dem Composable Part eine Instanz erzeugt werden muss.
Für das Beispiel wird wieder auf den ersten Teil zurückgegriffen. Es gibt eine Anwendung (CarHost.exe) die per Import verschiedene Autos (BMW.dll und Mercedes.dll) an sich bindet. Ein Contract (CarContract.dll) beinhaltet die Schnittstelle, über die der Host auf die Exports zugreift.
Die Metadaten sollen aus drei Werten bestehen. Zum einen ein String, in dem der Name (Name) steht. Als zweites eine Aufzählung, mit der Angabe einer Farbe (Color). Als letztes ein Integer mit dem Preis (Price).
Es gibt mehrere Varianten, wie die Exports dem Import die Metadaten zur Verfügung stellen können:
- nicht typsicher
- typsicher per Schnittstelle
- typsicher per Schnittstelle und benutzerdefinierten Exportattributen
- typsicher per Schnittstelle und Aufzählungen bei benutzerdefinierten Exportattributen
1. Variante: nicht typsicher
Metadaten werden mithilfe 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. Evtl. kann es notwendig sein, einen Wert explizit durch den cast-Operator in den gewünschten Datentyp zu wandeln. In diesem Fall muss bei Price der Wert nach dem Datentyp uint gewandelt werden.
Es werden zwei Exports angelegt, die jeweils die gleichen Metadaten anbieten. Die Werte der Metadaten sind allerdings unterschiedlich.
using System; using System.ComponentModel.Composition; using CarContract; namespace CarMercedes { [ExportMetadata("Name", "Mercedes")] [ExportMetadata("Color", CarColor.Blue)] [ExportMetadata("Price", (uint)48000)] [Export(typeof(ICarContract))] public class Mercedes : ICarContract { private Mercedes() { Console.WriteLine("Mercedes constructor."); } public string StartEngine(string name) { return String.Format("{0} starts the Mercedes.", name); } } } using System; using System.ComponentModel.Composition; using CarContract; namespace CarBMW { [ExportMetadata("Name", "BMW")] [ExportMetadata("Color", CarColor.Black)] [ExportMetadata("Price", (uint)55000)] [Export(typeof(ICarContract))] public class BMW : ICarContract { private BMW() { Console.WriteLine("BMW constructor."); } public string StartEngine(string name) { return String.Format("{0} starts the BMW.", name); } } }
Die Schnittstelle ICarContract bietet die Methode an, auf die der Import später zugreifen kann. Er stellt auch den ‘Vertrag’ zwischen dem Import und den Exports da. Im gleichen Namespace ist auch die Aufzählung CarColor definiert.
using System; namespace CarContract { public interface ICarContract { string StartEngine(string name); } public enum CarColor { Unkown, Black, Red, Blue, White } }
Erreichbar sind die Metadaten für den Import über die Klasse Lazy<T, TMetadata>. Hierzu bietet die Klasse die Eigenschaft Metadata an. Metadata ist vom Typ Dictionary<string, object>.
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using CarContract; namespace CarHost { class Program { [ImportMany(typeof(ICarContract))] private IEnumerable<Lazy<ICarContract, Dictionary<string, object>>> CarParts { get; set; } static void Main(string[] args) { new Program().Run(); } void Run() { var catalog = new DirectoryCatalog("."); var container = new CompositionContainer(catalog); container.ComposeParts(this); foreach (Lazy<ICarContract, Dictionary<string, object>> car in CarParts) { if (car.Metadata.ContainsKey("Name")) Console.WriteLine(car.Metadata["Name"]); if (car.Metadata.ContainsKey("Color")) Console.WriteLine(car.Metadata["Color"]); if (car.Metadata.ContainsKey("Price")) Console.WriteLine(car.Metadata["Price"]); Console.WriteLine(""); } foreach (Lazy<ICarContract> car in CarParts) Console.WriteLine(car.Value.StartEngine("Sebastian")); container.Dispose(); } } }
Wird auf ein bestimmtes Element der Metadaten zugegriffen, so sollte geprüft werden ob der Export das gewünschte Element überhaupt definiert hat. Es kann durchaus sein, dass Imports unterschiedliche Metadaten anbieten.
Bei der Ausführung des Programms ist gut zu erkennen, dass die Export-Parts durch den Zugriff auf die Metadaten nicht initialisiert werden. Erst der Zugriff auf die Methode StartEngine() erzeugt eine Instanz und ruft dadurch den Konstruktor auf.
Da die Metadaten in eine Klasse vom Typ Dictionary<string, object> abgelegt werden, können diese beliebig viele Metadaten enthalten. Dieses hat Vor- und Nachteile. Der Vorteil ist, dass alle Metadaten optional sind und beliebige Informationen anbieten können. Der Wert ist ja vom Typ Object. Genau hierdurch geht aber die Typsicherheit verloren. Dieses ist ein großer Nachteil. Beim Zugriff muss immer geprüft werden, ob die gewünschten Metadaten überhaupt vorhanden sind. Ansonsten kann es zu unangenehmen Laufzeitfehlern kommen.
Beispiel 1 (Visual Studio 2010) auf GitHub
2. Variante: typsicher per Schnittstelle
So wie die zur Verfügung stehenden Methoden und Eigenschaften eines Exports durch eine Schnittstelle festgelegt werden (ICarContract), so können auch die Metadaten durch eine Schnittstelle definiert werden. In diesem Fall legen Eigenschaften die einzelnen Werte fest, die später verfügbar sind. Es dürfen nur Eigenschaften definiert werden, die über einen get-Accessor zugänglich sind (wird trotzdem ein set-Accessor definiert, kommt es zu einem Laufzeitfehler).
Für unser Beispiel werden die drei Eigenschaften vom gewünschten Datentyp angelegt. Die Definition der Schnittstelle für die Metadaten ist somit wie folgt:
public interface ICarMetadata { string Name { get; } CarColor Color { get; } uint Price { get; } }
Die Schnittstelle für die Metadaten wird bei der Prüfung zwischen Import und Export benutzt. Alle Exporte müssen die definierten Metadaten bereitstellen. Fehlende Metadaten führen ebenfalls zu einem Laufzeitfehler. Das Attribut DefaultValue kann benutzt werden, wenn eine Eigenschaft optional ist.
[DefaultValue((uint)0)] uint Price { get; }
Damit nicht alle Metadaten bei einem Export definiert werden müssen, werden alle Eigenschaften in diesem Beispiel mit dem Attribute DefaultValue dekoriert.
using System; using System.ComponentModel; namespace CarContract { public interface ICarMetadata { [DefaultValue("NoName")] string Name { get; } [DefaultValue(CarColor.Unkown)] CarColor Color { get; } [DefaultValue((uint)0)] uint Price { get; } } }
Die Schnittstelle ICarContract und die Exports werden genauso erstellt wie in dem ersten Beispiel.
Für den Zugriff auf die Metadaten wird bei der Klasse Lazy<T, TMetadata> für TMetadata die Schnittstelle für die Metadaten angegeben. In diesem Beispiel ist es die Schnittstelle ICarMetadata. Über die Eigenschaft Metadata stehen somit die einzelnen Metadaten zur Verfügung.
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq; using CarContract; namespace CarHost { class Program { [ImportMany(typeof(ICarContract))] IEnumerable<Lazy<ICarContract, ICarMetadata>> CarParts { get; set; } static void Main(string[] args) { new Program().Run(); } void Run() { var catalog = new DirectoryCatalog("."); var container = new CompositionContainer(catalog); container.ComposeParts(this); foreach (Lazy<ICarContract, ICarMetadata> car in CarParts) { Console.WriteLine(car.Metadata.Name); Console.WriteLine(car.Metadata.Color); Console.WriteLine(car.Metadata.Price); Console.WriteLine(""); } // invokes the method only of black cars var blackCars = from lazyCarPart in CarParts let metadata = lazyCarPart.Metadata where metadata.Color == CarColor.Black select lazyCarPart.Value; foreach (ICarContract blackCar in blackCars) Console.WriteLine(blackCar.StartEngine("Sebastian")); Console.WriteLine("."); // invokes the method of all imports foreach (Lazy<ICarContract> car in CarParts) Console.WriteLine(car.Value.StartEngine("Sebastian")); container.Dispose(); } } }}
Da die Schnittstelle ICarMetadata Name und Typ der Metadaten festlegt, kann auf diese direkt zugegriffen werden. Diese Typsicherheit bringt noch einen kleinen, aber hilfreichen Vorteil mit sich. Auf die Eigenschaft CarParts kann jetzt mit LINQ zugegriffen werden. So lässt sich an Hand der Metadaten eine Filterung durchführen, so dass nur bestimmte Imports benutzt werden.
In der ersten foreach-Schleife werden von allen Exports die Metadaten ausgegeben. Danach wird mit LINQ eine Abfrage erstellt, die in einer Auflistung nur die Exports bereitstellt, deren Metadaten einen bestimmten Wert haben. Hier muss Color den Wert CarColor.Black haben. Nur von diesen Exports wird die Methode StartEngine() aufgerufen. In der letzten foreach-Schleife erfolgt der Aufruf der Methode bei allen Exports.
Auch hier ist gut zu erkennen, dass weder das Ausgeben aller Metadaten noch die LINQ-Abfrage einen Export initialisiert. Erst der Aufruf der Methode StartEngine() führt zum Anlegen einer Instanz und somit zum Aufruf des Konstruktors.
Beispiel 2 (Visual Studio 2010) auf GitHub
Aus meiner Sicht sollte bei Metadaten nach Möglichkeit immer mit Schnittstellen gearbeitet werden. Auch wenn der Aufwand etwas größer ist, so bleiben einem doch ungeliebte Laufzeitfehler erspart.
3. Variante: typsicher per Schnittstelle und benutzerdefinierten Exportattributen
Die Angaben der Metadaten bei den Exports hat noch einen Nachteil. Der Name muss als String angegeben werden. Gerade bei langen Namen können sich hier schnell Schreibfehler einschleichen. Der Compiler kann diese Schreibfehler nicht erkennen. Solche Fehler machen sich erst zur Laufzeit bemerkbar. Angenehmer wäre es, wenn Visual Studio bei der Eingabe alle gültigen Metadaten auflistet und Schreibfehler vom Compiler bemerkt werden. Erreicht werden kann dieser Komfort durch das Anlegen einer eigenen Attribut-Klasse für die Metadaten. Hierzu braucht das vorherige Beispiel nur um eine Klasse erweitert werden.
using System; using System.ComponentModel.Composition; namespace CarContract { [MetadataAttribute] public class CarMetadataAttribute : Attribute { public string Name { get; set; } public CarColor Color { get; set; } public uint Price { get; set; } } }
Die Klasse muss mit dem Attribut MetadataAttribute dekoriert werden und von der Klasse Attribute abgeleitet werden. Eigenschaften legen die einzelnen Werte fest, die über die Metadaten exportiert werden sollen. Datentyp und Name der Eigenschaften muss mit der Schnittstelle für die Metadaten übereinstimmen. Die Schnittstelle ICarContract wurde zuvor wie folgt definiert:
using System; using System.ComponentModel; namespace CarContract { public interface ICarMetadata { [DefaultValue("NoName")] string Name { get; } [DefaultValue(CarColor.Unkown)] CarColor Color { get; } [DefaultValue((uint)0)] uint Price { get; } } }
Das Dekorieren eines Exports mit Metadaten kann jetzt mit dem selbst definierten Attribut erfolgen.
[CarMetadata(Name="BMW", Color=CarColor.Black, Price=55000)] [Export(typeof(ICarContract))] public class BMW : ICarContract { // ... }
Visual Studio kann jetzt den Entwickler bei der Eingabe unterstützen. Alle gültigen Metadaten werden beim Editieren angezeigt. Auch kann der Compiler jetzt erkennen, ob nur gültige Metadaten angegeben wurden.
Beispiel 3 (Visual Studio 2010) auf GitHub
4. Variante: typsicher per Schnittstelle und Aufzählungen bei benutzerdefinierten Exportattributen
Bisher durften bei den Metadaten keine mehrfachen Einträge vorkommen. Vorstellbar wäre aber eine Auflistung, die Optionen enthält, die miteinander kombiniert werden können. Das Auto-Beispiel soll so erweitert werden, dass auch die Ausstattung des Audio-Systems angegeben werden kann. Hierzu wird erst einmal ein enum definiert, das alle möglichen Optionen enthält:
public enum AudioSystem { Without, Radio, CD, MP3 }
Das Interface ICarMetadata wird jetzt um eine Eigenschaft vom Typ AudioSystem erweitert.
using System; using System.ComponentModel; namespace CarContract { public interface ICarMetadata { [DefaultValue("NoName")] string Name { get; } [DefaultValue(CarColor.Unkown)] CarColor Color { get; } [DefaultValue((uint)0)] uint Price { get; } [DefaultValue(AudioSystem.Without)] AudioSystem[] Audio { get; } } }
Da ein Radio auch ein CD-Player enthalten kann, muss es möglich sein, bestimmte Metadaten mehrfach anzugeben. Bei dem Export sieht die Deklaration der Metadaten wie folgt aus:
using System; using System.ComponentModel.Composition; using CarContract; namespace CarBMW { [CarMetadata(Name="BMW", Color=CarColor.Black, Price=55000)] [CarMetadataAudio(AudioSystem.CD)] [CarMetadataAudio(AudioSystem.MP3)] [CarMetadataAudio(AudioSystem.Radio)] [Export(typeof(ICarContract))] public class BMW : ICarContract { private BMW() { Console.WriteLine("BMW constructor."); } public string StartEngine(string name) { return String.Format("{0} starts the BMW.", name); } } } using System; using System.ComponentModel.Composition; using CarContract; namespace CarMercedes { [CarMetadata(Name="Mercedes", Color=CarColor.Blue, Price=48000)] [CarMetadataAudio(AudioSystem.Radio)] [Export(typeof(ICarContract))] public class Mercedes : ICarContract { private Mercedes() { Console.WriteLine("Mercedes constructor."); } public string StartEngine(string name) { return String.Format("{0} starts the Mercedes.", name); } } }
Während der Mercedes nur ein Radio besitzt, so enthält der BMW zusätzlich einen CD-Player und einen MP3-Player.
Um das zu erreichen, wird eine weitere Attribut-Klasse angelegt. Diese Attribut-Klasse repräsentiert die Metadaten für die Audioausstattung (CarMetadataAudio).
using System; using System.ComponentModel.Composition; namespace CarContract { [MetadataAttribute] [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class CarMetadataAudioAttribute : Attribute { public CarMetadataAudioAttribute(AudioSystem audio) { this.Audio = audio; } public AudioSystem Audio { get; set; } } }
Damit das Attribut mehrfach angegeben werden kann, muss die Klasse mit dem Attribut AttributeUsage dekoriert werden. Bei diesem Attribut wird AllowMultiple anschließend auf true gesetzt. Die Attribut-Klasse wurde hier mit einem Konstruktor versehen, der als Parameter den Wert direkt entgegen nimmt.
Die Ausgabe der mehrfachen Metadaten erfolgt in einer weiteren Schleife (siehe Zeile 28 und Zeile 29):
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq; using CarContract; namespace CarHost { class Program { [ImportMany(typeof(ICarContract))] private IEnumerable<Lazy<ICarContract, ICarMetadata>> CarParts { get; set; } static void Main(string[] args) { new Program().Run(); } void Run() { var catalog = new DirectoryCatalog("."); var container = new CompositionContainer(catalog); container.ComposeParts(this); foreach (Lazy<ICarContract, ICarMetadata> car in CarParts) { Console.WriteLine("Name: " + car.Metadata.Name); Console.WriteLine("Price: " + car.Metadata.Price.ToString()); Console.WriteLine("Color: " + car.Metadata.Color.ToString()); foreach (AudioSystem audio in car.Metadata.Audio) Console.WriteLine("Audio: " + audio); Console.WriteLine(""); } foreach (Lazy<ICarContract> car in CarParts) Console.WriteLine(car.Value.StartEngine("Sebastian")); container.Dispose(); } } }
Die Ausführung des Programms liefert das erwartete Ergebnis:
Beispiel 4 (Visual Studio 2010) auf GitHub
Es gibt noch eine weitere Variante. Diese werde ich aber erst unter dem Kapitel Vererbung von Exporten, in einem späteren Blog vorstellen. Bei dieser Variante können durch ein Attribut der Export und die Metadaten gleichzeitig dekoriert werden.
Erstellungsrichtlinien (Creation Policy)
In den bisherigen Beispielen wurden durch die Attribute Export und ImportMany mehrere Exporte nur an einen Import gebunden. Wie verhält sich aber MEF, wenn ein Export mehreren Imports zu Verfügung steht? Dazu soll das obere Beispiel etwas angepasst werden. Die Exports und der Contract bleiben unverändert. Im Host werden statt einer, zwei Listen angelegt. Beide Listen sollen die gleichen Exporte aufnehmen.
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using CarContract; namespace CarHost { class Program { [ImportMany(typeof(ICarContract))] private IEnumerable<Lazy<ICarContract>> CarPartsA { get; set; } [ImportMany(typeof(ICarContract))] private IEnumerable<Lazy<ICarContract>> CarPartsB { get; set; } static void Main(string[] args) { new Program().Run(); } void Run() { var catalog = new DirectoryCatalog("."); var container = new CompositionContainer(catalog); container.ComposeParts(this); foreach (Lazy<ICarContract> car in CarPartsA) Console.WriteLine(car.Value.StartEngine("Sebastian")); Console.WriteLine(""); foreach (Lazy<ICarContract> car in CarPartsB) Console.WriteLine(car.Value.StartEngine("Michael")); container.Dispose(); } } }
Mit dieser Anpassung wird jedem Export zwei Listen (Imports) zugewiesen. Die Ausgabe des Programms lässt allerdings vermuten, dass jeder Export nur einmal instanziiert wird.
Beispiel 5 (Visual Studio 2010) auf GitHub
Findet das Managed Extensibility Framework zu einem Import den passenden Export, so wird von dem Export eine Instanz erzeugt. Diese Instanz wird mit allen weiteren passenden Imports geteilt. MEF behandelt jeden Export als Singleton.
Dieses Standardverhalten kann sowohl auf der Seite der Exports, als auch bei den Imports durch die Erstellungsrechtlinien (Creation Policy) beeinflusst werden. Die jeweiligen Erstellungsrichtlinien können den Zustand Shared, NonShared oder Any haben. Standardeinstellung ist Any. Ein Export, für den Shared oder NonShared angegeben wird, stimmt nur mit einem Import überein, für den der gleiche Wert oder Any angegeben wurde. Importe oder Exporte müssen zueinander kompatible Erstellungsrichtlinien besitzen, damit diese als übereinstimmend betrachtet werden. Wird für die Importe als auch für die Exporte Any angegeben (oder auch keine Angabe), dann werden beide Parts auf Shared festgelegt.
Bei einem Export wird die Erstellungsrichtlinie durch das Attribut PartCreationPolicy festgelegt.
[Export(typeof(ICarContract))] [PartCreationPolicy(CreationPolicy.NonShared)]
Die Eigenschaft RequiredCreationPolicy legt bei dem Attribut Import oder ImportAny die Erstellungsrichtlinie fest.
[ImportMany(typeof(ICarContract), RequiredCreationPolicy = CreationPolicy.NonShared)] private IEnumerable<Lazy<ICarContract>> CarPartsA { get; set; }
Die folgende Ausgabe zeigt das Beispiel, bei dem die Erstellungsrichtlinie auf NonShared gesetzt wurde. Von jedem Export existieren jetzt zwei Instanzen.
Beispiel 6 (Visual Studio 2010) auf GitHub
Erstellungsrichtlinien können auch miteinander kombiniert werden. Ich habe bei dem Import eine Liste mit NonShared und zwei weitere mit Shared dekoriert.
[ImportMany(typeof(ICarContract), RequiredCreationPolicy = CreationPolicy.NonShared)] private IEnumerable<Lazy<ICarContract>> CarPartsA { get; set; } [ImportMany(typeof(ICarContract), RequiredCreationPolicy = CreationPolicy.Shared)] private IEnumerable<Lazy<ICarContract>> CarPartsB { get; set; } [ImportMany(typeof(ICarContract), RequiredCreationPolicy = CreationPolicy.Shared)] private IEnumerable<Lazy<ICarContract>> CarPartsC { get; set; }
Die Ausgabe zeigt, wie MEF die Instanzen anlegt und den einzelnen Imports zuordnet
Die erste Liste hat eigenständige Instanzen von den Exports. Liste zwei und Liste drei teilen sich die gleichen Instanzen.
Ausblick
Ich finde es sehr erfreulich, dass solch ein Framework standardisiert wurde. Einige Teams von Microsoft setzen MEF bereits erfolgreich ein. Die bekanntesten Beispiele hierfür sind Visual Studio 2010 und Expression Blend. Bleibt zu hoffen, dass noch mehr Produkte folgen werden und dass dadurch die Weiterentwicklung von MEF sichergestellt wird.
Im 3. Teil geht es um den Lifecycle der Composable Parts.
Hi Stefan,
Ich bin auch ein Freund von MEF.
Super Artikel! Bin schon auf den nächsten Teil gespannt.
Nur eine Kleinigkeit:
Als Du das Audio System eingeführt hast, hast Du auch das ICarMetadata Interface um ein Audio Property erweitert. Das solltest Du noch mal abbilden.
Viele Grüße
Uwe
Hy, Stefan, in dem Interface ist auf einmal der namespace “CarContract”. In Deinem Sample01 ist der namespace CarHost.
Was ist denn nun richtig??
Als Anfänger ist das sehr verwirrend.
Liebe Grüße
Alex
Hallo Alex,
die einzelnen Assemblies können verschiedene Namespaces haben; warum auch nicht? Host, Contract und AddIns liegen bei meinen Beispielen in separate Assemblies. Die Beispiele für Visual Studio, die du dir runterladen kannst, helfen dir hoffentlich weiter.
Stefan
Danke für die rasche Antwort.
Prima Artikel. Ich habe zum erstenmal die Funktion des MEF verstanden. Jetzt heißt es üben, üben, üben….
Hallo Stefan, eine kurze Verständnisfrage:
Bei dem letzten Beispiel mit den drei Listen (1 unshared, 2 shared, also mit Sebastian, Michael und Stefan): welche Creation Policy ist hier für den Export eingestellt? Deinen Ausführungen von oben nach müsste das doch dann Any (bzw. ohne Angabe) sein, oder täusche ich mich da?
(der Sourcecode zu diesem Beispiel ist in den Samples nicht vorhanden)
Gruß
Rolf
Hallo Rolf,
als Grundlage kannst du Sample06 benutzen. Die Methode Run() muss dann wie folgt aussehen:
var catalog = new DirectoryCatalog(“.”);
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
foreach (Lazy car in CarPartsA)
Console.WriteLine(car.Value.StartEngine(“Sebastian”));
Console.WriteLine(“”);
foreach (Lazy car in CarPartsB)
Console.WriteLine(car.Value.StartEngine(“Michael”));
Console.WriteLine(“”);
foreach (Lazy car in CarPartsC)
Console.WriteLine(car.Value.StartEngine(“Stefan”));
container.Dispose();
Bei den Exports muss
[PartCreationPolicy(CreationPolicy.Any)]
angegeben werden. Alternativ kann das Attribut PartCreationPolicy entfallen.
Stefan
Hallo Stefan,
nochmals eine wahrscheinlich typische Anfängerfrage.
Ich habe gerade Dein Beispiel 4 ausprobiert, da ich aktuell gerade an einem Teilproblem eines größeren Projektes sitze, das vom formalen Aufbau her nahezu identisch ist. Mir ist jetzt aufgefallen, dass bei allen Autoklassen mindestens eine Zeile
[CarMetadataAudio(AudioSystem.———)]
erforderlich ist. Wenn man z.B. bei der Merdesklasse diese Zeile auskommentiert (ein Mercedes ohne Audiosystem? – na gibt es sowas?…), kommt es zu einer CompositionContractMismatchException
“Unable to create an Instance of the Metadata view ‘CarContract.ICarMetadata’ because the exporter exported the metadata for the item ‘Audio’ with the value ‘Without’ as type ‘CarContract.AudioSystem’ but the view imports it as type ‘CarContract.AudioSystem[]’.”
,
wenn im Interface ICarMetadata der Eintrag [DefaultValue(AudioSystem.Without)] drinsteht.
Der Exporttyp AudioSystem passt nicht mit dem Importtyp Array AudioSystem[] zusammen, wenn AudioSystem.Without als Default drinsteht. Leuchtet mir auch irgendwie ein.
Aber wie realisiert man dann am besten ein leeres Array AudioSystem[] in den Metadaten?
Bem.: Beim Ersetzen des Eintrags durch “[DefaultValue(null)]” wird zwar keine Exception mehr geworfen und die Metadaten zum anderen Auto BMW kommen auch richtig, aber das kann wohl kaum eine besonders sinnvolle Lösung sein, da dann anstelle eines Arrays eine Nullreference vorhanden wäre, die man im Composable Part abfangen müsste.)
Gruß
Rolf
Hi Rolf,
teste mal folgendes Attribut:
[DefaultValue((object)(new AudioSystem[0]))]
AudioSystem[] Audio { get; }
Der Defaultwert wird dadurch eine leere Liste vom Typ AudioSystem[].
Stefan
Statt
[DefaultValue((object)(new AudioSystem[0]))]
würde ich
[DefaultValue(new [] {AudioSystem.Without}]
nehmen.
Dann hat man nicht ne leere Liste sondern den ursprünglich gewollten Default Wert als einziger Wert in dem Array.
Super Reihe – Danke Stefan!!
Super Artikel, leider fehlt im Car-Beispiel Variante 2 (typsicherer Zugriff) wie man das Interface ICarMetadata als ExportMetadata-Attribut angeben muss…
Hallo Stefan, prima Tutorial! Sehr gut! Vielen Dank! Bitte erwähne noch, dass
[MetadataAttribute] in System.ComponentModel.Composition enthalten ist!