Working with ZipFile and GZipStream in C

Handling data compression in .NET is primarily managed through the System.IO.Compression namespace. This article provides a practical guide on how to work with compressed archives in C# using both ZipFile and GZipStream. You will learn when to choose each approach, how to create and extract multi-file ZIP archives using ZipFile, and how to compress and decompress single files or memory streams using GZipStream.

Understanding ZipFile vs. GZipStream

While both classes compress data, they serve different purposes:

  • ZipFile (and ZipArchive): Designed to work with standard .zip files containing multiple files and nested directories. It manages file metadata, paths, and directory structures.
  • GZipStream: Designed to compress a single stream of raw bytes using the GZIP format (.gz). It does not store directory structures or multiple files natively unless paired with a container format like TAR.

Working with ZipFile

The static ZipFile class provides straightforward methods for creating, extracting, and opening .zip archives on disk.

1. Creating a ZIP Archive from a Directory

You can compress an entire directory into a .zip file using ZipFile.CreateFromDirectory:

using System;
using System.IO.Compression;

class Program
{
    static void Main()
    {
        string sourceDirectory = @"C:\data\reports";
        string destinationZip = @"C:\data\reports.zip";

        // Compresses the folder and includes the base directory structure
        ZipFile.CreateFromDirectory(sourceDirectory, destinationZip, CompressionLevel.Optimal, includeBaseDirectory: false);

        Console.WriteLine("ZIP archive created successfully.");
    }
}

2. Extracting a ZIP Archive to a Directory

To unpack a .zip archive to a target folder, use ZipFile.ExtractToDirectory:

using System;
using System.IO.Compression;

class Program
{
    static void Main()
    {
        string zipPath = @"C:\data\reports.zip";
        string extractPath = @"C:\data\extracted_reports";

        // Extracts all entries; overwriteFiles can be set to true if needed
        ZipFile.ExtractToDirectory(zipPath, extractPath, overwriteFiles: true);

        Console.WriteLine("ZIP archive extracted successfully.");
    }
}

3. Adding or Reading Individual Entries with ZipArchive

If you need finer control over archive contents without extracting everything to disk, use ZipArchive:

using System;
using System.IO;
using System.IO.Compression;

class Program
{
    static void Main()
    {
        string zipPath = @"C:\data\reports.zip";

        // Open an existing archive to add a new file
        using (FileStream zipToOpen = new FileStream(zipPath, FileMode.Open))
        using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
        {
            ZipArchiveEntry entry = archive.CreateEntry("notes.txt");

            using (StreamWriter writer = new StreamWriter(entry.Open()))
            {
                writer.WriteLine("Archive created on: " + DateTime.UtcNow);
            }
        }

        // Reading entries from an archive
        using (ZipArchive archive = ZipFile.OpenRead(zipPath))
        {
            foreach (ZipArchiveEntry entry in archive.Entries)
            {
                Console.WriteLine($"File: {entry.FullName}, Compressed Size: {entry.CompressedLength} bytes");
            }
        }
    }
}

Working with GZipStream

GZipStream wraps an underlying stream to compress or decompress data on the fly. It is commonly used for network transfers, caching, or single-file compression.

1. Compressing a File with GZipStream

To compress a file into .gz format, read the source file into a stream and pipe it through a GZipStream:

using System;
using System.IO;
using System.IO.Compression;

class Program
{
    static void Main()
    {
        string sourceFile = @"C:\data\large_log.txt";
        string compressedFile = @"C:\data\large_log.txt.gz";

        using (FileStream sourceStream = File.OpenRead(sourceFile))
        using (FileStream targetStream = File.Create(compressedFile))
        using (GZipStream compressionStream = new GZipStream(targetStream, CompressionLevel.Optimal))
        {
            sourceStream.CopyTo(compressionStream);
        }

        Console.WriteLine("File compressed with GZip.");
    }
}

2. Decompressing a File with GZipStream

To decompress a .gz file back to its original state, wrap the compressed file stream in a GZipStream set to CompressionMode.Decompress:

using System;
using System.IO;
using System.IO.Compression;

class Program
{
    static void Main()
    {
        string compressedFile = @"C:\data\large_log.txt.gz";
        string decompressedFile = @"C:\data\restored_log.txt";

        using (FileStream sourceStream = File.OpenRead(compressedFile))
        using (FileStream targetStream = File.Create(decompressedFile))
        using (GZipStream decompressionStream = new GZipStream(sourceStream, CompressionMode.Decompress))
        {
            decompressionStream.CopyTo(targetStream);
        }

        Console.WriteLine("File decompressed successfully.");
    }
}

3. Compressing In-Memory Byte Arrays

You can also use GZipStream with MemoryStream to compress payloads in memory before storing them in a database or sending them across a network:

using System;
using System.IO;
using System.IO.Compression;
using System.Text;

class Program
{
    static byte[] CompressBytes(byte[] rawData)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (GZipStream gzip = new GZipStream(memoryStream, CompressionLevel.Fastest))
            {
                gzip.Write(rawData, 0, rawData.Length);
            }
            return memoryStream.ToArray();
        }
    }

    static byte[] DecompressBytes(byte[] compressedData)
    {
        using (MemoryStream sourceStream = new MemoryStream(compressedData))
        using (MemoryStream outputStream = new MemoryStream())
        using (GZipStream gzip = new GZipStream(sourceStream, CompressionMode.Decompress))
        {
            gzip.CopyTo(outputStream);
            return outputStream.ToArray();
        }
    }

    static void Main()
    {
        string text = "Repeated text data designed for compression demonstration.";
        byte[] raw = Encoding.UTF8.GetBytes(text);

        byte[] compressed = CompressBytes(raw);
        byte[] decompressed = DecompressBytes(compressed);

        Console.WriteLine($"Original: {raw.Length} bytes");
        Console.WriteLine($"Compressed: {compressed.Length} bytes");
        Console.WriteLine($"Restored String: {Encoding.UTF8.GetString(decompressed)}");
    }
}

Summary of Best Practices

  • Always Dispose Streams: Ensure that GZipStream and ZipArchive instances are wrapped in using statements. Compression buffers are often flushed only upon disposal.
  • Select the Right CompressionLevel:
    • CompressionLevel.Optimal: Maximum compression, higher CPU usage.
    • CompressionLevel.Fastest: Faster compression, slightly larger output.
    • CompressionLevel.NoCompression: Useful only when wrapping files into a container format without size reduction.
  • Path Traversal Protection: When extracting entries via ZipArchiveEntry, validate that entry.FullName does not resolve outside the target directory to prevent zip-slip vulnerabilities.