zoukankan      html  css  js  c++  java
  • 嵌套使用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(); } }

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

    希望对大家有帮助。

    作者:ColdJokeLife
    出处:http://www.cnblogs.com/ColdJokeLife/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,请联系我,非常感谢。
  • 相关阅读:
    转载cocos3.17cmake打包
    cocos-lua3.17 Lua tableView工具类
    cocso引擎整体流程
    letCode-1
    letCode-2
    图像语义分割的前世今生
    简要介绍弱监督学*
    win10下乌龟git安装和使用
    STM32F4+Wi-Fi+EDP 向 OneNet 上传数据
    GoogLeNet学习
  • 原文地址:https://www.cnblogs.com/ColdJokeLife/p/3134396.html
Copyright © 2011-2022 走看看