.NET Fx:Load ハンドラの中で Let's Close!

調査のきっかけ

Form.Load イベントハンドラの中で、Form.Close メソッドを呼び出しても大丈夫なのかと疑問に思ったので調べてみました。

注意事項

説明の内容が正しい保証は一切ありません。
Microsoft が公式に文書化していない内容を前提としてプログラムを書くことは推奨されません。

調査環境

検証用プログラムの概要

  • Form1 のボタンがクリックされた時に Form2 を生成し、続けて ShowDialog() を呼び出します
  • Form2 の Load イベントの中で Form2 を Close します

各種イベントハンドラブレークポイントを仕掛けて調査しました。

public partial class Form1 : Form
{
    private void button1_Click(object sender, EventArgs e)
    {
        new Form2().ShowDialog();
    }
}

public partial class Form2 : Form
{
    private void Form2_HandleCreated(object sender, EventArgs e) {}

    protected override void OnCreateControl()
    {
        base.OnCreateControl();
    }

    private void Form2_Load(object sender, EventArgs e)
    {
        this.Close();
    }

    private void Form2_FormClosing(object sender, FormClosingEventArgs e) {}

    private void Form2_FormClosed(object sender, FormClosedEventArgs e) {}
}

Form.Close() の実装

Form.Close() の実装は以下のようになっています。

参考:Form.Close() リファレンス実装

public void Close() {
    if (GetState(STATE_CREATINGHANDLE))
        throw new InvalidOperationException(SR.GetString(SR.ClosingWhileCreatingHandle, "Close"));

    if (IsHandleCreated) {
        closeReason = CloseReason.UserClosing;
        SendMessage(NativeMethods.WM_CLOSE, 0, 0);
    }
    else{
        // MSDN: When a form is closed, all resources created within the object are closed and the form is disposed.
        Dispose();
    }
}

ウィンドウハンドルが作成されるまでの流れ

InvalidOperationException 例外(内容:CreateHandle() の実行中は Close() を呼び出せません)が発生するのは、コントロールの状態変数に STATE_CREATINGHANDLE フラグが立っている時に Close() が呼び出された時のようです。
では、このフラグはいつ設定されて、いつ解除されるのでしょうか。

new Form2().ShowDialog() // (1)
  Forms.Form.ShowDialog()
    Forms.Form.ShowDialog(owner:null) // (A)
      Forms.Control.Handle.get()
        Forms.Form.CreateHandle() // (2)
          Forms.Control.CreateHandle()
            SetState(STATE_CREATINGHANDLE, true) // (3)
              Form.NativeWindow.CreateHandle(cp)
                Forms.UnsafeNativeMethods.CreateWindowEx(...)
                  [...]
Form2_HandleCreated(sender, e) // (4)
            SetState(STATE_CREATINGHANDLE, false) // (5)
  • (1) で Form2 を ShowDialog() する
  • (2) でハンドルの作成が始まる。ハンドルはフォームを new した時ではなく ShowDialog() の中で作成されていた
  • (3) で状態変数に STATE_CREATINGHANDLE フラグが設定される
  • その先で Control.HandleCreated イベントが発生し、ユーザコードのイベントハンドラ (4) が実行される
  • (5) で状態変数の STATE_CREATINGHANDLE フラグが解除される
  • (4) の中で Close() を呼び出したところ、InvalidOperationException 例外が発生した

Load イベントが発生するまで

次に、Form.Load イベントが発生するまでの流れを見てみます。

  Forms.Form.ShowDialog(owner:null) // 上のコードの (A) と対応
   [...]
    Forms.Application.RunDialog(form:Form2)
      Forms.Application.ThreadContext.RunMessageLoop(reason:msoloopModalForm, context)
        Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
          Forms.Control.Visible.set(true)
            Forms.Form.SetVisibleCore(ture)
              Forms.Control.SetVisibleCore(true)
                SafeNativeMethods.ShowWindow(hwnd, SW_SHOW:0x24) // (1)
                [Unmanaged Code]
                [Mnaged Code]
                Forms.NativeWindow.DebuggableCallback(hWnd, WM_SHOWWINDOW, w, l)
                  Forms.Control.ControlNativeWindow.WndProc(m)
                    Forms.Control.ControlNativeWindow.OnMessage(m)
                      Forms.Form.WndProc(m)
                        Forms.Form.WmShowWindow(m)
                          Forms.ScrollableControl.WndProc(m)
                            Forms.Control.WndProc(m)
                              Forms.Control.WmShowWindow(m)
                                Forms.Control.CreateControl() // (2)
                                  Forms.Control.CreateControl(fIgnoreVisible)
                                    Forms.Form.OnCreateControl() // (3)
                                      Forms.Form.OnLoad(e) // (4)
Form2_Load(sender, e) // (5)

Let's Close!

お待ちかねの Form.Load イベントハンドラの中で Close() を呼び出してみます。

Form2_Load(sender, e)
  this.Close() // (1)
    Forms.Form.Close()
      Forms.Control.SendMessage(msg:WM_CLOSE, wparam:0, lparam:0) // (2)
      [...]
        Forms.Form.WmClose(m)
          dialogResult = DialogResult.Cancel // (3)
          Forms.Form.CheckCloseDialog(closingOnly:true) // (4)
            Forms.Form.OnFormClosing(e) // (5)
Form2_FormClosing(sender, e) // (6)
[...]
Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
  UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(...)
    UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop(...) // (7)
      Forms.Form.CheckCloseDialog(closingOnly:false) // (8)
        Forms.Form.OnFormClosed(e) // (9)
Form2_FormClosed(sender, e) // (10)
    UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop(...) // (11)
  UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(...)
  Forms.Application.ThreadContext.EndModalMessageLoop(context)
Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
  • (1) のロードイベントハンドラの中で Close() を呼び出す
  • (2) で WM_CLOSE メッセージが送信される
  • (3) WM_CLOSE ハンドラ WmClose() の中で dialogResultDialogResult.Cancel が設定される
  • (4)dialogResult != DialogResult.None && CalledClosing == false の場合(この時点では true)、(5)Form.OnFormClosed メソッドが呼ばれ、ユーザーコードのイベントハンドラ (6) が実行される。その後、(4) の中で CalledClosingtrue が設定される

ここで話は ShowDialog のメッセージループの中に移ります。

まとめ