WindowsFormsとDI

#id:sugimotokazuyaさんの日記に反応してみる。

WindowsアプリケーションでS2Container.NETを利用する場合、どこでS2Containerからコンポーネントを取得するのか、System.Windows.Forms.Formクラスはどこでインスタンス化するのかを決めなくてはなりません。[WindowsアプリケーションでのDI/ sugimotokazuyaの日記]

私自身、Seasar2(s2dotnet)等のDIContainerのライフサイクルは、通常のWindowsFormsアプリケーションのようにSystem.Windows.Forms.Formクラスの特定のインスタンスライフサイクルに依存すべきではないと考えている。特定のFormクラスではなくアプリケーション全体のライフサイクルをコントロールするクラスと結びつければ良いと思うが、それに適当なのはApplicationContextクラスが適当だと思うがどうだろう。

public class ApplicationContext : IDisposable

ApplicationContext クラスを使用すると、メッセージ ループを終了させる条件を再定義できます。既定では、ApplicationContext がアプリケーションのメイン Form の Closed イベントを待機してから、スレッドのメッセージ ループを終了します。[MSDN クラスラィブラリィリファレンスより

このクラスの派生クラスを作り、インスタンスのスコープ内でDIContainerインスタンスを管理する。(サンプルなので名前がベタだが、拙作のフレームワークでは殆ど同じことをしている)

public class ApplicationContextWithDI : ApplicationContext
{
    private IDIContainer diContainer;
    public CitrusApplicationImpl()
        : base()
    {
        this.diContainer = DIContainerFactory.Create();
        〜
    }
    public CitrusApplicationImpl(IDIContainer diContainer)
        : base()
    {
        this.diContainer = diContainer;
        〜
    }
    public IDIContainer DIContainer
    {
        get { return this.diContainer; }
        set { this.diContainer = value; }
    }
    〜

コンストラクタを2種類用意したが、デフォルトコンストラクタを使って、内部でDIContainerインスタンスを管理しても良いし、プロパティを使用してプロパティインジェクションでも良いだろう。さもなければApplicationContextWithDI自体をDIContainerで生成したい場合は2つ目のコンストラクタのようにDIContainerインスタンスを外部から注入するようにしても良いと思う。その場合以下のように設定ファイルとエントリポイントクラスを書く。(好みだが、私はFormクラスをエントリポイントにはしない)

  • 使用する設定ファイル

    
        container
    

  • アプリケーションのエントリポイント
class EntryPoint
{
    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        IDIContainer di = DIContainerFactory.Create();
        ApplicationContext ctx = (ApplicationContext)di.GetComponent("applicationContext");
        Application.Run(ctx);
    }
}

生成したアプリケーションコンテキストは上のように、Applicationオブジェクトの引数にしてアプリケーションを起動できる。このようにするもうひとつのメリットはアプリケーションの終了処理をコントロールできることだ。具体的にはApplicationContext.ExitThread メソッドをオーバライドすることでメインスレッドの終了の時を検知できると共に、確実に終了処理ができる。

protected override void ExitThreadCore()
{
    //DIContainerの後始末処理や最終的なクリーンアップがあればここに書ける
    〜
    //スーパクラスの同メソッドを呼ばないとメインスレッドが終了しないので注意
    base.ExitThreadCore();
}

この辺は他の、自分のフレームワークで必要なクラスのインスタンスコントロールもあるので、いろいろ考えるのが面白い部分だ。

追記:

普通のWindowsFormsアプリケーションで見られるMainFormの指定が無いが、MainFormはApplicationContextクラス、又はその派生クラスで同名のプロパティを設定することでApplication.RunメソッドにMainFormをセットするのと同義になるので、以下のようにDI設定ファイルを用意することで、これまた外部から注入することが可能である。このように、外部からGUIクラスの依存を制御できるのも、DIContainerを使うことのメリットのひとつだと考えている。


    
        container
        gui.hogeForm
    
        

  • Form定義用ファイル(form.config)

     
        "HogeFormです"