Правильная реализация IDisposable для этого кода - PullRequest
11 голосов
/ 18 ноября 2011

У меня есть следующий код

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 установлен, он оставляет основной поток (мс) открытым из-за последнего параметра конструктора (оставьте открытым = true).

Если я немного изменю свой код .. удалите блок 'using' вокруг MemoryStream и измените параметр 'leftOpen' на 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.

Я не могу победить .. (если я не пропустил что-то очевидное) Я пробовал разные вещи, такие как установка try / finally вокруг блока и удаление там MemoryStream, но это либо говорит, что я Я избавляюсь от этого дважды, или нет вообще !!

Ответы [ 5 ]

6 голосов
/ 18 ноября 2011

Помимо проблемы утилизации, ваш код также не работает.Вы должны закрыть поток 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();
}

I 'Я просто подавил бы предупреждение, так как это ложное срабатывание.Анализ кода должен служить вам, а не наоборот.

3 голосов
/ 18 ноября 2011

С эта страница в 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)

не удалось, поток не будет утилизирован.

2 голосов
/ 18 ноября 2011

В действительности, эффективный вызов 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;
        }
    }
1 голос
/ 18 ноября 2011

Иногда возникает проблема с запуском 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 за вас независимо от опции leftOpen, так как она вызывает base.Dispose().

РЕДАКТИРОВАТЬ Игнорировать вышеизложенное о DeflateStream.Dispose. Я смотрел на неправильную реализацию в Reflector. Смотрите комментарии для деталей.

0 голосов
/ 18 ноября 2011

Вы должны пойти в старую школу:

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

...