Исправлена ​​реализация 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 должно быть идемпотентным (и в этом случае).