LoginSignup
6
11

More than 5 years have passed since last update.

MEFの使い方

Last updated at Posted at 2017-08-22

はじめに

この記事では、MEFの使い方(実装方法)を解説します。

(この投稿ではMEFそのものについては説明を省略します)

手順

  1. 拡張機能向けインターフェースを作成する
  2. 拡張機能を作成する
  3. アプリケーションからの利用

事前準備

MEFを使うには、各プロジェクトの参照設定で「System.ComponentModel.Composition」アセンブリをインポートしておきます。

サンプルのアセンブリ構成

このサンプルのアセンブリ構成と、ソースコードツリーは下記の通りです。

MEF_0a1.jpg

拡張機能のDLL(MefApplication.Extensionのビルド結果)は、MefApplication配下のDebug/Extensionsフォルダに配置しアプリケーションを実行することで動作確認を行います。

├─MefApplication
│  ├─bin
│  │  └─Debug
│  │      └─Extensions ←②:ここに、拡張機能のDLL(①のファイル)を配置する
│  ├─obj
│  │  └─Debug
│  │      └─TempPE
│  └─Properties
├─MefApplication.Extension
│  ├─bin
│  │  ├─Debug ←①:ここに、拡張機能のオブジェクトファイル(DLL)が出力される
│  │  └─Release
│  ├─obj
│  │  └─Debug
│  │      └─TempPE
│  └─Properties
└─MefApplication.If
    ├─bin
    │  ├─Debug
    │  └─Release
    ├─obj
    │  └─Debug
    │      └─TempPE
    └─Properties

実装方式

拡張機能向けインターフェースの実装

MEFで読み込む拡張機能が提供するロジックを呼び出すためのメソッドを定義します。
拡張機能はアプリケーションからこのインターフェースを通じて呼び出しを行います。

このサンプルでは、何らかの処理を行うメソッドを提供する拡張機能向けのメソッドを定義します。

IMyPlugin.cs
/// <summary>
/// 拡張機能向けインターフェース
/// </summary>
public interface IMyPlugin
{
    void Execute();
}

拡張情報のメタデータを追加したExport属性

Export属性を付与するだけでもMEFによりエクスポート対象となるため拡張機能を利用できますが、この時拡張機能のコンストラクタが呼び出されるためインスタンスが作成されてしまいます。
1つ2つの拡張機能を読み込むだけならば、インスタンス化に消費されるリソースは僅かですが大量の拡張機能を読み込む可能性がある場合などでは、できるだけ拡張機能を利用する場面までインスタンス化を避けたいと考えます。
しかし、インスタンスは不要ですが、アプリケーションが利用可能なプラグインの一覧情報だけは作成したいというケースも多々あります。

このケースでは、プラグインの情報(プラグイン名やプラグイン概要など)をメタデータで実装する方法が利用できます。

メタデータを追加するため、既存のExport属性をスーパークラスとしたカスタム属性(MyExportAttribute)を作成します。

IMyExportAttribute.cs
public interface IMyExportAttribute
{
    string ExtensionName { get; } //< 拡張機能の名称
}

MEFの拡張機能を示すExport属性に、追加情報を加えたカスタム属性を実装します。
MEFにメタデータであることを認識させるため、カスタム属性クラスにMetadata属性を付与します。

MyExportAttribute.cs
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class MyExportAttribute: ExportAttribute, IMyExportAttribute
{
    public MyExportAttribute(string extensionName)
        : base(typeof(IMyPlugin))
    {
        this.ExtensionName = extensionName;
    }

    public string ExtensionName { get; private set; }
}

拡張機能の実装

拡張機能を実装したDLLを作成用のプロジェクトを、ソリューションに追加します。
このサンプルでは、MefApplication.Extensionプロジェクトを追加し、2つの拡張機能クラス(MyPluginA、MyPluginB)をそれぞれ実装します。

MEF_1a1.jpg

プラグインのソースコード

MyPluginA.cs
[MyExport("MyPluginA")]
public class MyPluginA : IMyPlugin
{
    public MyPluginA()
    {
        Console.WriteLine("PluginAのコンストラクタ");
    }

    public void Execute()
    {
        Console.WriteLine("PluginAのExecute");
    }
}
MyPluginB.cs
[MyExport("MyPluginB")]
class MyPluginB : IMyPlugin
{
    public MyPluginB()
    {
        Console.WriteLine("PluginBのコンストラクタ");
    }

    public void Execute()
    {
        Console.WriteLine("PluginBのExecute");
    }
}

アプリケーションからの利用

拡張機能呼び出し用拡張機能(クライアントクラス)

拡張機能を呼び出すための拡張機能(クライアントクラス)をアプリケーション内に作成します。

Client.cs
[Export]
class Client
{
    [ImportMany]
    public IEnumerable<Lazy<IMyPlugin, IMyExportAttribute>> Plugins { get; set; }

    public void Run()
    {
        foreach (var ex in this.Plugins)
        {
            Console.WriteLine($"[{ex.Metadata.ExtensionName}] ");
            ex.Value.Execute();
        }
    }
}

拡張機能呼び出し

このサンプルでは、拡張機能を「Extensionsフォルダ」に配置する事を想定しているため、DirectoryCatalogを使用し、MEFがインポートするDLLファイルを格納するフォルダパスを指定します。
※下記のコードでは、exeファイルの位置をカレントフォルダとした相対パスを指定します。

また、MEFのその他の拡張機能をインポートするためのClientクラスは、exeファイル自身のアセンブリに組み込まれるためAssemblyCatalogを使用し、exeファイル自身も拡張機能のインポート元に指定しています。

Program.cs
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("アプリケーションを開始します。");

        if (!Directory.Exists("Extensions"))
            Directory.CreateDirectory("Extensions");

        var assm = new AssemblyCatalog(Assembly.GetExecutingAssembly()); // "Application"アセンブリ内にある拡張機能を読み込む
        var extensions = new DirectoryCatalog("Extensions"); // "Extensions"フォルダ内にある拡張機能(DLLファイル)を読み込む

        var agg = new AggregateCatalog(assm, extensions); // 2つのカタログをマージしたカタログを作成する
        var container = new CompositionContainer(agg);

        var app = container.GetExportedValue<Client>();
        app.Run();

        Console.WriteLine("アプリケーションを終了します。");
        Console.ReadLine();
    }
}

実行結果

「サンプルのアセンブリ構成」章を参照し、拡張機能のDLLファイルをExtensionフォルダに配置し、アプリケーションを実行します。

この実行結果では、拡張機能のメソッドを呼び出しているためex.Value.Execute()の呼び出しにより、拡張機能がインスタンス化されコンストラクタが実行されていることを確認できます。

アプリケーションを開始します。
[MyPluginA]
PluginAのコンストラクタ
PluginAのExecute
[MyPluginB]
PluginBのコンストラクタ
PluginBのExecute
アプリケーションを終了します。

メタデータのみを読み込む

リソース消費を抑えるために、遅延読み込みにより拡張機能のインスタンスを作成せずにメタデータだけ読み込むように、ソースコードを変更します。

変更後のソースコード

Client.cs
[Export]
class Client
{
    [ImportMany]
    public IEnumerable<Lazy<IMyPlugin, IMyExportAttribute>> Plugins { get; set; }

    public void Run()
    {
        foreach (var ex in this.Plugins)
        {
            Console.Write($"[{ex.Metadata.ExtensionName}] ");
            // ex.Value.Execute(); // 拡張機能クラスのインスタンスは作成しない
        }
    }
}

実行結果

アプリケーションを開始します。
[MyPluginA]
[MyPluginB]
アプリケーションを終了します。

参照

上記のコードは、githubからダウンロードできます。

6
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
11