C# Tips | ファイル・ディレクトリ操作:ZIP解凍

C sharp C#
スポンサーリンク

はじめに 「ZIP解凍」ができると“受け取ったファイルをさばける人”になる

業務だと、相手から渡されるファイルがそのままではなく、ZIP で固められていることが本当に多いです。
日次バッチの結果、帳票一式、マスタデータ、ログ、バックアップ。どれも ZIP で来がちです。

ここで「手で解凍してから置いてください」と人間に任せていると、ミスも起きるし手間もかかります。
C# から ZIP を自動で解凍できるようになると、「受け取った ZIP をそのまま処理フローに流す」ことができて、一気に業務っぽくなります。

ここでは、初心者向けに、標準ライブラリだけでできる ZIP 解凍を、
一番シンプルなところから「実務でちゃんと使える形」まで、例題付きでかみ砕いて説明します。


基本の道具 System.IO.Compression.ZipFile を知る

どの名前空間・クラスを使うのか

C# で ZIP を扱うときの入り口は、System.IO.Compression 名前空間です。
解凍に関しては、ZipFile クラスの ExtractToDirectory メソッドが“まず覚えるべき 1 本”です。

using System.IO.Compression;
C#

を using に書いておけば、ZipFile.ExtractToDirectory が使えるようになります。

.NET Framework の場合は、プロジェクトに System.IO.Compression.FileSystem への参照追加が必要なことがあります。
.NET 6 以降などでは、通常は何も意識せずに使えます。


一番シンプルな ZIP 解凍 ZipFile.ExtractToDirectory

「ZIP → フォルダ」に丸ごと展開する

まずは、ZIP を指定したフォルダにそのまま展開する、最も基本的な例です。

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

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

        if (!Directory.Exists(extractDirectory))
        {
            Directory.CreateDirectory(extractDirectory);
        }

        ZipFile.ExtractToDirectory(zipPath, extractDirectory);

        Console.WriteLine("解凍完了");
    }
}
C#

このコードの流れを言葉で整理します。

まず、zipPath に解凍したい ZIP ファイルのパスを指定します。
次に、extractDirectory に展開先フォルダを指定します。存在しなければ Directory.CreateDirectory で作ります。
最後に、ZipFile.ExtractToDirectory(zipPath, extractDirectory); を呼ぶだけで、ZIP の中身がすべてそのフォルダに展開されます。

ここでのポイントは、「展開先フォルダは自分で用意する」ということです。
ExtractToDirectory はフォルダを自動では作ってくれないので、事前に存在確認と作成をしておくのが定番パターンです。

上書きの有無を制御する(overwriteFiles)

.NET 5 以降などでは、ExtractToDirectory に「上書きするかどうか」を指定できるオーバーロードがあります。

ZipFile.ExtractToDirectory(zipPath, extractDirectory, overwriteFiles: true);
C#

overwriteFiles: true にすると、展開先に同名ファイルがあっても上書きします。
false(またはこの引数なし)だと、既に存在するファイルがある場合に例外が発生します。

業務で使うときは、「既存ファイルを上書きしてよいか」を仕様として決めておくのが大事です。
例えば、「毎日空のフォルダに展開する前提なら false でもよい」「既存ファイルを更新したいなら true にする」といった感じです。


ZIP の中身を見ながら解凍する ZipArchive を使う

「全部じゃなくて、一部だけ解凍したい」ケース

ZipFile.ExtractToDirectory は「全部まとめて展開する」には便利ですが、
「CSV だけ取り出したい」「特定のサブフォルダだけ展開したい」といった柔らかい要件には向きません。

そういうときは、ZipArchive を使って ZIP の中身を 1 エントリずつ扱います。

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

public static class ZipExtractUtil
{
    public static void ExtractCsvOnly(string zipPath, string extractDirectory)
    {
        Directory.CreateDirectory(extractDirectory);

        using var zipStream = new FileStream(zipPath, FileMode.Open, FileAccess.Read, FileShare.Read);
        using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);

        foreach (var entry in archive.Entries)
        {
            if (!entry.FullName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            string destinationPath = Path.Combine(extractDirectory, entry.FullName);

            string? destDir = Path.GetDirectoryName(destinationPath);
            if (!string.IsNullOrEmpty(destDir) && !Directory.Exists(destDir))
            {
                Directory.CreateDirectory(destDir);
            }

            using var entryStream = entry.Open();
            using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None);

            entryStream.CopyTo(fileStream);
        }
    }
}
C#

使い方の例です。

class Program
{
    static void Main()
    {
        string zipPath = @"C:\work\mixed.zip";
        string extractDirectory = @"C:\work\csv_out";

        ZipExtractUtil.ExtractCsvOnly(zipPath, extractDirectory);

        Console.WriteLine("CSV のみ解凍完了");
    }
}
C#

ここでの重要ポイントを深掘りします。

archive.Entries で ZIP 内のファイル一覧を取れる
entry.FullName には、ZIP 内でのパス(例: "data/items.csv")が入っています。
これを見て、「どのファイルを解凍するか」を自分で決められます。

展開先パスを Path.Combine で組み立てる
Path.Combine(extractDirectory, entry.FullName) とすることで、
ZIP 内のフォルダ構造をそのまま展開先に再現できます。

サブフォルダを自分で作る必要がある
Directory.CreateDirectory(destDir) を呼んで、必要なフォルダを作っています。
これを忘れると、「ディレクトリが存在しない」という例外になります。

entry.Open()FileStreamCopyTo でつなぐ
entry.Open() で ZIP 内のエントリを読み出すストリームを取得し、
それを展開先ファイルのストリームに CopyTo で流し込んでいます。
これが「ZIP の中身をファイルとして書き出す」基本パターンです。


セキュリティ的な注意点 ZIP スリップ対策

entry.FullName をそのまま信用しない、という発想

少しレベルの高い話ですが、実務ではとても重要なので触れておきます。

悪意のある ZIP には、entry.FullName..\..\windows\system32\... のようなパスが入っていることがあります。
これをそのまま Path.Combine(extractDirectory, entry.FullName) に渡すと、
展開先フォルダの外側にファイルを書き出してしまう危険があります(これを「ZIP スリップ」と呼びます)。

対策としては、「展開先のルートからはみ出していないか」をチェックすることです。

private static string GetSafeDestinationPath(string extractDirectory, string entryFullName)
{
    string combined = Path.GetFullPath(Path.Combine(extractDirectory, entryFullName));
    string root = Path.GetFullPath(extractDirectory);

    if (!combined.StartsWith(root, StringComparison.OrdinalIgnoreCase))
    {
        throw new InvalidOperationException("ZIP 内のパスが不正です。");
    }

    return combined;
}
C#

これを使って、先ほどの destinationPath を作り直すと安全性が上がります。

string destinationPath = GetSafeDestinationPath(extractDirectory, entry.FullName);
C#

初心者のうちは「そんな攻撃 ZIP なんて来ないでしょ」と思うかもしれませんが、
「外部から渡された ZIP を自動で展開する」処理を書くときは、
こういうチェックを入れておく癖をつけておくと、後々自分を守ってくれます。


実務ユーティリティとしての設計ポイント

解凍の「単位」とフォルダ構成を決める

業務で ZIP 解凍を組み込むときは、最初に次のようなことを決めておくと設計が楽になります。

どこに ZIP を置いてもらうか(受信フォルダ)
どこに解凍するか(展開先ルート)
ZIP ごとに専用フォルダを作るか(例: data_20250128.zipdata_20250128 フォルダ)

例えば、「受信フォルダに ZIP が置かれたら、同名のフォルダを作ってそこに解凍する」というパターンはよくあります。

string zipPath = @"C:\in\data_20250128.zip";

string fileNameWithoutExt = Path.GetFileNameWithoutExtension(zipPath);
string extractDirectory = Path.Combine(@"C:\in\unzip", fileNameWithoutExt);

Directory.CreateDirectory(extractDirectory);

ZipFile.ExtractToDirectory(zipPath, extractDirectory, overwriteFiles: true);
C#

こうしておくと、「どの ZIP がどのフォルダに展開されたか」が一目で分かります。

例外処理とログ

解凍処理は、次のような理由で失敗することがあります。

ZIP ファイルが壊れている
展開先に書き込み権限がない
ディスク容量が足りない
展開先に同名ファイルがあり、上書き禁止設定になっている

業務ユーティリティとしては、「どの ZIP を解凍しようとして」「どこで」「どんな例外が出たか」をログに残しておくと、
運用時のトラブルシュートがかなり楽になります。

例えば、最低限こんなログを出すイメージです。

try
{
    ZipFile.ExtractToDirectory(zipPath, extractDirectory, overwriteFiles: true);
}
catch (Exception ex)
{
    Console.WriteLine($"解凍失敗: zip={zipPath} dir={extractDirectory} error={ex.Message}");
}
C#

本番では Console.WriteLine ではなく、ファイルログや監視システムに送ることになるでしょう。


まとめ 「ZIP解凍ユーティリティ」は“受け取ったデータを自動でさばく”ための基礎

ZIP 解凍は、業務システムと外部世界をつなぐときの超基本スキルです。
C# では、標準ライブラリだけでかなり実用的なことができます。

押さえておきたいポイントを最後に整理します。

フォルダ丸ごと展開なら ZipFile.ExtractToDirectory(zipPath, extractDirectory) が最初の一手。
上書きの有無は overwriteFiles で制御し、「既存ファイルをどう扱うか」を仕様として決める。
一部だけ解凍したいときは ZipArchive を使い、archive.Entries を見ながら条件に合うエントリだけ CopyTo で書き出す。
entry.FullName をそのまま信用せず、「展開先ルートからはみ出していないか」をチェックすると安全性が上がる。
どこに解凍するか、ZIP ごとにフォルダを分けるか、失敗時に何をログに残すか、といった“運用の絵”まで含めて設計すると、実務で使えるユーティリティになる。

ここまで理解できれば、「ZIP で渡されたデータを自動で展開して処理する」という、
現場でよくあるフローを自分の C# コードでしっかり支えられるようになります。

タイトルとURLをコピーしました