Managed Extensibility Framework(MEF)入門 -DirectoryCatalog-
MEFではコンテナーにある領域のパーツを取り込む為の機構として「カタログ」と言う物が用意されています。今回は「あるディレクトリにあるアセンブリパーツを取り込む」為のカタログ『System.ComponentModel.Composition.Hosting.DirectoryCatalog』クラスを取り上げます。DirectoryCatalogクラスはMSDNでこのように説明されています。
DirectoryCatalog は、指定されたディレクトリの内容を解析します。DLL ファイルに含まれている属性付きパーツは抽出され、カタログで利用可能になります。特定の DLL と解析を制限する検索パターンは、GetFiles と同じ構文を使用して指定することもできます
DirectoryCatalogはコンストラクタで指定されたパスを元に、そのパス内に存在しているアセンブリよりパーツを検出します。そのDirectoryCatalogクラスをコンテナーのコンストラクタ引数に指定するとコンテナーはカタログよりパーツを取得しコンテナー内に構成します。
簡単なサンプルコードを元に見てみましょう。
C# DirectoryCatalog.CSharpプロジェクトusing System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace DirectoryCatalogSample.CSharp
{
public class Program
{
/// <summary>
/// コンテナー
/// </summary>
private CompositionContainer container;
/// <summary>
/// コンストラクタ
/// </summary>
private Program()
{
DirectoryCatalog catalog = new DirectoryCatalog(@"C:\Parts.CSharp");
this.container = new CompositionContainer(catalog);
}
/// <summary>
/// View群
/// </summary>
[ImportMany()]
public IEnumerable<IView> Views { get; set; }
/// <summary>
/// 実行処理
/// </summary>
private void Run()
{
//コンテナーより直接サービスを取得する。
IEnumerable<IView> views = this.container.GetExportedValues<IView>();
foreach (IView view in views)
{
Console.WriteLine(string.Format("タイトルは[{0}]です。", view.GetTitle()));
}
//自身のインスタンスにサービスをインポートする。
this.container.ComposeParts(this);
foreach (IView view in this.Views)
{
Console.WriteLine(string.Format("タイトルは[{0}]です。", view.GetTitle()));
}
}
/// <summary>
/// エントリポイント
/// </summary>
/// <param name="args"></param>
public static void Main(string[] args)
{
Program program = new Program();
program.Run();
}
}
/// <summary>
/// IViewインターフェース
/// </summary>
public interface IView
{
string GetTitle();
}
}
C# ViewPartsプロジェクト(C:\Parts.CSharpに出力している)using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using DirectoryCatalogSample.CSharp;
namespace ViewParts.CSharp
{
/// <summary>
/// メインビュー
/// </summary>
[Export(typeof(IView))]
public class MainView : IView
{
public string GetTitle()
{
return "Main Window";
}
}
/// <summary>
/// メッセージダイアログビュー
/// </summary>
[Export(typeof(IView))]
public class MessageDialog : IView
{
public string GetTitle()
{
return "Message Dialog";
}
}
}
VB DirectoryCatalogSample.VBプロジェクトImports System
Imports System.ComponentModel.Composition
Imports System.ComponentModel.Composition.Hosting
Public Class Program
''' <summary>
''' コンテナー
''' </summary>
Private container As CompositionContainer
''' <summary>
''' View群
''' </summary>
<ImportMany()> _
Public Property Views As IEnumerable(Of IView)
''' <summary>
''' コンストラクタ
''' </summary>
Private Sub New()
Dim catalog As New DirectoryCatalog("C:\Parts.VB")
Me.container = New CompositionContainer(catalog)
End Sub
''' <summary>
''' 処理実行
''' </summary>
Private Sub Run()
'コンテナーより直接サービスを取得する。
Dim views As IEnumerable(Of IView) = Me.container.GetExportedValues(Of IView)()
For Each view As IView In views
Console.WriteLine(String.Format("タイトルは[{0}]です。", view.GetTitle()))
Next
'自身のインスタンスにサービスをインポートする。
Me.container.ComposeParts(Me)
For Each view As IView In Me.Views
Console.WriteLine(String.Format("タイトルは[{0}]です。", view.GetTitle()))
Next
End Sub
''' <summary>
''' エントリポイント
''' </summary>
Public Shared Sub Main()
Dim program As New Program()
program.Run()
End Sub
End Class
''' <summary>
''' IViewインターフェース
''' </summary>
Public Interface IView
Function GetTitle() As String
End Interface
VB ViewParts.VBプロジェクト(C:\Parts.VBに出力している)Imports System
Imports System.ComponentModel.Composition
Imports System.ComponentModel.Composition.Hosting
Imports DirectoryCatalogSample.VB
''' <summary>
''' メインビュー
''' </summary>
<Export(GetType(IView))> _
Public Class MainView
Implements IView
Public Function GetTitle() As String Implements DirectoryCatalogSample.VB.IView.GetTitle
Return "Main Window"
End Function
End Class
''' <summary>
''' メッセージダイアログ
''' </summary>
<Export(GetType(IView))> _
Public Class MessageDialog
Implements IView
Public Function GetTitle() As String Implements DirectoryCatalogSample.VB.IView.GetTitle
Return "Message Dialog"
End Function
End Class
サンプルコードを実行すると以下のように出力されます。
サンプルコードでは、実行アプリケーションとして「DirectoryCatalogSample.Charp / DirectoryCatalogSample.VB」プロジェクトを作成し、エクスポートインターフェースのIViewインターフェースを定義しています。又、パーツ群を定義した「ViewParts.CSharp / ViewParts.VB」プロジェクトを別途作成し、コンパイル後の出力先を「C:\Parts.Charp / C:\Parts.VB」に出力しています。DirectoryCatalogクラスはコンストラクタで渡されたパスを元にアセンブリを検索し、見つかったアセンブリ内より指定されたパーツを検索しにいきます。コンテナーはDirectoryCatalogクラスより検索されたパーツを取得し、構築して提供します。DirectoryCatalogクラスを使用すると、アプリケーションの実行ディレクトリ以外の場所で配置されているアセンブリを動的に読む事が出来ます。この機構を使用する事で、アプリケーション実行時であっても動的にパーツを取得し使用したりする事が出来ます。DirectoryCatalogクラスはとても使い勝手が良くお気に入りのCatalogクラスです。是非動的なアセンブリの取得をMEFで試してみては如何でしょうか。
Managed Extensibility Framework(MEF)入門 -AssemblyCatalog-
MEFではコンテナーにある領域のパーツを取り込む為の機構として「カタログ」と言う物が用意されています。今回は「アセンブリ内のパーツを取り込む」為のカタログ『System.ComponentModel.Composition.Hosting.AssemblyCatalog』クラスを取り上げます。AssemblyCatalogクラスはMSDNでこのように説明されています。
AssemblyCatalog は、指定されたアセンブリ内にあるすべてのパーツを解析するために使用されます。ターゲット アセンブリは、オブジェクト参照またはパスによって示すことができます
AssemblyCatalogはコンストラクタで指定されたパス、又はオブジェクト参照を元に指定アセンブリよりパーツを検出します。そのAssemblyCatalogクラスをコンテナーのコンストラクタ引数に設定するとコンテナーはカタログよりパーツを取得しコンテナー内に構成します。
簡単なサンプルコードを元に見てみましょう。
C#
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace AssemblyCatalogSample.CSharp
{
public class Program
{
/// <summary>
/// コンテナ
/// </summary>
private CompositionContainer container;
/// <summary>
/// View群
/// </summary>
[ImportMany()]
public IEnumerable<IView> Views { get; set; }
/// <summary>
/// コンストラクタ
/// </summary>
private Program()
{
//Programクラスが格納されているアセンブリよりパーツを検出する。
AssemblyCatalog catalog = new AssemblyCatalog(typeof(Program).Assembly);
//指定カタログを用いてコンテナがパーツを検出する。
this.container = new CompositionContainer(catalog);
}
/// <summary>
/// 処理実行
/// </summary>
private void Run()
{
//コンテナより直接サービスを取得する。
IEnumerable<IView> views = this.container.GetExportedValues<IView>();
foreach (IView view in views)
{
Console.WriteLine(string.Format("タイトルは[{0}]です。", view.GetTitle()));
}
//自分のインスタンスにサービスをインポートして貰う。
this.container.ComposeParts(this);
foreach (IView view in this.Views)
{
Console.WriteLine(string.Format("タイトルは[{0}]です。", view.GetTitle()));
}
}
/// <summary>
/// エントリポイント
/// </summary>
public static void Main()
{
Program program = new Program();
program.Run();
}
}
/// <summary>
/// IViewインターフェース
/// </summary>
public interface IView
{
/// <summary>
/// タイトル取得
/// </summary>
string GetTitle();
}
/// <summary>
/// メインビュー
/// </summary>
[Export(typeof(IView))]
public class MainView : IView
{
public string GetTitle()
{
return "Main Window";
}
}
/// <summary>
/// メッセージダイアログ
/// </summary>
[Export(typeof(IView))]
public class MessageDialog : IView
{
public string GetTitle()
{
return "Message Dialog";
}
}
}
VB
Imports System
Imports System.ComponentModel.Composition
Imports System.ComponentModel.Composition.Hosting
Public Class Program
''' <summary>
''' コンテナー
''' </summary>
Private container As CompositionContainer
''' <summary>
''' View群
''' </summary>
<ImportMany()> _
Public Property Views() As IEnumerable(Of IView)
''' <summary>
''' エントリポイント
''' </summary>
Public Shared Sub Main()
Dim program As New Program()
program.Run()
End Sub
''' <summary>
''' コンストラクタ
''' </summary>
Private Sub New()
'Programクラスが格納されているアセンブリよりパーツを検出する。
Dim catalog As New AssemblyCatalog(GetType(Program).Assembly)
'指定カタログを用いてコンテナがパーツを検出する。
Me.container = New CompositionContainer(catalog)
End Sub
''' <summary>
''' 処理実行
''' </summary>
''' <remarks></remarks>
Private Sub Run()
'コンテナより直接サービスを取得する。
Dim views As IEnumerable(Of IView) = Me.container.GetExportedValues(Of IView)()
For Each view As IView In views
Console.WriteLine(String.Format("タイトルは[{0}]です。", view.GetTitle()))
Next
'自分のインスタンスにサービスをインポートして貰う。
Me.container.ComposeParts(Me)
For Each view As IView In Me.Views
Console.WriteLine(String.Format("タイトルは[{0}]です。", view.GetTitle()))
Next
End Sub
End Class
''' <summary>
''' IViewインターフェース
''' </summary>
Public Interface IView
''' <summary>
''' タイトル取得
''' </summary>
Function GetTitle() As String
End Interface
''' <summary>
''' メインビュー
''' </summary>
<Export(GetType(IView))> _
Public Class MainView
Implements IView
''' <summary>
''' タイトル取得
''' </summary>
Public Function GetTitle() As String Implements IView.GetTitle
Return "Main View"
End Function
End Class
''' <summary>
''' メッセージダイアログ
''' </summary>
<Export(GetType(IView))> _
Public Class MessageDialog
Implements IView
''' <summary>
''' タイトル取得
''' </summary>
Public Function GetTitle() As String Implements IView.GetTitle
Return "Message Dialog"
End Function
End Class
サンプルコードを実行すると以下のように出力されます。
サンプルコードでは、Programクラスが格納されているアセンブリ内のパーツ群をAssemblyCatalogクラスを使用してコンテナーに取り込んでいます。今まで取り上げてきた記事では『System.ComponentModel.Composition.Hosting.CompositionBatch』クラスを使用してパーツをCompositionBatchクラスに追加していました。それではアプリケーションで使用するパーツ群を構成する為には一つずつ追加しなけらばならず、効果的とは言えません。カタログを使用する事によってある領域のパーツを取り込む事が出来ます。サンプルコードから分かるようにAssemblyCatalogクラスを使用すると、指定したアセンブリからパーツ群を検出して取り込む事が出来ます。MEFでは一般的にカタログを使用してパーツをコンテナーに取り込みます。「System.ComponentModel.Composition.Hosting」名前空間には様々なカタログが用意されていますので、用途に合わせてカタログを使い柔軟なパーツ構成を行えます。是非試してみては如何でしょうか。
Managed Extensibility Framework(MEF)入門 -Custom Export-
MEFではエクスポート時にSystem.ComponentModel.Composition.ExportMetadataAttributeクラスを使用してメタデータの設定出来ます。メタデータ設定ではExportAttributeとExportMetadataAttributeを併用して実装する必要がありました。今回取り上げるカスタムエクスポートを作成するとエクスポート定義にメタデータ定義を実装する事が出来ます。
カスタムエクスポートクラスを作成するには、『System.ComponentModel.Composition.ExportAttribute』クラスを継承して作成します。また、カスタムエクスポートを使用すると『[MEF]Managed Extensibility Framework(MEF)入門 -ExportMetadata-』の記事で取り上げたいくつかの問題を解消する事が出来ます。それは以降の説明にて取り上げていきます。まずはサンプルコードから見てみましょう。
C#1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel.Composition;
4 using System.ComponentModel.Composition.Hosting;
5
6 namespace CustomExport.CSharp
7 {
8 public class Program
9 {
10 /// <summary>
11 /// コンテナー
12 /// </summary>
13 private CompositionContainer container;
14
15 /// <summary>
16 /// コンストラクタ
17 /// </summary>
18 private Program()
19 {
20 this.container = new CompositionContainer();
21 CompositionBatch batch = new CompositionBatch();
22 batch.AddPart(new MainWindow());
23 batch.AddPart(new ErrorDialog());
24 this.container.Compose(batch);
25 }
26
27 /// <summary>
28 /// 実行処理
29 /// </summary>
30 private void Run()
31 {
32 IEnumerable<Lazy<IView, IMetadataView>> views = this.container.GetExports<IView, IMetadataView>();
33 foreach (Lazy<IView, IMetadataView> view in views)
34 {
35 //WindowView型のみ出力
36 if (view.Metadata.ViewType == ViewType.WindowView)
37 {
38 Console.WriteLine(string.Format("WindowViewのタイトルは{0}です。", view.Value.GetTitle()));
39 }
40 }
41 }
42
43 /// <summary>
44 /// エントリポイント
45 /// </summary>
46 public static void Main()
47 {
48 Program program = new Program();
49 program.Run();
50 }
51 }
52
53 /// <summary>
54 /// メインウィンドウ
55 /// </summary>
56 [ViewExport(ViewType = ViewType.WindowView)]
57 public class MainWindow : IView
58 {
59 public string GetTitle()
60 {
61 return "メインウィンドウ";
62 }
63 }
64
65 /// <summary>
66 /// エラーダイアログ
67 /// </summary>
68 [ViewExport(ViewType = ViewType.DialogView)]
69 public class ErrorDialog : IView
70 {
71 /// <summary>
72 /// タイトル取得
73 /// </summary>
74 public string GetTitle()
75 {
76 return "エラーダイアログ";
77 }
78 }
79
80 /// <summary>
81 /// エクスポート用インターフェース
82 /// </summary>
83 public interface IView
84 {
85 /// <summary>
86 /// タイトル取得
87 /// </summary>
88 string GetTitle();
89 }
90
91 /// <summary>
92 /// メタデータビュー
93 /// </summary>
94 public interface IMetadataView
95 {
96 ViewType ViewType { get; }
97 }
98
99 /// <summary>
100 /// 属性値識別用列挙体
101 /// </summary>
102 public enum ViewType
103 {
104 WindowView,
105 DialogView
106 }
107
108 /// <summary>
109 /// カスタムエクスポート属性クラス
110 /// </summary>
111 [MetadataAttribute]
112 [AttributeUsage(AttributeTargets.Class)]
113 public class ViewExportAttribute : ExportAttribute
114 {
115 /// <summary>
116 /// コンストラクタ
117 /// </summary>
118 /// <remarks>コントラクト型でコントラクトを登録します。</remarks>
119 public ViewExportAttribute()
120 : this(string.Empty)
121 { }
122
123 /// <summary>
124 /// コンストラクタ
125 /// </summary>
126 /// <param name="contractName">コントラクト名</param>
127 /// <remarks>コントラクト名とコントラクト型でコントラクトを登録します。</remarks>
128 public ViewExportAttribute(string contractName)
129 : base(contractName, typeof(IView))
130 { }
131
132 /// <summary>
133 /// メタデータ
134 /// </summary>
135 public ViewType ViewType { get; set; }
136 }
137 }
VB1 Imports System
2 Imports System.ComponentModel.Composition
3 Imports System.ComponentModel.Composition.Hosting
4
5 Public Class Program
6 ''' <summary>
7 ''' コンテナー
8 ''' </summary>
9 Private container As CompositionContainer
10
11 ''' <summary>
12 ''' コンストラクタ
13 ''' </summary>
14 Private Sub New()
15 Me.container = New CompositionContainer()
16 Dim batch As New CompositionBatch()
17 batch.AddPart(New MainWindow())
18 batch.AddPart(New ErrorDialog())
19 Me.container.Compose(batch)
20 End Sub
21
22 ''' <summary>
23 ''' 実行処理
24 ''' </summary>
25 Private Sub Run()
26 Dim views As IEnumerable(Of Lazy(Of IView, IMetadataView)) = Me.container.GetExports(Of IView, IMetadataView)()
27 For Each view As Lazy(Of IView, IMetadataView) In views
28 'WindowView型のみ出力
29 If view.Metadata.ViewType = ViewType.WindowView Then
30 Console.WriteLine(String.Format("WindowViewのタイトルは{0}です。", view.Value.GetTitle()))
31 End If
32 Next
33 End Sub
34
35 ''' <summary>
36 ''' エントリポイント
37 ''' </summary>
38 Public Shared Sub Main()
39 Dim program As New Program()
40 program.Run()
41 End Sub
42 End Class
43
44 ''' <summary>
45 ''' メインウィンドウ
46 ''' </summary>
47 <ViewExport(ViewType:=ViewType.WindowView)> _
48 Public Class MainWindow
49 Implements IView
50 ''' <summary>
51 ''' タイトル取得
52 ''' </summary>
53 Public Function GetTitle() As String Implements IView.GetTitle
54 Return "メインウィンドウ"
55 End Function
56 End Class
57
58 ''' <summary>
59 ''' エラーダイアログ
60 ''' </summary>
61 <ViewExport(ViewType:=ViewType.DialogView)> _
62 Public Class ErrorDialog
63 Implements IView
64 ''' <summary>
65 ''' タイトル取得
66 ''' </summary>
67 Public Function GetTitle() As String Implements IView.GetTitle
68 Return "エラーダイアログ"
69 End Function
70 End Class
71
72 ''' <summary>
73 ''' エクスポート用インターフェース
74 ''' </summary>
75 Public Interface IView
76 ''' <summary>
77 ''' タイトル取得
78 ''' </summary>
79 Function GetTitle() As String
80 End Interface
81
82 ''' <summary>
83 ''' メタデータビュー
84 ''' </summary>
85 Public Interface IMetadataView
86 ReadOnly Property ViewType() As ViewType
87 End Interface
88
89 ''' <summary>
90 ''' 属性値識別用列挙体
91 ''' </summary>
92 Public Enum ViewType
93 WindowView
94 DialogView
95 End Enum
96
97 ''' <summary>
98 ''' カスタムエクスポート属性クラス
99 ''' </summary>
100 <MetadataAttribute()> _
101 <AttributeUsage(AttributeTargets.Class)> _
102 Public Class ViewExportAttribute
103 Inherits ExportAttribute
104 ''' <summary>
105 ''' コンストラクタ
106 ''' </summary>
107 ''' <remarks>コントラクト型でコントラクトを登録します。</remarks>
108 Public Sub New()
109 Me.New(String.Empty)
110 End Sub
111
112 ''' <summary>
113 ''' コンストラクタ
114 ''' </summary>
115 ''' <param name="contractName">コントラクト名</param>
116 ''' <remarks>コントラクト名とコントラクト型でコントラクトを登録します。</remarks>
117 Public Sub New(ByVal contractName As String)
118 MyBase.New(contractName, GetType(IView))
119 End Sub
120
121 ''' <summary>
122 ''' メタデータ
123 ''' </summary>
124 Public Property ViewType() As ViewType
125 End Class
上記サンプルコードの実行結果は以下の通りです。
上記ではコントラクトがIViewインターフェース型のExportAttributeクラスを継承したカスタムエクスポート属性クラスのViewExportAttributeを作成しています。独自の属性クラスを作成する場合は、クラス名のサフィックス(接尾語)に『Attribute』を付けるルールがあります。又、上記サンプルではViewExportAttributeクラスに2つの属性を定義しています。
1つめの『System.ComponentModel.Composition.MetadataAttributeAttribute』クラスを属性定義すると、ExporAttributeクラス(今回はExportAttributeクラスを継承しているのでViewExportAttributeクラスが)で定義されたプロパティ、フィールド、メソッドをメタデータとして提供する事が可能となります。よってViewExportAttributeクラスで定義されているViewTypeプロパティがメタデータとして認識されます。
2つめの『System.AttributeUsageAttribute』クラスを属性として定義すると、定義したクラスの使用方法を制御する事が出来ます。AttributeUsageAttributeクラスのコンストラクタ引数に『System.AttributeTargets 』列挙体を用いて属性として定義出来るスコープを設定します。ViewExportAttributeクラスでは「AttributeTargets.Class」が設定されているのでクラスにのみ属性として定義が可能なクラスになります。
サンプルコードのViewExportAttributeクラスではコンストラクタを2つ定義し、1つはコントラクト名を設定出来るようにしてあります。これによってコントラクトとして内包されているIViewインターフェース型とコントラクト名を設定する事が可能になります。ExportAttributeクラスを使用した場合と比べてカスタムエクスポート属性クラスを作成した場合はエクスポート定義が明確になり、かつメタデータも同時に設定出来るようになります。出力結果からカスタムエキスポート属性クラスで定義されているコントラクトとメタデータが認識されている事が分かります。またメタデータビューも正しく取得されている事が分かります。ただ、上記サンプルでは1点問題があります。
カスタムエクスポート属性クラスのメタデータ(ViewType)とメタデータビューインターフェースのメタデータ(ViewType)を同じにし管理しなければならない。
例えばメタデータビューのメタデータ(ViewType)の名前をViewTypesと変更した場合、出力結果は以下の通りになります。
メタデータビューのメタデータ名を変更した事によって、カスタムエクスポート属性クラスのメタデータと関連性が損なわれてしまった為に正しくメタデータを認識出来なくなってしまいました。又、この関連性の欠損はコンパイル時でも発見する事は出来ず、原因特定するのが困難な状況を生み出してしまいます。この問題は、メタデータビューとカスタムエクスポート属性クラスのメタデータをインターフェース制約を使用して解決する事が出来ます。そのサンプルコードは以下になります。
C#/// <summary>
/// メタデータビュー
/// </summary>
public interface IMetadataView
{
[DefaultValue(typeof(ViewType), "WindowView")]
ViewType ViewType { get; }
}
/// <summary>
/// カスタムエクスポート属性クラス
/// </summary>
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ViewExportAttribute : ExportAttribute, IMetadataView
{
/// <summary>
/// コンストラクタ
/// </summary>
/// <remarks>コントラクト型でコントラクトを登録します。</remarks>
public ViewExportAttribute()
: this(string.Empty)
{ }
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="contractName">コントラクト名</param>
/// <remarks>コントラクト名とコントラクト型でコントラクトを登録します。</remarks>
public ViewExportAttribute(string contractName)
: base(contractName, typeof(IView))
{ }
/// <summary>
/// メタデータ
/// </summary>
public ViewType ViewType { get; set; }
}
}
VB''' <summary>
''' メタデータビュー
''' </summary>
Public Interface IMetadataView
ReadOnly Property ViewType() As ViewType
End Interface
''' <summary>
''' メインウィンドウ
''' </summary>
<ViewExport(ViewType.WindowView)> _
Public Class MainWindow
Implements IView
''' <summary>
''' タイトル取得
''' </summary>
Public Function GetTitle() As String Implements IView.GetTitle
Return "メインウィンドウ"
End Function
End Class
''' <summary>
''' カスタムエクスポート属性クラス
''' </summary>
<MetadataAttribute()> _
<AttributeUsage(AttributeTargets.Class)> _
Public Class ViewExportAttribute
Inherits ExportAttribute
Implements IMetadataView
''' <summary>
''' ViewTypeフィールド
''' </summary>
Private _viewType As ViewType
''' <summary>
''' コンストラクタ
''' </summary>
''' <remarks>コントラクト型でコントラクトを登録します。</remarks>
Public Sub New(Optional ByVal viewType As ViewType = ViewType.WindowView)
Me.New(String.Empty, viewType)
End Sub
''' <summary>
''' コンストラクタ
''' </summary>
''' <param name="contractName">コントラクト名</param>
''' <remarks>コントラクト名とコントラクト型でコントラクトを登録します。</remarks>
Public Sub New(ByVal contractName As String, Optional ByVal viewType As ViewType = ViewType.WindowView)
MyBase.New(contractName, GetType(IView))
Me._viewType = viewType
End Sub
''' <summary>
''' メタデータ
''' </summary>
Public ReadOnly Property ViewType As ViewType Implements IMetadataView.ViewType
Get
Return _viewType
End Get
End Property
End Class
上記ではカスタムエクスポート属性クラス(ViewExportAttribute)にメタデータビューインターフェース(IMetadataView)を継承させています。これによってメタデータビューで定義しているメタデータをインターフェース制約によって強制する事が出来ます。これによってどちらかのメタデータが変更された場合はコンパイル時にエラーとして検出出来るようになります。サンプルコードではC#とVBの言語仕様の違いで実装が一部異なっている所があります。
C#ではインターフェースで定義されている読み取り専用プロパティを書き込み可能なプロパティとして継承可能な為、カスタムエクスポート属性クラスにインターフェースを継承させるだけで済みます。又、メタデータを省略された場合を想定してメタデータビューのメタデータに『System.ComponentModel.DefaultValueAttribute』クラスを属性として定義し既定値を設定します。
VBではインターフェースで定義されている読み取り専用プロパティは読み取り専用プロパティとして継承しなければならない為、カスタムエクスポート属性クラスにインターフェースを継承させ読み取り専用メタデータの値をOptionalキーワードを使用してコンストラクタ引数で設定するようにします。これによってVBでは既定値をDefaultValueAttributeクラスを使用せずに設定できます。またエクスポート定義もコンストラクタ引数を使用するように変更します。
このようにしてメタデータとカスタムエクスポート属性クラスのメタデータの関連付けをする事で見つけずらいバグを防ぐ事が出来ます。カスタムエクスポート属性クラスを作成する事で、明確でかつ修正変更に対応出来るエクスポートが実装出来る事がご理解頂けたかと思います。是非カスタムエクスポート属性クラスを活用してみては如何でしょうか。
Managed Extensibility Framework(MEF)入門 -ExportMetadata-
MEFではエクスポートするサービスに『メタデータ(エクスポート属性)』を付ける事が出来ます。これを使用する事によって、同一コントラクトのサービス群を区別する事が出来ます。主にImportMany属性を使用して取り込んだ同一のサービス群やコンテナーより取得した同一コントラクトのサービス群に使用します。メタデータを識別する為に『Lazy<T, TMetadata>・Lazy(Of T, TMetadata)』クラスを使用します。遅延エクスポートで使用する「Lazy<T>・Lazy(Of T)」クラスを継承したクラスであり、遅延エクスポートの恩恵も得ます。サービス群を区別する際にサービスインスタンスを都度生成していては効率が良くなく、サービスの区別後にインスタンス生成を行うのが望ましい事から「Lazy<T>・Lazy(Of T)」クラスを拡張して作成されてます。
このようにサービスにメタデータとして「属性名(キー)、属性値(値)」を設定する事が出来ます。これには『System.ComponentModel.Composition.ExportMetadataAttribute』クラスを使用し、エクスポートするサービスに属性として定義します。サービスにはメタデータを複数設定する事も出来ます。それではサンプルコードを元に見ていきましょう。
基本実装
C#1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.ComponentModel.Composition;
5 using System.ComponentModel.Composition.Hosting;
6
7 namespace ExportMetadata.CSharp
8 {
9 public class Program
10 {
11 private CompositionContainer container;
12
13 private Program()
14 {
15 container = new CompositionContainer();
16 CompositionBatch batch = new CompositionBatch();
17 batch.AddPart(new MessageSender());
18 container.Compose(batch);
19 }
20
21 private void Run()
22 {
23 //Metadataは既定ではIDictionary<string, object>で取得出来る。
24 Lazy<IMessageSender, Dictionary<string, object>> messageSender = this.container.GetExport<IMessageSender, Dictionary<string, object>>();
25 //Lazy<T, TMetadata>のIMetadataプロパティからエクスポート属性値を取得する。
26 Console.WriteLine(string.Format("Value={0}", messageSender.Metadata["MetadataName"]));
27 //メタデータにアクセスしている時はまだインスタンスは生成されていない。
28 Console.WriteLine(string.Format("instance is created={0}", messageSender.IsValueCreated.ToString()));
29 Console.WriteLine(messageSender.Value.GetMessage());
30 }
31
32 public static void Main(string[] args)
33 {
34 Program program = new Program();
35 program.Run();
36 }
37 }
38
39 public interface IMessageSender
40 {
41 string GetMessage();
42 }
43
44 //メタデータに属性名="MetadataName"、属性値="MetadataValue"を設定。
45 [ExportMetadata("MetadataName", "MetadataValue")]
46 [Export(typeof(IMessageSender))]
47 public class MessageSender : IMessageSender
48 {
49 public string GetMessage()
50 {
51 return "メッセージです。";
52 }
53 }
54 }
VB1 Imports System
2 Imports System.ComponentModel.Composition
3 Imports System.ComponentModel.Composition.Hosting
4
5 Public Class Program
6 Private container As CompositionContainer
7
8 Private Sub New()
9 Me.container = New CompositionContainer()
10 Dim batch As New CompositionBatch()
11 batch.AddPart(New MessageSender())
12 Me.container.Compose(batch)
13 End Sub
14
15 Private Sub Run()
16 'Metadataは既定ではIDictionary<string, object>で取得出来る。
17 Dim messageSender As Lazy(Of IMessageSender, IDictionary(Of String, Object)) = Me.container.GetExport(Of IMessageSender, IDictionary(Of String, Object))()
18 'Lazy(Of T, TMetadata)のIMetadataプロパティからエクスポート属性値を取得する。
19 Console.WriteLine(String.Format("Value={0}", messageSender.Metadata("MetadataName")))
20 'メタデータにアクセスしている時はまだインスタンスは生成されていない。
21 Console.WriteLine(String.Format("instance is created = {0}", messageSender.IsValueCreated.ToString()))
22 Console.WriteLine(messageSender.Value.GetMessage())
23 End Sub
24
25 Public Shared Sub Main()
26 Dim program As New Program()
27 program.Run()
28 End Sub
29 End Class
30
31 Public Interface IMessageSender
32 Function GetMessage() As String
33 End Interface
34
35 'メタデータに属性名="MetadataName"、属性値="MetadataValue"を設定。
36 <ExportMetadata("MetadataName", "MetadataValue")> _
37 <Export(GetType(IMessageSender))> _
38 Public Class MessageSender
39 Implements IMessageSender
40 Public Function GetMessage() As String Implements IMessageSender.GetMessage
41 Return "メッセージです。"
42 End Function
43 End Class
上記サンプルコードの出力結果は以下の通りになります。
サンプルコードではMessageSenderクラスをサービスとしてエクスポートしており、ExportMetadataAttributeクラスを使用して「属性名=”MetadataName”、属性値=”MetadataValue”」をサービスに付与しています。エクスポートされたサービスを使用する側ではLazy<T, TMetadata>・Lazy(Of T, TMetadata)クラスを使用してサービスを受け取り、『Metadataプロパティ』を通じてエクスポートされたサービスのメタデータを見ています。Lazy<T, TMetadata>・Lazy(Of T, TMetadata)クラスは遅延エクスポートが行えるのでValueプロパティにアクセスするまでサービスのインスタンスが生成されておりません。同一コントラクトのサービス群を区別している時にインスタンスが生成されない事は理にかなっており、区別後の必要なサービスのみインスタンスが作られるわけです。又、ExportMetadataAttributeのコンストラクタ引数に属性名と属性値が設定されていますが、ExportMetadataAttributeクラスのコンストラクタ引数は『属性名=string型、属性値=object型』となっています。よってTMetadataに「IDictionary<string, object>・IDictionary(Of String, Object)」が使用出来るのです。
メタデータビューの使用
エクスポートしたサービスにメタデータを設定する方法と取得する方法は上記サンプルで分かりました。しかし上記サンプルコードには幾つかの問題があります。
- メタデータの属性名がstring型である為、エクスポート側と使用する側で同一の文字列を管理する必要がある。
- メタデータの属性値がobject型である為、使用する側ではエクスポート側で設定された型を意識する必要がある。
- コンパイル時の検出が行えない。
ExportMetadataAttributeを使用した場合にメタデータへアクセス用する為にIDictionary<string, object>・IDictionary(Of String, Object)を使用していましたが、上記問題を抱えてしまう為に推奨されていません。これらの問題を『使用する側で解決する』為の機構として『メタデータビュー』と言う物があります。メタデータビューとは以下のように定義されています。
メタデータ ビューは、エクスポートされるメタデータのキーに対応する、読み取り専用のプロパティを定義するインターフェイスです。metadata プロパティにアクセスすると、MEF では TMetadata が動的に実装され、エクスポートから提供されたメタデータを基に値が設定されます。
初めのサンプルコードではIDictionary<string, object>・IDictionary(Of String, Object)を使用していた部分をメタデータビューに変える事が出来ます。「属性名と同じ名前のプロパティを持ったインターフェース」を作成しメタデータビューとして使います。このサンプルコードを見てみましょう。
C#1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.ComponentModel.Composition;
5 using System.ComponentModel.Composition.Hosting;
6
7 namespace ExportMetadataUsingIMetadataView.CSharp
8 {
9 public class Program
10 {
11 private CompositionContainer container;
12
13 private Program()
14 {
15 container = new CompositionContainer();
16 CompositionBatch batch = new CompositionBatch();
17 batch.AddPart(new MessageSender());
18 container.Compose(batch);
19 }
20
21 private void Run()
22 {
23 //IMetadataViewインターフェースをメタデータビューとして使用している。
24 Lazy<IMessageSender, IMetadataView> messageSender = this.container.GetExport<IMessageSender, IMetadataView>();
25 Console.WriteLine(string.Format("Value={0}", messageSender.Metadata.FirstMetadataName));
26 Console.WriteLine(string.Format("Value={0}", messageSender.Metadata.SecondMetadataName));
27 Console.WriteLine(messageSender.Value.GetMessage());
28 }
29
30 static void Main(string[] args)
31 {
32 Program program = new Program();
33 program.Run();
34 }
35 }
36
37 /// <summary>
38 /// メタデータビュー用インターフェース
39 /// </summary>
40 public interface IMetadataView
41 {
42 //プロパティ名が属性名に対応している。
43 ViewTypes FirstMetadataName { get; }
44 ViewTypes SecondMetadataName { get; }
45 }
46
47 public interface IMessageSender
48 {
49 string GetMessage();
50 }
51
52 [ExportMetadata(ViewMetadata.FirstMetadataName, ViewTypes.FirstValue)]
53 [ExportMetadata(ViewMetadata.SecondMetadataName, ViewTypes.SecondValue)]
54 [Export(typeof(IMessageSender))]
55 public class MessageSender : IMessageSender
56 {
57 public string GetMessage()
58 {
59 return "メッセージです。";
60 }
61 }
62
63 /// <summary>
64 /// 属性名識別用クラス
65 /// </summary>
66 public class ViewMetadata
67 {
68 public const string FirstMetadataName = "FirstMetadataName";
69 public const string SecondMetadataName = "SecondMetadataName";
70 }
71
72 /// <summary>
73 /// 属性値識別用列挙体
74 /// </summary>
75 public enum ViewTypes
76 {
77 FirstValue,
78 SecondValue
79 }
80 }
VB1 Imports System
2 Imports System.ComponentModel.Composition
3 Imports System.ComponentModel.Composition.Hosting
4
5 Public Class Program
6 Private container As CompositionContainer
7
8 Private Sub New()
9 Me.container = New CompositionContainer()
10 Dim batch As New CompositionBatch()
11 batch.AddPart(New MessageSender())
12 Me.container.Compose(batch)
13 End Sub
14
15 Private Sub Run()
16 'IMetadataViewインターフェースをメタデータビューとして使用している。
17 Dim messageSender As Lazy(Of IMessageSender, IMetadataView) = Me.container.GetExport(Of IMessageSender, IMetadataView)()
18 Console.WriteLine(String.Format("Value={0}", messageSender.Metadata.FirstMetadataName))
19 Console.WriteLine(String.Format("Value={0}", messageSender.Metadata.SecondMetadataName))
20 Console.WriteLine(messageSender.Value.GetMessage())
21 End Sub
22
23 Public Shared Sub Main()
24 Dim program As New Program()
25 program.Run()
26 End Sub
27 End Class
28
29 ''' <summary>
30 ''' メタデータビュー用インターフェース
31 ''' </summary>
32 Public Interface IMetadataView
33 'プロパティ名が属性名に対応している
34 ReadOnly Property FirstMetadataName As ViewTypes
35 ReadOnly Property SecondMetadataName As ViewTypes
36 End Interface
37
38 Public Interface IMessageSender
39 Function GetMessage() As String
40 End Interface
41
42 <ExportMetadata(ViewMetadata.FirstMetadataName, ViewTypes.FirstValue)> _
43 <ExportMetadata(ViewMetadata.SecondMetadataName, ViewTypes.SecondValue)> _
44 <Export(GetType(IMessageSender))> _
45 Public Class MessageSender
46 Implements IMessageSender
47 Public Function GetMessage() As String Implements IMessageSender.GetMessage
48 Return "メッセージです。"
49 End Function
50 End Class
51
52 ''' <summary>
53 ''' 属性名識別用クラス
54 ''' </summary>
55 Public Class ViewMetadata
56 Public Const FirstMetadataName As String = "FirstMetadataName"
57 Public Const SecondMetadataName As String = "SecondMetadataName"
58 End Class
59
60 ''' <summary>
61 ''' 属性値識別用列挙体
62 ''' </summary>
63 Public Enum ViewTypes
64 FirstValue
65 SecondValue
66 End Enum
これを実行すると以下のように出力されます。
上記ではExportMetadataAttributeクラスに指定する属性名をリテラル文字ではなく定数を使用して指定しています。これにより同じ値を持った複数の属性名を一度に変更する事が出来ます。又、属性値も列挙体を用いて指定する値を限定させています。メタデータビューとしてIMetadataViewインターフェースを作成しました。このインターフェースを定義する場合には以下のルールがあります。
- 読み取り専用のプロパティである
- プロパティ名は属性名と同じ名前である
- プロパティ型は属性値と同じ型である
サンプルでは上記ルールに沿ったプロパティを2つ実装させています。このルールは必ず守る必要があります。ここでどれか規則を守っていない物が存在すると、サービスの取得時に例外が発生します。ルールに基づいたメタデータビューを作成したあと、Lazy<T, TMetadata>・Lazy(Of T, TMetadata)のTMetadataに指定する事でMetadataプロパティにアクセスした際に動的にMetadataインスタンスを作成してくれます。これは遅延エクスポートと同じ機構です。メタデータビューを使用する事で、使用する側へのタイプセーフな機構を提供する事が出来ます。
メタデータビューを使用する事で幾つかの問題は解消されましたが、未だまだ問題を抱えています。それは以下の通りです。
- エクスポートのメタデータの属性名を明示的に都度定義する必要がある。
上記問題はExportMetadataAttributeのコンストラクタ引数がstring型の属性名、object型の属性値と限定されている事によって発生しています。これを解決する為にはExportAttributeを継承したカスタムエクスポートクラスを作成する必要があります。このカスタムエクスポートクラスについては別途取り上げたいと思います。
Managed Extensibility Framework(MEF)入門 -Lazy(遅延エクスポート)-
MEFではサービスのエクスポートを遅延させる事が出来ます。コンテナーに登録されたサービスを取得する場合、コントラクトと取得型を指定して希望のサービスを取得するか、コンテナーよりパーツにサービスをインポートしてもらいサービスを使用するのが一般的です。ですが、取得対象のサービスのインスタンスが大きかったりインポートされるサービス数が膨大にある場合はメモリーに圧迫をかけてしまう事になります。こういった場合に「遅延エクスポート」という機構を使用する事によって、実際にサービスを使用する場合にのみインスタンスの生成を行わせる事が出来ます。遅延エクスポートを使用する場合には『System.Lazy<T>・System.Lazy(Of T)』クラスを使用します。
遅延エクスポートを使用するには、図のようにエクスポートされたサービスをインポート時、又はコンテナーより取得する際にLazyクラスに内包する形で取得します。Lazyクラスの『Valueプロパティ』を通じてサービスを取得します。LazyクラスはValueプロパティにアクセスされるまでサービスのインスタンスの生成を遅延させます。Valueプロパティにサービスインスタンスが生成されているか判別するプロパティの『IsValueCreatedプロパティ』も用意されています。これをサンプルコードを元に見てみましょう。
C#1 using System;
2 using System.ComponentModel.Composition;
3 using System.ComponentModel.Composition.Hosting;
4
5 namespace LazyExport.CSharp
6 {
7 public class Program
8 {
9 private CompositionContainer container;
10
11 [Import]
12 public Lazy<IMessageSender> MessageSender { get; set; }
13
14 private Program()
15 {
16 this.container = new CompositionContainer();
17 CompositionBatch batch = new CompositionBatch();
18 batch.AddPart(new MessageSender());
19 batch.AddPart(this);
20 this.container.Compose(batch);
21 }
22
23 private void Run()
24 {
25 //コンテナーより直接取得したLazyクラスを使用。
26 Lazy<IMessageSender> messageSender = this.container.GetExport<IMessageSender>();
27 Console.WriteLine(string.Format("instance is created = {0}", messageSender.IsValueCreated.ToString()));
28 Console.WriteLine(messageSender.Value.GetMessage());
29 Console.WriteLine(string.Format("instance is created = {0}", messageSender.IsValueCreated.ToString()));
30
31 Console.WriteLine();
32
33 //インポートされたLazyクラスを使用。
34 Console.WriteLine(string.Format("instance is created = {0}", this.MessageSender.IsValueCreated.ToString()));
35 Console.WriteLine(this.MessageSender.Value.GetMessage());
36 Console.WriteLine(string.Format("instance is created = {0}", this.MessageSender.IsValueCreated.ToString()));
37 }
38
39 public static void Main(string[] args)
40 {
41 Program program = new Program();
42 program.Run();
43 }
44 }
45
46 public interface IMessageSender
47 {
48 string GetMessage();
49 }
50
51 [Export(typeof(IMessageSender))]
52 public class MessageSender : IMessageSender
53 {
54 public string GetMessage()
55 {
56 return "メッセージです。";
57 }
58 }
59 }
VB1 Imports System
2 Imports System.ComponentModel.Composition
3 Imports System.ComponentModel.Composition.Hosting
4
5 Public Class Program
6 Private container As CompositionContainer
7
8 <Import()> _
9 Public Property MessageSender As Lazy(Of IMessageSender)
10
11 Private Sub New()
12 Me.container = New CompositionContainer()
13 Dim batch As New CompositionBatch()
14 batch.AddPart(New MessageSender())
15 batch.AddPart(Me)
16 Me.container.Compose(batch)
17 End Sub
18
19 Private Sub Run()
20 'コンテナーより直接取得したLazyクラスを使用。
21 Dim messageSender As Lazy(Of IMessageSender) = Me.container.GetExport(Of IMessageSender)()
22 Console.WriteLine(String.Format("instance is created = {0}", messageSender.IsValueCreated.ToString()))
23 Console.WriteLine(messageSender.Value.GetMessage())
24 Console.WriteLine(String.Format("instance is created = {0}", messageSender.IsValueCreated.ToString()))
25
26 Console.WriteLine()
27
28 'インポートされたLazyクラスを使用。
29 Console.WriteLine(String.Format("instance is created = {0}", Me.MessageSender.IsValueCreated.ToString()))
30 Console.WriteLine(Me.MessageSender.Value.GetMessage())
31 Console.WriteLine(String.Format("instance is created = {0}", Me.MessageSender.IsValueCreated.ToString()))
32 End Sub
33
34 Public Shared Sub Main()
35 Dim program As New Program()
36 program.Run()
37 End Sub
38 End Class
39
40 Public Interface IMessageSender
41 Function GetMessage() As String
42 End Interface
43
44 <Export(GetType(IMessageSender))> _
45 Public Class MessageSender
46 Implements IMessageSender
47 Public Function GetMessage() As String Implements IMessageSender.GetMessage
48 Return "メッセージです。"
49 End Function
50 End Class
上記サンプルコードを実行すると以下のような結果が出力されます。
出力結果から分かるとおりにLazyインスタンスのValueプロパティにアクセスするまでインスタンスが存在していない事が分かります。ValueプロパティにアクセスされるとLazyクラスはサービスのインスタンスを生成します。このようにエクスポートされたサービスを遅延させる事でコンテナーでエクスポートされたサービスのインスタンスを常にメモリに持っておく必要が無くなるのです。サービスインスタンスが大きい場合は即時使用されないサービス等がある場合はLazyクラスを上手く利用すると良いでしょう。
Managed Extensibility Framework(MEF)入門 -Contract-
MEFでサービスのエクスポートやインポート時に識別子を付与する事が出来ます。この識別子を『コントラクト』と言います。コントラクトには以下の種類があります。
名前コントラクトは文字列で識別子を設定し、型コントラクトは型で識別子を設定します。各パーツはサービスをエクスポートしたりインポートする際にコントラクトを指定します。
どのようにサービスに対してコントラクトを指定するかサンプルコードを元に見てみましょう。
エクスポートの既定のコントラクト
C#32 //既定では定義している型に依存する。
33 //今回は何も指定していないのでMessageSender型で型コントラクトが指定される。
34 [Export]
35 public class MessageSender
36 {
37 public string GetMessage()
38 {
39 return "MessageSenderからのメッセージです。";
40 }
41 }
VB26 '既定では定義している型に依存する。
27 '今回は何も指定していないのでMessageSender型で型コントラクトが指定される。
28 <Export()> _
29 Public Class MessageSender
30 Public Function GetMessage() As String
31 Return "MessageSenderからのメッセージです。"
32 End Function
33 End Class
上記ではMessageSenderクラスをサービスとしてエクスポートしている例です。ExportAttributeクラスは既定で定義された型を型コントラクトとして認識する為、サンプルコードではMessageSenderクラス型がコントラクトとして指定されている事になります。
エクスポートで型コントラクトの指定
C#32 public interface IMessageSender
33 {
34 string GetMessage();
35 }
36
37 //IMessageSenderインターフェース型をコントラクトに指定している。
38 [Export(typeof(IMessageSender))]
39 public class MessageSender : IMessageSender
40 {
41 public string GetMessage()
42 {
43 return "MessageSenderからのメッセージです。";
44 }
45 }
VB26 Public Interface IMessageSender
27 Function GetMessage() As String
28 End Interface
29
30 'IMessageSenderインターフェース型をコントラクトに指定している。
31 <Export(GetType(IMessageSender))> _
32 Public Class MessageSender
33 Implements IMessageSender
34 Public Function GetMessage() As String Implements IMessageSender.GetMessage
35 Return "MessageSenderからのメッセージです。"
36 End Function
37 End Class
上記ではIMessageSenderインターフェース型のコントラクトとしてMessageSenderクラスをエクスポートしている例です。ExportAttributeクラスのコンストラクタで型を指定すると型コントラクトとしてコントラクトに指定されます。ここで注意が必要です。「型コントラクトで指定した型(IMessageSenderインターフェース)は、エクスポートするクラス(MessageSenderクラス)と互換関係である」必要があります。これはコンパイルで検出されないので十分注意して下さい。
エクスポートで名前コントラクトの指定
C#37 //"MessageSender"という名前をコントラクトに指定している。
38 [Export("MessageSender")]
39 public class MessageSender
40 {
41 public string GetMessage()
42 {
43 return "MessageSenderからのメッセージです。";
44 }
45 }
VB30 '"MessageSender"という名前をコントラクトに指定している。
31 <Export("MessageSender")> _
32 Public Class MessageSender
33 Public Function GetMessage() As String
34 Return "MessageSenderからのメッセージです。"
35 End Function
36 End Class
上記では”MessageSender”という名前でMessageSenderクラスをエクスポートしている例です。ExportAttributeクラスのコンストラクタで文字列を指定すると名前コントラクトとしてコントラクトに指定されます。
エクスポートで型コントラクトと名前コントラクタの指定
C#
VB26 Public Interface IMessageSender
27 Function GetMessage() As String
28 End Interface
29
30 '名前コントラクトと型コントラクトを指定している。
31 <Export("MessageSender", GetType(IMessageSender))> _
32 Public Class MessageSender
33 Implements IMessageSender
34 Public Function GetMessage() As String Implements IMessageSender.GetMessage
35 Return "MessageSenderからのメッセージです。"
36 End Function
37 End Class
上記では名前コントラクトと型コントラクトを同時に指定している例です。このようにエクスポートを定義している型と互換性のある型コントラクトと名前コントラクトを指定する事で、サービスの識別子を柔軟に定義する事が出来ます。
インポートでのコントラクト
上記サンプルコードではエクスポートのコントラクトを中心に書きましたが、インポートでもエクスポートと同様にコントラクトを指定する事が出来ます。インポートでコントラクトを指定する事で柔軟なサービスの提供を受ける事が出来ます。
C#11 //既定で定義される型コントラクトにてインポート。
12 [Import]
13 public MessageSender MessageSender { get; set; }
14
15 //名前コントラクトにてインポート。
16 [Import("MessageSender")]
17 public IMessageSender MessageSender { get; set; }
18
19 //型コントラクトにてインポート。
20 [Import(typeof(IMessageSender))]
21 public object MessageSender { get; set; }
22
23 //名前コントラクトと型コントラクトにてインポート。
24 [Import("MessageSender", typeof(IMessageSender))]
25 public object MessageSender { get; set; }
VB55 '既定で定義される型コントラクトにてインポート。
56 <Import()> _
57 Public Property MessageSender() As MessageSender
58
59 ' 名前コントラクトにてインポート。
60 <Import("MessageSender")> _
61 Public Property MessageSender() As IMessageSender
62
63 '型コントラクトにてインポート。
64 <Import(GetType(IMessageSender))> _
65 Public Property MessageSender() As Object
66
67 '名前コントラクトと型コントラクトにてインポート。
68 <Import("MessageSender", GetType(IMessageSender))> _
69 Public Property MessageSender() As Object
上記のようにエクスポートと同様にインポートでも名前コントラクトと型コントラクトが使用出来ます。インポート側ではコントラクトを指定してコンテナーよりインポートしてもらうサービスにフィルタをかける事が出来ます。ただし、型コントラクトは一般的にはサービスがエクスポートされる時点で抽象化される事が大半の為、インポート時に型コントラクトを明示的に指定して利用するケースは少ないかと思います。またちょっとしたトピックスになりますが、インポート時の型コントラクトは具象型を指定する事が出来てしまいます。例えば、IMessageSenderインターフェース型のプロパティに型コントラクトとして具象クラスのMessageSenderクラスを指定すると言った事が可能です。ですが、インターフェースと実装の分離を実現する機構で、そのメリットは余りないように思えます。
コントラクトを使用したサンプル
最後にコントラクトを使用したサンプルです。
C#1 using System;
2 using System.ComponentModel.Composition;
3 using System.ComponentModel.Composition.Hosting;
4 using System.Collections.Generic;
5
6 namespace Contract.CSharp
7 {
8 public class Program
9 {
10 private CompositionContainer container;
11
12 [Import]
13 public IMessageSender MessageSender { get; set; }
14
15 [Import("Japanese")]
16 public IMessageSender JapaneseMessageSender { get; set; }
17
18 [Import("English")]
19 public IMessageSender EnglishMessageSender { get; set; }
20
21 private Program()
22 {
23 this.container = new CompositionContainer();
24 CompositionBatch batch = new CompositionBatch();
25 batch.AddPart(new MessageSender());
26 batch.AddPart(new JapaneseMessageSender());
27 batch.AddPart(new EnglishMessageSender());
28 batch.AddPart(this);
29 this.container.Compose(batch);
30 }
31
32 private void Run()
33 {
34 //IMessageSender型の取得
35 IMessageSender messageSender = this.container.GetExportedValue<IMessageSender>();
36 Console.WriteLine(MessageSender.GetMessage());
37
38 //JapaneseMessageSenderの取得
39 IMessageSender japaneseMessageSender = this.container.GetExportedValue<IMessageSender>("Japanese");
40 Console.WriteLine(japaneseMessageSender.GetMessage());
41
42 //EnglishMessageSenderの取得
43 IMessageSender englishMessageSender = this.container.GetExportedValue<IMessageSender>("English");
44 Console.WriteLine(englishMessageSender.GetMessage());
45
46 //インポートされたIMessageSender
47 Console.WriteLine(this.MessageSender.GetMessage());
48
49 //インポートされたJapaneseMessageSender
50 Console.WriteLine(this.JapaneseMessageSender.GetMessage());
51
52 //インポートされたEnglishMessageSender
53 Console.WriteLine(this.EnglishMessageSender.GetMessage());
54 }
55
56 public static void Main(string[] args)
57 {
58 Program program = new Program();
59 program.Run();
60 }
61 }
62
63 public interface IMessageSender
64 {
65 string GetMessage();
66 }
67
68 [Export(typeof(IMessageSender))]
69 public class MessageSender : IMessageSender
70 {
71 public string GetMessage()
72 {
73 return "MessageSenderからのメッセージです。";
74 }
75 }
76
77 [Export("Japanese", typeof(IMessageSender))]
78 public class JapaneseMessageSender : IMessageSender
79 {
80 public string GetMessage()
81 {
82 return "メッセージです。";
83 }
84 }
85
86 [Export("English", typeof(IMessageSender))]
87 public class EnglishMessageSender : IMessageSender
88 {
89 public string GetMessage()
90 {
91 return "It is a message.";
92 }
93 }
94 }
VB1 Imports System
2 Imports System.ComponentModel.Composition
3 Imports System.ComponentModel.Composition.Hosting
4
5 Public Class Program
6 Private container As CompositionContainer
7
8 <Import()> _
9 Public Property MessageSender As IMessageSender
10
11 <Import("Japanese")> _
12 Public Property JapaneseMessageSender As IMessageSender
13
14 <Import("English")> _
15 Public Property EnglishMessageSender As IMessageSender
16
17 Private Sub New()
18 Me.container = New CompositionContainer()
19 Dim batch As New CompositionBatch()
20 batch.AddPart(New MessageSender())
21 batch.AddPart(New JapaneseMessageSender())
22 batch.AddPart(New EnglishMessageSender())
23 batch.AddPart(Me)
24 Me.container.Compose(batch)
25 End Sub
26
27 Private Sub Run()
28 'IMessageSender型の取得
29 Dim messageSender As IMessageSender = Me.container.GetExportedValue(Of IMessageSender)()
30 Console.WriteLine(messageSender.GetMessage())
31
32 'JapaneseMessageSenderの取得
33 Dim japaneseMessageSender As IMessageSender = Me.container.GetExportedValue(Of IMessageSender)("Japanese")
34 Console.WriteLine(japaneseMessageSender.GetMessage())
35
36 'EnglishMessageSenderの取得
37 Dim englishMessageSender As IMessageSender = Me.container.GetExportedValue(Of IMessageSender)("English")
38 Console.WriteLine(englishMessageSender.GetMessage())
39
40 'インポートされたIMessageSender
41 Console.WriteLine(Me.MessageSender.GetMessage())
42
43 'インポートされたJapaneseMessageSender
44 Console.WriteLine(Me.JapaneseMessageSender.GetMessage())
45
46 'インポートされたEnglishMessageSender
47 Console.WriteLine(Me.EnglishMessageSender.GetMessage())
48 End Sub
49
50 Public Shared Sub Main()
51 Dim program As New Program()
52 program.Run()
53 End Sub
54 End Class
55
56 Public Interface IMessageSender
57 Function GetMessage() As String
58 End Interface
59
60 <Export(GetType(IMessageSender))> _
61 Public Class MessageSender
62 Implements IMessageSender
63 Public Function GetMessage() As String Implements IMessageSender.GetMessage
64 Return "MessageSenderからのメッセージです。"
65 End Function
66 End Class
67
68 <Export("Japanese", GetType(IMessageSender))> _
69 Public Class JapaneseMessageSender
70 Implements IMessageSender
71 Public Function GetMessage() As String Implements IMessageSender.GetMessage
72 Return "メッセージです。"
73 End Function
74 End Class
75
76 <Export("English", GetType(IMessageSender))> _
77 Public Class EnglishMessageSender
78 Implements IMessageSender
79 Public Function GetMessage() As String Implements IMessageSender.GetMessage
80 Return "It is a message."
81 End Function
82 End Class
上記サンプルを実行すると以下の結果が出力されます。
サンプルコードを通じてコントラクトの理解が深まりましたでしょうか?コントラクトでサービスの識別子を指定し使用する側で柔軟なサービスの利用が行えるようになります。コントラクトはMEFにおいて重要な物ですので是非ものにしてしまいましょう。