CA2202, как решить этот случай - PullRequest
98 голосов
/ 30 сентября 2010

Может кто-нибудь сказать мне, как удалить все предупреждения 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.Usage: объект 'cryptoStream' может быть размещен более одного раза в методе 'CryptoServices.Encrypt (string, byte [], byte [])'.Чтобы избежать генерирования исключения System.ObjectDisposedException, не следует вызывать метод Dispose для объекта более одного раза .: Строки: 34

Предупреждение. 8 CA2202: Microsoft.Usage: объект 'memoryStream' может быть удален более одного раза в методе.'CryptoServices.Encrypt (string, byte [], byte [])'.Во избежание генерации исключения System.ObjectDisposedException не следует вызывать метод Dispose для объекта более одного раза .: строки: 34, 37

. Для просмотра этих предупреждений необходим анализ кода Visual Studio (это не c #предупреждения компилятора).

Ответы [ 12 ]

134 голосов
/ 01 октября 2010

Вы должны подавить предупреждения в этом случае.Код, относящийся к одноразовым расходным материалам, должен быть непротиворечивым, и вам не нужно заботиться о том, чтобы другие классы вступали во владение созданными вами одноразовыми материалами, а также вызывали Dispose для них. В документации IDisposable.Dispose вы можете прочитать это:

Если метод Dispose объекта вызывается более одного раза, объект должен игнорировать все вызовы после первого,Объект не должен вызывать исключение, если его метод Dispose вызывается несколько раз.

Можно утверждать, что это правило существует, чтобы разработчики могли разумно использовать оператор using в каскаде одноразовых устройствкак я показал выше (или, может быть, это просто хороший побочный эффект).Таким же образом, CA2202 не служит никакой полезной цели, и это должно быть подавлено в отношении проекта.Настоящим виновником может быть ошибочная реализация Dispose, и CA1065 должен позаботиться об этом (если это под вашей ответственностью).

41 голосов
/ 30 сентября 2010

Ну, это точно, метод Dispose () для этих потоков будет вызываться более одного раза.Класс StreamReader получит «владение» cryptoStream, поэтому утилита streamWriter также избавится от cryptoStream.Точно так же класс CryptoStream берет на себя ответственность за memoryStream.

Это не совсем реальные ошибки, эти классы .NET устойчивы к нескольким вызовам Dispose ().Но если вы хотите избавиться от предупреждения, вам следует отказаться от оператора using для этих объектов.И немного мучитесь, рассуждая о том, что произойдет, если код выдаст исключение.Или закройте предупреждение с атрибутом.Или просто проигнорируйте предупреждение, так как это глупо.

9 голосов
/ 30 сентября 2010

Когда удаляется StreamWriter , он автоматически удаляет обернутый Stream (здесь: CryptoStream ). CryptoStream также автоматически удаляет завернутый Stream (здесь: MemoryStream ).

Таким образом, ваш MemoryStream удаляется как CryptoStream , так и с использованием оператора . И ваш CryptoStream утилизируется StreamWriter и внешним с помощью оператора .


После некоторых экспериментов кажется невозможным полностью избавиться от предупреждений. Теоретически 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();
8 голосов
/ 30 сентября 2010

Я бы сделал это, используя #pragma warning disable.

. Руководство по .NET Framework рекомендует реализовать IDisposable.Dispose таким образом, чтобы его можно было вызывать несколько раз.Начиная с описание MSDN IDisposable.Dispose :

Объект не должен вызывать исключение, если его метод Dispose вызывается несколько раз

Поэтомупредупреждение кажется почти бессмысленным:

Чтобы не создавать исключение System.ObjectDisposedException, не следует вызывать Dispose более одного раза для объекта

Я думаю, это можно утверждатьчто предупреждение может быть полезно, если вы используете плохо реализованный объект IDisposable, который не следует стандартным рекомендациям по реализации.Но при использовании классов из .NET Framework, как вы делаете, я бы сказал, что безопасно подавить предупреждение с помощью #pragma.И ИМХО, это предпочтительнее, чем проходить через обручи, как предложено в документации MSDN для этого предупреждения .

2 голосов
/ 20 ноября 2011

Я столкнулся с похожими проблемами в моем коде.

Похоже, что весь 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 голос
/ 11 июля 2014

Я хотел решить эту проблему правильным образом - без подавления предупреждений и правильной утилизации всех одноразовых предметов.

Я вытащил 2 из 3 потоков в виде полей и разместил их в методе 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();
                }
            }
        }
   }
1 голос
/ 30 сентября 2010

Криптопоток основан на потоке памяти.

То, что, по-видимому, происходит, заключается в том, что когда крипоток удаляется (в конце использования), поток памяти также удаляется, а затем поток памяти снова удаляется.

0 голосов
/ 04 июня 2015

Я просто хотел развернуть код, чтобы мы могли видеть несколько вызовов 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
  • продолжайте вызывать утилиту дважды, и надеюсь, что код не вылетит
  • подавить предупреждение
0 голосов
/ 22 октября 2012

Избегайте любого использования и используйте вложенные вызовы 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();
        }
    }
0 голосов
/ 12 октября 2010

Компилируется без предупреждения:

    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, как только ответственность за утилизацию передается другому объекту. Например. memoryStream устанавливается на null после успешного вызова конструктора CryptoStream. cryptoStream устанавливается на null после успешного вызова конструктора StreamWriter. Если исключений не возникает, streamWriter располагается в блоке finally и, в свою очередь, располагает CryptoStream и MemoryStream.

...