打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
原则15:使用 using 和 try/finally 清理资源

 原则15:使用 using 和 try/finally  清理资源

 

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处:http://dsqiu.iteye.com

 

      非托管资源类型必须使用 IDisposable 接口的 Dispose() 方法释放。.NET 的这个规则使得释放资源的职责是类型的使用者,而不是类型和系统。因此,任何时候你使用的类型有 Dispose() 方法,你就有责任调用 Dispose() 释放资源。最好的方法来保证 Dispose() 被调用时使用 using 语句或 try/finally 块。

 

      所有包含非托管资源的类型都应该实现 IDisposable 接口。此外,如果你没有恰当的回收这些类型,它们会被动创建析构函数。如果你忘记回收这些对象,这些非内存资源会在晚些时候析构函数被执行的时候释放。这就会使得这些对象在内存待的时间更久,从而会使得应用程序因占用系统资源过多而变慢。

 

       幸运的是, C# 语言设计者知道显示释放资源是一个很常见的操作。他们给语言添加了关键字使得会更容易。

 

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {   
  3.     SqlConnection myConnection = new SqlConnection(connString);  
  4.     SqlCommand mySqlCommand = new SqlCommand(commandString, myConnection);  
  5.     myConnection.Open();   
  6.     mySqlCommand.ExecuteNonQuery();  
  7. }  

  

 

       在这个例子中,有两个可回收的对象没有恰当地被清理:SqlConnection 和 SqlCommand 。这两个对象会一直停留在内存中直到它们的析构函数被调用。(这两个类都从 System.ComponentModel.Component 继承析构函数。

 

        通过在执行命令和连接结束的时候调用 Dispose 修复这个这个问题:

 

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {  
  3.     SqlConnection myConnection = new SqlConnection(connString);  
  4.     SqlCommand mySqlCommand = new SqlCommand(commandString, myConnection);  
  5.     myConnection.Open();   
  6.     mySqlCommand.ExecuteNonQuery();  
  7.     mySqlCommand.Dispose();   
  8.     myConnection.Dispose();  
  9. }  

  

 

       那样处理就很好了,除非这个 SQL 命令执行抛出了异常。这时,上面例子的 Dispose() 就不会被执行。使用 using 语句可以保证 Dispose() 被调用。你使用 using 语句分配对象,C# 编译器就会产生 try/finally 块包含这些对象:

 

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {  
  3.     using (SqlConnection myConnection = new SqlConnection(connString))   
  4.     {  
  5.         using (SqlCommand mySqlCommand = new SqlCommand(commandString, myConnection))  
  6.         {  
  7.             myConnection.Open();   
  8.             mySqlCommand.ExecuteNonQuery();  
  9.         }   
  10.     }  
  11. }  

  

 

       当你在函数使用一个可回收对象时, using 块是最简单方法确保对象被恰当回收。using 语句会产生 try/finally 块包裹被分配的对象。下面两段代码的 IL 代码是一样的:

 

C#代码  
  1. SqlConnection myConnection = null;  
  2. // Example Using clause:   
  3. using (myConnection = new SqlConnection(connString))   
  4. {  
  5.     myConnection.Open();   
  6. }  
  7. // example Try / Catch block:   
  8. try   
  9. {  
  10.     myConnection = new SqlConnection(connString);   
  11.     myConnection.Open();  
  12. }  
  13. finally   
  14. {  
  15.     myConnection.Dispose();   
  16. }  

  

 

       如果你对没有实现 IDisposable 类型上使用 using 语句,C# 编译器会报错。例如:

 

C#代码  
  1. // Does not compile:   
  2. // String is sealed, and does not support IDisposable.   
  3. using (string msg = "This is a message")  
  4. Console.WriteLine(msg);  

 

 

       using 语句对在编译时期类型实现 IDisposable 接口才能正常工作。你不能对任意对象使用:

 

C#代码  
  1. // Does not compile.   
  2. // Object does not support IDisposable.   
  3. using (object obj = Factory.CreateResource())  
  4. Console.WriteLine(obj.ToString());  
  5.    

 

 

       快速保护方法是使用 as 语句可以转换为安全可回收对象不管是否实现 IDisposable 接口:

 

C#代码  
  1. // The correct fix.   
  2. // Object may or may not support IDisposable.  
  3. object obj = Factory.CreateResource();  
  4. using (obj as IDisposable)   
  5. Console.WriteLine(obj.ToString());  

  

 

       如果 obj 实现了 IDisposable ,using 语句会产生清理的代码。否则,using  语句变为 using(null) 这样是安全的而且不做任何处理。如果你还是不确定使用 using 块包裹对象是否是正确的,为了安全:假设那样做是正确的并且像前面的方法一样使用 using 包裹对象。

 

       这是只是介绍一个简单的情况:当你在对象内使用可回收的局部对象,使用 using 语句包裹这个对象。现在看几个复杂的用法。在第一个例子两个不同对象需要回收:连接和命令。前面的做法是使用两个 using 语句,将需要回收的两个对象分别放在里面。每个 using 语句都会产生一个 try/finally 块。等价于你写了下面的代码:

 

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {   
  3.     SqlConnection myConnection = null;  
  4.     SqlCommand mySqlCommand = null;   
  5.     try   
  6.     {  
  7.         myConnection = new SqlConnection(connString);   
  8.         try   
  9.         {  
  10.             mySqlCommand = new SqlCommand(commandString, myConnection);  
  11.             myConnection.Open();   
  12.             mySqlCommand.ExecuteNonQuery();  
  13.         }   
  14.         finally   
  15.         {  
  16.         if (mySqlCommand != null)   
  17.             mySqlCommand.Dispose();  
  18.         }   
  19.     }   
  20.     finally   
  21.     {  
  22.     if (myConnection != null)   
  23.         myConnection.Dispose();  
  24.     }   
  25. }  

  

 

       每个 using 语句都会创建一个嵌套的 try/finally 块。幸好,我们很少会在一个分配两个实现 IDisposable 不同的对象。这种情况下,可以允许那样,因为它能正常工作。但是,如果你要分配多个实现 IDisposable 的对象时,会是一个很糟糕的实现,我更喜欢自己写 try/finally 块:

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {  
  3.     SqlConnection myConnection = null;  
  4.     SqlCommand mySqlCommand = null;   
  5.     try   
  6.     {  
  7.         myConnection = new SqlConnection(connString);   
  8.         mySqlCommand = new SqlCommand(commandString,  
  9.         myConnection);  
  10.         myConnection.Open();   
  11.         mySqlCommand.ExecuteNonQuery();  
  12.     }   
  13.     finally   
  14.     {  
  15.         if (mySqlCommand != null)   
  16.             mySqlCommand.Dispose();  
  17.         if (myConnection != null)   
  18.             myConnection.Dispose();  
  19.     }   
  20. }  

 

 

       唯一可以抛弃这种写法的理由是你能很容易的使用 using 和 as 语句实现:

 

这看上去很清晰,但是会有一个很狡猾的问题。 如果 SqlCommand() 构造抛出异常 SqlConnection 对象将不会被回收。 myConnection 已经被创建,但是当 SqlCommand 构造函数执行时代码就不会进入 using 块。在 using 块没有构造好, Dispose 的调用就会被跳过。你必须确保任何实现 IDisposable 的对象的分配要在 using 块或 try 块内。否则,资源泄露就会发生。

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {  
  3.     // Bad idea. Potential resource leak lurks!   
  4.     SqlConnection myConnection = new SqlConnection(connString);  
  5.     SqlCommand mySqlCommand = new SqlCommand(commandString,myConnection);   
  6.     using (myConnection as IDisposable)   
  7.     using (mySqlCommand as IDisposable)   
  8.     {  
  9.         myConnection.Open();   
  10.         mySqlCommand.ExecuteNonQuery();  
  11.     }   
  12. }  

 

       到目前为止,你已经学会了两种最常见的情况了。当你在一个函数只分配一个可回收对象,使用 using 语句是最好方法确保资源被释放。如果在一个方法中要分配多个对象,创建多个 using 块或自己写一个 try/finally 块。

 

       释放不同的可回收对象会有一些细微的差别。有些类型同时支持 Dispose 方法和 Close 方法去释放资源。 SqlConnection 就是其中之一。你像下面那样可以关闭 SqlConnection :

C#代码  
  1. public void ExecuteCommand(string connString, string commandString)  
  2. {   
  3.     SqlConnection myConnection = null;   
  4.     try   
  5.     {  
  6.         myConnection = new SqlConnection(connString);   
  7.         SqlCommand mySqlCommand = new SqlCommand  
  8.         (commandString, myConnection);  
  9.         myConnection.Open();   
  10.         mySqlCommand.ExecuteNonQuery();  
  11.     }   
  12.     finally   
  13.     {  
  14.         if (myConnection != null)   
  15.             myConnection.Close();  
  16.     }   
  17. }  

  

       这个版本是关闭连接,但跟回收它还是有一些不一样。Dispose 方法不只是释放资源:它还会告诉垃圾回收器这个对象不用执行析构函数。 Dispose 会调用 GC.SuppressFinalize() 。 Close 就没有这样的操作。结果,对象还待在析构队列中,即使析构已经不需要。如果你可以选择, Dispose 会比 Close 更好。你可以查看原则18了解所有细节。

 

       Dispose() 不会将对象从内存中移除。它会触发对象释放非托管资源。这意味着你使用已经回收的对象会有问题。上面 SQLConnection 就是例子。 SQLConnection 的 Dispose() 方法断开了数据库的连接。在你回收这个连接之后,SQLConnection 还留着内存中,但是不再和数据库保持连接。所以,在任何地方都不要回收还在被引用的对象。

 

       在某些方面,C# 的资源管理会比 C++ 更困难。你不能依赖最终的析构函数清理所有使用到的资源。但是垃圾回收机制对你再简单不过了。你使用到绝大多数类都没有实现 IDisposable 。在 .Net 框架的1500多个类,只有少于100个类实现了 IDisposable 。当你使用到其中的类,记得回收它们。你可以将这些对象放在 using 块或 try/finally 块中。无论你使用哪一种方式,确保每次所有对象总是都被恰当释放。

  

 

小结:

       D.S.Qiu 一直都不知道 Dispose 的作用,并且也很疑惑 Close 和 Dispose 的区别(文末给予了解答,痛快)。但是对 Dispose 还是没有吃透,C# 执行 Dispose 的内部流程是怎么样的,都做了哪些操作?

  

       欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!

       有关本书的其他章节翻译请点击查看,转载请注明出处,尊重原创!

 

       如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

       转载请在文首注明出处:http://dsqiu.iteye.com/blog/2078084

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
c#对于如何释放资源的解释
数据统计方法的封装
将dataGridView中的添加/删除等修改保存至数据库(winform - VS2005)
ASP.net连接SQL数据库的源代码
编写自己的高效分页SqlHelper
使用DBHelper
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服