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: объект '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 ответов
Это компилируется без предупреждения:
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
,
Вы должны подавить предупреждения в этом случае. Код, касающийся одноразовых изделий, должен быть согласованным, и вам не нужно заботиться о том, чтобы другие классы переходили во владение созданными вами одноразовыми материалами, а также звонить 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 не служит никакой полезной цели, и это должно быть подавлено в отношении проекта. Настоящим виновником может быть ошибочная реализация Dispose
и CA1065 должен позаботиться об этом (если это под вашей ответственностью).
Что ж, это точно, метод Dispose() для этих потоков будет вызываться более одного раза. Класс StreamReader получит "владение" cryptoStream, поэтому утилита streamWriter также избавится от cryptoStream. Точно так же класс CryptoStream берет на себя ответственность за memoryStream.
Это не совсем реальные ошибки, эти классы.NET устойчивы к нескольким вызовам Dispose(). Но если вы хотите избавиться от предупреждения, вам следует отказаться от оператора using для этих объектов. И немного мучитесь, рассуждая о том, что произойдет, если код выдаст исключение. Или закройте предупреждение с атрибутом. Или просто проигнорируйте предупреждение, так как это глупо.
Когда StreamWriter удаляется, он автоматически удаляет обернутый поток (здесь: CryptoStream). CryptoStream также автоматически удаляет упакованный поток (здесь: MemoryStream).
Таким образом, ваш MemoryStream утилизируется как CryptoStream, так и оператором using. А ваш 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 Framework Guidelines рекомендуется реализовать IDisposable.Dispose таким образом, чтобы его можно было вызывать несколько раз. Из описания MSDN IDisposable.Dispose:
Объект не должен вызывать исключение, если его метод Dispose вызывается несколько раз
Поэтому предупреждение кажется почти бессмысленным:
Чтобы избежать создания System.ObjectDisposedException, вы не должны вызывать Dispose более одного раза для объекта
Я предполагаю, что можно утверждать, что предупреждение может быть полезным, если вы используете плохо реализованный объект IDisposable, который не следует стандартным рекомендациям по реализации. Но при использовании классов из.NET Framework, как вы делаете, я бы сказал, что можно безопасно подавить предупреждение с помощью #pragma. И ИМХО, это предпочтительнее, чем проходить через обручи, как предлагается в документации MSDN для этого предупреждения.
Я столкнулся с похожими проблемами в моем коде.
Похоже, что вся вещь 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();
}
Вы получаете тот же результат.
Криптопоток основан на потоке памяти.
Кажется, что происходит то, что, когда крипоток располагается (в конце использования), поток памяти также удаляется, тогда поток памяти снова удаляется.
Я хотел решить эту проблему правильным образом, то есть без подавления предупреждений и правильной утилизации всех одноразовых предметов.
Я вытащил 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();
}
}
}
}
Не по теме, но я бы предложил вам использовать другую технику форматирования для группировки 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
здесь, чтобы избежать повторений действительно длинных имен классов.
PS Спасибо @ShellShock за указание, что я не могу опустить скобки для первого using
как бы это сделать memoryStream
в return
Заявление выходит за рамки.
Я просто хотел развернуть код, чтобы мы могли видеть несколько вызовов 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-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();
}
}
Я использовал такой код, который принимает 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);
}
Таким образом, все, что вам нужно сделать, это преобразовать строку в byte[] с использованием кодировок.