Managed Extensibility Framework(MEF)入門 -Contract-

MEFでサービスのエクスポートやインポート時に識別子を付与する事が出来ます。この識別子を『コントラクト』と言います。コントラクトには以下の種類があります。



名前コントラクトは文字列で識別子を設定し、型コントラクトは型で識別子を設定します。各パーツはサービスをエクスポートしたりインポートする際にコントラクトを指定します。

 

contract_details

 

どのようにサービスに対してコントラクトを指定するかサンプルコードを元に見てみましょう。

 

エクスポートの既定のコントラクト

C#
   32 //既定では定義している型に依存する。
   33 //今回は何も指定していないのでMessageSender型で型コントラクトが指定される。
   34 [Export]
   35 public class MessageSender
   36 {
   37    public string GetMessage()
   38    {
   39       return "MessageSenderからのメッセージです。";
   40    }
   41 }
VB
   26 '既定では定義している型に依存する。
   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 }
VB
   26 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 }
VB
   30 '"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#
   32 public interface IMessageSender
   33 {
   34    string GetMessage();
   35 }
   36 
   37 //名前コントラクトと型コントラクトを指定している。
   38 [Export("MessageSender", typeof(IMessageSender))]
   39 public class MessageSender : IMessageSender
   40 {
   41    public string GetMessage()
   42    {
   43       return "MessageSenderからのメッセージです。";
   44    }
   45 }
VB
   26 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; }
VB
   55 '既定で定義される型コントラクトにてインポート。
   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 }
VB
    1 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

上記サンプルを実行すると以下の結果が出力されます。

 

contract_details_output

 

サンプルコードを通じてコントラクトの理解が深まりましたでしょうか?コントラクトでサービスの識別子を指定し使用する側で柔軟なサービスの利用が行えるようになります。コントラクトはMEFにおいて重要な物ですので是非ものにしてしまいましょう。

 

Source code