Utilize using and try/finally for Resource Cleanup

 Effective C# Item 15: Utilize using and try/finally for Resource Cleanup

      当我们使用非托管资源(unmanaged resources)类型时,应当使用IDisposable接口的Dispose()方法来释放资源。在.Net环境中,对非托管资源的回收不是系统的责任,我们必须自己调用Dispose()方法来释放资源。确保非托管资源会释放的最好方法是使用using或者try/finally。

      所有的非托管资源类型都实现了IDisposable接口。另外当我们没有明确的释放资源,比如我们忘记了,C#还会防护性的通过创建终结器(finalizer)来释放资源。如果我们希望应用程序运行的更快时,就应当尽快释放一些不必要的资源。幸运的是在C#中有新的关键字来完成这项任务。我们先考虑下面的代码:

         public   void  ExcuteCommand( string  connectString,  string  commandString)
        
{
            SqlConnection myConnection 
= new SqlConnection(connectString);
            SqlCommand myCommand 
= new SqlCommand(commandString, myConnection);
            myConnection.Open();
            myCommand.ExecuteNonQuery();
        }

      有两个对象没有被释放掉:SqlConnection和SqlCommand。它们都会保存在内存中直到终结器被调用为止。

      通过下面的修改,我们可以释放它们:

         public   void  ExcuteCommand( string  connectString,  string  commandString)
        
{
            SqlConnection myConnection 
= new SqlConnection(connectString);
            SqlCommand myCommand 
= new SqlCommand(commandString, myConnection);
            myConnection.Open();
            myCommand.ExecuteNonQuery();

            myCommand.Dispose();
            myConnection.Dispose();
        }

 

      这样做是正确的,但前提是SqlCommand没有抛出异常。一旦出现异常,我们的Dispose()方法就不会运行了。using关键字可以帮助我们确保Dispose()会被运行。当我们使用using的时候,C#的编译器会将它转换成为类似与try/finally的形式:

         public   void  ExcuteCommand( string  connectString,  string  commandString)
        
{
            
using(SqlConnection myConnection = new SqlConnection(connectString))
            
{
                
using(SqlCommand myCommand = new SqlCommand(commandString, myConnection))
                
{
                    myConnection.Open();
                    myCommand.ExecuteNonQuery();
                }

            }

        }

 

      下例中的两段代码会生成非常相似的IL

using (SqlConnection myConnection  =   new  SqlConnection(connectString))
{
      myConnection.Open();
}


try
{
      SqlConnection myConnection 
= new SqlConnection(connectString);
      myConnection.Open();
}

finally
{
      myConnection.Dispose();
}

 

      当我们使用非托管资源时,使用using是确保资源合理释放的简单途径。如果我们对不支持IDisposable接口的类型使用using关键字,编译器会报错:

// 错误
using ( string  msg  =   " this is a message " )
{
      Console.WriteLine(msg);
}

      另外using只检验编译时类型是否支持IDisposable接口,它不能识别运行时的对象。下例中即便Factory.CreateResource()返回的类型支持IDisposable接口也是不能通过编译的:

// 错误
using ( object  obj  =  Factory.CreateResource)
{
}

 

      对于可能支持可能不支持IDisposable接口的对象,我们可以这样来处理:

object  obj  =  Factory.CreateResource();
using (obj  as  IDisposable)
{
}

 

      如果对象实现了IDisposable接口,就可以生成释放资源的代码。如果不支持,则生成using(null),虽然不做任何工作,但也是安全的。如果我们拿不准是否应该将对象放在using中,那么比较稳妥的做法是将它放进去。

      当我们在程序中使用了非托管资源类型时,我们应当将其放入using的括号中。当有多个需要释放的资源,例如前面的例子中的connection和command,我们应当创建多个using,每一个包含一个对应的对象。这些using会被转化为不同的try/finally块,在效果上看就好像是下面这段代码:

         public   void  ExcuteCommand( string  connectString,  string  commandString)
        
{
            SqlConnection myConnection 
= null;
            SqlCommand myCommand 
= null;
            
try
            
{
                myConnection 
= new SqlConnection(connectString);
                
try
                
{
                    myCommand 
= new SqlCommand(commandString, myConnection);
                    myConnection.Open();
                    myCommand.ExecuteNonQuery();
                }

                
finally
                
{
                    
if(myCommand != null)
                    
{
                        myCommand.Dispose();
                    }

                }

            }

            
finally
            
{
                
if(myConnection != null)
                
{
                    myConnection.Dispose();
                }

            }

        }

      每个using声明都创建了一个try/finally程序块。我们自己也可以通过这样写来取消多层嵌套:

         public   void  ExcuteCommand( string  connectString,  string  commandString)
        
{
            SqlConnection myConnection 
= null;
            SqlCommand myCommand 
= null;
            
try
            
{
                myConnection 
= new SqlConnection(connectString);
                myCommand 
= new SqlCommand(commandString, myConnection);

                myConnection.Open();
                myCommand.ExecuteNonQuery();
            }

            
finally
            
{
                
if(myCommand != null)
                
{
                    myCommand.Dispose();
                }

                
if(myConnection != null)
                
{
                    myConnection.Dispose();
                }

            }

        }

 

      虽然看起来很简洁,但是我们不要这样使用using声明:

         public   void  ExcuteCommand( string  connectString,  string  commandString)
        
{
            SqlConnection myConnection 
= new SqlConnection(connectString);
            SqlCommand myCommand 
= new SqlCommand(commandString, myConnection);
            
using(myConnection as IDisposable)
            
{
                
using(myCommand as IDisposable)
                
{
                    myConnection.Open();
                    myCommand.ExecuteNonQuery();
                }

            }

        }

 

      这样做是有潜在bug的。一旦SqlCommand的构造函数抛出异常,SqlConnection就无法被释放了。我们必须保证每个非托管资源对象都可以被顺利的释放,否则可能会造成内存资源的浪费。对于单个的非托管资源对象,使用using关键字是最好的方法。对于多个对象,我们可以使用嵌套using或者自己写try/finally的方法来释放资源。

      在释放资源上还有一个细节,有些类型不仅有Dispose()方法,还有Close()方法,例如SqlConnection。我们这样可以关闭SqlConnection的连接:

myConnection.Close();

      这样做的确能够释放连接,但是并不是和Dispose()方法一样。Dispose()方法除了释放资源之外,还有其他的工作:它会通知垃圾收集器(Garbage Collector)这个对象的资源已经被释放了,而不必在终结器中进行重复的操作。Dispose()调用了GC.SuppressFinalize()方法,这个方法请求系统不要调用指定对象的终结器。而Close()不是这样的。使用Close()释放的对象虽然已经不必调用终结器,但是它还是存在于终结器的释放资源队列当中。Dispose()比Close()的工作做的更加彻底。

      Dispose()并没有将对象移出内存。它为对象添加了一个释放资源的钩子(hook)。这就意味着我们可以对正在使用的对象使用Dispose(),我们应当小心这一点。

      在C#中大部分的类型都不支持Dispose()。在超过1500种类型中只有100来种实现了IDispose接口。当我们使用实现了这个接口的对象时,我们应当在适当的时候使用using或try/finally块的方法来释放资源。 

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值