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

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

Вам нужен Visual Studio Code Analysis, чтобы увидеть эти предупреждения (это не предупреждения С# компилятора).

Ответ 1

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

    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.

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

Итак, ваш MemoryStream расположен как CryptoStream и оператор using. И CryptoStream находится StreamWriter и внешний оператор 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();

Ответ 6

Криптосточник основан на запоминающем потоке.

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

Ответ 7

Вне темы, но я бы предложил вам использовать другой метод форматирования для группировки using s:

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

P.S. Благодаря @ShellShock для указания, я не могу опустить фигурные скобки для первого using, поскольку это сделало бы memoryStream в return выражении вне области видимости.

Ответ 8

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

Похоже, что вся работа 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).

По крайней мере, этот код работал у меня.

EDIT:

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

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

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

Ответ 9

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

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

Ответ 10

Я использовал такой код, который берет байты [] и возвращает байт [] без использования потоков

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

Таким образом, все, что вам нужно сделать, это преобразование из строки в байт [] с использованием кодировок.

Ответ 11

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

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

Ответ 12

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