Sdílet prostřednictvím


Přehled programovacího modelu s atributy (MEF)

V rozhraní MEF (Managed Extensibility Framework) je programovací model konkrétní metodou definování sady konceptuálních objektů, na kterých pracuje MEF. Tyto koncepční objekty zahrnují části, importy a exporty. Funkce MEF používá tyto objekty, ale nezadá způsob jejich znázornění. Proto je možné použít širokou škálu programovacích modelů, včetně přizpůsobených programovacích modelů.

Výchozím programovacím modelem používaným v MEF je přiřazený programovací model. V přiřazovaných částí programovacího modelu jsou importy, exporty a další objekty definovány atributy, které ozdobí běžné třídy rozhraní .NET Framework. Toto téma vysvětluje, jak pomocí atributů poskytovaných programovacím modelem s atributy vytvořit aplikaci MEF.

Základy importu a exportu

Export je hodnota, kterou část poskytuje jiným částem v kontejneru, a import je požadavek, kterou část sděluje kontejneru, která má být naplněna z dostupných exportů. V modelu programování založeném na atributech jsou importy a exporty deklarovány dekorováním tříd nebo členů atributy Import a Export. Atribut Export může ozdobit třídu, pole, vlastnost nebo metodu, zatímco atribut Import může ozdobit pole, vlastnost nebo parametr konstruktoru.

Aby se import shodoval s exportem, musí mít import a export stejný kontrakt. Kontrakt se skládá z řetězce, který se nazývá název kontraktu, a typ exportovaného nebo importovaného objektu, který se nazývá typ kontraktu. Pouze pokud se název kontraktu i typ kontraktu shodují, je export považován za splnění konkrétního importu.

Buď nebo oba parametry kontraktu mohou být implicitní nebo explicitní. Následující kód ukazuje třídu, která deklaruje základní import.

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

V tomto importu Import nemá atribut žádný typ kontraktu ani připojený parametr názvu kontraktu. Obě budou tedy odvozeny z dekorované vlastnosti. V tomto případě bude typ kontraktu IMyAddin, a název kontraktu bude jedinečný řetězec vytvořený z typu kontraktu. (Jinými slovy, název kontraktu bude odpovídat pouze exportům, jejichž názvy jsou odvozeny také z typu IMyAddin.)

Následující příklad ukazuje export, který odpovídá předchozímu importu.

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

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

V tomto exportu je IMyAddin typ kontraktu, protože je určen jako parametr atributu Export . Exportovaný typ musí být buď stejný jako typ kontraktu, odvozený od typu kontraktu, nebo implementovat typ kontraktu, pokud se jedná o rozhraní. V tomto exportu implementuje skutečný typ MyLogger rozhraní IMyAddin. Název kontraktu je odvozen z typu kontraktu, což znamená, že tento export bude odpovídat předchozímu importu.

Poznámka:

Exporty a importy by měly být obvykle deklarovány ve veřejných třídách nebo členech. Podporovány jsou i jiné deklarace, ale export nebo import soukromého, chráněného nebo interního člena přeruší model izolace pro tuto část, a proto se to nedoporučuje.

Typ kontraktu se musí přesně shodovat s exportem a importem, aby se považoval za shodu. Zvažte následující 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 { }

V tomto exportu je typ kontraktu MyLogger namísto IMyAddin. I když MyLogger implementuje IMyAddin, a proto lze přetypovat na IMyAddin objekt, tento export nebude odpovídat předchozímu importu, protože typy kontraktů nejsou stejné.

Obecně platí, že není nutné zadat název kontraktu a většina kontraktů by měla být definována z hlediska typu kontraktu a metadat. Za určitých okolností je však důležité zadat název smlouvy přímo. Nejběžnějším případem je, že třída exportuje několik hodnot, které sdílejí společný typ, například primitivy. Název kontraktu lze zadat jako první parametr atributuImport.Export Následující kód ukazuje import a export se zadaným názvem kontraktu 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;
}

Pokud typ kontraktu není zadaný, bude i nadále odvozen z typu importu nebo exportu. I když je však název kontraktu zadán explicitně, musí se typ kontraktu přesně shodovat s importem a exportem, který se má považovat za shodu. Pokud MajorRevision by například pole bylo řetězec, odvozené typy kontraktů se neshodovaly a export by se neshodoval s importem, i když má stejný název kontraktu.

Import a export metody

Atribut Export může také ozdobit metodu stejným způsobem jako třída, vlastnost nebo funkce. Export metody musí zadat typ kontraktu nebo název kontraktu, protože typ nelze odvodit. Zadaný typ může být buď vlastní delegát, nebo obecný typ, například Func. Následující třída exportuje metodu s názvem 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);
}

V této třídě metoda DoSomething přebírá jeden int parametr a vrátí hodnotu string. Aby odpovídala tomuto exportu, musí importující komponenta deklarovat odpovídající člen. Následující třída importuje metodu DoSomething .

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; }
}

Další informace o použití objektu Func<T, T> naleznete v tématu Func<T,TResult>.

Typy importů

MEF podporuje několik typů importu, včetně dynamických, líných, nezbytných a volitelných.

Dynamické importy

V některých případech může chtít importující třída vyhovovat exportům jakéhokoli typu, které mají konkrétní název smlouvy. V tomto scénáři může třída deklarovat dynamický import. Následující import odpovídá jakémukoli exportu s názvem kontraktu "TheString".

Public Class MyClass1

    <Import("TheString")>
    Public Property MyAddin

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

Pokud je typ kontraktu odvozen z klíčového dynamic slova, bude odpovídat libovolnému typu kontraktu. V tomto případě by měl import vždy zadat název kontraktu, který se má shodovat. (Pokud není zadán žádný název smlouvy, bude import považován za odpovídající žádnému exportu.) Oba následující exporty by odpovídaly předchozímu importu.

<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 { }

Importující třída musí být samozřejmě připravena vyrovnat se s objektem libovolného typu.

Opožděné importy

V některých případech může importující třída vyžadovat nepřímý odkaz na importovaný objekt, tak aby instance objektu nebyla vytvořena okamžitě. V tomto scénáři může třída deklarovat opožděný import pomocí typu kontraktu Lazy<T>. Následující vlastnost importu deklaruje líný import.

Public Class MyClass1

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

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

Z hlediska kompozičního motoru se typ kontraktu Lazy<T> považuje za identický s typem Tsmlouvy . Předchozí import by proto odpovídal následujícímu exportu.

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

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

Název kontraktu a typ kontraktu lze zadat v atributu Import opožděného importu, jak je popsáno výše v části "Základní importy a exporty".

Import požadovaných součástí

Exportované části MEF obvykle vytváří modul složení v reakci na přímý požadavek nebo potřebu vyplnit odpovídající import. Při vytváření části používá modul složení ve výchozím nastavení konstruktor bez parametrů. Pokud chcete, aby modul používal jiný konstruktor, můžete ho označit atributem ImportingConstructor .

Každá část může mít pouze jeden konstruktor pro použití s kompozičním strojem. Poskytnutí žádného konstruktoru bez parametrů a žádného ImportingConstructor atributu nebo poskytnutí více než jednoho ImportingConstructor atributu způsobí chybu.

Chcete-li vyplnit parametry konstruktoru označeného atributem ImportingConstructor , všechny tyto parametry se automaticky deklarují jako importy. Toto je pohodlný způsob, jak deklarovat importy, které se používají během inicializace částí. Následující třída používá ImportingConstructor k deklaraci importu.

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;
    }
}

Ve výchozím nastavení ImportingConstructor atribut používá odvozené typy kontraktů a názvy kontraktů pro všechny importy parametrů. To je možné přepsat dekorací parametrů s Import atributy, které pak mohou definovat typ kontraktu a název kontraktu explicitně. Následující kód ukazuje konstruktor, který používá tuto syntaxi k importu odvozené třídy místo nadřazené třídy.

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

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

Zejména byste měli být opatrní s parametry sbírky. Pokud například zadáte ImportingConstructor u konstruktoru s parametrem typu IEnumerable<int>, import se bude shodovat s jedním exportem typu IEnumerable<int>místo sady exportů typu int. Chcete-li se shodovat se sadou exportů typu int, musíte parametr ozdobit atributem ImportMany .

Parametry deklarované jako importy atributu ImportingConstructor jsou také označeny jako požadované importy. MeF obvykle umožňuje exporty a importy formovat cyklus. Například cyklus je místo, kde objekt A importuje objekt B, který zase importuje objekt A. Za běžných okolností není cyklus problémem a složení kontejneru vytváří oba objekty normálně.

Pokud konstruktor části vyžaduje importovanou hodnotu, nemůže se tento objekt účastnit cyklu. Pokud objekt A vyžaduje, aby byl objekt B vytvořen předtím, než jej lze vytvořit sám, a objekt B importuje objekt A, cyklus nebude schopen vyřešit a dojde k chybě složení. Importy deklarované u parametrů konstruktoru jsou proto nutné importy, které musí být vyplněny předtím, než může být použito jakékoli exportování z objektu, který je vyžaduje.

Volitelné importy

Atribut Import určuje požadavek, aby část fungovala. Pokud nelze provést import, složení této části selže a část nebude k dispozici.

Pomocí vlastnosti můžete určit, že import je volitelnýAllowDefault. V tomto případě bude složení úspěšné, i když import neodpovídá žádným dostupným exportům a vlastnost importu bude nastavena na výchozí hodnotu pro její typ vlastnosti (null pro vlastnosti objektu, false pro logické hodnoty nebo nula pro číselné vlastnosti.) Následující třída používá volitelný 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.
}

Import více objektů

Atribut Import se úspěšně vytvoří, pouze pokud odpovídá jednomu a pouze jednomu exportu. Jiné případy způsobí chybu složení. Pokud chcete importovat více než jeden export, který odpovídá stejnému ImportMany kontraktu, použijte atribut. Importy označené tímto atributem jsou vždy volitelné. Například složení nedojde k selhání, pokud nejsou k dispozici žádné odpovídající exporty. Následující třída importuje libovolný počet exportů typu IMyAddin.

Public Class MyClass1

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

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

K importovanému poli lze přistupovat pomocí běžné IEnumerable<T> syntaxe a metod. Je také možné použít běžné pole (IMyAddin[]) místo toho.

Tento vzor může být velmi důležitý, když ho použijete v kombinaci se syntaxí Lazy<T> . Například pomocí funkce ImportMany, IEnumerable<T>a Lazy<T>, můžete importovat nepřímé odkazy na libovolný počet objektů a vytvořit pouze instance těch, které jsou nezbytné. Následující třída ukazuje tento vzor.

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; }
}

Vyhněte se zjišťování

V některých případech můžete chtít zabránit, aby byla část objevena jako součást katalogu. Tato část může být například základní třídou, která je určena pro dědění, ale není použita. Existují dva způsoby, jak toho dosáhnout. Nejprve můžete použít abstract klíčové slovo u třídy součásti. Abstraktní třídy nikdy neposkytují exporty, i když mohou poskytovat zděděné exporty do tříd, které jsou z nich odvozeny.

Pokud třídu nelze vytvořit abstraktní, můžete ji ozdobit atributem PartNotDiscoverable . Část zdobená tímto atributem nebude zahrnuta do žádných katalogů. Následující příklad ukazuje tyto vzory. DataOne bude zjištěn katalogem. Vzhledem k tomu, že DataTwo je abstraktní, nebude objeven. Vzhledem k tomu, že DataThree použil atribut PartNotDiscoverable, nebude zjištěn.

<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.
}

Metadata a pohledy na metadata

Exporty můžou poskytovat další informace o sobě označovaných jako metadata. Metadata lze použít ke sdělení vlastností exportovaného objektu do části importu. Část importu může tato data použít k rozhodnutí, které exporty se mají použít, nebo ke shromažďování informací o exportu, aniž by bylo nutné je vytvářet. Z tohoto důvodu musí být import líný, aby používal metadata.

Pokud chcete používat metadata, obvykle deklarujete rozhraní známé jako zobrazení metadat, které deklaruje, jaká metadata budou k dispozici. Rozhraní zobrazení metadat musí mít pouze vlastnosti a tyto vlastnosti musí obsahovat get přístupové objekty. Následující rozhraní je příkladem zobrazení metadat.

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; }
}

Je také možné použít obecnou kolekci , IDictionary<string, object>jako zobrazení metadat, ale to propadá výhody kontroly typů a mělo by se jim vyhnout.

Obvykle jsou požadovány všechny vlastnosti pojmenované v zobrazení metadat a všechny exporty, které je neposkytují, se nepovažují za shodu. Atribut DefaultValue určuje, že vlastnost je volitelná. Pokud vlastnost není zahrnuta, bude přiřazena výchozí hodnota zadaná jako parametr .DefaultValue Následují dvě různé třídy zdobené metadaty. Obě tyto třídy by odpovídaly předchozímu zobrazení metadat.

<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
{
}

Metadata se vyjadřují za Export atributem pomocí atributu ExportMetadata . Každá část metadat se skládá z dvojice název/hodnota. Část s názvem metadat musí odpovídat názvu příslušné vlastnosti v zobrazení metadat a hodnota bude přiřazena k této vlastnosti.

Jedná se o dovozce, který určuje, jaké zobrazení metadat ( pokud existuje) se použije. Import s metadaty je deklarován jako opožděný import s rozhraním metadat jako druhým parametrem typu .Lazy<T,T> Následující třída importuje předchozí část s metadaty.

Public Class Addin

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

V mnoha případech budete chtít zkombinovat metadata s ImportMany atributem, abyste mohli analyzovat dostupné importy a vybrat a vytvořit instanci pouze jedné kolekce nebo filtrovat kolekci tak, aby odpovídala určité podmínce. Následující třída vytvoří instance pouze objektů IPlugin, které mají Name hodnotu "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 a export dědičnosti

Pokud třída dědí z části, může se tato třída stát také součástí. Importy se vždy dědí podtřídami. Proto bude podtřída komponent vždy součástí, používající stejné importy jako její nadřazená třída.

Exporty deklarované pomocí atributu Export nejsou zděděny podtřídami. Část se ale může exportovat pomocí atributu InheritedExport . Podtřídy komponenty zdědí a poskytnou stejnou exportní funkcionalitu, včetně názvu smlouvy a typu smlouvy. Na rozdíl od atributu ExportInheritedExport lze použít pouze na úrovni třídy, a ne na úrovni člena. Exporty na úrovni členů se proto nikdy nedědí.

Následující čtyři třídy demonstrují principy dědičnosti importu a exportu. NumTwo je zděděno z NumOne, takže NumTwo bude importovat IMyData. Běžné exporty nejsou děděné, takže NumTwo nic nebude exportováno. NumFour dědí z NumThree. Vzhledem k tomu NumThree, že se používá InheritedExport, NumFour má jeden export s typem NumThree kontraktu . Exporty na úrovni člena se nikdy nedědí, takže IMyData se neexportují.

<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.
}

Pokud jsou k atributu InheritedExport přidružená metadata, zdědí se také tato metadata. (Další informace najdete v předchozí části Metadata a zobrazení metadat.) Zděděná metadata nelze upravit podtřídou. Opětovným deklarováním atributu se stejným názvem kontraktu InheritedExport a typem kontraktu, ale s novými metadaty může podtřída nahradit zděděná metadata novými metadaty. Následující třída demonstruje tento princip. Část MegaLogger dědí z Logger a zahrnuje atribut InheritedExport. Protože MegaLogger znovu deklaruje nová metadata s názvem Status, nedědí metadata jméno a verzi z Logger.

<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.
}

Při opětovném deklarování atributu InheritedExport pro přepsání metadat se ujistěte, že typy kontraktů jsou stejné. (V předchozím příkladu IPlugin je typ kontraktu.) Pokud se liší, místo přepsání druhý atribut vytvoří druhý nezávislý export z komponenty. Obecně to znamená, že při přepsání InheritedExport atributu budete muset explicitně zadat typ kontraktu, jak je znázorněno v předchozím příkladu.

Vzhledem k tomu, že rozhraní nelze vytvořit instanci přímo, nelze je obecně dekorovat atributy Export ani Import. Rozhraní však může být zdobeno atributem InheritedExport na úrovni rozhraní a ten export spolu se všemi přidruženými metadaty bude zděděn všemi implementačními třídami. Samotné rozhraní však nebude k dispozici jako součást.

Vlastní atributy exportu

Základní exportní atributy, Export a InheritedExport, lze rozšířit tak, aby zahrnovaly metadata jako atributové vlastnosti. Tato technika je užitečná pro použití podobných metadat na mnoho částí nebo vytvoření stromu dědičnosti atributů metadat.

Vlastní atribut může zadat typ kontraktu, název kontraktu nebo jakákoli jiná metadata. Aby bylo možné definovat vlastní atribut, musí být třída, která dědí z ExportAttribute (nebo InheritedExportAttribute,) ozdobena atributem MetadataAttribute. Následující třída definuje vlastní atribut.

<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; }
}

Tato třída definuje vlastní atribut pojmenovaný MyAttribute s typem IMyAddin kontraktu a některá metadata s názvem MyMetadata. Všechny vlastnosti ve třídě označené atributem MetadataAttribute jsou považovány za metadata definovaná ve vlastním atributu. Následující dvě deklarace jsou ekvivalentní.

<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; }

V první deklaraci se explicitně definuje typ kontraktu a metadata. Ve druhé deklaraci jsou typ kontraktu a metadata implicitní v přizpůsobeném atributu. Zejména v případech, kdy je nutné použít velké množství identických metadat na mnoho částí (například autor nebo informace o autorských právech), může použití vlastního atributu ušetřit spoustu času a duplikace. Dále je možné vytvořit stromy dědičnosti vlastních atributů, které umožňují varianty.

Pokud chcete vytvořit volitelná metadata ve vlastním atributu, můžete tento atribut použít DefaultValue . Pokud se tento atribut použije na vlastnost ve vlastní třídě atributu, určuje, že dekorovaná vlastnost je volitelná a nemusí být dodána vývozcem. Pokud není zadána hodnota vlastnosti, bude přiřazena výchozí hodnota pro jeho typ vlastnosti (obvykle null, falsenebo 0.)

Zásady tvorby

Když část určuje import a kompozice je provedena, kontejner pro složení se pokusí najít odpovídající export. Pokud se import úspěšně shoduje s exportem, je člen importu nastavený na instanci exportovaného objektu. Kde tato instance pochází, je řízena zásadami vytváření části exportu.

Dvě možné zásady vytváření jsou sdílené a nesdílené. Část se zásadou vytvoření sdíleného sdílení se bude sdílet mezi každým importem v kontejneru pro část s tímto kontraktem. Když modul složení najde shodu a musí nastavit vlastnost importu, vytvoří instanci nové kopie části pouze v případě, že ještě neexistuje; v opačném případě poskytne existující kopii. To znamená, že mnoho objektů může mít odkazy na stejnou část. Tyto části by se neměly spoléhat na vnitřní stav, který může být změněn z různých míst. Tato zásada je vhodná pro statické části, části poskytující služby a části, které spotřebovávají velké množství paměti nebo jiných prostředků.

Při každém nalezení odpovídajícího importu pro jeden z jeho exportů se vytvoří část se zásadami vytváření nesdílených. Nová instance se proto vytvoří pro každý import v kontejneru, který odpovídá jednomu z kontraktů exportovaných částí. Vnitřní stav těchto kopií nebude sdílen. Tato zásada je vhodná pro komponenty, u kterých každý import vyžaduje svůj vlastní vnitřní stav.

Import i export mohou určit zásadu vytvoření části mezi hodnotami Shared, NonShared, nebo Any. Výchozí hodnota je Any pro importy i exporty. Export, který určuje Shared nebo NonShared bude odpovídat pouze importu, který určuje stejné nebo určuje Any. Podobně import, který určuje Shared nebo NonShared bude odpovídat pouze exportu, který určuje stejné nebo určuje Any. Importy a exporty s nekompatibilními zásadami vytváření se nepovažují za shodu, stejně jako importy a exporty, jejichž název kontraktu nebo typ kontraktu neodpovídá. Pokud import i export specifikují Any nebo nezadáte zásadu vytvoření a výchozí nastavení je Any, zásada vytvoření se ve výchozím nastavení nastaví jako sdílená.

Následující příklad ukazuje importy i exporty určující zásady vytváření. PartOne nezadá zásadu vytváření, takže výchozí hodnota je Any. PartTwo nezadá zásadu vytváření, takže výchozí hodnota je Any. Vzhledem k tomu, že import i export mají jako výchozí nastavení Any, bude PartOne sdíleno. PartThree určuje politiku vytváření Shared, takže PartTwo a PartThree budou sdílet stejnou kopii PartOne. PartFour určuje zásadu NonShared vytváření, takže PartFour nebude sdílena v PartFive. PartSix určuje zásadu vytvoření NonShared. PartFive a PartSix každý obdrží samostatné kopie PartFour. PartSeven určuje zásadu vytvoření Shared. Vzhledem k tomu, že není exportováno PartFour se zásadou vytváření Shared, PartSeven import neodpovídá ničemu a nebude vyplněn.

<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.
}

Životní cyklus a likvidace

Vzhledem k tomu, že části jsou hostovány v kontejneru složení, jejich životní cyklus může být složitější než běžné objekty. Části mohou implementovat dvě důležitá rozhraní související s životním cyklem: IDisposable a IPartImportsSatisfiedNotification.

Části, které vyžadují provedení práce při vypnutí nebo které potřebují uvolnit prostředky by měly implementovat IDisposable, jako obvykle pro objekty rozhraní .NET Framework. Vzhledem k tomu, že kontejner vytváří a udržuje odkazy na části, měl by metodu Dispose volat pouze kontejner, který vlastní část. Samotný kontejner implementuje IDisposable, a při čištění v Dispose bude volat Dispose na všechny části, které vlastní. Z tohoto důvodu byste měli vždy likvidovat kontejner složení, když už nejsou potřeba on ani žádné jeho části.

U dlouhožijících kontejnerů složení může spotřeba paměti částí s politikou vytváření jako nesdílených být problémem. Tyto nesdílené části lze vytvořit opakovaně a nebudou odstraněny, dokud se samotný kontejner neodstraní. Pro řešení tohoto postupu poskytuje kontejner metodu ReleaseExport . Voláním této metody pro nesdílený export se odebere export z kontejneru konfigurace a odstraní se. Části, které se používají pouze odebranými exporty, a tak dále dolů ve stromu, se také odeberou a odstraní. Tímto způsobem se prostředky dají uvolnit, aniž by bylo nutné odstranit samotný kontejner sestavy.

IPartImportsSatisfiedNotification obsahuje jednu metodu s názvem OnImportsSatisfied. Tato metoda je volána kompozičním kontejnerem na jakýchkoli částech, které implementují rozhraní, když bylo dokončeno složení a importy částí jsou připraveny k použití. Části jsou vytvořeny kompozičním enginem k vyplnění importů jiných částí. Před nastavením importu části nelze provést žádnou inicializaci, která spoléhá na importované hodnoty v konstruktoru částí ani s nimi manipuluje, pokud tyto hodnoty nebyly zadány jako předpoklady pomocí atributu ImportingConstructor . To je obvykle upřednostňovaná metoda, ale v některých případech nemusí být injektáž konstruktoru k dispozici. V těchto případech lze inicializaci provést v OnImportsSatisfieda část by měla implementovat IPartImportsSatisfiedNotification.