C#を用いたアプリケーション開発において、ファイルの圧縮や展開といったアーカイブ操作は非常に頻繁に求められる機能の一つです。

ログファイルの集約やユーザーデータのバックアップ、リソースファイルの配布など、ZIP形式を活用するシーンは多岐にわたります。

かつては外部ライブラリに頼る場面もありましたが、現在の.NET環境では標準ライブラリであるSystem.IO.Compressionが非常に強力な機能を提供しています。

本記事では、2026年時点での最新のベストプラクティスを踏まえ、C#でZIPファイルを効率的かつ安全に操作するための標準手法を詳しく解説します。

System.IO.Compression名前空間の基礎知識

C#でZIP操作を行うための中心的な役割を担うのが、System.IO.Compression名前空間です。

この名前空間には、単純な圧縮・展開から、アーカイブ内の個別ファイルの高度な操作までをカバーするクラス群が含まれています。

主に利用される主要なクラスは以下の3種類です。

クラス名主な役割
ZipFileディレクトリ全体の圧縮やアーカイブ全体の展開など、簡便な操作を提供します。
ZipArchiveZIPアーカイブ内の個別のエントリ(ファイル)を読み書きするための詳細な操作を提供します。
ZipArchiveEntryアーカイブ内に格納されている個々のファイルを表現し、データへのアクセスを可能にします。

これらのクラスを適切に使い分けることで、パフォーマンスとコードの可読性を両立させることが可能になります。

なお、これらの機能を利用するためには、プロジェクトの参照設定でSystem.IO.Compression.FileSystemなどのアセンブリが必要になる場合がありますが、現在のSDKスタイルのプロジェクトでは自動的に解決されることが一般的です。

最も簡単なZIP操作:ZipFileクラスの活用

特定のフォルダを丸ごとZIP化したい場合や、ZIPファイルを特定の場所にすべて解凍したい場合には、ZipFileクラスを使用するのが最も効率的です。

このクラスは静的メソッドを提供しており、わずか1行のコードで複雑なアーカイブ処理を完結させることができます。

ディレクトリをZIPファイルに圧縮する

指定したフォルダ内のすべてのファイルを1つのZIPファイルにまとめるには、CreateFromDirectoryメソッドを使用します。

C#
using System.IO.Compression;

string sourceDirectory = @"C:\App\Logs";
string destinationZip = @"C:\App\Backups\logs_backup.zip";

// ディレクトリの内容をZIPファイルとして保存
ZipFile.CreateFromDirectory(sourceDirectory, destinationZip);

Console.WriteLine("圧縮が完了しました。");

このメソッドは、引数として圧縮レベル(CompressionLevel)を指定することも可能です。

「Optimal(最適)」を指定すると圧縮率が優先され、「Fastest(最速)」を指定すると処理速度が優先されます。

ZIPファイルをディレクトリに展開する

既存のZIPファイルを指定したパスに展開するには、ExtractToDirectoryメソッドを利用します。

C#
using System.IO.Compression;

string zipPath = @"C:\App\Backups\data.zip";
string extractPath = @"C:\App\TransferredData";

// ZIPファイルを指定したディレクトリに展開
ZipFile.ExtractToDirectory(zipPath, extractPath);

Console.WriteLine("展開が完了しました。");

展開先に同名のファイルが存在する場合に上書きを許可するかどうかを、第3引数のブール値で制御できる点も覚えておくと便利です。

ZipFileクラスによる操作は非常に直感的ですが、メモリ消費を抑えたい場合や、特定のファイルだけを抽出したい場合には、次に紹介するZipArchiveクラスの方が適しています。

高度な制御を可能にするZipArchiveクラス

アーカイブ全体ではなく、内部の特定のファイルのみを読み取ったり、既存のZIPにファイルを追加したりする場合は、ZipArchiveクラスを使用します。

このクラスはIDisposableを実装しているため、usingステートメントと組み合わせてリソースの解放を確実に行うのが基本です。

ZIP内のファイル一覧を表示する

アーカイブを展開することなく、どのようなファイルが含まれているかを確認するコード例を以下に示します。

C#
using System;
using System.IO;
using System.IO.Compression;

string zipPath = "sample.zip";

using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
    foreach (ZipArchiveEntry entry in archive.Entries)
    {
        // ファイル名と最終更新日時を表示
        Console.WriteLine($"ファイル名: {entry.FullName}, サイズ: {entry.Length}バイト");
    }
}
実行結果
ファイル名: document.txt, サイズ: 1024バイト
ファイル名: images/photo.jpg, サイズ: 54230バイト

archive.Entriesプロパティを参照することで、ディレクトリ構造を維持したまま各ファイルの情報にアクセスできます。

既存のZIPファイルにファイルを追加する

新しくアーカイブを作成するのではなく、既存のZIPファイルに新しいデータを追記することも可能です。

この場合、ZipFile.Openメソッドを第2引数にZipArchiveMode.Updateを指定して呼び出します。

C#
using System.IO;
using System.IO.Compression;

string zipPath = "existing_archive.zip";
string fileToAdd = "new_data.xml";

using (FileStream zipToOpen = new FileStream(zipPath, FileMode.Open))
{
    using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
    {
        // アーカイブ内に新しいエントリを作成
        ZipArchiveEntry readmeEntry = archive.CreateEntryFromFile(fileToAdd, "notes/new_data.xml");
    }
}

このように、CreateEntryFromFileを使用すれば、ディスク上のファイルを直接アーカイブ内に取り込むことができます。

ストリームを利用したインメモリ圧縮・展開

Webアプリケーションやクラウドサービスでは、ディスクにファイルを書き込まず、メモリ上(Stream)でZIP処理を行いたいケースが多くあります。

System.IO.CompressionStreamをベースに設計されているため、MemoryStreamと組み合わせることで柔軟な処理が可能です。

MemoryStreamを用いたZIP生成

HTTPレスポンスとしてZIPファイルを返却する場合などに、メモリ上で動的にZIPを作成する手法は非常に有効です。

C#
using System.IO;
using System.IO.Compression;
using System.Text;

public byte[] CreateZipInMemory()
{
    using (var memoryStream = new MemoryStream())
    {
        using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
        {
            var demoEntry = archive.CreateEntry("hello.txt");
            using (var entryStream = demoEntry.Open())
            using (var writer = new StreamWriter(entryStream, Encoding.UTF8))
            {
                writer.WriteLine("これはメモリ上で生成されたテキストファイルです。");
            }
        }
        
        // 最後にToArrayでバイト配列として取得
        return memoryStream.ToArray();
    }
}

ZipArchiveのコンストラクタの第3引数(leaveOpen)にtrueを渡すことが重要です。

これにより、ZipArchiveが破棄された後も、背後にあるMemoryStreamが閉じられずにデータを読み取ることが可能になります。

2026年における最新のパフォーマンス最適化

近年の.NETアップデートでは、ZIP操作におけるパフォーマンスが劇的に向上しています。

特に、スパン(Span<T>)SIMDを活用した内部実装の最適化により、大量のファイルを扱う際のCPU負荷が軽減されています。

非同期I/Oの活用

サーバーサイドのアプリケーションでは、スレッドの枯渇を防ぐために非同期メソッドの活用が推奨されます。

ZIP操作そのものに組み込みの非同期版ExtractToDirectoryAsyncといったメソッドがない場合でも、ストリームのコピー部分でCopyToAsyncを利用することで非同期化を実現できます。

C#
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;

public async Task ExtractEntryAsync(ZipArchiveEntry entry, string destinationPath)
{
    using (Stream entryStream = entry.Open())
    using (FileStream fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
    {
        // 非同期でストリームをコピー
        await entryStream.CopyToAsync(fileStream);
    }
}

このように、entry.Open()で得られたストリームに対して非同期処理を適用することで、大規模なアーカイブ処理においてもUIスレッドやスレッドプールを占有しすぎない実装が可能です。

圧縮レベルの選択戦略

CompressionLevelには複数の選択肢がありますが、2026年のモダンな開発環境では以下のような基準で選定するのが一般的です。

  • SmallestSize: ネットワーク転送量を最小限に抑えたい場合に使用。CPU負荷は高い。
  • Optimal: 速度とサイズのバランスが最も良く、デフォルトの選択肢。
  • Fastest: リアルタイム性が求められるログ転送など、圧縮率よりも遅延を嫌う場合に使用。
  • NoCompression: 単なるアーカイブ(まとめ役)として機能させ、CPU負荷をほぼゼロにしたい場合に使用。

パスワード保護と標準ライブラリの限界

C#の標準ライブラリであるSystem.IO.Compressionを利用する上で、注意が必要なのがパスワード保護(暗号化)付きZIPの扱いです。

残念ながら、2026年時点の標準仕様においても、ZipArchiveクラスはパスワード付きZIPの作成や展開を直接サポートしていません。

もしパスワード保護が必要な業務要件がある場合は、以下のような代替策を検討する必要があります。

  1. DotNetZip (Ionic.Zip): 古くから使われているライブラリですが、パスワード対応が容易です。
  2. SharpZipLib: オープンソースで広く利用されており、AES暗号化などをサポートしています。
  3. 7-Zip などの外部プロセス呼び出し: セキュリティ要件が非常に厳しい場合、信頼性の高い外部ツールをProcess.Startで実行する手法も取られます。

しかし、標準ライブラリだけで完結させることは「依存関係を増やさない」という大きなメリットがあるため、パスワードが必須でない限りはSystem.IO.Compressionの利用が強く推奨されます。

エラーハンドリングと安全な実装のポイント

ZIP操作はファイルシステムへのアクセスを伴うため、実行時の例外処理が不可欠です。

特に「ディスク空き容量の不足」「ファイル名の重複」「不正なパス文字」などは頻繁に発生するトラブルです。

パス・トラバーサル脆弱性への対策

外部から受け取ったZIPファイルを展開する場合、../のような相対パスを含んだエントリによって、意図しないディレクトリにファイルが書き込まれる「Zip Slip」と呼ばれる脆弱性に注意が必要です。

現在の.NETのExtractToDirectoryメソッドはこの対策が組み込まれていますが、手動でエントリを操作する場合は、必ずPath.GetFullPathなどを用いて展開先が期待したディレクトリ内であることを確認してください。

C#
string destinationDirectory = Path.GetFullPath(@"C:\App\Extracted\");
foreach (ZipArchiveEntry entry in archive.Entries)
{
    string fullPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName));
    if (!fullPath.StartsWith(destinationDirectory, StringComparison.OrdinalIgnoreCase))
    {
        throw new IOException("不正なアーカイブエントリが検出されました。");
    }
    // 安全なパスへの書き出し処理
}

セキュリティを考慮したファイル操作は、プロフェッショナルな開発において必須のスキルです。

まとめ

C#におけるZIPファイルの操作は、System.IO.Compression名前空間を活用することで、非常にシンプルかつ高効率に実装できることがお分かりいただけたかと思います。

ZipFileクラスによる手軽な一括操作から、ZipArchiveクラスによる詳細なストリーム制御まで、用途に応じて最適な手段を選択することが重要です。

特に2026年の開発環境では、メモリ効率や非同期処理の重要性がさらに高まっており、これらを正しく理解して実装することが、高品質なアプリケーション開発への近道となります。

本記事で解説した手法を参考に、ぜひあなたのプロジェクトでも堅牢なZIP操作機能を実装してみてください。