6

我试图确保我的编码遵循对象的正确处理,因此我将这些规则作为错误执行。但是我在这段代码中遇到了麻烦

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,因为我没有处理该对象,但是如果我删除该注释,它会说我不止一次处理。

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

4

3 回答 3

8

这种情况现在也让我很烦。每隔几年,我决定通过运行 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,您不应在对象上多次调用 Dispose。”

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

自然,我将代码修改为如下所示:

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 语句混合的方式,因为我相信这更易于阅读和结构化这将防止某人只是将另一个使用包裹在它周围,从而在不知不觉中导致最初的问题。

于 2014-02-20T08:49:29.503 回答
1

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

如果您注释掉 objStream.Dispose() ,那么您甚至在到达嵌套的 try 块之前就会遇到抛出异常的机会 - 这将导致您的流没有被释放。

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

于 2012-04-13T11:17:28.097 回答
0

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

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();
        }
    }
}
于 2012-04-13T11:20:27.270 回答