MEFの仕組みとサンプル

さて、MEFの説明に戻る。

DIが「コンポーネント(クラス)間の依存関係を減らすことで、開発生産性の向上させること」に主眼を置いているのに対し、MEFはその名前の通り「アプリケーションの拡張性を向上させること」に重きを置いているようだ。

拡張性の向上というのは、たとえばプログラムにプラグイン機能を持たせたりする、といった仕組みである。MEFではプログラムの実行中であっても、あるクラスの実装を別のクラスの実装を差し替えること(MEFのドキュメントでは、現在は「recomposition」と呼ばれている)もできるようになっている。こういった仕組みは、やはりプラグイン機構における活用を意識しているのだと思われる。

あまり抽象的な説明ばかりしていても仕方ないので、具体例を見てみることにしよう(サンプルコードをこちらからダウンロードできる)。まず、Bark(吠える)というメソッドを持つIDogインタフェースとIDogインタフェースを実装した柴犬クラス(Shiba)を定義する。コードは次のようになる。

public interface IDog
{
    void Bark();
}

public class Shiba : IDog
{
    public void Bark()
    {
        MessageBox.Show("ワンワン");
    }
}

では、このShibaクラスのインスタンスを生成し、「ワンワン」というメッセージを表示させてみよう。ただし、直接Shibaクラスのインスタンスを生成してしまっては、いわゆる依存性の高いコードになってしまう。MEFの仕組みを利用して、IDogインタフェースを経由して呼び出す形にする。

まず、Shibaクラスを利用する側のコードを記述しよう。System.ComponentModel.Compositionへの参照を追加し、System.ComponentModel.Composition名前空間とSystem.ComponentModel.Composition.Hosting名前空間をインポートする。それからフォームに配置したボタンクリック時のイベントハンドラに、次のコードを記述する。

private void button1_Click(object sender, EventArgs e)
{
    //「カタログ」を生成し、その「カタログ」から「コンテナ」を生成
    var catalog = new AssemblyCatalog(typeof(Program).Assembly);
    var container = new CompositionContainer(catalog);

    //「コンテナ」の中から、IDogインタフェースを
    //実装したクラスのインスタンスを取り出す
    IDog dog = container.GetExportedObject<IDog>();
    dog.Bark();
}

このコードで行っていることを、順番に見ていこう。最初に、自分自身のアセンブリから「カタログ」を生成し、その「カタログ」を使用して「コンテナ」を生成している(「カタログ」とはMEFで使用されるコンポーネントの情報を保持しているクラスであり、「コンテナ」とはそれらのカタログが保持している情報を使用して、コンポーネントをひとまとまりに束ねているクラスである、というような理解でいいだろう)。あとは、コンテナからIDogインタフェースを実装したクラスのインスタンスを取り出し、そのインスタンスのBarkメソッドを呼び出している、というわけだ。

最後に、コンテナからIDogインタフェースを実装したクラスのインスタンスを取り出すときに、どのクラスのインスタンスを生成すればよいかという情報を、コンテナに教えてやる必要がある。今回使用するのはShibaクラスなので、Shibaクラスに次の属性を追加しておく。

[Export(typeof(IDog))]

以上で、MEFを使用したサンプルは完成である。ビルドして実行した後、ボタンをクリックすると「ワンワン」というメッセージが表示されることが確認できる。

では、今度はスピッツのクラス(Spitz)を定義して、Shibaクラスと差し替えてみよう。Shibaクラスをコメントアウトし、Spitzクラスを定義する。Spitzクラスのコードは以下のようになる。

[Export(typeof(IDog))]
public class Spits : IDog
{
    public void Bark()
    {
        MessageBox.Show("キャンキャン");
    }
}

Spitzクラスを定義した後にプログラムを実行してみれば、表示されるメッセージが「ワンワン」から「キャンキャン」に変わったことが確認できる。ここで注目して欲しいのは、呼び出す側のソースコード(Form1クラスのbutton1_Clickメソッド)は一切変更していないということである。このようにMEFを使用することで、クラス間の依存関係を弱めることができるのである。