かずきのBlog@hatena

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

Managed Extensibility Framework入門 その5「Import」

さて、Exportもひと段落したので、今度はExportされたクラスを取り込むImportを見ていこうと思います。これさえマスターすれば、基本はおさえたも当然です!!!ということで気合を入れていきましょう。

名前指定でImport

Importは、もともとExportされたものを取り込むためのものなのでExportで指定しているものは、ほとんど同じように使うことが出来ます。例えば名前を付けてExportされたものは、名前を指定してImportすることが出来ます。では例を見てみましょう。一番単純なAクラスとBクラスの例でプログラムを書いてみます。

[Export]
class A
{
    [Import]
    public B Value { get; set; }
}

[Export("でーる")]
class B
{
}

AクラスでBクラスをImportするという単純なものです。ただし、BクラスはExportするときに名前が付けられています。この状態で、下記のようなAクラスを取得するMainメソッドを実行するとどうなるか見てみようと思います。

static void Main(string[] args)
{
    // コンテナを作るところはお馴染み
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    // Aクラスを取得してみる
    var a = container.GetExportedValue<A>();
}
ハンドルされていない例外: System.ComponentModel.Composition.ImportCardinalityMis
matchException: 制約 '((exportDefinition.ContractName == "HelloMEF.A") AndAlso (
exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "HelloMEF.A"
.Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))' に一致する
有効なエクスポートが見つかりませんでした。無効なエクスポートが拒否されている可能
性があります。

あらかたの予想通り?怒られてしまいます。これはAクラスがImportしているクラスBを探そうとしたときに単純にBという型指定だけでは取得できるものがコンテナ内に無かったために起きています。これはImportに名前を指定してやることで動かせるようになります。

[Export]
class A
{
    // Import側でも名前を指定
    [Import("でーる")]
    public B Value { get; set; }
}

[Export("でーる")]
class B
{
}

この状態で実行すると例外は発生しません。(何も標準出力に出してないので実行結果は無いですが・・・)ということで、このように名前がつけられてExportされているものは名前をつけてImportしないといけません。

複数項目のImport

では、次に同じ型で複数のクラスがExportされている場合を見てみようと思います。名前が指定されていれば名前指定でImportすればいいのですが、今回は、名前がついていないケースです。前回やった以下のようなTanakaクラスとOkazukiクラスがいるような状態です。

// 挨拶屋
interface IGreeter
{
    void Greet();
}

// IGreeterとしてTnakaクラスをExport
[Export(typeof(IGreeter))]
class Tanaka : IGreeter
{
    public void Greet()
    {
        Console.WriteLine("こんにちは!たなかです!");
    }
}

// IGreeterとしてOkazukiクラスをExport
[Export(typeof(IGreeter))]
class Okazuki : IGreeter
{
    public void Greet()
    {
        Console.WriteLine("犬のアイコンです");
    }
}

このようなクラスがExportされている状態で、この2つのクラスをImportしたいというケースのやり方についてみていきます。これは単純にImportするだけではダメで、複数のクラスをImportする専用のImportManyという属性が必要になります。ImportManyはIEnumerable>T<型に指定することで動作します。では、IGreeterをImportする型を定義してみます。

[Export]
class GreeterClient
{
    // IGreeterとしてExportされているものを全てかき集める
    [ImportMany]
    public IEnumerable<IGreeter> Greeters { get; set; }

    // 全員に挨拶させる
    public void Execute()
    {
        foreach (var g in this.Greeters)
        {
            g.Greet();
        }
    }
}

このGreeterClientはIGreeter型としてExportされているものを全てImportして、Executeメソッドで全員に挨拶をさせるクラスです。では、Mainを以下のように書き換えて動作を見てみます。

static void Main(string[] args)
{
    // コンテナを作るところはお馴染み
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    // GreeterClientを取得して処理を実行
    var client = container.GetExportedValue<GreeterClient>();
    client.Execute();
}

実行結果は以下のようになります。

こんにちは!たなかです!
犬のアイコンです

以上でImportは、とりあえずおしまいです。

まとめ

  • 名前つきでExportされているものは名前つきでImport
  • 同じ型で複数ExportされているものはImportMany属性 + IEnumerable<T>