Managed Extensibility Framework(MEF)入門 -MEFとは-

MEFとは『Managed Extensibility Framework』の略語です。MEFは以前「CodePlex」にて提供されておりましたが、.NET Framework 4.0に標準機能として追加されました。まずはMEFでどのような事が行えるかを簡単に説明したいと思います。MSDNではこのように書かれています。


Managed Extensibility Framework (MEF) は、軽量で拡張可能なアプリケーションを作成するためのライブラリです。これにより、アプリケーション開発者は、拡張機能を見つけたら、それをそのまま使用できます。構成は必要ありません。拡張機能の開発者は、コードを簡単にカプセル化できるため、ハードコーディングによる脆弱な依存関係を回避できます。MEF により、アプリケーション内だけでなく、アプリケーション間でも拡張機能を再利用できます。


とても難しい説明です。MEFではインターフェースと実装を分離し、プログラムの拡張性を高める事が出来ます。インターフェースと実装の分離する仕組みとしては「DI(Dependency Injection)コンテナー」が有名です。.NET開発では「Seaser.NET」や「Spling.NET」等が有名です。MEFは.NET版のDIコンテナと理解頂ければ初めは問題ないと思います。

まずはDIコンテナーとはどんな物かを簡単なサンプルと共に見ていきたいと思います。初めにこのようなコードがあるとします。

C#
public class Program
{
   static void Main(string[] args)
   {
      MessageManager messageManager = new MessageManager();
      MessageSender messageSender = new MessageSender();
      messageManager.Output(messageSender);
   }
}
 
public class MessageManager
{
   public void Output(MessageSender messageSender)
   {
      Console.WriteLine(messageSender.GetMessage());
   }
}
 
public class MessageSender
{
   public string GetMessage()
   {
      return "送信メッセージです。";
   }
}
VB
Module Module1
   Sub Main()
      Dim messageManager As New MessageManager()
      Dim messageSender As New MessageSender()
      messageManager.Output(messageSender)
   End Sub
End Module
 
Public Class MessageManager
   Public Sub Output(ByVal messageSender As MessageSender)
      Console.WriteLine(messageSender.GetMessage())
   End Sub
End Class
 
Public Class MessageSender
   Public Function GetMessage() As String
      Return "送信メッセージです。"
   End Function
End Class


このサンプルではメッセージ出力用のMessageManagerクラスと出力文字成形用のMessageSenderクラスがあります。MessageManagerクラスはOutputメソッドでMessageSenderクラスのインスタンスを必要としており、MessageSenderインスタンスが存在しないと機能しません。この関係ではMessageManagerクラスはMessageSenderクラスに「依存している」と読み取れます。この依存性を下げる為に一般的にはMessageSenderクラスにインターフェースを実装し、MessageManagerクラスのOutputメソッドの引数でインターフェースを受け取るようにし依存性を下げます。

C#
public class Program
{
   static void Main(string[] args)
   {
      MessageManager messageManager = new MessageManager();
      IMessageSender messageSender = new MessageSender();
      messageManager.Output(messageSender);
   }
}
 
public class MessageManager
{
   public void Output(IMessageSender messageSender)
   {
      Console.WriteLine(messageSender.GetMessage());
   }
}
 
public interface IMessageSender
{
   string GetMessage();
}
 
public class MessageSender : IMessageSender
{
   public string GetMessage()
   {
      return "送信メッセージです。";
   }
}
VB
Module Module1
   Sub Main()
      Dim messageManager As New MessageManager()
      Dim messageSender As IMessageSender = New MessageSender()
      messageManager.Output(messageSender)
   End Sub
End Module
 
Public Class MessageManager
   Public Sub Output(ByVal messageSender As IMessageSender)
      Console.WriteLine(messageSender.GetMessage())
   End Sub
End Class
 
Public Interface IMessageSender
   Function GetMessage() As String
End Interface
 
Public Class MessageSender
   Implements IMessageSender
   Public Function GetMessage() As String Implements IMessageSender.GetMessage
      Return "送信メッセージです。"
   End Function
End Class


これでMessageManagerクラスはIMessageSenderインターフェースに依存する事になり、MessageSender具象クラスからの依存性がなくなりました。ですが、依然としてMessageSender具象クラスのインスタンスを生成する箇所が存在します。このインスタンス生成における依存性を局所化する為に一般的には「ファクトリークラス」を作成しますが、アプリケーション規模が多くなるにつれてファクトリークラスも膨大な数になる事も良くあります。これをDIコンテナを使用するとこのようなコードになります。(説明を目的としたDIコンテナの為、サンプルコードをコピー&ペーストしても実効出来ません)

C#
public class Program
{
   static void Main(string[] args)
   {
      MessageManager messageManager = new MessageManager();
      //依存性を注入
      DIContainer.Injection();
      IMessageSender messageSender = DIContainer.GetInjectedObject<IMessageSender>();
      messageManager.Output(messageSender);
   }
}
 
public class MessageManager
{
   public void Output(IMessageSender messageSender)
   {
      Console.WriteLine(messageSender.GetMessage());
   }
}
 
public interface IMessageSender
{
   string GetMessage();
}
 
[Injection(typeof(IMessageSender))]
public class MessageSender : IMessageSender
{
   public string GetMessage()
   {
      return "送信メッセージです。";
   }
}
VB
Module Module1
   Sub Main()
      Dim messageManager As New MessageManager()
      '依存性を注入
      DIContainer.Injection()
      Dim messageSender As IMessageSender = DIContainer.GetInjectedObject(Of IMessageSender)()
      messageManager.Output(messageSender)
   End Sub
End Module
 
Public Class MessageManager
   Public Sub Output(ByVal messageSender As IMessageSender)
      Console.WriteLine(messageSender.GetMessage())
   End Sub
End Class
 
Public Interface IMessageSender
   Function GetMessage() As String
End Interface
 
<Injection(GetType(IMessageSender))> _
Public Class MessageSender
   Implements IMessageSender
   Public Function GetMessage() As String Implements IMessageSender.GetMessage
      Return "送信メッセージです。"
   End Function
End Class


上記ではMessageSender具象クラスのインスタンス生成・管理をDIContainerクラスがおこなってます。DIContainerの「DI」とは「依存性の注入」という意味であり、MessageSenderインスタンス生成の依存性をDIContainerクラスが一括して管理しています。DIContainerのInjectionメソッドを実行する事とMessageSenderクラスでマークされている「Injection属性」をDIContainerは探し、IMessageSenderインターフェースをキーとしてMessageSenderインスタンスDIContainerに取り込まれます。取り込まれたインスタンスDIContainerのGetIndectedObjectメソッドで取得するといった流れになります。属性でマークせずに外部ファイルなどでマッピングを定義する事もできます。このように、クラス間の依存性をインターフェースによって下げ、実装を切り離す事が容易に行えます。MEFを使用すると.NET Frameworkで標準機能としてこのような事が行えるようになります。まだこの説明ではいまいち理解に欠ける部分が多く、活用点が分かりずらいですが今後のMEFの記事を通してそういった部分を説明していきたいと思います。