代码分析规则 CA2000 / CA2202

Posted

技术标签:

【中文标题】代码分析规则 CA2000 / CA2202【英文标题】:Code Anlysis Rule CA2000 / CA2202 【发布时间】:2012-04-25 18:02:50 【问题描述】:

我正在努力确保我的编码遵循对象的正确处置,因此我将这些规则作为错误执行。但是这部分代码我遇到了问题

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

class MyClass
  
    public String ToXml()
    
        var objSerializer = 
            new DataContractSerializer(GetType());
        var objStream = new MemoryStream();
        StreamReader objReader;

        String strResult;
        try
        
            // Serialize the object
            objSerializer.WriteObject(objStream, this);

            // Move to start of stream to read out contents
            objStream.Seek(0, SeekOrigin.Begin);

            objReader = new StreamReader(objStream);

            try
            
                // Read Contents into a string
                strResult = objReader.ReadToEnd();
            
            finally
            
                objReader.Dispose();
            
        
        finally
        
            if (objStream != null)
            
                // objStream.Dispose();
            
        

        return strResult;
    

如果我注释掉 objStream.Dispose(),我会得到 CA2000,因为我没有处理该对象,但如果我删除该注释,它会说我正在处理不止一次。

还有什么是处理对象?还是我在处理多个流时做错了?

【问题讨论】:

为了可读性,我更喜欢 using-blocks 而不是显式 try-finallydispose 我同意,最初是我一直在搞砸它以摆脱错误 【参考方案1】:

这种情况现在也让我很烦。每隔几年,我决定通过运行 fxcop 或 Visual Studio 中现在内置的代码分析来刷新自己的代码分析“规则”。

我最初编写这段代码,认为我是一个正确使用 usings 来处理的好公民:

using (MemoryStream msDecrypt = new MemoryStream())

    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
    
        csDecrypt.Write(eop.m_Ciphertext, 0, eop.m_Ciphertext.Length);
    

    decrypted = msDecrypt.ToArray();

这段代码导致CA2202“不要多次处理对象”这个规则的最大讽刺之处在于,它实际上并不是你的代码有问题,而是保护您了解其他代码中的问题。 Microsoft 一直有大量关于如何实现 Dispose 模式 (http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx) 的文档。但是,通过查看此代码分析规则 (http://msdn.microsoft.com/en-us/library/ms182334.aspx) 的详细信息,它揭示了此规则的目的

"一个正确实现的Dispose方法可以被多次调用 不抛出异常。但是,这并不能保证,并且 避免生成不应调用的 System.ObjectDisposedException 对一个对象进行多次处理。”

简而言之,这条规则旨在保护自己免受不遵守规则的人的伤害。

我很自然地把代码修改成这样:

MemoryStream msDecrypt = new MemoryStream()    
//using (MemoryStream msDecrypt = new MemoryStream())
//
    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
    
        csDecrypt.Write(eop.m_Ciphertext, 0, eop.m_Ciphertext.Length);
    

    decrypted = msDecrypt.ToArray();
//

现在这个堆栈溢出帖子上的每个人都痛苦地意识到了这个新问题,我们的朋友 CA2000 “在失去作用域之前处理对象” ...所以在这一点上,我只是面对手掌一分钟。做了一些谷歌搜索,并找到了这篇文章。那时我突然意识到,要通过这两个 CA 规则,您需要确保所有代码分支的所有内容都处理一次且仅处理一次。所以我开始这样做,一旦你意识到这是你需要做的,这不是一个难题。

代码自然演变成这样:

MemoryStream msDecrypt = null;
CryptoStream csDecrypt = null;

try
    
    msDecrypt = new MemoryStream();
    csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write);

    csDecrypt.Write(eop.m_Ciphertext, 0, eop.m_Ciphertext.Length);
    csDecrypt.FlushFinalBlock();
    decrypted = msDecrypt.ToArray();

finally

    if (csDecrypt != null)
    
        csDecrypt.Dispose();
    
    else if (msDecrypt != null)
    
        msDecrypt.Dispose();
    


最后,我的代码没有生成 CA2000 或 CA2202。这个故事的寓意是,由于代码分析规则已经以这种方式发展,USING 语句的价值远低于过去。

您可以通过几种不同的方式编写代码来完成这项工作,我只是选择了一种不将显式调用 dispose 与 using 语句混合的方式,因为我相信这更易于阅读和结构化以一种可以防止某人只是将另一个使用包裹在其周围的方式,从而在不知不觉中导致最初的问题。

【讨论】:

虽然从一般意义上看这在技术上是正确的,但在现实世界中,使用更整洁的“使用”构造(即成功处理两次时)证明完全没有问题。所以为了可读性,只有当我知道类正确处理它时,我才会正确地抑制这个错误。我认为这就是 MS 代码分析警告的重点(让我们思考一下)。最好的解决方案是让代码分析规则引擎自己发现这一点;给我们一些方式来声明这一点(例如代码合同)。无论如何,我对你的答案投了赞成票,因为这是一个很好的解释,必须加以考虑。【参考方案2】:

如果您释放 StreamReader,那么您也在释放底层流。

如果您注释掉 objStream.Dispose(),那么您可能会在到达嵌套的 try 块之前引发异常 - 这将导致您的流无法被释放。

这里有一个很好的解释: Does disposing streamreader close the stream?

【讨论】:

是的,我明白,如果您尝试代码,您会注意到您正在处理 objStream 两次,但在我的实际代码中我不是。是否也是 StreamReader.Dispose 处理传入的流的情况? IMO 这是StreamReader 的一个奇怪的设计怪癖,它经常困扰我,因为有时你不想关闭提供的流。 @Zonder 是的,这正是我要说的。如果您处置了一个读者(或作家),那么您也处置了底层流。所以在你的代码中(取消最后一次处理),它仍然会被处理两次。 @Steven 我同意。并且很难围绕它进行设计。我通常最终同时处理流和阅读器,这感觉有点多余。想象一下,如果我让一个作家参与进来。 嗯是的,所以这很尴尬,然后将这两个代码分析规则都作为错误。解决它的唯一方法是使 CA2202 成为警告,并希望程序员记得捕获 ObjectDisposedException。也许 IDisposed 应该有一个 IsDisposed 属性,但是你遇到了异步处理的问题。【参考方案3】:

这不是答案,但您可能会发现这段代码更具可读性:

public String ToXml()

    var objSerializer =
        new DataContractSerializer(GetType());

    using (var objStream = new MemoryStream())
    
        //  Serialize the object
        objSerializer.WriteObject(objStream, this);

        // Move to start of stream to read 
        // out contents
        objStream.Seek(0, SeekOrigin.Begin);

        using (var objReader =
            new StreamReader(objStream))
        
            // Read Contents into a string
            retirm objReader.ReadToEnd();
        
    

【讨论】:

这正是根据msdn.microsoft.com/en-us/library/ms182334 不应该做的事情,因为 objStream 对象上的 Dispose() 可能/将被调用两次。 另一方面:Jon Skeet 说没关系 - 这是法律规定的。 ***.com/a/1065196/40853 多次调用 Dispose 应该永远不会成为问题(可能除了性能之一),因为IDisposable 合同规定实现必须能够处理这个问题。 MSDN 文档已经指出“可以多次调用正确实现的 Dispose 方法而不会引发异常。”。从这个意义上说,CA2202 规则非常奇怪,或者至少它应该被归入 Microsoft.Performance 类别,而不是 Microsoft.Usage。

以上是关于代码分析规则 CA2000 / CA2202的主要内容,如果未能解决你的问题,请参考以下文章

代码分析:TableCell如何攻克CA2000

使用链式构造函数避免代码分析 CA2000 警告?

代码分析警告 CA2000:在对象“new ContainerControlledLifetimeManager()”上调用 Dispose

MemoryStream、XmlTextWriter 和警告 4 CA2202:Microsoft.Usage

代码分析规则 CA1062 行为

代码分析规则 CA1040:避免空接口,抱怨非空接口