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
されます。
MyLogger
はIMyAddin
を実装しているため、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>
構文と組み合わせて使用する場合に非常に重要な場合があります。 たとえば、 ImportMany
、 IEnumerable<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 つのクラスは、インポートとエクスポートの継承の原則を示しています。
NumTwo
は NumOne
から継承されるため、 NumTwo
は IMyData
をインポートします。 通常のエクスポートは継承されないため、 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
属性で修飾でき、関連付けられているメタデータと共にエクスポートは実装クラスによって継承されます。 ただし、インターフェイス自体は一部として使用できません。
カスタム エクスポート属性
基本的なエクスポート属性 ( Export
と InheritedExport
) は、メタデータを属性プロパティとして含むように拡張できます。 この手法は、類似のメタデータを多数のパーツに適用したり、メタデータ属性の継承ツリーを作成したりする場合に便利です。
カスタム属性では、コントラクトの種類、コントラクト名、またはその他のメタデータを指定できます。 カスタム属性を定義するには、 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; }
}
このクラスは、コントラクト型IMyAddin
と MyMetadata
という名前のメタデータを持つ 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
属性を使用できます。 この属性がカスタム属性クラスのプロパティに適用される場合、修飾プロパティは省略可能であり、エクスポーターによって提供される必要がないことを指定します。 プロパティの値が指定されていない場合は、プロパティの種類 (通常は null
、 false
、または 0) の既定値が割り当てられます。
作成ポリシー
パーツがインポートとコンポジションを指定すると、コンポジション コンテナーは一致するエクスポートを検索しようとします。 インポートとエクスポートが正常に一致した場合、インポートメンバーはエクスポートされたオブジェクトのインスタンスに設定されます。 このインスタンスの取得元は、エクスポート パーツの 作成ポリシーによって制御されます。
可能な 2 つの作成ポリシーは 、共有 と 非共有です。 共有の作成ポリシーを持つパーツは、そのコントラクトを持つパーツについて、コンテナー内のすべてのインポート間で共有されます。 コンポジション エンジンは、一致するものを検出し、インポート プロパティを設定する必要がある場合、パーツの新しいコピーがまだ存在しない場合にのみインスタンス化します。それ以外の場合は、既存のコピーが提供されます。 これは、多くのオブジェクトが同じパーツへの参照を持つ可能性があることを意味します。 このような部分は、多くの場所から変更される可能性のある内部状態に依存しないでください。 このポリシーは、静的パーツ、サービスを提供するパーツ、大量のメモリまたはその他のリソースを消費するパーツに適しています。
非共有の作成ポリシーを持つパーツは、エクスポートの 1 つに対して一致するインポートが見つかるたびに作成されます。 そのため、パーツのエクスポートされたコントラクトのいずれかに一致するコンテナー内のすべてのインポートに対して、新しいコピーがインスタンス化されます。 これらのコピーの内部状態は共有されません。 このポリシーは、各インポートで独自の内部状態が必要な部分に適しています。
インポートとエクスポートの両方で、 Shared
、 NonShared
、または Any
の値の中から、パーツの作成ポリシーを指定できます。 既定値は、インポートとエクスポートの両方に Any
です。
Shared
またはNonShared
を指定するエクスポートは、同じものを指定するインポート、またはAny
を指定するインポートにのみ一致します。 同様に、 Shared
または NonShared
を指定するインポートは、同じものを指定するエクスポート、または Any
を指定するエクスポートにのみ一致します。 互換性のない作成ポリシーを使用したインポートとエクスポートは、コントラクト名またはコントラクトの種類が一致しないインポートおよびエクスポートと同じ方法で、一致とは見なされません。 インポートとエクスポートの両方で Any
を指定するか、作成ポリシーを指定せず、既定で Any
場合、作成ポリシーは既定で共有されます。
次の例は、作成ポリシーを指定するインポートとエクスポートの両方を示しています。
PartOne
は作成ポリシーを指定しないため、既定値は Any
。
PartTwo
は作成ポリシーを指定しないため、既定値は Any
。 インポートとエクスポートの両方が既定で Any
されるため、 PartOne
は共有されます。
PartThree
は Shared
作成ポリシーを指定するため、 PartTwo
と PartThree
は同じ PartOne
のコピーを共有します。
PartFour
は NonShared
作成ポリシーを指定するため、 PartFour
は PartFive
で共有されなくなります。
PartSix
は、 NonShared
作成ポリシーを指定します。
PartFive
と PartSix
は、それぞれ 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.
}
ライフサイクルと廃棄
パーツはコンポジション コンテナーでホストされるため、そのライフサイクルは通常のオブジェクトよりも複雑になる可能性があります。 パーツは、 IDisposable
と IPartImportsSatisfiedNotification
という 2 つの重要なライフサイクル関連インターフェイスを実装できます。
シャットダウン時に作業を実行する必要がある部分や、リソースを解放する必要がある部分は、.NET Framework オブジェクトに対して通常どおり、 IDisposable
を実装する必要があります。 ただし、コンテナーはパーツへの参照を作成して維持するため、パーツを所有するコンテナーのみが、そのパーツに対して Dispose
メソッドを呼び出す必要があります。 コンテナー自体は IDisposable
を実装し、 Dispose
でのクリーンアップの一環として、所有するすべての部分で Dispose
を呼び出します。 このため、コンポジション コンテナーとそのコンテナーが所有するパーツが不要になった場合は、常に破棄する必要があります。
有効期間の長いコンポジション コンテナーの場合、非共有の作成ポリシーを持つパーツによるメモリ消費量が問題になる可能性があります。 これらの非共有パーツは複数回作成でき、コンテナー自体が破棄されるまで破棄されません。 これに対処するために、コンテナーは ReleaseExport
メソッドを提供します。 非共有エクスポートでこのメソッドを呼び出すと、そのエクスポートがコンポジション コンテナーから削除され、破棄されます。 削除されたエクスポートでのみ使用されるパーツ、およびツリーの下にあるパーツも削除され、破棄されます。 このようにして、コンポジション コンテナー自体を破棄することなく、リソースを再利用できます。
IPartImportsSatisfiedNotification
には、 OnImportsSatisfied
という名前のメソッドが 1 つ含まれています。 このメソッドは、コンポジションが完了し、パーツのインポートが使用できる状態になったときに、インターフェイスを実装するすべてのパーツでコンポジション コンテナーによって呼び出されます。 パーツは、他のパーツのインポートを満たすためにコンポジション エンジンによって作成されます。 パーツのインポートを設定する前に、 ImportingConstructor
属性を使用してそれらの値が前提条件として指定されていない限り、パーツ コンストラクター内のインポートされた値に依存または操作する初期化を実行することはできません。 通常、これは推奨される方法ですが、場合によってはコンストラクターの挿入が使用できない場合があります。 このような場合、初期化は OnImportsSatisfied
で実行でき、パーツは IPartImportsSatisfiedNotification
を実装する必要があります。
.NET