次の方法で共有


属性付きプログラミング モデルの概要 (MEF)

Managed Extensibility Framework (MEF) では、 プログラミング モデル は、MEF が動作する概念オブジェクトのセットを定義する特定の方法です。 これらの概念オブジェクトには、パーツ、インポート、エクスポートが含まれます。 MEF はこれらのオブジェクトを使用しますが、それらの表現方法は指定しません。 そのため、カスタマイズされたプログラミング モデルを含め、さまざまなプログラミング モデルが可能です。

MEF で使用される既定のプログラミング モデルは、 属性付きプログラミング モデルです。 属性付きプログラミング モデル パーツでは、インポート、エクスポート、およびその他のオブジェクトは、通常の .NET Framework クラスを装飾する属性で定義されます。 このトピックでは、属性付きプログラミング モデルによって提供される属性を使用して MEF アプリケーションを作成する方法について説明します。

インポートとエクスポートの基本

エクスポートは、パーツがコンテナー内の他のパーツに提供する値であり、インポートは、使用可能なエクスポートからデータを入力するために、パーツがコンテナーに対して表す要件です。 属性付きプログラミング モデルでは、インポートとエクスポートは、 Import 属性と Export 属性を使用してクラスまたはメンバーを修飾することによって宣言されます。 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 { }

このエクスポートでは、コントラクト型は Export 属性のパラメーターとして指定されているため、IMyAddinされます。 エクスポートされる型は、コントラクト型と同じか、コントラクト型から派生するか、インターフェイスである場合はコントラクト型を実装する必要があります。 このエクスポートでは、実際の型 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 { }

このエクスポートでは、コントラクト型はIMyAddinではなくMyLoggerされます。 MyLoggerIMyAddinを実装しているため、IMyAddin オブジェクトにキャストできますが、コントラクトの種類が同じではないため、このエクスポートは前のインポートと一致しません。

一般に、コントラクト名を指定する必要はありません。ほとんどのコントラクトは、コントラクトの種類とメタデータの観点から定義する必要があります。 ただし、特定の状況では、コントラクト名を直接指定することが重要です。 最も一般的なケースは、クラスが共通の型を共有する複数の値 (プリミティブなど) をエクスポートする場合です。 コントラクト名は、 Import または Export 属性の最初のパラメーターとして指定できます。 次のコードは、 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 メソッドは 1 つの 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 属性でマークします。

各パーツには、コンポジション エンジンで使用するコンストラクターが 1 つだけ含まれる場合があります。 パラメーターなしのコンストラクターと 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;
}

特に、コレクション パラメーターには注意する必要があります。 たとえば、IEnumerable<int>型のパラメーターを持つコンストラクターにImportingConstructorを指定した場合、インポートは、int型のエクスポートのセットではなく、IEnumerable<int>型の 1 つのエクスポートと一致します。 int型のエクスポートのセットを照合するには、パラメーターを ImportMany 属性で装飾する必要があります。

ImportingConstructor属性によってインポートとして宣言されたパラメーターも、前提条件のインポートとしてマークされます。 MEF では通常、エクスポートとインポートを 使用してサイクルを形成できます。 たとえば、サイクルとは、オブジェクト A がオブジェクト B をインポートし、オブジェクト A をインポートするサイクルです。通常の状況では、サイクルは問題ではなく、コンポジション コンテナーは両方のオブジェクトを正常に構築します。

パーツのコンストラクターによってインポートされた値が必要な場合、そのオブジェクトはサイクルに参加できません。 オブジェクト A がオブジェクト B 自体を構築する前にオブジェクト B を構築する必要があり、オブジェクト B がオブジェクト A をインポートする場合、サイクルは解決できず、コンポジション エラーが発生します。 したがって、コンストラクター パラメーターで宣言されたインポートは前提条件のインポートであるため、必要なオブジェクトからのエクスポートを使用する前にすべて入力する必要があります。

オプションのインポート

Import属性は、パーツが機能するための要件を指定します。 インポートを実行できない場合、そのパーツの構成は失敗し、パーツは使用できなくなります。

AllowDefault プロパティを使用して、インポートが省略可能であることを指定できます。 この場合、インポートが使用可能なエクスポートと一致しない場合でも、合成は成功し、インポートするプロパティはプロパティの種類 (オブジェクト プロパティの場合はnull 、ブール値の場合は false 、数値プロパティの場合は 0) に設定されます。次のクラスでは、オプションのインポートを使用します。

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属性は、1 つだけのエクスポートと一致する場合にのみ正常に構成されます。 それ以外の場合は、合成エラーが発生します。 同じコントラクトに一致する複数のエクスポートをインポートするには、 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; }
}

検出の回避

場合によっては、カタログの一部としてパーツが検出されないようにすることができます。 たとえば、パーツは継承を目的とした基底クラスであっても、使用されない場合があります。 これを実現するには、2 つの方法があります。 まず、パーツ クラスで 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のパラメーターとして指定された既定値が割り当てられます。 メタデータで修飾された 2 つの異なるクラスを次に示します。 これらのクラスはどちらも、前のメタデータ ビューと一致します。

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

メタデータは、ExportMetadata属性を使用して、Export属性の後に表されます。 メタデータの各部分は、名前と値のペアで構成されます。 メタデータの名前部分は、メタデータ ビュー内の適切なプロパティの名前と一致する必要があり、値はそのプロパティに割り当てられます。

インポーターは、使用されるメタデータ ビュー (ある場合) を指定します。 メタデータを含むインポートは遅延インポートとして宣言され、メタデータ インターフェイスは Lazy<T,T>する 2 番目の型パラメーターとして宣言されます。 次のクラスは、前の部分をメタデータと共にインポートします。

Public Class Addin

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

多くの場合、メタデータを ImportMany 属性と組み合わせて、使用可能なインポートを解析し、1 つだけを選択してインスタンス化するか、コレクションをフィルター処理して特定の条件に一致させる必要があります。 次のクラスは、Name値 "Logger" を持つIPluginオブジェクトのみをインスタンス化します。

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はクラス レベルでのみ適用でき、メンバー レベルでは適用できません。 そのため、メンバー レベルのエクスポートを継承することはできません。

次の 4 つのクラスは、インポートとエクスポートの継承の原則を示しています。 NumTwoNumOneから継承されるため、 NumTwoIMyDataをインポートします。 通常のエクスポートは継承されないため、 NumTwo は何もエクスポートしません。 NumFour は、NumThree から継承されます。 NumThree InheritedExport使用されるため、NumFourにはコントラクト型のNumThreeを含むエクスポートが 1 つ含まれます。 メンバー レベルのエクスポートは継承されないため、 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から名前とバージョンのメタデータは継承されません。

<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 はコントラクト型です)。異なる場合は、オーバーライドする代わりに、2 番目の属性によってパーツから独立した 2 つ目のエクスポートが作成されます。 通常、これは、前の例に示すように、 InheritedExport 属性をオーバーライドするときにコントラクト型を明示的に指定する必要があることを意味します。

インターフェイスは直接インスタンス化できないため、一般に、 Export 属性または Import 属性で修飾することはできません。 ただし、インターフェイスはインターフェイス レベルで InheritedExport 属性で修飾でき、関連付けられているメタデータと共にエクスポートは実装クラスによって継承されます。 ただし、インターフェイス自体は一部として使用できません。

カスタム エクスポート属性

基本的なエクスポート属性 ( ExportInheritedExport) は、メタデータを属性プロパティとして含むように拡張できます。 この手法は、類似のメタデータを多数のパーツに適用したり、メタデータ属性の継承ツリーを作成したりする場合に便利です。

カスタム属性では、コントラクトの種類、コントラクト名、またはその他のメタデータを指定できます。 カスタム属性を定義するには、 ExportAttribute (または InheritedExportAttribute) から継承するクラスを 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; }
}

このクラスは、コントラクト型IMyAddinMyMetadata という名前のメタデータを持つ MyAttribute という名前のカスタム属性を定義します。 MetadataAttribute属性でマークされたクラス内のすべてのプロパティは、カスタム属性で定義されたメタデータと見なされます。 次の 2 つの宣言は同等です。

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

最初の宣言では、コントラクトの型とメタデータが明示的に定義されています。 2 番目の宣言では、コントラクトの型とメタデータは、カスタマイズされた属性で暗黙的です。 特に、多数のパーツ (作成者や著作権情報など) に大量の同一メタデータを適用する必要がある場合は、カスタム属性を使用すると、時間と重複を大幅に節約できます。 さらに、カスタム属性の継承ツリーを作成して、バリエーションを可能にすることができます。

カスタム属性に省略可能なメタデータを作成するには、 DefaultValue 属性を使用できます。 この属性がカスタム属性クラスのプロパティに適用される場合、修飾プロパティは省略可能であり、エクスポーターによって提供される必要がないことを指定します。 プロパティの値が指定されていない場合は、プロパティの種類 (通常は nullfalse、または 0) の既定値が割り当てられます。

作成ポリシー

パーツがインポートとコンポジションを指定すると、コンポジション コンテナーは一致するエクスポートを検索しようとします。 インポートとエクスポートが正常に一致した場合、インポートメンバーはエクスポートされたオブジェクトのインスタンスに設定されます。 このインスタンスの取得元は、エクスポート パーツの 作成ポリシーによって制御されます。

可能な 2 つの作成ポリシーは 、共有非共有です。 共有の作成ポリシーを持つパーツは、そのコントラクトを持つパーツについて、コンテナー内のすべてのインポート間で共有されます。 コンポジション エンジンは、一致するものを検出し、インポート プロパティを設定する必要がある場合、パーツの新しいコピーがまだ存在しない場合にのみインスタンス化します。それ以外の場合は、既存のコピーが提供されます。 これは、多くのオブジェクトが同じパーツへの参照を持つ可能性があることを意味します。 このような部分は、多くの場所から変更される可能性のある内部状態に依存しないでください。 このポリシーは、静的パーツ、サービスを提供するパーツ、大量のメモリまたはその他のリソースを消費するパーツに適しています。

非共有の作成ポリシーを持つパーツは、エクスポートの 1 つに対して一致するインポートが見つかるたびに作成されます。 そのため、パーツのエクスポートされたコントラクトのいずれかに一致するコンテナー内のすべてのインポートに対して、新しいコピーがインスタンス化されます。 これらのコピーの内部状態は共有されません。 このポリシーは、各インポートで独自の内部状態が必要な部分に適しています。

インポートとエクスポートの両方で、 SharedNonShared、または Anyの値の中から、パーツの作成ポリシーを指定できます。 既定値は、インポートとエクスポートの両方に Any です。 SharedまたはNonSharedを指定するエクスポートは、同じものを指定するインポート、またはAnyを指定するインポートにのみ一致します。 同様に、 Shared または NonShared を指定するインポートは、同じものを指定するエクスポート、または Anyを指定するエクスポートにのみ一致します。 互換性のない作成ポリシーを使用したインポートとエクスポートは、コントラクト名またはコントラクトの種類が一致しないインポートおよびエクスポートと同じ方法で、一致とは見なされません。 インポートとエクスポートの両方で Anyを指定するか、作成ポリシーを指定せず、既定で Any場合、作成ポリシーは既定で共有されます。

次の例は、作成ポリシーを指定するインポートとエクスポートの両方を示しています。 PartOne は作成ポリシーを指定しないため、既定値は AnyPartTwo は作成ポリシーを指定しないため、既定値は Any。 インポートとエクスポートの両方が既定で Anyされるため、 PartOne は共有されます。 PartThreeShared 作成ポリシーを指定するため、 PartTwoPartThree は同じ PartOneのコピーを共有します。 PartFourNonShared 作成ポリシーを指定するため、 PartFourPartFiveで共有されなくなります。 PartSix は、 NonShared 作成ポリシーを指定します。 PartFivePartSix は、それぞれ PartFourの個別のコピーを受け取ります。 PartSeven は、 Shared 作成ポリシーを指定します。 Sharedの作成ポリシーを使用してエクスポートされたPartFourがないため、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という 2 つの重要なライフサイクル関連インターフェイスを実装できます。

シャットダウン時に作業を実行する必要がある部分や、リソースを解放する必要がある部分は、.NET Framework オブジェクトに対して通常どおり、 IDisposableを実装する必要があります。 ただし、コンテナーはパーツへの参照を作成して維持するため、パーツを所有するコンテナーのみが、そのパーツに対して Dispose メソッドを呼び出す必要があります。 コンテナー自体は IDisposableを実装し、 Dispose でのクリーンアップの一環として、所有するすべての部分で Dispose を呼び出します。 このため、コンポジション コンテナーとそのコンテナーが所有するパーツが不要になった場合は、常に破棄する必要があります。

有効期間の長いコンポジション コンテナーの場合、非共有の作成ポリシーを持つパーツによるメモリ消費量が問題になる可能性があります。 これらの非共有パーツは複数回作成でき、コンテナー自体が破棄されるまで破棄されません。 これに対処するために、コンテナーは ReleaseExport メソッドを提供します。 非共有エクスポートでこのメソッドを呼び出すと、そのエクスポートがコンポジション コンテナーから削除され、破棄されます。 削除されたエクスポートでのみ使用されるパーツ、およびツリーの下にあるパーツも削除され、破棄されます。 このようにして、コンポジション コンテナー自体を破棄することなく、リソースを再利用できます。

IPartImportsSatisfiedNotification には、 OnImportsSatisfiedという名前のメソッドが 1 つ含まれています。 このメソッドは、コンポジションが完了し、パーツのインポートが使用できる状態になったときに、インターフェイスを実装するすべてのパーツでコンポジション コンテナーによって呼び出されます。 パーツは、他のパーツのインポートを満たすためにコンポジション エンジンによって作成されます。 パーツのインポートを設定する前に、 ImportingConstructor 属性を使用してそれらの値が前提条件として指定されていない限り、パーツ コンストラクター内のインポートされた値に依存または操作する初期化を実行することはできません。 通常、これは推奨される方法ですが、場合によってはコンストラクターの挿入が使用できない場合があります。 このような場合、初期化は OnImportsSatisfiedで実行でき、パーツは IPartImportsSatisfiedNotificationを実装する必要があります。