共用方式為


屬性化程式設計模型概觀 (MEF)

在 Managed Extensibility Framework (MEF) 中, 程式設計模型 是定義 MEF 運作之一組概念物件的特定方法。 這些概念物件包括元件、匯入和匯出。 MEF 會使用這些物件,但未指定應該如何表示它們。 因此,可以進行各種不同的程序設計模型,包括自定義的程序設計模型。

MEF 中使用的預設程式設計模型是 屬性化程序設計模型。 在屬性化程序設計模型元件中,會使用裝飾一般 .NET Framework 類別的屬性來定義匯入、導出和其他物件。 本主題說明如何使用屬性化程序設計模型所提供的屬性來建立MEF應用程式。

匯入和匯出基本概念

匯出是元件提供給容器中其他元件的值,而入是元件向容器表示的要求,必須從可用的導出填入。 在屬性化程式設計模型中,匯入和導出是藉由使用ImportExport屬性裝飾類別或成員來宣告。 屬性 Export 可以裝飾類別、欄位、屬性或方法,而 Import 屬性可以裝飾欄位、屬性或建構函式參數。

為了讓匯入與導出相符,匯入和導出必須有相同的 合約。 合約包含稱為 合約名稱的字串,以及導出或匯入物件的型別,稱為 合約類型。 只有當合約名稱和合約類型都相符時,匯出才被視為符合特定匯入。

任一或兩個合約參數可以是隱含或明確的。 下列程式代碼顯示宣告基本匯入的類別。

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

在此匯入中 Import ,屬性既沒有合約類型,也沒有附加合約名稱參數。 因此,這兩個方面都會從被裝飾的屬性中推斷出來。 在此情況下,合約類型會是 IMyAddin,而合約名稱將是從合約類型建立的唯一字元串。 (換句話說,只有名稱也從類型 IMyAddin 推斷的匯出項目才會與合約名稱匹配。)

以下顯示符合先前匯入的匯出的內容。

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

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

在此匯出中,合約類型是 IMyAddin,因為它被指定為 Export 屬性的參數。 導出的類型必須與合約類型相同、衍生自合約類型,或如果是介面,則實作合約類型。 在這裡匯出中,實際類型 MyLogger 會實作 介面 IMyAddin。 合約名稱是從合約類型推斷而來,這表示此匯出將會符合先前的匯入。

備註

匯出和匯入通常應該在公用類別或成員上宣告。 支援其他宣告,但導出或匯入私人、受保護或內部成員會中斷元件的隔離模型,因此不建議這麼做。

合約類型必須完全一致,匯出與匯入才能被視為相符。 請考慮下列出口。

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

在這裡匯出中,合約類型是 MyLogger ,而不是 IMyAddin。 即使 MyLogger 實作 IMyAddin,因此可以轉換成 IMyAddin 物件,但此導出將不會符合先前的匯入,因為合約類型不相同。

一般而言,不需要指定合約名稱,而且大部分的合約都應該以合約類型和元數據來定義。 不過,在某些情況下,請務必直接指定合約名稱。 最常見的情況是類別導出數個共用通用類型的值,例如基本類型。 合約名稱可以指定為 ImportExport 屬性的第一個參數。 下列程式碼顯示匯入和匯出的合約名稱為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;
}

如果未指定合約類型,它仍會從匯入或導出的類型推斷。 不過,即使明確指定合約名稱,合約類型也必須完全相符,才能將匯入和匯出視為相符專案。 例如,如果 MajorRevision 欄位是字串,則推斷的合約類型不會相符,而且匯出與匯入不符,儘管具有相同合約名稱。

匯入和匯出方法

屬性 Export 也可以以與類別、屬性或函式相同的方式裝飾方法。 方法匯出必須指定合約類型或合約名稱,因為無法推斷類型。 指定的型別可以是自訂委派或泛型型別,例如 Func。 下列類別會匯出名為 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);
}

在這堂課中 DoSomething 方法會採用單一 int 參數並回傳 string。 若要符合此導出,匯入元件必須宣告適當的成員。 下列類別會匯入 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; }
}

如需如何使用 Func<T, T> 物件的詳細資訊,請參閱 Func<T,TResult>

匯入類型

MEF 支援數種匯入類型,包括動態、延遲、必要條件和選擇性。

動態匯入

在某些情況下,匯入類別可能會想要匹配任何具有特定合約名稱的類型匯出。 在此案例中,類別可以宣告 動態匯入。 下列匯入會比對合約名稱為 「TheString」 的任何導出。

Public Class MyClass1

    <Import("TheString")>
    Public Property MyAddin

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

dynamic 關鍵詞推斷合約類型時,它會比對任何合約類型。 在此情況下,匯入應該 一律 指定要比對的合約名稱。 (如果未指定合約名稱,則會將匯入視為不符合任何匯出。下列兩個導出都會符合先前的匯入。

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

顯然,匯入類別必須準備好處理任意類型的物件。

延遲匯入

在某些情況下,匯入類別可能需要匯入物件的間接參考,因此不會立即具現化物件。 在此案例中,類別可以使用合約類型來宣告Lazy<T>。 下列匯入屬性會宣告延遲匯入。

Public Class MyClass1

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

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

從組合引擎的觀點來看,的 Lazy<T> 合約類型與的 T合約類型相同。 因此,先前的匯入會符合下列匯出。

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

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

合約名稱和合約類型可以在Import屬性中為延遲匯入指定,如先前在“基礎匯入和匯出”部分所述。

必要的匯入項目

導出的MEF零件通常由組合引擎建立,這是為了回應直接要求或填補符合的匯入需求。 根據預設,建立元件時,組合引擎會使用無參數建構函式。 若要讓引擎使用不同的建構函式,您可以使用 屬性來標記它 ImportingConstructor

每個元件可能只有一個建構函式可供組合引擎使用。 提供無參數建構函式和沒有 ImportingConstructor 屬性,或提供一個 ImportingConstructor 以上的屬性,將會產生錯誤。

若要填滿以 ImportingConstructor 屬性標示的建構函式參數,所有參數都會自動宣告為匯入。 這是宣告部分初始化期間所使用之匯入的便利方式。 下列類別會使用 ImportingConstructor 宣告匯入。

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

根據預設, ImportingConstructor 屬性會針對所有參數匯入使用推斷的合約類型和合約名稱。 您可以藉由使用 Import 屬性裝飾參數來覆寫這個參數,然後可以明確定義合約類型和合約名稱。 下列程式代碼示範使用這個語法匯入衍生類別而非父類別的建構函式。

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

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

特別是,您應該小心收集參數。 例如,如果您在參數類型為 ImportingConstructor 的建構函式中指定 IEnumerable<int>,則匯入會匹配類型為 IEnumerable<int> 的單一匯出,而非類型為 int 的多個匯出。 若要比對 int 類型的一組匯出,您必須使用 ImportMany 屬性來裝飾參數。

屬性 ImportingConstructor 宣告為匯入的參數也會標示為 必要條件匯入。 MEF 通常允許匯出和匯入形成 迴圈。 例如,迴圈是物件 A 匯入物件 B,接著會匯入物件 A。在一般情況下,迴圈不是問題,而組合容器通常會建構這兩個物件。

當元件建構函式需要匯入的值時,該對象就無法參與迴圈。 如果物件 A 要求先建構物件 B,才能自行建構物件 B,而物件 B 會匯入物件 A,則迴圈將無法解決,而且會發生組合錯誤。 因此,在建構函式參數上宣告的匯入是先決條件匯入,這些匯入必須全部完成,才能使用需要這些匯入的物件的任何輸出。

選擇性匯入

屬性 Import 會指定元件運作的需求。 如果無法完成匯入,該元件的組成將會失敗,而且元件將無法使用。

您可以使用屬性來指定匯入為AllowDefault。 在這種情況下,即使匯入不匹配任何可用的匯出,匯入屬性也將被設定為其屬性類型的預設值(物件屬性為null、布爾值為false,數值屬性則為零)。以下類別將運用選擇性匯入。

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屬性只有在符合唯一一個導出時,才會成功組合。 其他情況會產生結構錯誤。 若要匯入多個符合相同合約的導出,請使用 ImportMany 屬性。 以此屬性標示的匯入一律為選擇性。 例如,如果沒有相符的出口,組合將不會失敗。 下列類別會匯入 類型 IMyAddin的任何數目匯出。

Public Class MyClass1

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

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

您可以使用一 IEnumerable<T> 般語法和方法來存取匯入的陣列。 您也可以改用一般陣列 (IMyAddin[]) 。

當您將其與 Lazy<T> 語法搭配使用時,此模式非常重要。 例如,藉由使用 ImportManyIEnumerable<T>Lazy<T>,您可以匯入任意數目對象的間接參考,並只具現化所需的物件。 下列類別顯示此模式。

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

避免被發現

在某些情況下,您可能會想要防止某一部分被發現為目錄的一部分。 例如,這個元件可能是個供繼承的基類,但不會直接使用。 有兩種方式可以達成此目的。 首先,您可以在 abstract 元件類別上使用 關鍵詞。 抽象類絕不會提供導出,不過它們可以提供衍生自它們的類別繼承導出。

如果無法將類別設為抽象,您可以使用 屬性來裝飾它 PartNotDiscoverable 。 以此屬性裝飾的元件將不會包含在任何目錄中。 下列範例示範這些模式。 目錄將會發現 DataOne。 因為 DataTwo 是抽象的,所以不會探索它。 由於 DataThree 已使用 PartNotDiscoverable 屬性,因此不會探索它。

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

元數據和元數據檢視

匯出可以提供自己稱為 元數據的其他資訊。 元數據可用來將匯出物件的屬性傳達至匯入元件。 匯入元件可以使用此數據來決定要使用的導出,或收集導出的相關信息,而不需要建構匯出。 因此,匯入必須延遲才能使用元數據。

若要使用元數據,您通常會宣告稱為 元數據檢視的介面,這會宣告可用的元數據。 元數據檢視介面必須只有屬性,而且這些屬性必須有 get 存取子。 下列介面是範例元數據檢視。

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

您也可以使用泛型集合 IDictionary<string, object>做為元數據檢視,但這會喪失型別檢查的優點,因此應該避免。

一般而言,元數據檢視中所有提到的屬性都是必需的,任何未提供這些屬性的出口都不會被視為匹配。 屬性 DefaultValue 會指定屬性是選擇性的。 如果未包含 屬性,則會將指定為 參數的 DefaultValue預設值指派給 。 以下是以元數據裝飾的兩個不同的類別。 這兩個類別都會符合先前的元數據檢視。

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

元數據會使用 Export 屬性在 ExportMetadata 屬性之後表示。 每個元數據片段都是由名稱/值組所組成。 元數據的名稱部分必須符合元數據檢視中適當屬性的名稱,並將值指派給該屬性。

是匯入者指定將使用何種元數據檢視(如果有的話)。 具有元數據的匯入被宣告為 lazy import,且元數據介面作為 Lazy<T,T> 的第二個類型參數。 下列類別會使用元數據匯入上一個部分。

Public Class Addin

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

在許多情況下,您會想要結合元數據與 ImportMany 屬性,以便剖析可用的匯入,並選擇並只具現化一個,或篩選集合以符合特定條件。 下列類別只會 IPlugin 實例化具有 Name 「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;
    }
}

匯入和導出繼承

如果類別繼承自元件,該類別也可能成為元件。 匯入一律由子類別繼承。 因此,元件的子類別一律會是元件,其匯入與父類別相同。

使用Export標籤宣告的匯出不會被子類別繼承。 不過,部件可以使用InheritedExport屬性來匯出本身。 元件的子類別將會繼承並提供相同的導出,包括合約名稱和合約類型。 Export不同於屬性,InheritedExport只能在類別層級套用,而不是在成員層級套用。 因此,成員層級的功能永遠無法被繼承。

下列四個類別示範匯入和導出繼承的原則。 NumTwo 繼承自 NumOne,因此 NumTwo 會匯入 IMyData。 一般導出不會被繼承,所以 NumTwo 不會匯出任何東西。 NumFour 繼承自 NumThree。 由於 NumThree 使用了 InheritedExportNumFour 因此匯出了一個合約類型為 NumThree 的項目。 成員層級的輸出永遠不會被繼承,因此 IMyData 不會被輸出。

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

如果有與 InheritedExport 屬性相關聯的元數據,該元數據也會繼承。 (如需詳細資訊,請參閱先前的 "元數據及元數據檢視" 一節。)子類別無法修改繼承的元數據。 不過,藉由使用相同的合約名稱和合約類型重新宣告 InheritedExport 屬性,但使用新的元數據,子類別就可以以新的元數據取代繼承的元數據。 下列類別示範此原理。 元件 MegaLogger 繼承自 Logger ,並包含 InheritedExport 屬性。 由於 MegaLogger 重新宣告名為 Status 的新元數據,因此不會從 Logger繼承 Name 和 Version 元數據。

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

重新宣告 InheritedExport 屬性以覆寫元數據時,請確定合約類型相同。 (在上一個範例中, IPlugin 是合約類型。)如果它們不同,而不是覆寫,第二個屬性會從元件建立第二個獨立匯出。 一般而言,這表示您必須在覆寫 InheritedExport 屬性時明確指定合約類型,如上一個範例所示。

由於介面無法直接具現化,因此通常不能以 ExportImport 屬性裝飾它們。 不過,介面可以使用介面層級的InheritedExport屬性進行裝飾,並且該匯出以及任何相關的元數據將會被任何實作類別繼承。 不過,介面本身將無法作為一部分使用。

自定義匯出屬性

您可以擴充基本匯出屬性 ExportInheritedExport,以包含元數據作為屬性屬性。 這項技術適用於將類似元數據套用至許多部分,或建立元數據屬性的繼承樹狀結構。

自訂屬性可以指定合約類型、合約名稱或任何其他元數據。 必須將繼承自 ExportAttributeInheritedExportAttribute 的類別以 MetadataAttribute 屬性裝飾,才能定義自定義屬性。 下列類別會定義自定義屬性。

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

此類別會定義名為 MyAttribute 的自定義屬性,其中包含合約類型 IMyAddin ,以及一些名為的 MyMetadata元數據。 以 MetadataAttribute 屬性標示的類別中的所有屬性都會被視為自定義屬性中定義的元數據。 下列兩個宣告相等。

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

在第一個宣告中,會明確定義合約類型和元數據。 在第二個宣告中,合約類型和元數據會隱含在自定義屬性中。 特別是在必須套用大量相同元數據給許多部分(例如作者或著作權資訊)的情況下,使用自定義屬性可以節省大量時間和重複。 此外,可以建立自定義屬性的繼承樹狀結構,以允許變化。

若要在自訂屬性中建立選擇性元數據,您可以使用 DefaultValue 屬性。 當此屬性套用至自定義屬性類別中的屬性時,它會指定裝飾屬性是選擇性的,而且不需要由匯出者提供。 如果未提供屬性的值,則會為其屬性類型指派預設值(通常是 nullfalse或 0。

創建政策

當元件指定匯入和組合執行時,組合容器會嘗試尋找相符的匯出。 如果匯入能夠成功匹配到匯出,那麼匯入成員將被設定為匯出物件的實例。 此實例的來源是由導出元件的 建立原則所控制。

這兩個可能的建立策略是共用非共用。 具有共用建立原則的元件,將會在該合約之元件的每個匯入容器之間共用。 當組合引擎找到匹配的部分且需要設定匯入屬性時,如果該部分尚不存在,則會實例化一個新的副本;否則,將提供現有的副本。 這表示許多物件可能具有相同元件的參考。 這類元件不應依賴可能從許多位置變更的內部狀態。 此原則適用於靜態元件、提供服務的元件,以及耗用大量記憶體或其他資源的元件。

每次找到其中一個導出的相符匯入時,都會建立具有非共用建立原則的元件。 因此,系統會針對容器中符合其中一個導出合約的匯入,具現化新的複本。 這些複本的內部狀態將不會分享。 此原則適用於每個匯入都需要自己的內部狀態的部分。

匯入和匯出都可以從SharedNonSharedAny這些值中指定元件的建立原則。 預設值為 Any,適用於匯入和匯出。 指定 SharedNonShared 的匯出,只有當匯入指定相同或指定 Any 時,才能匹配。 同樣地,指定SharedNonShared的匯入只會匹配指定相同的匯出,或者指定Any的匯出。 與合約名稱或合約類型不相符的匯入和匯出一樣,使用不相容建立原則的匯入和匯出也不會被視為相符。 如果匯入和匯出同時指定Any,或未指定建立原則且預設為Any,則建立原則將預設為共用。

下列範例顯示設定創建策略的匯入和匯出。 PartOne 不會指定建立原則,因此預設值為 AnyPartTwo 不會指定建立原則,因此預設值為 Any。 由於匯入和匯出都會預設為 AnyPartOne 因此將會共用。 PartThreeShared 指定建立原則,因此 PartTwoPartThree 會共用相同的複本 PartOnePartFour指定了一個NonShared建立原則,因此PartFourPartFive中將不會共用。 PartSix 指定建立 NonShared 原則。 PartFivePartSix 會各自接收到 PartFour 的複本。 PartSeven 指定建立 Shared 原則。 因為 沒有任何符合「建立原則 PartFour」的 Shared 匯出,因此 PartSeven 匯入不會匹配任何項目,也不會被填入。

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

生命週期和處置

由於元件裝載於組合容器中,因此其生命週期可能會比一般物件更複雜。 元件可以實作兩個重要的生命週期相關介面: IDisposableIPartImportsSatisfiedNotification

需要在關機時執行工作的元件,或需要釋放資源的元件,應該像 .NET Framework 物件的常規實作方式一樣,實作 IDisposable。 然而,由於容器會建立及維護對部分的參考,因此只有擁有某部分的容器應該在該部分上呼叫Dispose方法。 容器本身會實作 IDisposable,並在 Dispose 中作為清除工作的一部分,會對其擁有的所有部分呼叫 Dispose。 因此,您應在不再需要組合容器及其所擁有的任何元件時,將其處置。

對於長期存在的組合容器,具有非共享創建策略的元件的記憶體使用量可能會成為問題。 這些非共享部分可以被多次創建,並且在容器本身被處置之前將不會被處置。 為了處理這個問題,容器會提供 ReleaseExport 方法。 在非共享匯出上調用此方法時,會從組合容器中移除該匯出並將其處置掉。 被移除的導出所屬的元件,以及在該樹狀結構下延伸的部分,亦會一併移除和處置。 如此一來,就可以回收資源,而不需要處置組合容器本身。

IPartImportsSatisfiedNotification 包含一個名為 OnImportsSatisfied的方法。 當組合完成且元件匯入可供使用時,撰寫容器會在任何實作 介面的元件上呼叫此方法。 元件是由組合引擎所建立,以填滿其他元件的匯入。 在尚未設定元件的匯入之前,您不能在元件建構函式中執行任何依賴或操作匯入值的初始化,除非這些匯入值已使用 ImportingConstructor 屬性指定為必要條件。 這通常是慣用的方法,但在某些情況下,建構函式插入可能無法使用。 在這些情況下,可以在OnImportsSatisfied中執行初始化,而元件應該實作IPartImportsSatisfiedNotification