Managed Extensibility Framework - Documentation の 第二回です。Defining Composable Parts and Contracts を見ていきます。
System.ComponentModel.Composition.Import と System.ComponentModel.Composition.Export 属性を指定してインジェクションするようです。コンテナには、少なくとも一つの Export が無いとダメだそうです。
Export を一つも用意せずに試してみた。
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
public class Program
{
[Import("Hoge")]
private IMessageSender _sender;
public static void Main(string[] args)
{
Program p = new Program();
p.Run();
}
public void Run()
{
Compose();
_sender.Send("aaaa");
Console.ReadKey();
}
private void Compose()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
public interface IMessageSender
{
void Send(string message);
}
実行すると、以下の例外が出ます。
System.ComponentModel.Composition.ChangeRejectedException はハンドルされませんでした。
Message=The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.
1) No valid exports were found that match the constraint '((exportDefinition.ContractName == "Hoge") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "IMessageSender".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.
Resulting in: Cannot set import 'Program._sender (ContractName="Hoge")' on part 'Program'.
Element: Program._sender (ContractName="Hoge") --> Program
Source=System.ComponentModel.Composition
次に Import と Export の紐付け方は、
Import 属性を付与した項目(フィールドやプロパティ等)の型と属性のパラメータ ContractName が、
Export 属性を付与した項目(クラスやメソッド、プロパティ等)の型と属性のパラメータ ContractName が完全一致する必要があるようです。但し、Export 属性は ContractType というパラメータで型を指定する事も出来ます。
これも試してみた。
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
public class Program
{
[Import("Hoge")]
private IMessageSender _sender;
public static void Main(string[] args)
{
Program p = new Program();
p.Run();
Console.ReadKey();
}
public void Run()
{
Compose();
_sender.Send("aaaa");
Console.ReadKey();
}
private void Compose()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
public interface IMessageSender
{
void Send(string message);
}
[Export()]
public class NonNameTypeSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message + this.GetType().FullName);
}
}
[Export("Hoge")]
public class NameSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message + this.GetType().FullName);
}
}
[Export(typeof(IMessageSender))]
public class TypeSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message + this.GetType().FullName);
}
}
[Export("Hoge", typeof(IMessageSender))]
public class NameTypeSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message + this.GetType().FullName);
}
}
この場合、Import の型は IMessageSender 、ContractName は "Hoge" です。なので、Export されるのは、Export の ContractName に "Hoge"、ContractType に IMessageSender を指定している NameTypeSender Class が Export されます。
ここで注意が必要なのは、型は厳密名で一致する必要があるようです。Export を指定している全てのクラスは IMessageSender を実装していますが、IMessageSender と型の厳密名は異なります。なので、ContractType で IMessageSender を指定する必要があります。
これは、interface や abstract class の時に気を付ける必要がありそうです。って使うケースの殆どが interface や abstract class だと思うので、ContractType の指定は必須かな。
※ちなみに、ContractName は文字列なので他のライブラリを使用した際に競合する可能性があるから、名前空間等を含めといた方が良いよとの事。