読者です 読者をやめる 読者になる 読者になる

MEF(Managed Extensibility Framework)

C#

最近、MEF(Managed Extensibility Framework)って言葉をよく聞くようになったなと思っていたのですが、Visual Studio 2010(.NET Framework 4.0)で標準搭載される機能なんですね。

動的に機能を拡張するためのフレームワークだそうです。

使い方は簡単で、MEF Programming Guideを読めば、だいたい分かると思います。

と、この記事を書いているうちに、MEFのバージョンがPreview 3からPreview 4にあがっていて、使い方が変わっていました(゚д゚lll)

まだ正式リリース前なので、利用する際には注意が必要ですね。


以下は、Preview 4での使い方です。

まずは、機能を拡張される側のソースです。これをコンソールアプリケーションとして作ります。

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

class Program
{
    [Import]
    public IMessageSender MessageSender { get; set; }

    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }

    public void Run()
    {
        Compose();
        MessageSender.Send("Message Sent");
    }
    private void Compose()
    {
        // アプリと同じディレクトリから拡張機能を検索する。
        var catalog = new DirectoryCatalog(@".\");
        var container = new CompositionContainer(catalog);
        CompositionBatch batch = new CompositionBatch();
        batch.AddPart(this);
        container.Compose(batch);
    }
}

public interface IMessageSender
{
    void Send(string message);
}

次に拡張機能のソース。こちらはクラスライブラリとして作ります。

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Primitives;

[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

そして、dllとexeを同じディレクトリに置いて実行すると、拡張機能が利用されます。


さて、次にこのExportAttributeに、独自のプロパティを追加したいなと思ったのですが、ExportAttributeはsealedクラスなので、継承することはできません。(MEFのソースを書き換えてsealedを外せば一応うごきましたが。)

どうしたものかと困っていたら、僕と同じようなことを言っている人がいました。

ImportRequiredMetadata、ExportMetadataを使うといいみたいですね。


このExportAttributeがsealedクラスになっている理由として、MSDNへのリンクが張られていました。

属性をシールすると、属性の取得時にリフレクションのパフォーマンスが向上します。

http://msdn.microsoft.com/ja-jp/library/ms229023.aspx

ということらしいです。

へーそうなんだと思いつつ、以下のようなコードで性能を測ってみたのですが、大きな差異は見られませんでした。むしろUnsealedの方がはやいくらい。何か勘違いしてる?

using System;
using System.Diagnostics;
using System.Reflection;

class AttributePerformance
{
    static void Main(string[] args)
    {
        Assembly asm = Assembly.GetExecutingAssembly();
        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i < 10000; i++)
        foreach (var type in asm.GetTypes())
        {
            var attr = GetAttribute<MyUnsealedAttribute>(type);
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedTicks);
        sw.Reset();
        sw.Start();
        for (int i = 0; i < 10000; i++)
        foreach (var type in asm.GetTypes())
        {
            var attr = GetAttribute<MySealedAttribute>(type);
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedTicks);
    }

    private static T GetAttribute<T>(Type type) where T : class
    {
        object[] attrs = type.GetCustomAttributes(typeof(T), true);
        if (attrs.Length == 0) return null;
        return (T)attrs[0];
    }
}

sealed class MySealedAttribute : Attribute
{
}

class MyUnsealedAttribute : Attribute
{
}

[MySealed]
class Hoge
{
}

[MyUnsealed]
class Fuga
{
}