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を継承したカスタムエクスポートクラスを作成する必要があります。このカスタムエクスポートクラスについては別途取り上げたいと思います。