(本帖在版工的旧 Blog 中,发表日期为 2007/05/01)
在 .NET 1.x 的 C#、.NET 2.0 的各种语言中,有所谓的 using statement (如本 blog 上一篇帖子「使用ADO.NET 的NextResult 方法取得多个Result Set」的代码范例),可保证自动 dispose (释放) unmanaged object (对象) 所占用的资源,包括因未处理的 exception 而造成区块结束 (但 StackOverflowException 除外),系统都会 dispose 资源。因此若您在 using 区块中建立了数据库的 connection,即无须再手动 close connection,亦无须再下 Connection.Dispose()、Command.Dispose() 等指令。根据 MSDN Library 和目前市面上几本 ADO.NET 2.0 原文书都有提到,在 using 区块中会自动去做 dispose 的动作。
.NET 的 garbage collector 会自动释放不再使用的 managed resources 所占用的内存,不用程序员手动撰码;但 unmanaged resources 则需要程序员自行下 Dispose() 去做处理,以让对象彻底终止 unmanaged resources 的使用。例如传统的做法,常会在 try-catch-finally pattern 中去呼叫 Dispose 方法;但若是数据库的联机,则必须有不同考虑,因为若任意下 Dispose 提早回收,也可能导致联机无法有效地被重复引用。
现在大部分的数据库和 Data Provider 都有支持 Connection Pooling 机制,亦即在建立完数据库的联机后,当程序员呼叫 Close 方法关闭一个数据库的 Connection 对象时,.NET 的 Data Provider 并不会将这个对象所占用的内存空间释放掉,而是将此对象暂存至 Pool 之中 ,以便待会可以再重复使用。
若在设定时间 (默认为 60 秒) 内,没有应用程序使用到此对象,或是呼叫了 Dispose 方法,则 .NET Data Provider 才会真正关闭这个联机,并由 Garbage Collector 自动将资源收回。因此,常有 web 程序员在网络上各讨论区提到,是否有必要在呼叫 Close 方法后,再呼叫 Dispose 方法,并将 Connection 设为 Nothing (或 Null)?答案是不必要的。因为 GC 过一阵子就会自动回收未再被参照的联机,手动呼叫 Dispose 只不过提早回收的动作而已。而且若是该联机,可能会在短时间内被大量使用者同时存取的话,也应让其待在 Pool 中待命,而应避免手动呼叫 Dispose 方法,导致它被真正关闭并被回收,而无法有效地被重复使用。
由于 GC 只会在系统闲置或内存不足时才启动,因此除非是使用频率非常繁复的资源,否则交由 GC 自行处理即可。而设为 Nothing (或 Null) 也只是将 Connection 变量的位置清除 (Null Reference),事实上原来 New 出来的 Connection 对象还是存在。而 Dispose 方法是用来处理自行建立的 Windows 资源,但又不会自行释放的对象,如档案 (开档与关文件)、GDI 对象 (直接由 Win API 叫用)…等等。
接下来,再回头探讨 .NET 的 VB/C# 语言中都有的 using statement。using 语句算是简易版的 try-finally pattern,可让程序员以较简便的写法尽早去释放资源,尤其最适用在有限的 unmanaged resources 上,例如:档案和串流 I/O、Socket 网络连接、File Handle (档案控制代码)、COM 组件、绘图和字形、数据库存取、WorkflowRuntime (WF)、…等等的内存自动释放。using statement 遇到例外时,也会抛出例外 (throw),但不会去 catch 处理例外;因此若您想要自行处理例外的话,只能回归传统的 try-catch-finally 写法。
提供给 using statement 的对象必须实作 IDisposable 接口。若是自己写的 class,只要实作 System.IDisposable 接口,即拥有 Dispose 方法。之后若引用 using statement 去释放这个 class 的 instance,即会自动做 object 的 Close()、Dispose()、设定为 null (Nothing) 这三种动作,不需要再自己手动处理。反而若是自己手动多下一次 Close(),会让 CLR 浪费资源多做一次处理,反倒会影响程序「性能 (performance)」。根据国外网站及 ADO.NET 2.0 书籍证实,若 using 语句搭配 CommandBehavior.CloseConnection 一起使用,其重复关闭数据库联机的动作,会大幅地降低程序性能,处理时间甚至会多出 84% 以上 (叫用 ExecuteReader() 时,若搭配 CommandBehavior 枚举值 (enumerated value),可要求在查询完成后,自动关闭数据库联机)。
此外,using statement 也可多层巢状地使用,例如:第一层的 using statement 里包 SqlConnection 的宣告及 instance 的新增,第二层包 SqlCommand,第三层包 SqlDataReader。您亦可在巢状的 using statement 中指定多种的系统资源,包括数据库的 transaction 交易处理。
-------------------------------------------------------------------------
参考文件:
[1] 「Prescriptive Architecture - Improving ADO.NET Performance」, MSDN
[2] 「Choose the right .NET data provider, optimize application performance」, SearchWinDevelopment.com
[3] 多本中英文 ADO.NET 书籍、网络技术文件