CA2202,如何解决这种情况


102

谁能告诉我如何从以下代码中删除所有CA2202警告?

public static byte[] Encrypt(string data, byte[] key, byte[] iv)
{
    using(MemoryStream memoryStream = new MemoryStream())
    {
        using (DESCryptoServiceProvider cryptograph = new DESCryptoServiceProvider())
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write))
            {
                using(StreamWriter streamWriter = new StreamWriter(cryptoStream))
                {
                    streamWriter.Write(data);
                }
            }
        }
        return memoryStream.ToArray();
    }
}

警告7 CA2202:Microsoft。用法:对象“ cryptoStream”可以在方法“ CryptoServices.Encrypt(string,byte [],byte [])”中处置多次。为了避免生成System.ObjectDisposedException,不应在一个对象上调用Dispose多次。:行:34

警告8 CA2202:Microsoft。用法:对象'memoryStream'可以在方法'CryptoServices.Encrypt(string,byte [],byte [])'中多次处置。为了避免生成System.ObjectDisposedException,不应在一个对象上调用Dispose多次。:行:34,37

您需要Visual Studio代码分析才能查看这些警告(这些不是c#编译器警告)。


1
此代码不会生成这些警告。
朱利安·霍劳

1
我为此收到0条警告(警告级别4,VS2010)。对于在此区域使用Google搜索功能的人,请同时添加警告文字。
Henk Holterman 2010年

29
CAxxxx警告由代码分析和FxCop 生成。
dtb

此警告不适用于显示的代码-完全可以针对这种情况取消警告。审查代码并同意评估后,将其置于您的方法上方:“ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]”-确保using System.Diagnostics.CodeAnalysis;在usings块中有一个“ ”语句。
BrainSlugs83

Answers:


-3

编译时没有警告:

    public static byte[] Encrypt(string data, byte[] key, byte[] iv)
    {
        MemoryStream memoryStream = null;
        DESCryptoServiceProvider cryptograph = null;
        CryptoStream cryptoStream = null;
        StreamWriter streamWriter = null;
        try
        {
            memoryStream = new MemoryStream();
            cryptograph = new DESCryptoServiceProvider();
            cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
            var result = memoryStream;              
            memoryStream = null;
            streamWriter = new StreamWriter(cryptoStream);
            cryptoStream = null;
            streamWriter.Write(data);
            return result.ToArray();
        }
        finally
        {
            if (memoryStream != null)
                memoryStream.Dispose();
            if (cryptograph != null)
                cryptograph.Dispose();
            if (cryptoStream != null)
                cryptoStream.Dispose();
            if (streamWriter != null)
                streamWriter.Dispose();
        }
    }

根据评论进行编辑:我再次验证了此代码不会生成警告,而原始代码会生成警告。在原始代码中,CryptoStream.Dispose()MemoryStream().Dispose(实际上被调用了两次(可能有问题也可能没有问题)。

修改后的代码的工作方式如下:null将处置责任转移到另一个对象后,将引用设置为。例如memoryStreamnullCryptoStream成功调用构造函数之后,将其设置为。成功调用构造函数后,将cryptoStream设置为。如果没有异常发生,则将其放置在块中,然后依次将和放置。nullStreamWriterstreamWriterfinallyCryptoStreamMemoryStream


85
-1创建丑陋的代码只是为了遵守应该被禁止的警告,这确实很糟糕。
–Jordão

4
我同意您不应该为将来可能在某些时候固定的某些事情而编码。
peteski 2011年

3
这如何解决问题?仍然报告CA2202,因为memoryStream仍可以在finally块中处置两次。
克里斯·盖斯勒

3
由于CryptoStream在内部调用MemoryStream上的Dispose,因此可以调用两次,这是发出警告的原因。我尝试了您的解决方案,但仍然收到警告。
克里斯·盖斯勒

2
哦,老天,您是对的-我没想到您的逻辑中会混入清理逻辑...-逻辑很奇怪而且很神秘-当然很聪明-但是再说一遍,可怕-请不要在生产代码中执行此操作;要明确:您确实知道这没有解决任何实际的功能问题,对吗?(可以多次放置这些对象是可以的。)-如果可以的话,我将删除不赞成表决的投票(因此,我阻止了,说您必须编辑答案),但我只会无奈地这样做……认真地说,永远不要这样做。
BrainSlugs83 2013年

142

在这种情况下,您应禁止显示警告。处理一次性用品的代码应该是一致的,并且您不必在乎其他类是否拥有您创建的一次性用品的所有权,也可以调用Dispose它们。

[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static byte[] Encrypt(string data, byte[] key, byte[] iv) {
  using (var memoryStream = new MemoryStream()) {
    using (var cryptograph = new DESCryptoServiceProvider())
    using (var cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write))
    using (var streamWriter = new StreamWriter(cryptoStream)) {
      streamWriter.Write(data);
    }
    return memoryStream.ToArray();
  }
}

更新:IDisposable.Dispose文档中,您可以阅读以下内容:

如果对象的Dispose方法被多次调用,则该对象必须忽略第一个之后的所有调用。如果多次调用其Dispose方法,则该对象不得引发异常。

可以说存在此规则,以便开发人员可以using在一系列一次性物品中合理地使用该语句,就像我上面显示的那样(或者这只是一个很好的副作用)。同样,CA2202也无济于事,应从项目角度加以抑制。真正的罪魁祸首是的错误实现DisposeCA1065应该解决这一问题(如果这是您的责任)。


14
我认为这是fxcop中的错误,此规则完全是错误的。dispose方法永远不要抛出ObjectDisposedException,如果确实存在,那么您应该在那时通过向实现这种方式实现代码的代码作者提交错误来处理它。
justin.m.chase 2014年

14
我在另一个线程中同意@HansPassant:该工具正在执行其工作,并警告您有关类的意外的实现细节。我个人认为真正的问题是API本身的设计。使嵌套类默认为假定可以拥有在其他位置创建的另一个对象的所有权似乎很成问题。我可以看到,如果要返回结果对象,那在什么地方有用,但是默认情况下的假设似乎违反直觉,并且违反了正常的IDisposable模式。
BTJ 2014年

8
但是,msdn不建议您禁止这种消息。看一看:msdn.microsoft.com/en-us/library/...
阿迪勒·马马多夫

2
感谢您提供@AdilMammadov链接,该链接提供了有用的信息,但是Microsoft并不总是正确地处理这些事情。
蒂姆·阿贝尔

40

好吧,准确的是,这些流上的Dispose()方法将被多次调用。StreamReader类将获得cryptoStream的“所有权”,因此处理streamWriter也将处理cryptoStream。同样,CryptoStream类负责memoryStream的工作。

这些不是完全错误,这些.NET类可以抵抗多个Dispose()调用。但是,如果要消除警告,则应删除这些对象的using语句。在推理如果代码引发异常会发生什么时,请让自己感到痛苦。或使用属性关闭警告。或者只是忽略警告,因为它很愚蠢。


10
必须具有有关类的内部行为的专门知识(例如,对另一个拥有者的一次性所有权),以至于无法询问是否要设计可重用的API。因此,我认为using声明应该保留。这些警告确实很愚蠢。
Jordão酒店

4
@Jordão-这不是工具的目的吗?要警告您可能不了解的内部行为?
汉斯·帕桑

8
我同意。但是,我仍然不会删除这些using声明。依靠另一个对象来处理我创建的对象只是感觉不对。对于此代码,这是确定的,但也有很多种实现方式Stream,并TextWriter在那里(不仅对BCL)。使用它们全部的代码应该是一致的。
Jordão酒店

3
是的,请与Jordão保持一致。如果您真的希望程序员知道api的内部行为,请通过将函数命名为DoSomethingAndDisposeStream(Stream stream,OtherData data)来大声说出来。
ZZZ

4
@HansPassant您能指出该XmlDocument.Save()方法将Dispose在提供的参数上调用的地方吗?我在Save(XmlWriter)(我遇到FxCop错误的)Save()文档中,方法本身或文档中都没有看到它XmlDocument
伊恩·博伊德

9

StreamWriter的设置,它将自动处置包裹(这里是:所述的CryptoStream)。CryptoStream还自动处置包装的Stream(在这里:MemoryStream)。

因此,您的MemoryStreamCryptoStreamusing语句处置。并且您的CryptoStreamStreamWriter和外部using语句处置。


经过一些试验,似乎不可能完全摆脱警告。从理论上讲,需要处理MemoryStream,但从理论上讲,您将无法再访问其ToArray方法。实际上,不需要处理MemoryStream,因此我会采用这种解决方案并禁止CA2000警告。

var memoryStream = new MemoryStream();

using (var cryptograph = new DESCryptoServiceProvider())
using (var writer = new StreamWriter(new CryptoStream(memoryStream, ...)))
{
    writer.Write(data);
}

return memoryStream.ToArray();

9

我会使用#pragma warning disable

.NET Framework指南建议实现IDisposable.Dispose,以便可以多次调用它。从IDisposable.Dispose的MSDN描述

如果多次调用其Dispose方法,则该对象不得引发异常

因此,警告似乎几乎毫无意义:

为避免生成System.ObjectDisposedException,不应在一个对象上调用Dispose多次。

我猜这可能会引起争议,如果您使用的是错误实现的IDisposable对象,而该对象没有遵循标准的实现准则,那么该警告可能会有所帮助。但是,当像您一样使用.NET Framework中的类时,我会说使用#pragma禁止显示警告是安全的。恕我直言,这比MSDN文档中针对此警告建议的通过箍要好。


4
CA2202是代码分析警告,而不是编译器警告。#pragma warning disable仅可用于禁止编译器警告。要禁止显示“代码分析”警告,您需要使用一个属性。
Martin Liversage 2015年

2

我的代码中也遇到了类似的问题。

看起来整个CA2202事物均已触发,因为MemoryStream如果构造函数(CA2000)中发生异常,则可以将其处置。

可以这样解决:

 1 public static byte[] Encrypt(string data, byte[] key, byte[] iv)
 2 {
 3    MemoryStream memoryStream = GetMemoryStream();
 4    using (DESCryptoServiceProvider cryptograph = new DESCryptoServiceProvider())
 5    {
 6        CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
 7        using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
 8        {
 9            streamWriter.Write(data);
10            return memoryStream.ToArray();
11        }
12    }
13 }
14
15 /// <summary>
16 /// Gets the memory stream.
17 /// </summary>
18 /// <returns>A new memory stream</returns>
19 private static MemoryStream GetMemoryStream()
20 {
21     MemoryStream stream;
22     MemoryStream tempStream = null;
23     try
24     {
25         tempStream = new MemoryStream();
26
27         stream = tempStream;
28         tempStream = null;
29     }
30     finally
31     {
32         if (tempStream != null)
33             tempStream.Dispose();
34     }
35     return stream;
36 }

注意,我们必须返回memoryStream最后一条using语句(第10行)的内部,cryptoStream因为它在第11行处被处理(因为它在streamWriter using语句中使用),这导致memoryStream在第11行处也被处理(因为memoryStream用于创建cryptoStream)。

至少这段代码对我有用。

编辑:

听起来很有趣,但我发现如果您用GetMemoryStream以下代码替换该方法,

/// <summary>
/// Gets a memory stream.
/// </summary>
/// <returns>A new memory stream</returns>
private static MemoryStream GetMemoryStream()
{
    return new MemoryStream();
}

您得到相同的结果。


1

密码流基于内存流。

似乎正在发生的事情是,当处置低温流(在使用结束时)时,也将处置存储器流,然后再次处置存储器流。


1

我想以正确的方式解决此问题-即不抑制警告并正确处理所有一次性物品。

我从3个流中抽出2个作为字段,并Dispose()以我的课程的方法进行处理。是的,实现IDisposable接口不一定是您想要的,但是与dispose()从代码中所有随机位置进行的调用相比,该解决方案看起来很干净。

public class SomeEncryption : IDisposable
    {
        private MemoryStream memoryStream;

        private CryptoStream cryptoStream;

        public static byte[] Encrypt(string data, byte[] key, byte[] iv)
        {
             // Do something
             this.memoryStream = new MemoryStream();
             this.cryptoStream = new CryptoStream(this.memoryStream, encryptor, CryptoStreamMode.Write);
             using (var streamWriter = new StreamWriter(this.cryptoStream))
             {
                 streamWriter.Write(plaintext);
             }
            return memoryStream.ToArray();
        }

       public void Dispose()
        { 
             this.Dispose(true);
             GC.SuppressFinalize(this);
        }

       protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.memoryStream != null)
                {
                    this.memoryStream.Dispose();
                }

                if (this.cryptoStream != null)
                {
                    this.cryptoStream.Dispose();
                }
            }
        }
   }

0

偏离主题,但我建议您使用其他格式化技术对usings 进行分组:

using (var memoryStream = new MemoryStream())
{
    using (var cryptograph = new DESCryptoServiceProvider())
    using (var encryptor = cryptograph.CreateEncryptor(key, iv))
    using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
    using (var streamWriter = new StreamWriter(cryptoStream))
    {
        streamWriter.Write(data);
    }

    return memoryStream.ToArray();
}

我也主张使用 var在这里 s来避免重复很长的类名。

PS感谢@ShellShock指出我不能首先省略大括号,using因为它会使memoryStreamin return语句超出范围。


5
memoryStream.ToArray()不会超出范围吗?
Polyfun

这绝对等同于原始代码。我只是省略了花括号,就像您可以使用ifs一样(尽管我不建议使用s以外using的其他方法)。
Dan Abramov 2010年

2
在原始代码中,memoryStream.ToArray()在第一次使用的范围内;您已经超出了范围。
Polyfun

非常感谢您,我才意识到您的意思是return声明。如此真实。我编辑了答案以反映这一点。
Dan Abramov 2010年

我个人认为using不带括号的代码会使代码更脆弱(请考虑多年的差异和合并)。joelonsoftware.com/2005/05/11/making-wrong-code-look-wrongimperialviolet.org/2014/02/22/applebug.html
蒂姆·阿贝尔

0

避免所有使用,并使用嵌套的Dispose-Call!

    public static byte[] Encrypt(string data, byte[] key, byte[] iv)
    {
        MemoryStream memoryStream = null;
        DESCryptoServiceProvider cryptograph = null;
        CryptoStream cryptoStream = null;
        StreamWriter streamWriter = null;

        try
        {
            memoryStream = new MemoryStream();
            cryptograph = new DESCryptoServiceProvider();
            cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
            streamWriter = new StreamWriter(cryptoStream);

            streamWriter.Write(data);
            return memoryStream.ToArray();
        }
        finally 
        {
            if(streamWriter != null)
                streamWriter.Dispose();
            else if(cryptoStream != null)
                cryptoStream.Dispose();
            else if(memoryStream != null)
                memoryStream.Dispose();

            if (cryptograph != null)
                cryptograph.Dispose();
        }
    }

1
请说明为什么using在这种情况下应避免使用。
StuperUser 2012年

1
您可以将使用状态声明放在中间,但必须解决其他问题。为了获得逻辑上一致且全方位的可升级解决方案,我决定删除所有用法!
哈里·萨尔茨曼

0

我只是想解开代码,所以我们可以看到Dispose对对象的多次调用:

memoryStream = new MemoryStream()
cryptograph = new DESCryptoServiceProvider()
cryptoStream = new CryptoStream()
streamWriter = new StreamWriter()

memoryStream.Dispose(); //implicitly owned by cryptoStream
cryptoStream.Dispose(); //implicitly owned by streamWriter
streamWriter.Dispose(); //through a using

cryptoStream.Dispose(); //INVALID: second dispose through using
cryptograph.Dispose(); //through a using
memorySTream.Dipose(); //INVALID: second dispose through a using

return memoryStream.ToArray(); //INVALID: accessing disposed memoryStream

尽管大多数.NET类(希望)能够抵御多次调用的错误.Dispose,但并非所有类都可以防御程序员的滥用。

FX Cop知道这一点,并警告您。

您有几种选择。

  • 只能Dispose在任何对象上调用一次;不要使用using
  • 继续调用两次dispose,希望代码不会崩溃
  • 禁止警告

-1

我使用了这样的代码,它不需要使用流就可以使用byte []和return byte []

public static byte[] Encrypt(byte[] data, byte[] key, byte[] iv)
{
  DES des = new DES();
  des.BlockSize = 128;
  des.Mode = CipherMode.CBC;
  des.Padding = PaddingMode.Zeros;
  des.IV = IV
  des.Key = key
  ICryptoTransform encryptor = des.CreateEncryptor();

  //and finaly operations on bytes[] insted of streams
  return encryptor.TransformFinalBlock(plaintextarray,0,plaintextarray.Length);
}

这样,您所需要做的就是使用编码从字符串转换为byte []。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.