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において重要な物ですので是非ものにしてしまいましょう。