カスタム属性なしのMEF

CodePlexにあるMEF v2ではカスタム属性ではなくコードでImport/Exportが可能になっています。「おいおい、依存関係の解決をコードでやらずに設定ファイルに出して、設定ファイルが複雑になるのでカスタム属性で指定できるようになったのに、また、コードに戻るのかいな?」と疑問に思われるかも知れません。確かにその通り。ただ、カスタム属性は便利ですが、属性を付けるにはソースコードの変更が必要で、バイナリレベルでの再利用はし難いという制約もあり、その場合の逃げ手になったり、依存関係の指定方法の自作をする場合に便利かも知れません。

で、やり方。

まずは、いつものインタフェース。

namespace MefSample
{
    public interface IGreeting
    {
        void Say(string name);
    }
}

次に、実装クラスを2つばかり。

using System;
using System.ComponentModel.Composition;

namespace MefSample
{
    // [Export(typeof(IGreeting))]
    public class Hello : IGreeting
    {
        public void Say(string name)
        {
            Console.WriteLine(@"Hello, {0}", name);
        }
    }
}
using System;
using System.ComponentModel.Composition;

namespace MefSample
{
    // [Export(typeof(IGreeting))]
    public class Goodby : IGreeting
    {
        public void Say(string name)
        {
            Console.WriteLine(@"Goodby, {0}", name);
        }
    }
}

カスタム属性がコメントアウトされていることに注意してください。

では、依存関係の解決をします。

using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;

namespace MefSample
{
    // [Export]
    public class Program
    {
        // [ImportMany]
        public IEnumerable<IGreeting> Greetings { get; set; }

        public void Say()
        {
            foreach (var greeting in Greetings)
                greeting.Say("mei");
        }

        static void Main(string[] args)
        {
            var registration = new RegistrationBuilder();
            // Helloクラスを登録してIGreetingインタフェースをエクスポート
            registration.OfType<Hello>().Export<IGreeting>();
            // Goodbyクラスを登録してIGreetingインタフェースをエクスポート
            registration.OfType<Goodby>().Export<IGreeting>();

#if false
            // IGreetingインタフェースを実装しているクラスを登録してIGreetingインタフェースをエクスポート
            // こうすると、Hello, Goodby両方を一度に登録できる(IGreetingを実装しているので)
            registration.Implements<IGreeting>().Export<IGreeting>();
#endif

            // Programを登録し、型がIEnumerable<IGreeting>のプロパティにインポートして、Programをエクスポート
            // ImportManyの場合は、第二引数のdelegateでAsMany()とする必要がある
            registration.OfType<Program>()
                .ImportProperty(property => property.PropertyType == typeof (IEnumerable<IGreeting>), (p, b) => b.AsMany())
                .Export<Program>();

            var catalog = new AssemblyCatalog(typeof (Program).Assembly, registration);
            var container = new CompositionContainer(catalog);
            // エクスポートされているProgramを取得
            var program = container.GetExportedValue<Program>();
            program.Say();
        }
    }
}

/* 結果
Hello, mei
Goodby mei
*/

と、こんな感じです。もちろん、カスタム属性と組み合わせることもできるので、外部DLLはコードでインポート/エクスポートして自前はカスタム属性を使うというのもアリです。