Исправлена реализация IDisposable для этого кода
У меня есть следующий код
public static byte[] Compress(byte[] CompressMe)
{
using (MemoryStream ms = new MemoryStream())
{
using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress,true))
{
gz.Write(CompressMe, 0, CompressMe.Length);
ms.Position = 0;
byte[] Result = new byte[ms.Length];
ms.Read(Result, 0, (int)ms.Length);
return Result;
}
}
}
Это отлично работает, но когда я запускаю анализ кода на нем, в нем появляется следующее сообщение
CA2202 : Microsoft.Usage : Object 'ms' can be disposed more than once in
method 'Compression.Compress(byte[])'. To avoid generating a
System.ObjectDisposedException you should not call Dispose more than one
time on an object.
Насколько мне известно, когда GZipStream является Disposed, он оставляет открытый поток (ms) открытым из-за последнего параметра конструктора (leaveOpen = true).
Если я немного изменил свой код, удалите блок "using" вокруг MemoryStream и измените параметр "leaveOpen" на false.
public static byte[] Compress(byte[] CompressMe)
{
MemoryStream ms = new MemoryStream();
using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress, false))
{
gz.Write(CompressMe, 0, CompressMe.Length);
ms.Position = 0;
byte[] Result = new byte[ms.Length];
ms.Read(Result, 0, (int)ms.Length);
return Result;
}
}
Затем это происходит..
CA2000 : Microsoft.Reliability : In method 'Compression.Compress(byte[])',
object 'ms' is not disposed along all exception paths. Call
System.IDisposable.Dispose on object 'ms' before all references to
it are out of scope.
Я не могу выиграть.. (если я не пропущу что-то очевидное) Я пробовал разные вещи, например, положить попытку/наконец вокруг блока и избавиться от MemoryStream там, но он либо говорит, что я "Я распоряжаюсь им дважды, или совсем нет!"
Ответы
Ответ 1
Это иногда проблема с запуском CodeAnalysis, иногда вы просто не можете выиграть, и вам нужно выбрать меньшее зло.
В этой ситуации я считаю, что правильная реализация - второй пример. Зачем? Согласно .NET Reflector, реализация GZipStream.Dispose()
будет использовать MemoryStream
для вас, поскольку GZipStream владеет MemoryStream.
Соответствующие части класса GZipStream
ниже:
public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen)
{
this.deflateStream = new DeflateStream(stream, mode, leaveOpen, true);
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing && (this.deflateStream != null))
{
this.deflateStream.Close();
}
this.deflateStream = null;
}
finally
{
base.Dispose(disposing);
}
}
Поскольку вы не хотите полностью отключать правило, вы можете подавить этот метод только с использованием атрибута CodeAnalysis.SupressMessage
.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability ", "CA2000:?", Justification = "MemoryStream will be disposed by the GZipStream.")]
Примечание. Вы должны заполнить полное имя правила (т.е. CA2000:?
), поскольку я не знал, что было из сообщения об ошибке, которое вы опубликовали.
НТН,
EDIT:
@CodeInChaos:
Глядя глубже на реализацию DeflateStream.Dispose
Я полагаю, что он по-прежнему будет использовать MemoryStream для вас независимо от опции leaveOpen, поскольку он вызывает base.Dispose()
.
РЕДАКТИРОВАТЬ Игнорировать выше о DeflateStream.Dispose
. Я искал неправильную реализацию в Reflector. См. Комментарии для деталей.
Ответ 2
Помимо проблемы с размещением, ваш код также не работает. Вы должны закрыть поток zip перед чтением данных.
Также существует метод ToArray()
на MemoryStream
, не нужно его реализовать самостоятельно.
using (MemoryStream ms = new MemoryStream())
{
using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress,true))
{
gz.Write(CompressMe, 0, CompressMe.Length);
}
return ms.ToArray();
}
Я просто подавил бы предупреждение, так как это ложный позитив. Анализ кода поможет вам, а не наоборот.
Ответ 3
От эта страница в MSDN
Stream stream = null;
try
{
stream = new FileStream("file.txt", FileMode.OpenOrCreate);
using (StreamWriter writer = new StreamWriter(stream))
{
stream = null;
// Use the writer object...
}
}
finally
{
if(stream != null)
stream.Dispose();
}
Это попытка... наконец, что отсутствует ваше решение, которое вызывает второе сообщение.
Если это:
GZipStream gz = new GZipStream(ms, CompressionMode.Compress, false)
невозможно, поток не будет удален.
Ответ 4
На самом деле эффективный вызов dispose дважды в потоке памяти не вызовет каких-либо проблем, было бы легко закодировать это в классе MemoryStream и при тестировании Microsoft. Поэтому, если вы не хотите подавлять правило, другой альтернативой является разделение вашего метода на два:
public static byte[] Compress(byte[] CompressMe)
{
using (MemoryStream ms = new MemoryStream())
{
return Compress(CompressMe, ms);
}
}
public static byte[] Compress(byte[] CompressMe, MemoryStream ms)
{
using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress, true))
{
gz.Write(CompressMe, 0, CompressMe.Length);
ms.Position = 0;
byte[] Result = new byte[ms.Length];
ms.Read(Result, 0, (int)ms.Length);
return Result;
}
}
Ответ 5
Вам нужно пойти в старую школу:
public static byte[] Compress(byte[] CompressMe)
{
MemoryStream ms = null;
GZipStream gz = null;
try
{
ms = new MemoryStream();
gz = new GZipStream(ms, CompressionMode.Compress, true);
gz.Write(CompressMe, 0, CompressMe.Length);
gz.Flush();
return ms.ToArray();
}
finally
{
if (gz != null)
{
gz.Dispose();
}
else if (ms != null)
{
ms.Dispose();
}
}
}
Выглядит ужасно, я знаю, но это единственный способ успокоить предупреждение.
Лично мне не нравится писать код, подобный этому, поэтому просто подавляйте предупреждение о множественном размещении (если это применимо) на том основании, что Dispose calls должно быть идемпотентным (и в этом случае).