かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Managed Extensibility Framework入門 その6「拡張可能なアプリケーション作成」

ここまで基本をみっちりやってきました。まだまだMEFの底は遥かかなたにありますが、ここでやった知識だけでも一応拡張可能なアプリケーションというやつは作れます。なのでさっくり作ってみようと思います。

作りたいもの

起動すると、何かしら画面に表示されて終了するアプリケーション。
何かしら画面に表示されるものは、拡張可能でDLLをExtensionsフォルダに入れることで拡張される。

プロジェクトの外枠作成

ExAppSampleという名前でコンソールアプリケーションのプロジェクトを作成します。次に、ExAppSample.ExtensionPointという名前でクラスライブラリのプロジェクトを作成します。そして両方のプロジェクトにSystem.ComponentModel.Compositionの参照を追加します。
そしてExAppSampleからExAppSample.ExtensionPointへの参照を追加します。
ここまでの操作で、以下のような感じにプロジェクトはなっています。

では、次に拡張点を表すインターフェースの定義を行います。これはシンプルで文字列を返すだけのメソッドを持つインターフェースです。

namespace ExAppSample.ExtensionPoint
{
    // 拡張が定義する必要のあるインターフェース
    public interface IExtensionPoint
    {
        // 何か文字列返してね
        string GenerateText();
    }
}

そして、この拡張点を取りこむApplicationというクラスをExAppSampleプロジェクトに追加します。

public class Application
{
    // 拡張を取り込む
    [ImportMany]
    public IEnumerable<IExtensionPoint> ExtensionPoints { get; set; }

    public void Run()
    {
        // 取り込んだ拡張を列挙して文字列を画面に表示する
        Console.WriteLine("Application start.");
        foreach (var p in this.ExtensionPoints)
        {
            Console.WriteLine(p.GenerateText());
        }
        Console.WriteLine("Application end.");
    }
}

役者が揃ったのでMainメソッドで、この拡張を読み込んで実行するようにしてみようと思います。

static void Main(string[] args)
{
    // 拡張機能を入れるフォルダが無い場合は作っておく
    if (!Directory.Exists("Extensions"))
    {
        Directory.CreateDirectory("Extensions");
    }
    // 現在のアセンブリのカタログ
    var assm = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    // Extensionsフォルダにあるアセンブリのカタログ
    var extensions = new DirectoryCatalog("Extensions");

    // 2つのカタログを束ねる
    var agg = new AggregateCatalog(assm, extensions);
    // カタログをもとにコンテナを作る
    var container = new CompositionContainer(agg);

    // Applicationのインスタンスを取得して処理開始!
    var app = container.GetExportedValue<Application>();
    app.Run();
}

この状態で実行すると、まだ拡張点が1つもない状態なので、アプリケーションの開始と終了を知らせる表示しかされません。

Application start.
Application end.

拡張の作成

では、同じソリューション内にもう1つクラスライブラリ形式のプロジェクトを作成します。名前は、ExAppSample.Extensionsにします。System.ComponentModel.Compositionへの参照とExAppSample.ExtensionPointへの参照を追加します。そして、以下のようなクラスを定義します。

namespace ExAppSample.Extensions
{
    using System;
    using System.ComponentModel.Composition;
    using ExAppSample.ExtensionPoint;

    // 現在日を返す
    [Export(typeof(IExtensionPoint))]
    public class TodayText : IExtensionPoint
    {
        public string GenerateText()
        {
            return DateTime.Now.ToShortDateString();
        }
    }

    [Export(typeof(IExtensionPoint))]
    public class TanakaText : IExtensionPoint
    {
        public string GenerateText()
        {
            return "こんにちは田中です";
        }
    }

}

現在日の文字列とこんにちは田中ですという文字列を返すIExtensionPointを実装したクラスをIExtensionPoint型でExportしています。ちなみにこのままExAppSampleを実行しても実行結果は変わりません。作成した拡張機能を拡張機能を入れるフォルダに入れる必要があります。

ExAppSample.Extensionsプロジェクトフォルダのbin\Debugの下にあるExAppSample.Extensions.dllをコピーして、ExAppSampleプロジェクトフォルダのbin\Debug\Extensionsフォルダに貼り付けます。

この状態でアプリケーションを実行してみると、アプリケーションの動作が拡張されて、表示される文字列が変わります!!!

Application start.
2011/02/21
こんにちは田中です
Application end.

ちゃんとExAppSample.Extensionsプロジェクトで作ったTodayTextクラスとTanakaTextクラスが読み込まれて実行されているのがわかります。

簡単な説明

このプログラムの簡単な流を説明します。

  • AssemblyCatalogでExAppSample内でExportが定義されているクラスのカタログが作られます。
  • DirectoryCatalogでExtensionsフォルダにあるdll内でExportが定義されているクラスのカタログが作られます
    • ここでExAppSample.Extensionsで定義したTodayTextとTanakaTextがIExtensionPoint型としてExportされます
  • そして、上記で作ったカタログを元にCompositionContainerがApplicationクラスを組み立てます。
    • ApplicationクラスではIExtensionPointをImportMany指定で取り込んでいるのでTodayTextとTanakaTextが設定されます
  • Runの中でTodayTextとTanakaTextの処理が実行されます

今回の例は、単純でしたが、これをメニュー項目やページ内のタブやコンテキストメニューの拡張などに応用することで柔軟に拡張可能なアプリケーションを作ることが出来るようになります。今日は以上です。