.NET GZipStream распаковать производя пустой поток

Я пытаюсь сериализовать и сжать WPF FlowDocument, а затем сделать обратное - распаковать массив байтов и десериализовать для воссоздания FlowDocument - используя класс .NET GZipStream. Я следую примеру, описанному в MSDN, и у меня есть следующая тестовая программа:

var flowDocumentIn = new FlowDocument();
flowDocumentIn.Blocks.Add(new Paragraph(new Run("Hello")));
Debug.WriteLine("Compress");
byte[] compressedData;
using (var uncompressed = new MemoryStream())
{
    XamlWriter.Save(flowDocumentIn, uncompressed);
    uncompressed.Position = 0;
    using (var compressed = new MemoryStream())
    using (var compressor = new GZipStream(compressed, CompressionMode.Compress))
    {
        Debug.WriteLine(" uncompressed.Length: " + uncompressed.Length);
        uncompressed.CopyTo(compressor);
        Debug.WriteLine(" compressed.Length: " + compressed.Length);
        compressedData = compressed.ToArray();
    }
}

Debug.WriteLine("Decompress");
FlowDocument flowDocumentOut;
using (var compressed = new MemoryStream(compressedData))
using (var uncompressed = new MemoryStream())
using (var decompressor = new GZipStream(compressed, CompressionMode.Decompress))
{
    Debug.WriteLine(" compressed.Length: " + compressed.Length);
    decompressor.CopyTo(uncompressed);
    Debug.WriteLine(" uncompressed.Length: " + uncompressed.Length);
    flowDocumentOut = (FlowDocument) XamlReader.Load(uncompressed);
}

Assert.AreEqual(flowDocumentIn, flowDocumentOut);

Однако я получаю исключение в строке XamlReader.Load, которая является нормальной, поскольку вывод отладки говорит о том, что несжатый поток имеет нулевую длину.

Compress
 uncompressed.Length: 123
 compressed.Length: 202
Decompress
 compressed.Length: 202
 uncompressed.Length: 0

Почему окончательный поток uncompressed не содержит исходных 123 байта?

(Пожалуйста, игнорируйте тот факт, что "сжатый" массив байтов больше, чем "несжатый" массив байтов - я обычно буду работать с гораздо большими документами потока)

Ответ 1

Вам нужно закрыть GZipStream, прежде чем получать сжатые байты из потока памяти. В этом случае закрытие обрабатывается Dispose, вызванным использованием.

using (var compressed = new MemoryStream())
{
    using (var compressor = new GZipStream(compressed, CompressionMode.Compress))
    {
        uncompressed.CopyTo(compressor);
    }
    // Get the compressed bytes only after closing the GZipStream
    compressedBytes = compressed.ToArray();
}

Это работает, и вы даже можете удалить using для MemoryStream, так как он будет удален GZipStream, если вы не используете перегрузку конструктора, которая позволяет вам указать, что базовый поток должен быть оставлен открытым. Это подразумевает, что этот код вы вызываете ToArray в удаленном потоке, но это разрешено, потому что байты все еще доступны, что делает распоряжения потоками памяти немного странными, но если вы этого не сделаете, FXCop будет вас раздражать.

Ответ 2

Ответ Жоаа сделал трюк. Я скопировал полный рабочий пример ниже. Я добавил строку для вывода compressedData.Length. Интересно, что это выводит 218 байт, тогда как compressedStream.Length выводит только 202 байта. Если вы не закрываете GZipStream перед чтением байтового массива, тогда compressedData.Length - 202. Я не уверен, почему закрытие GZipStream дает дополнительные 16 байт.

var flowDocumentIn = new FlowDocument();
flowDocumentIn.Blocks.Add(new Paragraph(new Run("Hello")));

Debug.WriteLine("Compress");

byte[] compressedData;

using (var uncompressedStream = new MemoryStream())
{
    XamlWriter.Save(flowDocumentIn, uncompressedStream);
    uncompressedStream.Position = 0;
    using (var compressedStream = new MemoryStream())
    {
        using (var gZipCompressor = new GZipStream(compressedStream, CompressionMode.Compress))
        {
            Debug.WriteLine(" uncompressedStream.Length: " + uncompressedStream.Length);
            uncompressedStream.CopyTo(gZipCompressor);
            Debug.WriteLine(" compressedStream.Length: " + compressedStream.Length);
        }
        compressedData = compressedStream.ToArray();
    }
}

Debug.WriteLine(" compressedData.Length: " + compressedData.Length);

Debug.WriteLine("Decompress");

FlowDocument flowDocumentOut;

using (var compressedStream = new MemoryStream(compressedData))
using (var uncompressedStream = new MemoryStream())
{
    using (var gZipDecompressor = new GZipStream(compressedStream, CompressionMode.Decompress))
    {
        Debug.WriteLine(" compressedStream.Length: " + compressedStream.Length);
        gZipDecompressor.CopyTo(uncompressedStream);
        Debug.WriteLine(" uncompressedStream.Length: " + uncompressedStream.Length);
    }
    uncompressedStream.Position = 0;
    flowDocumentOut = (FlowDocument)XamlReader.Load(uncompressedStream);
}

Отладочный вывод:

Compress
 uncompressedStream.Length: 123
 compressedStream.Length: 202
 compressedData.Length: 218
Decompress
 compressedStream.Length: 218
 uncompressedStream.Length: 123

Обратите внимание также на дополнительный uncompressedStream.Position = 0; перед вызовом XamlReader.Load.

Ответ 3

После копирования распакованных байтов в ваш поток вам нужно установить его положение в ноль, чтобы вы могли его правильно прочитать