Freigeben über


Übersicht über attributbasiertes Programmiermodell (MEF)

Im Managed Extensibility Framework (MEF) ist ein Programmiermodell eine bestimmte Methode zum Definieren der Gruppe konzeptioneller Objekte, auf denen MEF ausgeführt wird. Zu diesen konzeptionellen Objekten gehören Teile, Importe und Exporte. MEF verwendet diese Objekte, gibt jedoch nicht an, wie sie dargestellt werden sollen. Daher sind eine Vielzahl von Programmiermodellen möglich, einschließlich angepasster Programmiermodelle.

Das in MEF verwendete Standardprogrammiermodell ist das Attributprogrammiermodell. In den zugeordneten Programmiermodellteilen werden Importe, Exporte und andere Objekte mit Attributen definiert, die gewöhnliche .NET Framework-Klassen versehen. In diesem Thema wird erläutert, wie Sie die vom Attributprogrammiermodell bereitgestellten Attribute verwenden, um eine MEF-Anwendung zu erstellen.

Grundlagen des Imports und Exports

Ein Export ist ein Wert, den ein Teil für andere Teile im Container bereitstellt. Ein Import ist der Ausdruck einer Anforderung, die ein Teil an den Container stellt, damit er diese aus den verfügbaren Exporten erfüllt. Im Attributierten Programmiermodell werden Importe und Exporte durch das Dekorieren von Klassen oder Membern mit den Attributen Import und Export deklariert. Das Export Attribut kann eine Klasse, ein Feld, eine Eigenschaft oder eine Methode schmücken, während das Import Attribut ein Feld, eine Eigenschaft oder einen Konstruktorparameter schmücken kann.

Damit ein Import mit einem Export abgeglichen werden kann, muss der Import und Export denselben Vertrag haben. Der Vertrag besteht aus einer Zeichenfolge, die als Vertragsname bezeichnet wird, und dem Typ des exportierten oder importierten Objekts, der als Vertragstyp bezeichnet wird. Nur wenn sowohl der Vertragsname als auch der Vertragstyp übereinstimmen, wird ein Export als Erfüllung eines bestimmten Imports angesehen.

Eine oder beide Vertragsparameter können implizit oder explizit sein. Der folgende Code zeigt eine Klasse, die einen einfachen Import deklariert.

Public Class MyClass1
    <Import()>
    Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
    [Import]
    public IMyAddin MyAddin { get; set; }
}

In diesem Import hat das Import Attribut weder einen Vertragstyp noch einen Vertragsnamenparameter angefügt. Daher werden beide in der ergänzten Eigenschaft abgeleitet. In diesem Fall lautet IMyAddinder Vertragstyp , und der Vertragsname ist eine eindeutige Zeichenfolge, die aus dem Vertragstyp erstellt wird. (Mit anderen Worten, der Vertragsname entspricht nur Exporten, deren Namen ebenfalls vom Typ IMyAddinabgeleitet werden.)

Im Folgenden sehen Sie einen Export, der dem vorherigen Import entspricht.

<Export(GetType(IMyAddin))>
Public Class MyLogger
    Implements IMyAddin

End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }

In diesem Export ist der Vertragstyp IMyAddin, da er als Parameter des Export Attributs angegeben wird. Der exportierte Typ muss entweder identisch mit dem Vertragstyp sein, vom Vertragstyp abgeleitet sein oder den Vertragstyp implementieren, wenn es sich um eine Schnittstelle handelt. In diesem Export implementiert der tatsächliche Typ MyLogger die Schnittstelle IMyAddin. Der Vertragsname wird vom Vertragstyp abgeleitet, was bedeutet, dass dieser Export mit dem vorherigen Import übereinstimmt.

Hinweis

Exporte und Importe sollten normalerweise für öffentliche Klassen oder Member deklariert werden. Andere Deklarationen werden unterstützt, aber das Exportieren oder Importieren eines privaten, geschützten oder internen Members unterbricht das Isolationsmodell für den Teil und wird daher nicht empfohlen.

Der Vertragstyp muss genau übereinstimmen, damit der Export und Import als Übereinstimmung betrachtet werden. Betrachten Sie den folgenden Export.

<Export()> 'WILL NOT match the previous import!
Public Class MyLogger
    Implements IMyAddin

End Class
[Export] //WILL NOT match the previous import!
public class MyLogger : IMyAddin { }

In diesem Export ist MyLogger der Vertragstyp anstelle von IMyAddin. Obwohl MyLoggerIMyAddin implementiert und daher in ein IMyAddin-Objekt umgewandelt werden kann, entspricht dieser Export nicht dem vorherigen Import, da die Vertragstypen nicht identisch sind.

Im Allgemeinen ist es nicht erforderlich, den Vertragsnamen anzugeben, und die meisten Verträge sollten in Bezug auf den Vertragstyp und die Metadaten definiert werden. Unter bestimmten Umständen ist es jedoch wichtig, den Vertragsnamen direkt anzugeben. Der häufigste Fall ist, wenn eine Klasse mehrere Werte exportiert, die einen gemeinsamen Typ aufweisen, z. B. Grundtypen. Der Vertragsname kann als erster Parameter des Import Oder Export Attributs angegeben werden. Der folgende Code zeigt einen Import und einen Export mit einem angegebenen Vertragsnamen von MajorRevision.

Public Class MyExportClass

    'This one will match
    <Export("MajorRevision")>
    Public ReadOnly Property MajorRevision As Integer
        Get
            Return 4
        End Get
    End Property

    <Export("MinorRevision")>
    Public ReadOnly Property MinorRevision As Integer
        Get
            Return 16
        End Get
    End Property
End Class
public class MyClass
{
    [Import("MajorRevision")]
    public int MajorRevision { get; set; }
}

public class MyExportClass
{
    [Export("MajorRevision")] //This one will match.
    public int MajorRevision = 4;

    [Export("MinorRevision")]
    public int MinorRevision = 16;
}

Wenn der Vertragstyp nicht angegeben ist, wird er weiterhin vom Typ des Import- oder Exporttyps abgeleitet. Auch wenn der Vertragsname explizit angegeben ist, muss der Vertragstyp auch genau übereinstimmen, damit der Import- und Exportvorgang als Übereinstimmung betrachtet wird. Wenn das MajorRevision Feld beispielsweise eine Zeichenfolge war, würden die abgeleiteten Vertragstypen nicht übereinstimmen, und der Export würde nicht mit dem Import übereinstimmen, obwohl derselbe Vertragsname vorhanden ist.

Importieren und Exportieren einer Methode

Das Export Attribut kann auch eine Methode auf die gleiche Weise wie eine Klasse, Eigenschaft oder Funktion dekorieren. Methodenexporte müssen einen Vertragstyp oder vertragsnamen angeben, da der Typ nicht abgeleitet werden kann. Der angegebene Typ kann entweder ein benutzerdefinierter Delegat oder ein generischer Typ sein, wie z. B. Func. Die folgende Klasse exportiert eine Methode mit dem Namen DoSomething.

Public Class MyAddin

    'Explicitly specifying a generic type
    <Export(GetType(Func(Of Integer, String)))>
    Public Function DoSomething(ByVal TheParam As Integer) As String
        Return Nothing 'Function body goes here
    End Function

End Class
public class MyAddin
{
    //Explicitly specifying a generic type.
    [Export(typeof(Func<int, string>))]
    public string DoSomething(int TheParam);
}

In dieser Klasse verwendet die DoSomething Methode einen einzelnen int Parameter und gibt einen string. Um diesem Export zu entsprechen, muss die importierende Komponente ein entsprechendes Element deklarieren. Die folgende Klasse importiert die DoSomething Methode.

Public Class MyClass1

    'Contract name must match!
    <Import()>
    Public Property MajorRevision As Func(Of Integer, String)
End Class
public class MyClass
{
    [Import] //Contract name must match!
    public Func<int, string> DoSomething { get; set; }
}

Weitere Informationen zur Verwendung des Func<T, T> Objekts finden Sie unter Func<T,TResult>.

Importtypen

MEF unterstützt mehrere Importtypen, einschließlich "Dynamisch", "Verzögert", "Erforderlich" und "Optional".

Dynamische Importe

In einigen Fällen soll die importierende Klasse mit Exporten eines beliebigen Typs übereinstimmen, die einen bestimmten Vertragsnamen besitzen. In diesem Szenario kann die Klasse einen dynamischen Import deklarieren. Der folgende Import stimmt mit jedem Export mit dem Vertragsnamen "TheString" überein.

Public Class MyClass1

    <Import("TheString")>
    Public Property MyAddin

End Class
public class MyClass
{
    [Import("TheString")]
    public dynamic MyAddin { get; set; }
}

Wenn der Vertragstyp aus dem dynamic Schlüsselwort abgeleitet wird, stimmt er mit jedem Vertragstyp überein. In diesem Fall sollte ein Import immer einen Vertragsnamen angeben, der übereinstimmen soll. (Wenn kein Vertragsname angegeben ist, wird der Import als nicht mit Exporten übereinstimmend betrachtet.) Die beiden folgenden Exporte würden mit dem vorherigen Import übereinstimmen.

<Export("TheString", GetType(IMyAddin))>
Public Class MyLogger
    Implements IMyAddin

End Class

<Export("TheString")>
Public Class MyToolbar

End Class
[Export("TheString", typeof(IMyAddin))]
public class MyLogger : IMyAddin { }

[Export("TheString")]
public class MyToolbar { }

Offensichtlich muss die importierende Klasse darauf vorbereitet sein, ein Objekt von beliebigem Typ zu behandeln.

Lazy Imports

In einigen Fällen erfordert die importierende Klasse möglicherweise einen indirekten Verweis auf das importierte Objekt, sodass das Objekt nicht sofort instanziiert wird. In diesem Szenario kann die Klasse einen faulen Import mithilfe eines Vertragstyps deklarieren Lazy<T>. Die folgende importierende Eigenschaft deklariert einen verzögerten Import.

Public Class MyClass1

    <Import()>
    Public Property MyAddin As Lazy(Of IMyAddin)

End Class
public class MyClass
{
    [Import]
    public Lazy<IMyAddin> MyAddin { get; set; }
}

Aus Sicht des Kompositionsmoduls gilt ein Vertragstyp Lazy<T> als identisch mit dem Vertragstyp von T. Daher würde der vorherige Import mit dem folgenden Export übereinstimmen.

<Export(GetType(IMyAddin))>
Public Class MyLogger
    Implements IMyAddin

End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }

Der Vertragsname und der Vertragstyp können im Import Attribut für einen faulen Import angegeben werden, wie weiter oben im Abschnitt "Grundlegende Importe und Exporte" beschrieben.

Erforderliche Importe

Exportierte MEF-Teile werden in der Regel vom Kompositionsmodul erstellt, als Reaktion auf eine direkte Anforderung oder die Notwendigkeit, einen übereinstimmenden Import auszufüllen. Standardmäßig verwendet das Kompositionsmodul beim Erstellen eines Teils den parameterlosen Konstruktor. Damit die Engine einen anderen Konstruktor verwendet, können Sie sie mit dem ImportingConstructor Attribut markieren.

Jeder Teil verfügt möglicherweise nur über einen Konstruktor für die Verwendung durch die Kompositions-Engine. Wenn Sie keinen parameterlosen Konstruktor und kein ImportingConstructor Attribut angeben oder mehrere ImportingConstructor Attribute angeben, wird ein Fehler erzeugt.

Um die Parameter eines Konstruktors auszufüllen, der mit dem ImportingConstructor Attribut gekennzeichnet ist, werden alle diese Parameter automatisch als Importe deklariert. Dies ist eine einfache Möglichkeit zum Deklarieren von Importen, die während der Teileinitialisierung verwendet werden. Die folgende Klasse verwendet ImportingConstructor , um einen Import zu deklarieren.

Public Class MyClass1

    Private _theAddin As IMyAddin

    'Parameterless constructor will NOT be used
    'because the ImportingConstructor
    'attribute is present.
    Public Sub New()

    End Sub

    'This constructor will be used.
    'An import with contract type IMyAddin
    'is declared automatically.
    <ImportingConstructor()>
    Public Sub New(ByVal MyAddin As IMyAddin)
        _theAddin = MyAddin
    End Sub

End Class
public class MyClass
{
    private IMyAddin _theAddin;

    //Parameterless constructor will NOT be
    //used because the ImportingConstructor
    //attribute is present.
    public MyClass() { }

    //This constructor will be used.
    //An import with contract type IMyAddin is
    //declared automatically.
    [ImportingConstructor]
    public MyClass(IMyAddin MyAddin)
    {
        _theAddin = MyAddin;
    }
}

Standardmäßig verwendet das ImportingConstructor Attribut abgeleitete Vertragstypen und Vertragsnamen für alle Parameterimporte. Sie können dies überschreiben, indem Sie die Parameter mit Import Attributen versehen, wodurch der Vertragstyp und der Vertragsname explizit definiert werden können. Der folgende Code veranschaulicht einen Konstruktor, der diese Syntax verwendet, um eine abgeleitete Klasse anstelle einer übergeordneten Klasse zu importieren.

<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)

End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
    _theAddin = MyAddin;
}

Insbesondere sollten Sie mit Sammlungsparametern vorsichtig sein. Wenn Sie z. B. ImportingConstructor für einen Konstruktor mit einem Parameter vom Typ IEnumerable<int> angeben, entspricht der Import einem einzelnen Export vom Typ IEnumerable<int>, anstelle einer Gruppe von Exporten vom Typ int. Um einer Gruppe von Exporten vom Typ intzu entsprechen, müssen Sie den Parameter mit dem ImportMany Attribut versehen.

Parameter, die als Importe durch das ImportingConstructor Attribut deklariert werden, werden auch als erforderliche Importe gekennzeichnet. MEF erlaubt normalerweise Exporten und Importen, einen Zyklus zu bilden. Ein Zyklus liegt beispielsweise vor, wenn Objekt A Objekt B importiert, das wiederum Objekt A importiert. Unter normalen Umständen ist ein Zyklus kein Problem, und der Kompositionscontainer konstruiert beide Objekte normalerweise.

Wenn ein importierter Wert vom Konstruktor eines Teils erforderlich ist, kann dieses Objekt nicht an einem Zyklus teilnehmen. Wenn objekt A erfordert, dass objekt B erstellt werden muss, bevor es selbst erstellt werden kann, und Objekt B importiert Objekt A, dann kann der Zyklus nicht aufgelöst werden, und ein Kompositionsfehler tritt auf. Importe, die für Konstruktorparameter deklariert sind, sind daher erforderliche Importe, die alle ausgefüllt werden müssen, bevor alle Exporte aus dem Objekt, für das sie erforderlich sind, verwendet werden können.

Optionale Importe

Das Import Attribut gibt eine Anforderung für die Funktion des Teils an. Wenn ein Import nicht erfüllt werden kann, schlägt die Zusammensetzung dieses Teils fehl, und der Teil ist nicht verfügbar.

Sie können angeben, dass ein Import optional ist, indem Sie die AllowDefault Eigenschaft verwenden. In diesem Fall wird die Komposition erfolgreich ausgeführt, auch wenn der Import keinem verfügbaren Export entspricht, und die Importeigenschaft wird auf den Standardwert für den Eigenschaftentyp festgelegt (null für Objekteigenschaften, false für Booleans oder Null für numerische Eigenschaften.) Die folgende Klasse verwendet einen optionalen Import.

Public Class MyClass1

    <Import(AllowDefault:=True)>
    Public Property thePlugin As Plugin

    'If no matching export is available,
    'thePlugin will be set to null.
End Class
public class MyClass
{
    [Import(AllowDefault = true)]
    public Plugin thePlugin { get; set; }

    //If no matching export is available,
    //thePlugin will be set to null.
}

Importieren mehrerer Objekte

Das Import Attribut wird nur erfolgreich erstellt, wenn es mit einem und nur einem Export übereinstimmt. Andere Fälle führen zu einem Kompositionsfehler. Verwenden Sie das ImportMany Attribut, um mehrere Exporte zu importieren, die demselben Vertrag entsprechen. Mit diesem Attribut gekennzeichnete Importe sind immer optional. Die Komposition schlägt z. B. nicht fehl, wenn keine übereinstimmenden Exporte vorhanden sind. Die folgende Klasse importiert eine beliebige Anzahl von Exporten vom Typ IMyAddin.

Public Class MyClass1

    <ImportMany()>
    Public Property MyAddin As IEnumerable(Of IMyAddin)

End Class
public class MyClass
{
    [ImportMany]
    public IEnumerable<IMyAddin> MyAddin { get; set; }
}

Auf das importierte Array kann mithilfe gewöhnlicher IEnumerable<T> Syntax und Methoden zugegriffen werden. Es ist auch möglich, stattdessen ein normales Array (IMyAddin[]) zu verwenden.

Dieses Muster kann sehr wichtig sein, wenn Sie es in Kombination mit der Lazy<T> Syntax verwenden. Zum Beispiel können Sie durch die Verwendung von ImportMany, IEnumerable<T> und Lazy<T> indirekte Verweise auf eine beliebige Anzahl von Objekten importieren und nur diejenigen instanziieren, die erforderlich werden. Die folgende Klasse zeigt dieses Muster.

Public Class MyClass1

    <ImportMany()>
    Public Property MyAddin As IEnumerable(Of Lazy(Of IMyAddin))

End Class
public class MyClass
{
    [ImportMany]
    public IEnumerable<Lazy<IMyAddin>> MyAddin { get; set; }
}

Vermeiden der Ermittlung

In einigen Fällen möchten Sie möglicherweise verhindern, dass ein Teil als Teil eines Katalogs erkannt wird. Beispielsweise kann es sich bei dem Teil um eine Basisklasse handeln, die geerbt werden soll, aber nicht verwendet wird. Hierfür gibt es zwei Möglichkeiten. Zuerst können Sie das abstract Schlüsselwort für die Part-Klasse verwenden. Abstrakte Klassen stellen niemals Exporte bereit, obwohl sie geerbte Exporte in Klassen bereitstellen können, die von ihnen abgeleitet werden.

Wenn die Klasse nicht abstrahierbar ist, können Sie sie mit dem PartNotDiscoverable Attribut versehen. Ein teil, der mit diesem Attribut versehen ist, wird nicht in katalogen enthalten sein. Im folgenden Beispiel werden diese Muster veranschaulicht. DataOne wird vom Katalog gefunden. Da DataTwo abstrakt ist, wird es nicht entdeckt. Da DataThree das PartNotDiscoverable Attribut verwendet wurde, wird es nicht entdeckt.

<Export()>
Public Class DataOne
    'This part will be discovered
    'as normal by the catalog.
End Class

<Export()>
Public MustInherit Class DataTwo
    'This part will not be discovered
    'by the catalog.
End Class

<PartNotDiscoverable()>
<Export()>
Public Class DataThree
    'This part will also not be discovered
    'by the catalog.
End Class
[Export]
public class DataOne
{
    //This part will be discovered
    //as normal by the catalog.
}

[Export]
public abstract class DataTwo
{
    //This part will not be discovered
    //by the catalog.
}

[PartNotDiscoverable]
[Export]
public class DataThree
{
    //This part will also not be discovered
    //by the catalog.
}

Metadaten und Metadatenansichten

Exporte können zusätzliche Informationen zu sich selbst bereitstellen, die als Metadaten bezeichnet werden. Metadaten können verwendet werden, um Eigenschaften des exportierten Objekts an den importierbaren Teil zu übermitteln. Der Importteil kann diese Daten verwenden, um zu entscheiden, welche Exporte verwendet werden sollen, oder um Informationen zu einem Export zu sammeln, ohne ihn erstellen zu müssen. Aus diesem Grund muss ein Import verzögert sein, um Metadaten verwenden zu können.

Um Metadaten zu verwenden, deklarieren Sie in der Regel eine Schnittstelle, die als Metadatenansicht bezeichnet wird, wodurch deklariert wird, welche Metadaten verfügbar sind. Die Metadatenansichtsschnittstelle darf nur Eigenschaften aufweisen, und diese Eigenschaften müssen über get Accessoren verfügen. Die folgende Schnittstelle ist eine Beispielmetadatenansicht.

Public Interface IPluginMetadata

    ReadOnly Property Name As String

    <DefaultValue(1)>
    ReadOnly Property Version As Integer

End Interface
public interface IPluginMetadata
{
    string Name { get; }

    [DefaultValue(1)]
    int Version { get; }
}

Es ist auch möglich, als Metadatenansicht eine generische Auflistung ( IDictionary<string, object>) zu verwenden, doch dadurch gehen die Vorteile der Typüberprüfung verloren, weshalb dies vermieden werden sollte.

Im Allgemeinen sind alle im Metadatensatz genannten Eigenschaften erforderlich, und alle Exporte, die diese nicht bereitstellen, werden nicht als übereinstimmend betrachtet. Das DefaultValue Attribut gibt an, dass eine Eigenschaft optional ist. Wenn die Eigenschaft nicht enthalten ist, wird ihm der Standardwert zugewiesen, der als Parameter angegeben DefaultValueist. Es folgen zwei verschiedene Klassen, die mit Metadaten versehen sind. Beide Klassen entsprechen der vorherigen Metadatenansicht.

<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class MyLogger
    Implements IPlugin

End Class

'Version is not required because of the DefaultValue
<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Disk Writer")>
Public Class DWriter
    Implements IPlugin

End Class
[Export(typeof(IPlugin)),
    ExportMetadata("Name", "Logger"),
    ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
}

[Export(typeof(IPlugin)),
    ExportMetadata("Name", "Disk Writer")]
    //Version is not required because of the DefaultValue
public class DWriter : IPlugin
{
}

Metadaten werden nach dem Export Attribut mithilfe des ExportMetadata Attributs ausgedrückt. Jedes Metadatenelement besteht aus einem Namen/Wert-Paar. Der Namensteil der Metadaten muss mit dem Namen der entsprechenden Eigenschaft in der Metadatenansicht übereinstimmen, und der Wert wird dieser Eigenschaft zugewiesen.

Es ist der Importeur, der angibt, welche Metadatenansicht (falls vorhanden) verwendet wird. Ein Import mit Metadaten wird als Lazy-Import deklariert, wobei die Metadatenschnittstelle als zweiter Typparameter bei Lazy<T,T> verwendet wird. Die folgende Klasse importiert den vorherigen Teil mit Metadaten.

Public Class Addin

    <Import()>
    Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
    [Import]
    public Lazy<IPlugin, IPluginMetadata> plugin;
}

In vielen Fällen möchten Sie Metadaten mit dem ImportMany Attribut kombinieren, um die verfügbaren Importe zu analysieren und nur eines auszuwählen und zu instanziieren oder eine Auflistung zu filtern, um einer bestimmten Bedingung zu entsprechen. Die folgende Klasse instanziiert nur IPlugin Objekte mit dem Name Wert "Logger".

Public Class User

    <ImportMany()>
    Public Property plugins As IEnumerable(Of Lazy(Of IPlugin, IPluginMetadata))

    Public Function InstantiateLogger() As IPlugin

        Dim logger As IPlugin
        logger = Nothing

        For Each Plugin As Lazy(Of IPlugin, IPluginMetadata) In plugins
            If Plugin.Metadata.Name = "Logger" Then
                logger = Plugin.Value
            End If
        Next
        Return logger
    End Function

End Class
public class User
{
    [ImportMany]
    public IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins;

    public IPlugin InstantiateLogger()
    {
        IPlugin logger = null;

        foreach (Lazy<IPlugin, IPluginMetadata> plugin in plugins)
        {
            if (plugin.Metadata.Name == "Logger")
                logger = plugin.Value;
        }
        return logger;
    }
}

Import- und Exportvererbung

Wenn eine Klasse von einem Teil erbt, kann sie selbst auch ein Teil werden. Importe werden immer von Unterklassen geerbt. Daher ist eine Unterklasse eines Teils immer ein Teil mit denselben Importen wie die übergeordnete Klasse.

Exporte, die mithilfe des Export Attributs deklariert werden, werden nicht von Unterklassen geerbt. Ein Teil kann sich jedoch mithilfe des InheritedExport Attributs selbst exportieren. Unterklassen des Teils erben und stellen denselben Export bereit, einschließlich Vertragsname und Vertragstyp. Im Gegensatz zu einem Export Attribut InheritedExport kann nur auf Klassenebene und nicht auf Memberebene angewendet werden. Aus diesem Grund können Exporte auf der Ebene der Mitglieder niemals geerbt werden.

Die folgenden vier Klassen veranschaulichen die Prinzipien der Import- und Exportvererbung. NumTwo erbt von NumOne, also wird NumTwoIMyData importieren. Gewöhnliche Exporte werden nicht geerbt, sodass NumTwo keine Daten exportiert. NumFour erbt von NumThree. Da NumThreeInheritedExport verwendet, hat NumFour einen Export mit Vertragstyp NumThree. Exporte auf Memberebene werden nie geerbt, sodass IMyData nicht exportiert wird.

<Export()>
Public Class NumOne
    <Import()>
    Public Property MyData As IMyData
End Class

Public Class NumTwo
    Inherits NumOne

    'Imports are always inherited, so NumTwo will
    'Import IMyData

    'Ordinary exports are not inherited, so
    'NumTwo will NOT export anything.  As a result it
    'will not be discovered by the catalog!

End Class

<InheritedExport()>
Public Class NumThree

    <Export()>
    Public Property MyData As IMyData

    'This part provides two exports, one of
    'contract type NumThree, and one of
    'contract type IMyData.

End Class

Public Class NumFour
    Inherits NumThree

    'Because NumThree used InheritedExport,
    'this part has one export with contract
    'type NumThree.

    'Member-level exports are never inherited,
    'so IMyData is not exported.

End Class
[Export]
public class NumOne
{
    [Import]
    public IMyData MyData { get; set; }
}

public class NumTwo : NumOne
{
    //Imports are always inherited, so NumTwo will
    //import IMyData.

    //Ordinary exports are not inherited, so
    //NumTwo will NOT export anything. As a result it
    //will not be discovered by the catalog!
}

[InheritedExport]
public class NumThree
{
    [Export]
    Public IMyData MyData { get; set; }

    //This part provides two exports, one of
    //contract type NumThree, and one of
    //contract type IMyData.
}

public class NumFour : NumThree
{
    //Because NumThree used InheritedExport,
    //this part has one export with contract
    //type NumThree.

    //Member-level exports are never inherited,
    //so IMyData is not exported.
}

Wenn einem InheritedExport Attribut Metadaten zugeordnet sind, werden diese Metadaten ebenfalls geerbt. (Weitere Informationen finden Sie im abschnitt "Metadaten- und Metadatenansichten".) Geerbte Metadaten können von der Unterklasse nicht geändert werden. Durch erneutes Deklarieren des InheritedExport Attributs mit demselben Vertragsnamen und -vertragstyp, aber mit neuen Metadaten kann die Unterklasse die geerbten Metadaten durch neue Metadaten ersetzen. Die folgende Klasse veranschaulicht dieses Prinzip. Der MegaLogger Teil erbt von Logger und enthält das InheritedExport Attribut. Da MegaLogger neu deklarierte Metadaten mit dem Namen Status verwendet, erbt es die Metadaten "Name" und "Version" von Logger nicht.

<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class Logger
    Implements IPlugin

    'Exports with contract type IPlugin
    'and metadata "Name" and "Version".
End Class

Public Class SuperLogger
    Inherits Logger

    'Exports with contract type IPlugin and
    'metadata "Name" and "Version", exactly the same
    'as the Logger class.

End Class

<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Status", "Green")>
Public Class MegaLogger
    Inherits Logger

    'Exports with contract type IPlugin and
    'metadata "Status" only. Re-declaring
    'the attribute replaces all metadata.

End Class
[InheritedExport(typeof(IPlugin)),
    ExportMetadata("Name", "Logger"),
    ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
    //Exports with contract type IPlugin and
    //metadata "Name" and "Version".
}

public class SuperLogger : Logger
{
    //Exports with contract type IPlugin and
    //metadata "Name" and "Version", exactly the same
    //as the Logger class.
}

[InheritedExport(typeof(IPlugin)),
    ExportMetadata("Status", "Green")]
public class MegaLogger : Logger        {
    //Exports with contract type IPlugin and
    //metadata "Status" only. Re-declaring
    //the attribute replaces all metadata.
}

Stellen Sie beim erneuten Deklarieren des InheritedExport Attributs zum Überschreiben von Metadaten sicher, dass die Vertragstypen identisch sind. (Im vorherigen Beispiel ist IPlugin der Vertragstyp.) Wenn sie sich unterscheiden, erstellt das zweite Attribut anstatt zu überschreiben einen zweiten, unabhängigen Export aus dem Teil. Im Allgemeinen bedeutet dies, dass Sie den Vertragstyp explizit angeben müssen, wenn Sie ein InheritedExport Attribut außer Kraft setzen, wie im vorherigen Beispiel gezeigt.

Da Schnittstellen nicht direkt instanziiert werden können, können sie in der Regel nicht mit Export oder Import Attributen versehen werden. Eine Schnittstelle kann jedoch mit einem InheritedExport Attribut auf Schnittstellenebene versehen werden, und dieser Export mit allen zugeordneten Metadaten wird von allen implementierenden Klassen geerbt. Die Schnittstelle selbst ist jedoch nicht als Teil verfügbar.

Benutzerdefinierte Exportattribute

Die grundlegenden Exportattribute, Export und InheritedExport, können erweitert werden, um Metadaten als Attributeigenschaften einzuschließen. Diese Technik ist nützlich, um ähnliche Metadaten auf viele Teile anzuwenden oder eine Vererbungsstruktur von Metadatenattributen zu erstellen.

Ein benutzerdefiniertes Attribut kann den Vertragstyp, den Vertragsnamen oder andere Metadaten angeben. Um ein benutzerdefiniertes Attribut zu definieren, muss eine Klasse, die von ExportAttribute (oder InheritedExportAttribute) erbt, mit dem MetadataAttribute Attribut versehen werden. Die folgende Klasse definiert ein benutzerdefiniertes Attribut.

<MetadataAttribute()>
<AttributeUsage(AttributeTargets.Class, AllowMultiple:=false)>
Public Class MyAttribute
    Inherits ExportAttribute

    Public Property MyMetadata As String

    Public Sub New(ByVal myMetadata As String)
        MyBase.New(GetType(IMyAddin))

        myMetadata = myMetadata
    End Sub

End Class
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MyAttribute : ExportAttribute
{
    public MyAttribute(string myMetadata)
        : base(typeof(IMyAddin))
    {
        MyMetadata = myMetadata;
    }

    public string MyMetadata { get; private set; }
}

Diese Klasse definiert ein benutzerdefiniertes Attribut mit dem Namen MyAttribute, dem Vertragstyp IMyAddin und einigen Metadaten namens MyMetadata. Alle Eigenschaften in einer Klasse, die mit dem MetadataAttribute Attribut gekennzeichnet ist, gelten als Metadaten, die im benutzerdefinierten Attribut definiert sind. Die folgenden beiden Deklarationen sind gleichwertig.

<Export(GetType(IMyAddin))>
<ExportMetadata("MyMetadata", "theData")>
Public Property myAddin As MyAddin
<MyAttribute("theData")>
Public Property myAddin As MyAddin
[Export(typeof(IMyAddin)),
    ExportMetadata("MyMetadata", "theData")]
public MyAddin myAddin { get; set; }
[MyAttribute("theData")]
public MyAddin myAddin { get; set; }

In der ersten Deklaration werden der Vertragstyp und die Metadaten explizit definiert. In der zweiten Deklaration sind der Vertragstyp und die Metadaten im benutzerdefinierten Attribut implizit. Insbesondere in Fällen, in denen eine große Menge identischer Metadaten auf viele Teile angewendet werden muss (z. B. Autoren- oder Copyrightinformationen), kann die Verwendung eines benutzerdefinierten Attributs viel Zeit und Duplizierung sparen. Darüber hinaus können Vererbungsstrukturen von benutzerdefinierten Attributen erstellt werden, um Variationen zu ermöglichen.

Um optionale Metadaten in einem benutzerdefinierten Attribut zu erstellen, können Sie das DefaultValue Attribut verwenden. Wenn dieses Attribut auf eine Eigenschaft in einer benutzerdefinierten Attributklasse angewendet wird, gibt es an, dass die versehene Eigenschaft optional ist und nicht von einem Exporter bereitgestellt werden muss. Wenn kein Wert für die Eigenschaft angegeben wird, wird ihm der Standardwert für den Eigenschaftstyp zugewiesen (normalerweise null, falseoder 0.)

Erstellungsrichtlinien

Wenn ein Teil einen Import spezifiziert und eine Zusammensetzung durchgeführt wird, versucht der Zusammensetzungscontainer, einen passenden Export zu finden. Wenn der Import erfolgreich mit einem Export übereinstimmt, wird das Importelement auf eine Instanz des exportierten Objekts gesetzt. Der Ort, von dem die Instanz stammt, wird durch die Erstellungsrichtliniedes exportierenden Teils bestimmt.

Die beiden möglichen Erstellungsrichtlinien sind geteilt und nicht geteilt. Ein Teil mit der Erstellungsrichtlinie "Freigegeben" wird zwischen jedem Import im Container für einen Teil mit diesem Vertrag freigegeben. Wenn das Kompositionsmodul eine Übereinstimmung findet und eine Importeigenschaft festlegen muss, instanziiert es nur dann eine neue Kopie des Teils, wenn noch keine vorhanden ist. andernfalls wird die vorhandene Kopie bereitgestellt. Dies bedeutet, dass viele Objekte verweise auf denselben Teil haben können. Solche Teile sollten sich nicht auf den internen Zustand verlassen, der an vielen Stellen geändert werden kann. Diese Richtlinie ist für statische Teile, Teile, die Dienste bereitstellen, und Komponenten geeignet, die viel Arbeitsspeicher oder andere Ressourcen verbrauchen.

Ein Teil mit der Erstellungsrichtlinie "Nicht freigegeben" wird bei jedem Auffinden eines übereinstimmenden Imports für einen der Exporte erstellt. Eine neue Kopie wird daher für jeden Importvorgang im Container instanziiert, der einem der exportierten Verträge der Komponente entspricht. Der interne Zustand dieser Kopien wird nicht freigegeben. Diese Richtlinie ist geeignet für Teile, bei denen jeder Import einen eigenen internen Zustand benötigt.

Sowohl der Import als auch der Export können die Erstellungsrichtlinie eines Teils mit den Werten Shared, NonSharedoder Anyangeben. Der Standardwert ist Any für Importe und Exporte. Ein Export, der Shared oder NonShared angibt, stimmt nur mit einem Import überein, der dasselbe angibt oder Any angibt. Entsprechend entspricht ein Import, der Shared oder NonShared angibt, nur einem Export, der dasselbe angibt oder Any angibt. Importe und Exporte mit inkompatiblen Erstellungsrichtlinien werden nicht als Übereinstimmung betrachtet, genauso wie ein Import und Export, dessen Vertragsname oder Vertragstyp keine Übereinstimmung darstellen. Wenn sowohl für Importe als auch Exporte Anyangegeben wird, oder wenn keine Erstellungsrichtlinie angegeben wird und standardmäßig Anyfestgelegt wird, wird für die Erstellungsrichtlinie als Standardeinstellung "Freigegeben" festgelegt.

Das folgende Beispiel zeigt sowohl Importe als auch Exporte, die Erstellungsrichtlinien angeben. PartOne gibt keine Erstellungsrichtlinie an, daher ist Anydie Standardeinstellung . PartTwo gibt keine Erstellungsrichtlinie an, daher ist Anydie Standardeinstellung . Da sowohl Import als auch Export standardmäßig auf Any gesetzt sind, wird PartOne gemeinsam genutzt. PartThree gibt eine Erstellungsrichtlinie Shared an, sodass PartTwo und PartThree dieselbe Kopie von PartOne teilen. PartFour legt eine Erstellungsrichtlinie NonShared fest, sodass PartFour nicht geteilt wird in PartFive. PartSix Gibt eine Erstellungsrichtlinie NonShared an. PartFive und PartSix erhalten jeweils separate Kopien von PartFour. PartSeven Gibt eine Erstellungsrichtlinie Shared an. Da es keinen exportierten PartFour mit der Erstellungsrichtlinie Sharedgibt, gibt es für den PartSeven -Import keine Entsprechung, und er wird nicht ausgefüllt.

<Export()>
Public Class PartOne
    'The default creation policy for an export is Any.
End Class

Public Class PartTwo

    <Import()>
    Public Property partOne As PartOne

    'The default creation policy for an import is Any.
    'If both policies are Any, the part will be shared.

End Class

Public Class PartThree

    <Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
    Public Property partOne As PartOne

    'The Shared creation policy is explicitly specified.
    'PartTwo and PartThree will receive references to the
    'SAME copy of PartOne.

End Class

<Export()>
<PartCreationPolicy(CreationPolicy.NonShared)>
Public Class PartFour
    'The NonShared creation policy is explicitly specified.
End Class

Public Class PartFive

    <Import()>
    Public Property partFour As PartFour

    'The default creation policy for an import is Any.
    'Since the export's creation policy was explicitly
    'defined, the creation policy for this property will
    'be non-shared.

End Class

Public Class PartSix

    <Import(RequiredCreationPolicy:=CreationPolicy.NonShared)>
    Public Property partFour As PartFour

    'Both import and export specify matching creation
    'policies.  PartFive and PartSix will each receive
    'SEPARATE copies of PartFour, each with its own
    'internal state.

End Class

Public Class PartSeven

    <Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
    Public Property partFour As PartFour

    'A creation policy mismatch.  Because there is no
    'exported PartFour with a creation policy of Shared,
    'this import does not match anything and will not be
    'filled.

End Class
[Export]
public class PartOne
{
    //The default creation policy for an export is Any.
}

public class PartTwo
{
    [Import]
    public PartOne partOne { get; set; }

    //The default creation policy for an import is Any.
    //If both policies are Any, the part will be shared.
}

public class PartThree
{
    [Import(RequiredCreationPolicy = CreationPolicy.Shared)]
    public PartOne partOne { get; set; }

    //The Shared creation policy is explicitly specified.
    //PartTwo and PartThree will receive references to the
    //SAME copy of PartOne.
}

[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class PartFour
{
    //The NonShared creation policy is explicitly specified.
}

public class PartFive
{
    [Import]
    public PartFour partFour { get; set; }

    //The default creation policy for an import is Any.
    //Since the export's creation policy was explicitly
    //defined, the creation policy for this property will
    //be non-shared.
}

public class PartSix
{
    [Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
    public PartFour partFour { get; set; }

    //Both import and export specify matching creation
    //policies.  PartFive and PartSix will each receive
    //SEPARATE copies of PartFour, each with its own
    //internal state.
}

public class PartSeven
{
    [Import(RequiredCreationPolicy = CreationPolicy.Shared)]
    public PartFour partFour { get; set; }

    //A creation policy mismatch.  Because there is no
    //exported PartFour with a creation policy of Shared,
    //this import does not match anything and will not be
    //filled.
}

Lebenszyklus und Entsorgen

Da Teile im Kompositionscontainer gehostet werden, kann ihr Lebenszyklus komplexer sein als gewöhnliche Objekte. Teile können zwei wichtige Lebenszyklusschnittstellen implementieren: IDisposable und IPartImportsSatisfiedNotification.

Teile, für die beim Herunterfahren bestimmte Schritte ausgeführt werden oder die Ressourcen freigeben müssen, sollten wie gewöhnlich für .NET Framework-Objekte IDisposableimplementieren. Da der Container jedoch Verweise auf Teile erstellt und verwaltet, sollte nur der Container, der einen Teil besitzt, die Dispose Methode darauf aufrufen. Der Container selbst implementiert IDisposable, und er ruft als Teil der Bereinigung in Dispose für alle Teile, die er besitzt, Dispose auf. Aus diesem Grund sollten Sie den Kompositionscontainer immer löschen, wenn er und alle Teile, die es besitzt, nicht mehr benötigt werden.

Für langlebige Kompositionscontainer, kann der Speicherbedarf von Teilen mit der Erstellungsrichtlinie "Nicht freigegeben" ein Problem darstellen. Diese nicht freigegebenen Teile können mehrmals erstellt werden und werden erst freigegeben, wenn der Container selbst freigegeben wurde. Um dies zu bewältigen, stellt der Container die ReleaseExport Methode bereit. Durch Aufrufen dieser Methode für einen nicht freigegebenen Export wird der Export aus dem Kompositionscontainer entfernt und freigegeben. Teile, die nur vom entfernten Export usw. weiter unten in der Struktur verwendet werden, werden ebenfalls entfernt und freigegeben. Auf diese Weise können Ressourcen wieder beansprucht werden, ohne den Kompositionscontainer selbst zu entsorgen.

IPartImportsSatisfiedNotification enthält eine Methode mit dem Namen OnImportsSatisfied. Diese Methode wird vom Kompositionscontainer für alle Teile aufgerufen, die die Schnittstelle implementieren, wenn die Komposition abgeschlossen wurde und die Importe des Teils einsatzbereit sind. Teile werden vom Kompositionsmodul erstellt, um die Importe anderer Teile auszufüllen. Bevor die Importe eines Teils festgelegt wurden, können Sie keine Initialisierung durchführen, die importierte Werte im Webpartkonstruktor verwendet oder bearbeitet, es sei denn, diese Werte wurden mit dem ImportingConstructor Attribut als Voraussetzungen angegeben. Dies ist normalerweise die bevorzugte Methode, doch in einigen Fällen ist die Konstruktoreinfügung möglicherweise nicht verfügbar. In diesen Fällen kann die Initialisierung in OnImportsSatisfiedausgeführt werden, und der Teil sollte IPartImportsSatisfiedNotificationimplementieren.