2010年5月30日日曜日

番外編 MEFを使ってみる

MEFは、プラグインなどのあとから拡張できる構造を
簡単に実現することができる仕組みだと認識しています。
以前はプラグインを実行時に読み込むには、
リフレクションを使ってそれなりのコードを書かないといけなかったのですが、
MEFによりだいぶ単純になっていて、また柔軟さを持っているようです。
※使いこなしていないので断言できません

本当はこれまでのように実際のコードで例を示したかったのですが、
複雑になりそうなのでサンプルコードで示します。

まず一つのクラス内でむりやり書いたコードです。

一応twitterクライアントで使えそうなシチュエーションということで、
タイムラインを表すITimelineインターフェイスと、
その具体的な実装PublicTimelineクラスとFriendTimelineクラスがあります。

そしてExport属性を付けたクラスを
ImportまたはImportMany属性を付けたプロパティが受け取ります。

コンストラクタでやっていることは、
CatalogクラスがExportを探してくる役目、
CompositionContainerがExportとImportの参照関係を構築する役目、
といったところです。(たぶん)



using System.ComponentModel.Composition;
using System.Reflection;
using System.ComponentModel.Composition.Hosting;


 public partial class MainWindow : Window {
  public MainWindow() {
   DataContext = this;
   var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
   var container = new CompositionContainer(catalog);
   container.ComposeParts(this);
   InitializeComponent();
  }

  public interface ITimeline {
   string Name { get; }
  }

  [Export(typeof(ITimeline))]
  class PublicTimeline : ITimeline {
   public string Name {
    get { return "みんなのタイムライン"; }
   }
  }

  [Export(typeof(ITimeline))]
  class FriendTimeline : ITimeline {
   public string Name {
    get { return "友達のタイムライン"; }
   }
  }

  [ImportMany]
  public IEnumerable<ITimeline> AllTimelines {
   get;
   set;
  }
 }

もちろん実際Exportするのははインナークラスである必要はありません。

ためしにViewを以下のように書いたらタイムライン名が表示されるはずです。


<Window x:Class="MEFSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
  <ListBox ItemsSource="{Binding Path=AllTimelines}" DisplayMemberPath="Name"/>
 </Grid>
</Window>

使う側(Importする側)でITimelineの実装クラスを列挙する必要がないことに注目してください。
また、タイムラインの実装クラスを増やしたい場合はExportがついたクラスを増やすだけです。
この場合も使う側(Importする側)に変更がいりません。

ただこの方法では、同じアセンブリからしかImportできません。
他のファイル(.dll)からImportするには、DirectoryCatalogを使います。
上のサンプルとは全く関係ありませんが、
作成中のアプリのコードで示すと、
Pluginディレクトリのなかのすべてのdllファイルの中の
IServiceインターフェイスの実装を探すコードは
たとえば以下のような感じになります。


   using (var dirCatalog = new DirectoryCatalog("Plugins"))
   using (var container = new CompositionContainer(dirCatalog)) {
    _services = container.GetExportedValues<IService>();
   }



(1例目はMSDNのサンプルがそうなっていたのでやっていませんでしたが、
Dispose()があるので実際のコードの2例目はusingで囲っています。)

これで、「プラグインはIServiceを実装してPluginディレクトリに入れる」というルールを決めるだけで
アプリケーション側はプラグインの一覧を簡単に探すことができます。
実用にはもっといろいろと決めることがありますが、
基本はこんなところです。

他にもCatalogの種類はあるし、いろんな呼び出し方がありそうなので、
詳しくは仕様を調べてください。
クラスではなくてメソッドをインポートする、などできます。
インポートに条件を付けたりもできます。
たくさんページがありますが、公式では以下のページが比較的わかりやすいかなと思いました。
http://msdn.microsoft.com/ja-jp/magazine/ee291628.aspx

0 件のコメント:

コメントを投稿