嵌套使用Using Statement造成对象被dispose多次 CA2202

前言:

Using语句的用途有好几种,这几种用法都很简单。

我们这里说的C# Using语句是用来释放资源的那个,不是引用的那个。

这里不是教大家如何使用using,只是ColdJokeLife在使用的过程中出现了一点问题,在这里分享一下。

ColdJokeLife在使用FxCop过程中,遇到的CA2202问题,下面分享一下解决过程和分析结果。

没有什么高深的技术,园中的老牛、大虾们可以绕行了。

 

一、Using简介

使用Using语句来释放资源的原理很简单,使用try/finally包裹代码段,并在代码块结尾,调用对象的Dispose方法。

例如:

/*代码取自MSDN官网*/

// 使用using

using (Font font1 = new Font("Arial", 10.0f)) 

{

    byte charset = font1.GdiCharSet;

}



// 等效代码

{

  Font font1 = new Font("Arial", 10.0f);

  try

  {

    byte charset = font1.GdiCharSet;

  }

  finally

  {

    if (font1 != null)

      ((IDisposable)font1).Dispose();

  }

}

 

二、出现的问题

前面已经简要介绍了using 的一些用法,我的问题是更复杂一点点的:嵌套使用using。

示例代码如下:

// nested using statements

using (Stream stream = new FileStream("file.txt", FileMode.OpenOrCreate))

{

    using (StreamWriter writer = new StreamWriter(stream))

    {

        // Use the writer object...

    }

}

上例,嵌套使用了using语句,看上去貌似没有什么问题,但是这里会有bug。

bug原因:当内层using代码段执行完毕后,会释放writer,但此时stream也被释放了。

所以,当外层using代码段执行完毕后,会造成stream被释放两次。这就出现问题了。

 

补充一点:按照正常Dispose的实现要求,实现Dispose方法使其被多次调用时,不会出现错误。

所以,如果正确实现Dispose方法,那么这里的问题就不是错误。

所以,这里这里只是一个隐患,不一定会出现问题。

 

三、解决办法

这里的解决办法非常简单就是修改代码,不使用嵌套using的方式,自己写try/finally代码段。

// 解决办法:自己写try/finally

Stream stream = null;

try

{

    stream = new FileStream("file.txt", FileMode.OpenOrCreate);

    using (StreamWriter writer = new StreamWriter(stream))

    {

        stream = null; // 必须在最开始的位置,原因后面讲 
     //Use the writer object...
} } finally { if(stream != null) stream.Dispose(); }

 

四、原因分析

最开始,我猜想是using在代码结束时,将用到的所有对象都释放了,包括外层using中定义的对象。

但是这种猜想不太可能,因为前面对using的分析看来,编译器不可能去做这些多余的事情,它只会帮你释放using创建的那些对象。

去度娘、谷哥、StackOverflow都没有找到想要的答案,就去看内层Using中创建的StreamWriter的Dispose方法,

果然找到了答案!

StreamWriter的Dispose方法中一段这样的代码:

// 如果LeaveOpen为false,那么就释放掉steam对象

// 这里的stream是通过构造函数传入的,即我们例子中的FileStream

if (!this.LeaveOpen)

{

          if (this.stream != null)

          {

            try

            {

              if (disposing)

                this.stream.Close();

            }

            // ...

}

从这段代码中,就可以看出来是内层的对象在Dispose方法时,帮我们把外层的对象也释放了。

这是StreamWriter的机制,不是using嵌套的问题。

所以,我们再来看看刚才写的解决办法:

1、将外层using换成我们写的try/finally

  这么做是为了防止嵌套的时候,外层using再次去dispose造成问题。

  如果没有出现异常,内层using正常dispose streamWriter时,stream也被dispose,而且stream也被设置为null。

  所以不会再被dispose。如果出现异常,streamWriter没有被正常dispose,那么保证stream可以在finally中被释放。

2、stream = null; 语句必须放在最前面

  因为假如内层using中出现异常,因为using会包裹一层try/finally,所以streamWriter肯定会被dispose,所以stream不用被dispose。

  所以,只要正常运行到using代码块内,说明不用再去释放stream了,那么就必须把它设置为null,防止多次dispose。

 

五、总结

1、嵌套的using并不是不能使用

  产生多次Dispose的原因,是内层对象(例如:StreamWriter)的Dispose机制(LeaveOpen)。

  它与using的嵌套无关,所以如果内层对象与外层对象间无关系的话,应该还是可以正常使用的。

 

2、使用嵌套using要小心

  当我们不太清除内层对象是否与外层对象有关系时,需要小心,因为一旦不注意,就会造成外层对象被多次释放。

  而且在多层次嵌套时,更加要小心。

 

最后,贴上一个4层嵌套的例子:

// 4层嵌套代码

using (var cryptoProvider = new DESCryptoServiceProvider())

{

       using (var ms = new MemoryStream(byEnc))

       {

              using (var cst = new CryptoStream(ms, cryptoProvider.CreateDecryptor(byKey, byIv), CryptoStreamMode.Read))

              {

                    using (var sr = new StreamReader(cst))

                    {

                            // 业务逻辑

                    }

               }

        }

}



// 修改后的代码

DESCryptoServiceProvider cryptoProvider = null;

MemoryStream memoryStream = null;

CryptoStream cryptoStream = null;



 try

{

         cryptoProvider = new DESCryptoServiceProvider();

         memoryStream = new MemoryStream(encryptData);

         cryptoStream = new CryptoStream(memoryStream, cryptoProvider.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Read);



         using (var streamReader = new StreamReader(cryptoStream))

         {

               memoryStream = null;

               cryptoStream = null;
// 业务逻辑
} } finally { if (cryptoProvider != null) { cryptoProvider.Dispose(); } if (cryptoStream != null) { cryptoStream.Dispose(); memoryStream = null; } if (memoryStream != null) { memoryStream.Dispose(); } }

自己查资料、分析,尝试后的一些东西,希望与大家分享一下。

希望对大家有帮助。

你可能感兴趣的:(statement)