利用using和try/finally语句来清理资源

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

        所有包含非托管资源的类型应该实现IDisposable接口,另外,当你忘记恰当的处理这些类型时,它们会被动的创建析构函数。如果你忘记处理这些对 象,那些非内存资源会在晚些时候,析构函数被确切调用时得到释放。这就使得这些对象在内存时待的时间更长,从而会使你的应用程序会因系统资源占用太多而速 度下降。

        幸运的是,C#语言的设计者精确的释放资源是一个常见的任务。他们添加了一个关键字来使这变得简单了。假设你写了下面的代码:

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

        这个例子中的两个可处理对象没有被恰当的释放:SqlConnection和SqlCommand。两个对象同时保存在内存里直到析构函数被调用。(这两 个类都是从System.ComponentModel.Component继承来的。)

 

        解决这个问题的方法就是在使用完命令和链接后就调用它们的Dispose:

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

        这很好,除非SQL命令在执行时抛出异常,这时你的Dispose()调用就永远不会成功。using语句可以确保Dispose()方法被调用。当你把 对象分配到using语句内时,C#的编译器就把这些对象放到一个try/finally块内:

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

        当你在一个函数内使用一个可处理对象时,using语句是最简单的方法来保证这个对象被恰当的处理掉。当这些对象被分配时,会被编译器放到一个 try/finally块中。下面的两段代码编译成的IL是一样的

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

        如果你把一个不能处理类型的变量放置在using语句内,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只能在编译时,那些支持IDispose接口的类型可以使用,并不是任意的对象

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

        如果obj实现了IDispose接口,那么using语句就会生成资源清理代码,如果不是,using就退化成使用using(null),这是安全 的,但没有任何作用。如果你对一个对象是否应该放在using语句中不是很确定,宁可为了更安全:假设要这样做,而且按前面的方法把它放到using语句 中。

        这里讲了一个简单的情况:无论何时,当你在某个方法内使用一个可处理对象时,把这个对象放在using语句内。现在你学习一些更复杂的应用。还是前面那个 例子里须要释放的两个对象:链接和命令。前面的例子告诉你创建了两个不同的using语句,一个包含一个可处理对象。每个using语句就生成了一个不同 的try/finally块。等效于你写了这样的代码:

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

        每一个using语句生成了一个新的嵌套的try/finally块。我发现这是很糟糕的结构,所以,如果是遇到多个实现了IDisposable接口的 对象时,我更愿意写自己的try/finally块:

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

(译注:作者里的判断对象是否为null是很重要的,特别是一些封装了COM的对象,有些时候的释放是隐式的,当你再释放一些空对象时会出现异常。 例如:同一个COM被两个不同接口的变量引用时,在其中一个上调用了Dispose后,另一个的调用就会失败。在.Net里也要注意这样的问题,所以要判 断对象是否为null)

        然而,请不要自作聪明试图用as来写这样的using语句:

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

        这看上去很清爽,但有一个狡猾的(subtle )的bug。如果SqlCommand()的构造函数抛出异常,那么SqlConnection对象就不可能被处理了。你必须确保每一个实现了 IDispose接口的对象分配在在using范围内,或者在try/finally块内。否则会出现资源泄漏。

        目前为止,你已经学会了两种最常见的情况。无论何时在一个方法内处理一个对象时,使用using语句是最好的方法来确保申请的资源在各种情况下都得到释 放。当你在一个方法里分配了多个(实现了IDisposable接口的)对象时,创建多个using块或者使用你自己的try/finally块。

        对可处理对象的理解有一点点细微的区别。有一些对象同时支持Disponse和Close两个方法来释放资源。SqlConnection就是其中之一, 你可以像这样关闭SqlConnection:

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

        这个版本关闭了链接,但它确实与处理对象是不一样的。Dispose方法会释放更多的资源,它还会告诉GC,这个对象已经不再须要析构了。Dispose 会调用GC.SuppressFinalize(),但Close()一般不会。结果就是,对象会到析构队列中排队,即使析构并不是须要的。当你有选择 时,Dispose()比Colse()要好。你会在条款18里学习更更精彩的内容。

        Dispose()并不会从内存里把对象移走,对于让对象释放非托管资源来说是一个hook。这就是说你可能遇到这样的难题,就是释放一个还在使用的对 象。不要释放一个在程序其它地方还在引用的对象。

        在某些情况下,C#里的资源管理比C++还要困难。你不能指望确定的析构函数来清理你所使用的所有资源。但垃圾回收器却让你更轻松,你的大从数类型不必实 现IDisposable接口。在.Net框架里的1500多个类中,只有不到100个类实现了IDisposable接口。当你使用一个实现了 IDisposeable接口的对象时,记得在所有的类里都要处理它们。你应该把它们包含在using语句中,或者try/finally块中。不管用哪 一种,请确保在任何时候对象都得到了正确的释放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#using语句是用来确保使用的对象在使用完毕后能够被正确释放的一种语法结构。使用using语句可以实现了IDisposable接口的类型对象调用Dispose方法,以确保资源的正确释放。\[1\]使用using语句可以避免手动调用Dispose方法的繁琐操作,同时也能够保证在使用过程中出现异常时,对象的Dispose方法仍然会被调用,确保资源释放。\[1\]使用using语句的语法非常简洁,编译器会自动为using语句加上try/finally,使得代码更加清晰易读。\[1\]使用using语句的对象应该在using语句开始后进行初始化,以确保所有的对象都能够被正确释放。\[1\]使用using语句是一种高效的调用对象Dispose方法的方式,对于任何实现了IDisposable接口的类型都可以使用using语句,而对于没有实现IDisposable接口的类型,使用using语句会导致编译错误。\[3\]总的来说,C#using语句是一种简洁高效的资源管理方式,能够确保对象的Dispose方法在使用完毕后被正确调用,从而避免资源泄漏的问题。\[2\] #### 引用[.reference_title] - *1* *2* *3* [C#using语句的用法](https://blog.csdn.net/su_xiao_wei/article/details/125619009)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值