CA2202, как решить этот случай


может кто-нибудь сказать мне, как удалить все 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 способ '.Шифрование (строка, байт [], байт [])'. Чтобы избежать создания системы.ObjectDisposedException не следует вызывать Dispose более одного раза для объекта.: Lines: 34

предупреждение 8 CA2202: Microsoft.Использование: Объект 'memoryStream 'может быть размещен более одного раза в методе' криптосервисы.Шифрование (строка, байт [], байт [])'. Чтобы избежать создания системы.ObjectDisposedException не следует вызывать Dispose более одного раза для объекта.: Строк: 34, 37

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

12 93

12 ответов:

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

    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();
        }
    }

Edit в ответ на замечания: Я только что снова проверил, что этот код не генерирует предупреждение, в то время как исходный делает. В исходном коде CryptoStream.Dispose() и MemoryStream().Dispose() фактически вызываются дважды (что может быть или не быть проблемой).

измененный код работает следующим образом: ссылки для null, а как только ответственность за утилизацию переносится на другой объект. Например. memoryStream установлено значение null после вызова CryptoStream конструктор удалось. cryptoStream установлено значение null, после обращения к StreamWriter конструктор удалось. Если исключение не происходит, streamWriter расположена в finally блок и, в свою очередь, распоряжаться CryptoStream и MemoryStream.

вы должны подавить предупреждения в этом случае. Код, который имеет дело с disposables, должен быть последовательным, и вам не нужно заботиться о том, чтобы другие классы брали на себя ответственность за созданные вами disposables, а также вызывали 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 вызывается несколько раз.

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

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

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

Когда a StreamWriter утилизируется, он автоматически утилизирует завернутый поток (здесь: the CryptoStream). CryptoStream также автоматически удаляет обернутое поток (здесь: the 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();

Я бы сделал это с помощью #pragma warning disable.

интернет .NET и руководящие принципы рекомендуют, чтобы реализовать IDisposable.Утилизируйте таким образом, чтобы его можно было вызвать несколько раз. От описание MSDN IDisposable.Утилизировать:

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

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

чтобы избежать генерации a Система.ObjectDisposedException вы не должны вызывать Dispose более одного раза на объекте

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

cryptostream основан на memorystream.

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

вне темы, но я бы предложил вам использовать другой метод форматирования для группировки 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();
}

Я также выступаю за использование vars здесь, чтобы избежать повторения действительно длинных имен классов.

П. С. Спасибо @Shellshock для отметить, что я не могу опустить скобки для первого using как бы memoryStream на return выписка из области видимости.

я столкнулся с аналогичными проблемами в моем коде.

похоже, что вся вещь 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 (потому что он используется в streamWriterusing утверждение), что приводит memoryStream получить также расположен в строке 11 (потому что memoryStream используется для создания cryptoStream).

по крайней мере этот код работал для меня.

EDIT:

забавно, как это может звучать, я обнаружил, что если вы замените GetMemoryStream метод со следующим кодом

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

вы получите тот же результат.

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

Я вытащил 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();
                }
            }
        }
   }

я использовал такой код, который принимает byte[] и возвращает 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);
}

таким образом, все, что вам нужно сделать-это преобразование из String в Byte[] с помощью кодировки.

избегайте всех использований и используйте вложенные Dispose-вызовы!

    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();
        }
    }

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