.NET Framework 4 で提供されている Managed Extensibility Framework (以下 MEF) は、シンプルな DI 機能を提供していて、アプリにプラグイン機能を実装するのに使えます。私も最近、MEF を使ったプラグイン機能の実装を検討し始めました。
MEF を使ってプラグイン機能を実装するにしても、あちらこちらで MEF の CompositContainer を new するわけにはいけないので、プラグインを管理するクラスを用意することになると思います。そのクラスの実装で現在悩み中。
まず最初に書いたのは超シンプルなファクトリクラス。もうほとんど CompositContainer をラップしただけ。
public static class PluginFactory { private readonly static CompositContainer _container; static PluginFactory() { var path = Path.Combine(AppDomain.Current.BaseDirectory, "plugins"); var catalog = new DirectoryCatalog(path); _container = new CompositContainer(catalog); } public static T Create<T>() { return _container.GetExportedValue(); } }
これだとコンテナに T に指定したクラスが登録されていないとき例外が出てしまいます。
例外が出ないように修正。
public static class PluginFactory { private readonly static CompositContainer _container; static PluginFactory() { var path = Path.Combine(AppDomain.Current.BaseDirectory, "plugins"); var catalog = new DirectoryCatalog(path); _container = new CompositContainer(catalog); } public static T Create<T>() { var instance = _container.GetExportedValueOrDefault(); if (instance == null) { instance = Activator.CreateInstance<T>(); } return instance; } }
GetExportedValueOrDefault を使えば、コンテナに登録されてないとき null が返ってきます。Activator.CreateInstance でインスタンス作成してるけど、これだとインタフェースや抽象クラスのとき例外が出ます。これだけだとイマイチ。
デフォルトで使うクラスを指定できるようにしてみます。
public static class PluginFactory { private readonly static CompositContainer _container; static PluginFactory() { var path = Path.Combine(AppDomain.Current.BaseDirectory, "plugins"); var catalog = new DirectoryCatalog(path); _container = new CompositContainer(catalog); } public static T CreateOrDefault<T, TDefault>() where TDefault : T, new() { var instance = _container.GetExportedValueOrDefault(); if (instance == null) { instance = new TDefault(); } return instance; } }
デフォルトで使うクラスは、コンストラクタに引数が必要、なんてケースがあるかもしれない。
いっそ、関数を渡せるようにしてしまえ。
public static class PluginFactory { private readonly static CompositContainer _container; static PluginFactory() { var path = Path.Combine(AppDomain.Current.BaseDirectory, "plugins"); var catalog = new DirectoryCatalog(path); _container = new CompositContainer(catalog); } public static T CreateOrDefault<T>(Func<T> createDefault) { var instance = _container.GetExportedValueOrDefault(); if (instance == null) { instance = createDefault(); } return instance; } }
毎回生成用関数を引数で渡すのは面倒かな…。
そんな紆余曲折があって、いまのところこんな感じです。
public static class PluginFactory { private readonly static CompositContainer _container; static PluginFactory() { var path = Path.Combine(AppDomain.Current.BaseDirectory, "plugins"); var catalog = new DirectoryCatalog(path); _container = new CompositContainer(catalog); } public static T Create<T>() { var instance = _container.GetExportedValueOrDefault(); if (instance == null) { instance = Activator.CreateInstance<T>(); } return instance; } public static T CreateOrDefault<T>(Func<T> createDefault) { var instance = _container.GetExportedValueOrDefault(); if (instance == null) { instance = createDefault(); } return instance; } }
GetExportedValue 系のメソッドでは、コンストラクタに渡す引数を指定できないから、しっくりきてないけど。
Prism が MEF を使い倒してるみたいなので、ソースコード読んでみようかな。