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 (рядок, байт [], байт [])". Щоб уникнути генерації System.ObjectDisposedException, ви не повинні викликати розпорядження не один раз на об’єкт .: Рядки: 34

Попередження 8 CA2202: Microsoft.Usage: Об'єкт 'memoryStream' може бути розміщений не один раз у методі 'CryptoServices.Encrypt (рядок, байт [], байт [])'. Щоб уникнути генерації System.ObjectDisposedException, ви не повинні викликати розпорядження не один раз на об’єкт .: Рядки: 34, 37

Для перегляду цих попереджень вам потрібен аналіз коду Visual Studio (це не попередження компілятора c #).


1
Цей код не генерує цих попереджень.
Жульєн Хоарау

1
За це я отримую 0 попереджень (рівень попередження 4, VS2010). І для тих, хто має проблеми з гуглами в цій галузі, до благань додайте і текст попереджень.
Хенк Холтерман

29
Попередження CAxxxx генеруються за допомогою аналізу коду та FxCop.
dtb

Це попередження не стосується показаного коду - попередження можуть бути придушені саме для цього сценарію. Після того, як ви переглянули свій код і погоджуєтесь з цією оцінкою, поставте це вище свого методу: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - переконайтеся, що using System.Diagnostics.CodeAnalysis;у вашому блоці " " використовується " ".
BrainSlugs83

Відповіді:


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


85
-1 Справді погано створювати некрасивий код лише для дотримання попередження, яке слід придушити .
Йордао

4
Я погоджуюся з тим, що ви не повинні розкреслювати код, що може в майбутньому виправитись, просто придушити.
петески

3
Як це вирішує проблему? Про все ще повідомляється про CA2202, оскільки пам'ять пам'яті може бути розміщена двічі в остаточному блоці.
Кріс Гесслер

3
Оскільки CryptoStream викликає внутрішнє використання Dispose на MemoryStream, його можна було викликати двічі, що є причиною попередження. Я спробував ваше рішення і досі отримую попередження.
Кріс Гесслер

2
О, єси, ти маєш рацію - я не очікував, що тут буде змішана логіка очищення з твоєю ... логікою-логікою ... - це просто химерно і виразно - це, безумовно, розумно - але знову ж таки, страшно - будь ласка, не робіть цього у виробничому коді; щоб було зрозуміло: ви розумієте, що немає жодного актуального функціонального питання, яке це вирішує, правильно? (Добре розміщувати ці об'єкти кілька разів.) - Я б зняв голос, що не допустив (Так мені заважає, він говорить, що ви повинні відредагувати відповідь), - але я би робив це лише неохоче ... - і серйозно, ніколи цього не робіть.
BrainSlugs83

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 викликається кілька разів.

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


14
На мою думку, це помилка у fxcop, це правило просто неправильне. Метод dispose ніколи не повинен кидати ObjectDisposedException, і якщо це так, то вам слід впоратися з ним на той час, подавши помилку на автора коду, який реалізує, розпоряджатися таким чином.
justin.m.chase

14
Я погоджуюся з @HansPassant в іншому потоці: інструмент виконує свою роботу і попереджає вас про несподівану деталізацію впровадження класів. Особисто я думаю, що справжньою проблемою є дизайн самих API. Наявність вкладених класів за замовчуванням вважати, що нормально брати право власності на інший об’єкт, створений в іншому місці, здається вельми сумнівним. Я бачу, де це може бути корисним, якщо отриманий об’єкт збирається повернути, але дефолт до цього припущення здається контрінтуїтивним, а також порушує нормальні шаблони ID, що не використовуються.
BTJ

8
Але msdn не рекомендує придушити такий тип повідомлень. Подивіться на: msdn.microsoft.com/en-us/library/…
Аділ Мамедов

2
Дякуємо за посилання @AdilMammadov, корисна інформація, але microsoft не завжди має рацію щодо цих речей.
Тім Абелл

40

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

Це не зовсім реальні помилки, ці .NET-класи стійкі до декількох викликів Dispose (). Але якщо ви хочете позбутися попередження, то вам слід відмовитися від використання оператора для цих об'єктів. І трохи поболійте, міркуючи, що буде, якщо код кине виняток. Або закрийте попередження атрибутом. Або просто ігноруйте попередження, оскільки воно нерозумно.


10
Мати спеціальних знань про внутрішню поведінку класів (як одноразове використання власності на інший) - це занадто багато запитань, чи хочеться створити API багаторазового використання. Тому я думаю, що usingзаяви повинні залишатися. Ці застереження справді нерозумні.
Йордао

4
@ Jordão - це не для чого інструмент? Щоб попередити про внутрішню поведінку, про яку ви, можливо, не знали?
Ганс Пасант

8
Я згоден. Але я все одно не відмовився б від usingзаяв. Просто помилково покладатися на інший об’єкт, щоб розпоряджатися створеним мною об’єктом. Для цього коду це нормально, але є багато реалізацій Streamі TextWriterтам (не тільки на BCL). Код для їх використання повинен бути послідовним.
Йордао

3
Так, погоджуйтеся з Йордау. Якщо ви дійсно хочете, щоб програміст знав про внутрішню поведінку api, тоді говоріть, називаючи свою функцію DoSomethingAndDisposeStream (потік потоку, дані OtherData).
ZZZ

4
@HansPassant Ви можете вказати, де задокументовано, що XmlDocument.Save()метод буде викликати Disposeдоданий параметр? я не бачу його в документації Save(XmlWriter)(де я відчуваю помилку FxCop), або в самому Save()методі, або в документації про XmlDocumentсебе.
Ян Бойд

9

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

9

Я б це зробив, використовуючи #pragma warning disable.

Настанови .NET Framework рекомендують реалізовувати IDisposable.Розпоряджуйте таким чином, щоб його можна було викликати кілька разів. З MSDN опису IDisposable.Sispose :

Об'єкт не повинен викидати виняток, якщо його метод Dispose викликається кілька разів

Тому попередження здається майже безглуздим:

Щоб уникнути генерації System.ObjectDisposedException, ви не повинні викликати розпорядження не один раз на об’єкт

Я думаю, можна стверджувати, що попередження може бути корисним, якщо ви використовуєте неправильно реалізований об’єкт IDisposable, який не відповідає стандартним вказівкам щодо впровадження. Але коли ви використовуєте класи з .NET Framework, як ви це робите, я б сказав, що безпечно придушити попередження за допомогою #pragma. І IMHO для цього попередньо бажано пройти обручі, як це запропоновано в документації MSDN .


4
CA2202 - це попередження аналізу коду, а не попередження компілятора. #pragma warning disableможе використовуватися лише для придушення попереджень компілятора. Для придушення попередження про аналіз коду потрібно використовувати атрибут.
Мартін Ліверсанс

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

Я хотів вирішити це правильним шляхом - тобто не придушуючи попередження та правильно розміщуючи всі одноразові предмети.

Я витягнув 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();
                }
            }
        }
   }

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як це було б memoryStreamу returnзаяві поза сферою.


5
Не буде memoryStream.ToArray () поза сферою?
Polyfun

Це абсолютно еквівалентно оригінальному фрагменту коду. Я просто замислював фігурні брекети, як і ви можете це зробити з ifs (хоча я б не радив цю техніку для інших, крім usings).
Дан Абрамов

2
У вихідному коді memoryStream.ToArray () знаходився в межах першого використання; у вас це є поза межами сфери.
Polyfun

Дякую тобі, я щойно зрозумів, що ти маєш на увазі returnзаяву. Такий справжній. Я відредагував відповідь, щоб це відобразити.
Дан Абрамов

Я особисто думаю, що usingбез дужок робить код більш крихким (думаю, що роки відрізняються і зливаються). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & imperialviolet.org/2014/02/22/applebug.html
Тім Абел

0

Уникайте всіх угод та використовуйте вкладені виклики 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();
        }
    }

1
Поясніть, будь ласка, чому вам слід уникати usingцього випадку.
StuperUser

1
Ви можете зберігати оператор use-посеред, але ви повинні вирішити інші. Щоб отримати логічне узгоджене і в усіх напрямках оновлене рішення, я вирішив видалити всі пристосування!
Гаррі Зальцман

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
  • продовжуйте дзвонити розпорядження двічі, і сподіваємось, що код не вийде з ладу
  • придушити попередження

-1

Я використовував такий тип коду, який займає байт [] і повертає байт [], не використовуючи потоків

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

Таким чином, все, що вам потрібно зробити, це перетворення з рядка в байт [], використовуючи кодування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.