かずきのBlog@hatena

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

Managed Extensibility Framework入門 その10「初期化処理」

Managed Extensibility Framework入門もついに10回になりました。結構長いものです。今回は、MEFのコンテナ内で作成されたインスタンスの初期化処理について書きます。

通常のインスタンスの初期化

オブジェクト指向言語とうたってる言語では、全ての言語が兼ね備えてる初期化処理があります。俗にいうコンストラクタですね。MEFで管理されるインスタンスも、インスタンスが作成されるタイミングでコンストラクタが実行されるので、初期化処理を書くことが出来ます。しかし、この状態だと不都合なケースがあります。以下に例を示します。

namespace MEFInitSample
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            // コンテナ初期化
            var c = new CompositionContainer(
                new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            // PersonClientを取得する
            Console.WriteLine("PersonClient取得前");
            var client = c.GetExportedValue<PersonClient>();
            Console.WriteLine("PersonClient取得後");
        }
    }

    [Export]
    public class PersonClient
    {
        [Import]
        public Person Person { get; set; }

        public PersonClient()
        {
            // ここでPersonを使って処理をしたい!
            Console.WriteLine("PersonClient.ctor() called");
            Console.WriteLine("  this.Person = " + this.Person); // この時点ではthis.Personはnull
        }
    }

    [Export]
    public class Person
    {
    }
}

特に何か処理をするプログラムではありませんが、PersonClientクラスがPersonクラスをMEF経由で設定されるように構成しています。この状態でコンストラクタで初期化処理をしようとすると、その時点では、まだPersonClientクラスのPersonプロパティには値が設定されていません。

MEFから依存性の注入をしてもらったタイミングで処理をやる方法がないと、ちょっと困りそうです。役者が揃わないと仕事が出来ませんからね。

依存性注入直後に処理をする方法

そのための仕掛けとしてMEFではSystem.ComponentModel.Composition.IPartImportsSatisfiedNotificationインターフェースを提供しています。MSDNでは以下のように説明されています。

パーツのインポートが満たされたときに、そのパーツに通知します。

このインターフェースはOnImportsSatisfiedという引数も戻り値も無いメソッドが定義されています。このメソッドが、MEFのコンテナ内で初期化が終わったタイミングで呼び出されます。

実装してみよう

ということで、先ほどのPersonClientにこのインターフェースを実装して書き換えてみます。

namespace MEFInitSample
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            // コンテナ初期化
            var c = new CompositionContainer(
                new AssemblyCatalog(Assembly.GetExecutingAssembly()));

            // IPartImportsSatisfiedNotification.OnImportsSatisfiedの動作確認
            Console.WriteLine("PersonClient取得前");
            var client = c.GetExportedValue<PersonClient>();
            Console.WriteLine("PersonClient取得後");
        }
    }

    [Export]
    public class PersonClient : IPartImportsSatisfiedNotification
    {
        [Import]
        public Person Person { get; set; }

        public PersonClient()
        {
            Console.WriteLine("PersonClient.ctor() called");
            Console.WriteLine("  this.Person = " + this.Person);
        }

        public void OnImportsSatisfied()
        {
            // ここでは、Personクラスの設定が完了しているので好きなように使える
            Console.WriteLine("PersonClient.OnImportsSatisfied() called");
            Console.WriteLine("  this.Person = " + this.Person);
        }
    }

    [Export]
    public class Person
    {
    }    
}

先ほど説明したIPartImportsSatisfiedNotificationをPersonClientに実装しています。OnImportsSatistfiedでPersonプロパティの値を表示しています。これを実行すると、以下の結果が表示されます。

PersonClient取得前
PersonClient.ctor() called
  this.Person =
PersonClient.OnImportsSatisfied() called
  this.Person = MEFInitSample.Person
PersonClient取得後

最初のthis.Personの出力は、まだ値が設定されていないので何も表示されていません。そして、OnImportsSatisfiedではインスタンスが設定されているので、きちんと表示されています。

ということで、MEFから設定されるインスタンスが設定されたあとに初期か処理を行いたいときはIPartImportsSatisfiedNotificationを実装するということを今回は習得しました。

サンプルプログラム

サンプルプログラムは以下のページからダウンロードできます。

http://code.msdn.microsoft.com/Managed-Extensibility-3332350b