さて、前回は、リハビリも兼ねてLazyクラスの簡単な使い方を説明しました。今回はMEFでのLazyの使い方を説明します。
そもそも拡張可能アプリケーションでの遅延初期化とは?
そもそも、なんで遅延初期化が必要なのかというと前回でも言ったかもしれませんが、拡張可能なアプリケーションという観点で少し説明してみたいと思います。拡張可能なアプリケーションは、小規模なものだと数個のプラグインで完結するかもしれませんが、大規模なものになってくると数百以上のプラグインから構成されることも珍しくありません。さらに、1つ1つのプラグインも、そこそこ高機能だったりします。
これらのプラグインが全て起動時に初期化されるとどうなるでしょう?アプリケーションの起動時間だけでコーヒーを飲んでこれるくらい時間がかかってしまうかもしれません。このような問題を回避するために、とりあえず不要なものは初期化しないというアプローチがよくとられます。起動時には必要最低限の初期化処理だけやって、必要になったタイミングでプラグインのインスタンスを生成するという感じです。MEFでは前回紹介した遅延初期化のためのクラスのLazyを使用することで、これらの遅延初期化の機能をサポートしています。
サンプルプログラムの解説
とりあえず、ApplicationにIPluginというインターフェースを実装したクラスをプラグインとして差し込む小さなプログラムで動作について説明します。
IPluginインターフェースの定義
サンプルなのでExecuteという何かしらの処理を実行するというだけのシンプルなインターフェースです。特に解説は必要ないと思います。
// Applicationへのプラグインのインターフェース interface IPlugin { void Execute(); }
IPluginの実装クラス
上記のIPluginを実装したクラスを2つ用意しました。(何個でもかまいませんが、サンプルの動作の説明には個数は特に関係ないので)
両方ともIPlugin型でExportしています。あと、インスタンスが生成されたタイミングを確認するために、コンストラクタで標準出力にメッセージを出力するようにしています。Executeメソッドでも、簡単な文字列を標準出力に出力しています。
// プラグインの実装その1 [Export(typeof(IPlugin))] class PluginA : IPlugin { public PluginA() { // コンストラクタが実行されたことを示すログ Console.WriteLine("PluginA#ctor called."); } public void Execute() { // PluginAの主処理 Console.WriteLine("PluginA#Execute called"); } } // プラグインの実装その2 [Export(typeof(IPlugin))] class PluginB : IPlugin { public PluginB() { // コンストラクタが実行されたことを示すログ Console.WriteLine("PluginB#ctor called."); } public void Execute() { // PluginBの主処理 Console.WriteLine("PluginB#Execute called"); } }
Applicationクラスの作成
まずは、Lazyを使用しないケースのアプリケーションクラスを作成します。アプリケーションクラスではImportManyでIPlugin型をMEFから差し込んでもらうようにしています。Runメソッドで、すべてのプラグインの処理を実行しています。
// 遅延初期化ではない場合 [Export] class Application { // Importの段階でクラスのインスタンスが作成される [ImportMany] public IEnumerable<IPlugin> Plugins { get; set; } public void Run() { foreach (var plugin in this.Plugins) { plugin.Execute(); } } }
Mainメソッドの実装
では、MEFの初期化をしてApplicationクラスのRunメソッドを呼び出してみます。
Console.WriteLine("遅延初期化をしていない場合"); var c = new CompositionContainer( new AssemblyCatalog(Assembly.GetExecutingAssembly())); var app = c.GetExportedValue<Application>(); app.Run();
上記のプログラムを実行すると、以下のような結果になります。
遅延初期化をしていない場合 PluginA#ctor called. PluginB#ctor called. PluginA#Execute called PluginB#Execute called
まず、PluginAとPluginBのインスタンスが作成されてからRunメソッド内で呼び出されたExecuteメソッドのログが出力されています。これが通常時の動作です。
Lazyを使用したアプリケーション
では、次にLazyを使用して遅延初期化を実現します。やりかたは簡単で、ImportするときにIPlugin型を指定していたところをLazy
// 遅延初期化の場合 [Export] class LazyApplication { [ImportMany] public IEnumerable<Lazy<IPlugin>> Plugins { get; set; } public void Run() { foreach (var plugin in this.Plugins) { // ここで初めてクラスのインスタンスが作成される plugin.Value.Execute(); } } }
そして、LazyApplicationを使用するようにMainメソッドを書き換えます。
Console.WriteLine("遅延初期化の場合"); var c = new CompositionContainer( new AssemblyCatalog(Assembly.GetExecutingAssembly())); var app = c.GetExportedValue<LazyApplication>(); app.Run();
プログラムを実行すると、以下のような結果になります。
遅延初期化の場合 PluginA#ctor called. PluginA#Execute called PluginB#ctor called. PluginB#Execute called
最初の例ではPluginAとPluginBのインスタンスが最初に生成されていましたが、今回の例ではExecuteが呼ばれる直前までPluginAとPluginBの初期化が遅延していることがわかります。
まとめ
MEFとLazyを使えば遅延初期化の仕組みが簡単に作れます。これは結構素敵なことじゃないかと思います。やることは以下のことだけです。
- Importする側をLazy
にする - Lazy
のValueプロパティにアクセスしたタイミングでインスタンスが作られる
- Lazy
サンプルプロジェクトの入手
コチラからダウンロードできます。
過去の記事
- Managed Extensibility Framework入門 その1「はじめに」
- Managed Extensibility Framework入門 その2「使うに当たって覚えておきたいこと」
- Managed Extensibility Framework入門 その3「Export」
- Managed Extensibility Framework入門 その4「もっとExport」
- Managed Extensibility Framework入門 その5「Import」
- Managed Extensibility Framework入門 その6「拡張可能なアプリケーション作成」
- Managed Extensibility Framework入門 その7「クラス以外のExportとImport」
- Managed Extensibility Framework入門 その7.5「遅延初期化のLazy」