我们在Winform开发的时候,使用From.Show来显示窗口,使用Form.Close来关闭窗口。熟悉Winform开发的想必对这些非常熟悉。但是Form类型实现了IDisposable接口,那我们是否需要每次关闭窗口后都去调用Dispose呢?对于这个问题我们可以查看一下Form的源码。
public void Close() { if (this.GetState(262144)) throw new InvalidOperationException(SR.GetString("ClosingWhileCreatingHandle", new object[1] { (object) "Close" })); else if (this.IsHandleCreated) { this.closeReason = CloseReason.UserClosing; this.SendMessage(16, 0, 0); } else base.Dispose(); }
很明显这个方法有3个分支。第一个分支是关闭出现异常的情况,第二个分支是句柄已经创建的时候执行,很明显第三个分支的时候直接调用了基类的Dispose方法。大部分时候窗口调用Close时句柄肯定是被创建了,那就会进入第二个分支。很明显第二个分支没有调用Dispose,那是不是真的呢?继续跟进去。
SendMessage
internal IntPtr SendMessage(int msg, int wparam, int lparam) { return UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), msg, wparam, lparam); }
调了一个静态方法继续进去
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
明白了吧。SendMessage其实是调用了user32.dll的API,向指定的窗口发送消息。这里就是向自己发送一个消息,注意msg=16哦。既然有发送消息的,那肯定得有拦截消息的方法啊。(这里多扯一句,.NET Winform使用了事件驱动机制,事件机制其实也是封装了消息机制。)
protected override void WndProc(ref Message m) { switch (m.Msg) { case 529: this.WmEnterMenuLoop(ref m); break; case 530: this.WmExitMenuLoop(ref m); break; case 533: base.WndProc(ref m); if (!this.CaptureInternal || Control.MouseButtons != MouseButtons.None) break; this.CaptureInternal = false; break; case 546: this.WmMdiActivate(ref m); break; case 561: this.WmEnterSizeMove(ref m); this.DefWndProc(ref m); break; case 562: this.WmExitSizeMove(ref m); this.DefWndProc(ref m); break; case 288: this.WmMenuChar(ref m); break; case 293: this.WmUnInitMenuPopup(ref m); break; case 274: this.WmSysCommand(ref m); break; case 279: this.WmInitMenuPopup(ref m); break; case 167: case 171: case 161: case 164: this.WmNcButtonDown(ref m); break; case 71: this.WmWindowPosChanged(ref m); break; case 130: this.WmNCDestroy(ref m); break; case 132: this.WmNCHitTest(ref m); break; case 134: if (this.IsRestrictedWindow) this.BeginInvoke((Delegate) new MethodInvoker(this.RestrictedProcessNcActivate)); base.WndProc(ref m); break; case 16: if (this.CloseReason == CloseReason.None) this.CloseReason = CloseReason.TaskManagerClosing; this.WmClose(ref m); break; case 17: case 22: this.CloseReason = CloseReason.WindowsShutDown; this.WmClose(ref m); break; case 20: this.WmEraseBkgnd(ref m); break; case 24: this.WmShowWindow(ref m); break; case 36: this.WmGetMinMaxInfo(ref m); break; case 1: this.WmCreate(ref m); break; case 5: this.WmSize(ref m); break; case 6: this.WmActivate(ref m); break; default: base.WndProc(ref m); break; } }
WndProc就是用来拦截窗口消息的。看一下代码,Form重写了这个方法,一个很简单的switch。Case 16调用了 WmClose方法,继续跟进去。
private void WmClose(ref Message m) { FormClosingEventArgs e1 = new FormClosingEventArgs(this.CloseReason, false); if (m.Msg != 22) { if (this.Modal) { if (this.dialogResult == DialogResult.None) this.dialogResult = DialogResult.Cancel; this.CalledClosing = false; e1.Cancel = !this.CheckCloseDialog(true); } else { e1.Cancel = !this.Validate(true); if (this.IsMdiContainer) { FormClosingEventArgs e2 = new FormClosingEventArgs(CloseReason.MdiFormClosing, e1.Cancel); foreach (Form form in this.MdiChildren) { if (form.IsHandleCreated) { form.OnClosing((CancelEventArgs) e2); form.OnFormClosing(e2); if (e2.Cancel) { e1.Cancel = true; break; } } } } Form[] ownedForms = this.OwnedForms; for (int index = this.Properties.GetInteger(Form.PropOwnedFormsCount) - 1; index >= 0; --index) { FormClosingEventArgs e2 = new FormClosingEventArgs(CloseReason.FormOwnerClosing, e1.Cancel); if (ownedForms[index] != null) { ownedForms[index].OnFormClosing(e2); if (e2.Cancel) { e1.Cancel = true; break; } } } this.OnClosing((CancelEventArgs) e1); this.OnFormClosing(e1); } if (m.Msg == 17) m.Result = (IntPtr) (e1.Cancel ? 0 : 1); if (this.Modal) return; } else e1.Cancel = m.WParam == IntPtr.Zero; if (m.Msg == 17 || e1.Cancel) return; this.IsClosing = true; if (this.IsMdiContainer) { FormClosedEventArgs e2 = new FormClosedEventArgs(CloseReason.MdiFormClosing); foreach (Form form in this.MdiChildren) { if (form.IsHandleCreated) { form.OnClosed((EventArgs) e2); form.OnFormClosed(e2); } } } Form[] ownedForms1 = this.OwnedForms; for (int index = this.Properties.GetInteger(Form.PropOwnedFormsCount) - 1; index >= 0; --index) { FormClosedEventArgs e2 = new FormClosedEventArgs(CloseReason.FormOwnerClosing); if (ownedForms1[index] != null) { ownedForms1[index].OnClosed((EventArgs) e2); ownedForms1[index].OnFormClosed(e2); } } FormClosedEventArgs e3 = new FormClosedEventArgs(this.CloseReason); this.OnClosed((EventArgs) e3); this.OnFormClosed(e3); base.Dispose(); }
WmClose这个方法略有点复杂,主要是用来出发OnCloseing,OnClosed等事件。看看最后,它终于调用了base.Dispose()。看来Close方法确实会自动调用Dispose。是吗,不要高兴的太早。仔细看看前面的代码,if (this.Modal) return; 看到没,当窗口是模态的时候,方法直接return了。
到这里就差不多了。所以当我们使用ShowDialog来显示窗体的时候,当你关闭的时候,最好手动Dispose一下。为什么是最好呢,因为其实在GC回收垃圾的时候还是会调用窗体的Dispose的,因为在Form的基类的终结器里面有调用Dispose(false);
~Component() { this.Dispose(false); }
其实在MSDN上微软就对这有说明,顺便吐槽一下中文MSDN的翻译,实在是太烂了。
有2种情况下需要手工调用Dispose:
1. 窗口是MDI的一部分且是不可见的
2.模态的时候
第二种情况就是现在说的,但是第一种情况我测试了下,没有复现出来,MDI里面的子窗口调用Close的时候跟正常一样,每次都会自动Dispose。试了很久还是一样的结果,求高人指定吧。