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, aby se část vyjádřila do kontejneru, aby se vyplnila z dostupných exportů. V modelu programování s atributem jsou importy a exporty deklarovány dekorací tříd nebo členů s Import atributy.Export Atribut Export může ozdobit třídu, pole, vlastnost nebo metodu Import , zatímco atribut může ozdobit pole, vlastnost nebo konstruktor parametr.

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. Proto budou obě odvozeny z dekorované vlastnosti. V tomto případě bude IMyAddintyp kontraktu 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. Podporují se 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 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 MyLogger typ kontraktu IMyAddinnamísto . 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 se shodovaly s tímto exportem, musí část importu deklarovat odpovídajícího člena. 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, opožděných, požadovaných a volitelných.

Dynamické importy

V některých případech může být třída importu chtít shodovat s exporty libovolného typu, který má konkrétní název kontraktu. 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 { }

Třída importu musí být samozřejmě připravená k řešení objektu libovolného typu.

Opožděné importy

V některých případech může třída importu vyžadovat nepřímý odkaz na importovaný objekt, aby objekt nebyl okamžitě vytvořena instance. 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 opoždě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í kompozičním motorem. 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 kolekce. 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 normálně umožňuje exporty a importy vytvořit 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 požadované importy, které musí být vyplněny před exportem z objektu, který je vyžaduje, je možné použít.

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í nebude neúspěš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. Místo toho je také možné použít běžnou matici (IMyAddin[]).

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 tomu, aby se část zjistila jako součást katalogu. Tato část může být například základní třídou, která má být zděděna, ale není použita. Existují dva způsoby, jak toho dosáhnout. Nejprve můžete použít abstract klíčové slovo v části třídy. 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 DataTwo , že je abstraktní, nebude zjištěn. Vzhledem k tomu, že DataThree byl atribut použit 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 zobrazení metadat

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 opoždě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ří instanci pouze IPlugin objekty, 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 části vždy součástí se stejnými importy jako 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 části dědí a poskytnou stejný export, včetně názvu kontraktu a typu kontraktu. 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. NumTwozdědí z NumOne, takže NumTwo bude import .IMyData Běžné exporty nejsou zděděné, takže NumTwo se nic neexportuje. NumFour dědí z NumThree. Vzhledem k tomu NumThree , že se používá InheritedExport, NumFour má jeden export s typem NumThreekontraktu . 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 atributu a zahrnuje ho InheritedExport . Vzhledem k tomu MegaLogger , že znovu deklaruje nová metadata s názvem Stav, nedědí metadata název a verze 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 části. 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 Export atributy ani Import atributy. Rozhraní však může být zdobeno atributem InheritedExport na úrovni rozhraní a že export spolu s 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í atributy exportu a InheritedExportlze je rozšířit tak, Export aby zahrnovaly metadata jako vlastnosti atributu. 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 zděděná z ExportAttribute (nebo InheritedExportAttribute) 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 vytváření

Když část určuje import a složení se provede, kontejner 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ý by se mohl změnit z mnoha 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á kopie se proto vytvoří instance pro každý import v kontejneru, který odpovídá jednomu z exportovaných kontraktů části. Vnitřní stav těchto kopií nebude sdílen. Tato zásada je vhodná pro části, ve kterých každý import vyžaduje vlastní vnitřní stav.

Import i export mohou určit zásadu vytvoření části, z hodnot Shared, NonSharednebo 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 import a export, jehož název kontraktu nebo typ kontraktu neodpovídá. Pokud import i export určíte Anynebo nezadáte zásadu vytvoření a výchozí Anynastavení, zásada vytváření se ve výchozím nastavení nastaví na sdílenou.

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, PartOne že se import i export ve výchozím nastavení Anysdílí. PartThree určuje zásadu Shared vytvoření, takže PartTwo bude PartThree 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 NonShared vytvoření. PartFive a PartSix každý obdrží samostatné kopie PartFour. PartSeven určuje zásadu Shared vytvoření. Vzhledem k tomu, že se nevyexportuje PartFour zásada Sharedvytváření , PartSeven import nic neodpovídá 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 dispozice

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 IDisposablea jako část čištění v Dispose něm bude volat Dispose všechny části, které vlastní. Z tohoto důvodu byste měli kontejner složení vždy likvidovat, když je a žádné části, které vlastní, už nejsou potřeba.

U dlouhodobých kontejnerů složení může být problém spotřeba paměti podle částí se zásadou vytváření nesdílených kontejnerů. Tyto nesdílené části lze vytvořit vícekrát a nebudou uvolněny, dokud se samotný kontejner neodepíše. Pro řešení tohoto postupu poskytuje kontejner metodu ReleaseExport . Voláním této metody pro nesdílený export se odebere export z kontejneru složení a odstraní ho. Čá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 se odložily samotný kontejner složení.

IPartImportsSatisfiedNotification obsahuje jednu metodu s názvem OnImportsSatisfied. Tato metoda je volána kompozičním kontejnerem na všech částech, které implementují rozhraní při dokončení složení a importy částí jsou připraveny k použití. Díly jsou vytvořeny kompozičním motorem k vyplnění importu 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.