C#でZIPファイルを圧縮・解凍する方法|ZipFile・ZipArchiveの使い方をサンプルコードで解説

はじめに

C#でZIPファイルを圧縮・解凍する場合、標準ライブラリのSystem.IO.Compressionを使うのが基本です。外部ライブラリを使わなくても、フォルダを丸ごとZIP化したり、ZIPファイルを指定フォルダへ解凍したり、ZIP内のファイル一覧を取得したりできます。

C#のZIP処理で主に使うクラスは、ZipFileZipArchiveZipArchiveEntryの3つです。フォルダ単位で簡単に圧縮・解凍したい場合はZipFile、ZIP内のファイルを個別に追加・削除・更新したい場合はZipArchiveを使うと分かりやすく実装できます。Microsoftの公式ドキュメントでも、ディレクトリからZIPを作成する場合はZipFile.CreateFromDirectory、ZIPの内容をディレクトリに展開する場合はZipFile.ExtractToDirectory、既存ZIPにファイルを追加する場合はZipArchive.CreateEntryなどを使う構成が示されています。Microsoft Learn+1

この記事では、「c# zip」でよく検索されるZIPファイルの圧縮・解凍方法について、ZipFileZipArchiveの使い方をサンプルコード付きで解説します。

1. C#でZIPファイルを扱う方法の全体像

1-1. C#ではSystem.IO.CompressionでZIPの圧縮・解凍ができる

C#でZIPファイルを扱うには、System.IO.Compression名前空間を使用します。System.IO.Compressionには、ZIPファイルを扱うためのZipFileZipArchiveZipArchiveEntryのほか、ストリーム圧縮用のDeflateStreamGZipStreamなどが含まれています。Microsoft Learn

基本的な使い分けは次のとおりです。

クラス主な用途
ZipFileフォルダ全体をZIP化する、ZIP全体を解凍する
ZipArchiveZIPファイルを開いて、内部のエントリを操作する
ZipArchiveEntryZIP内の1ファイルまたは1エントリを表す
ZipFileExtensionsCreateEntryFromFileExtractToFileなどの拡張メソッドを提供する

たとえば、フォルダをZIPファイルにするだけなら、次のように数行で実装できます。

C#
using System.IO.Compression;

string sourceDirectory = @"C:\work\input";
string zipPath = @"C:\work\output.zip";

ZipFile.CreateFromDirectory(sourceDirectory, zipPath);

解凍も同様にシンプルです。

C#
using System.IO.Compression;

string zipPath = @"C:\work\output.zip";
string extractDirectory = @"C:\work\extract";

ZipFile.ExtractToDirectory(zipPath, extractDirectory);

このように、C#では標準機能だけでZIPの基本的な圧縮・解凍を実装できます。

1-2. ZipFileとZipArchiveの違い

ZipFileZipArchiveはどちらもC#でZIPファイルを扱うためのクラスですが、目的が異なります。

ZipFileは、フォルダ全体をZIP化したり、ZIPファイル全体を解凍したりするための便利な静的メソッドを提供します。代表的なメソッドはCreateFromDirectoryExtractToDirectoryです。これらは「フォルダをZIPにする」「ZIPをフォルダへ展開する」という単純な処理に向いています。Microsoft Learn+1

一方、ZipArchiveはZIPファイルをアーカイブとして開き、内部のファイルを1つずつ操作するために使います。ZIP内の一覧取得、特定ファイルの追加、削除、更新、ストリーム経由の書き込みなどを行いたい場合に便利です。ZipArchive.Entriesを使うとZIP内のエントリ一覧を取得でき、ZipArchiveEntry.Deleteを使うとエントリを削除できます。Microsoft Learn+1

1-3. フォルダ単位ならZipFile、ファイル単位の操作ならZipArchiveを使う

C#でZIP処理を実装するときは、最初に「フォルダ単位の処理か」「ファイル単位の処理か」を考えると選びやすくなります。

フォルダを丸ごと圧縮したい場合はZipFile.CreateFromDirectoryを使います。

C#
ZipFile.CreateFromDirectory(@"C:\work\input", @"C:\work\backup.zip");

ZIPファイルを丸ごと解凍したい場合はZipFile.ExtractToDirectoryを使います。

C#
ZipFile.ExtractToDirectory(@"C:\work\backup.zip", @"C:\work\restore");

ZIP内に特定のファイルだけを追加したい場合はZipArchiveを使います。

C#
using System.IO.Compression;

using (ZipArchive archive = ZipFile.Open(@"C:\work\backup.zip", ZipArchiveMode.Update))
{
archive.CreateEntryFromFile(@"C:\work\readme.txt", "readme.txt");
}

ZIP内のファイル一覧を取得したい場合もZipArchiveが適しています。

C#
using System.IO.Compression;

using (ZipArchive archive = ZipFile.OpenRead(@"C:\work\backup.zip"))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
Console.WriteLine(entry.FullName);
}
}

単純な圧縮・解凍はZipFile、ZIP内部の細かい操作はZipArchiveと覚えておくとよいでしょう。

1-4. .NET Framework・.NET Core・.NET 5以降での対応状況

System.IO.Compressionは、.NET Framework、.NET Core、.NET 5以降のプロジェクトで利用できます。ただし、プロジェクトの種類や対象フレームワークによって、参照やパッケージの追加が必要になる場合があります。

.NET FrameworkアプリでZipFileクラスを使用する場合は、System.IO.Compression.FileSystemアセンブリへの参照が必要です。Microsoftの公式ドキュメントにも、.NET FrameworkアプリでZipFileを使用するにはSystem.IO.Compression.FileSystemへの参照を追加する必要があると記載されています。Microsoft Learn+1

.NET Coreや.NET 5以降のSDKスタイルのプロジェクトでは、多くの場合、using System.IO.Compression;を追加するだけで利用できます。ただし、古いプロジェクトや特殊なターゲットでは、System.IO.CompressionSystem.IO.Compression.ZipFileのNuGetパッケージが必要になることがあります。Microsoftのサンプルでは、ZIP処理の例に対してSystem.IO.CompressionSystem.IO.Compression.ZipFileのパッケージまたは参照が案内されています。Microsoft Learn+1

2. C#でZIPを扱うための準備

2-1. 必要なusingディレクティブ

C#でZIPファイルを扱うときは、基本的に次のusingディレクティブを追加します。

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

日本語ファイル名の文字コードを指定する場合は、Encodingを使うために次も追加します。

C#
using System.Text;

たとえば、ZIP解凍時にShift_JISを指定したい場合は、次のようにEncoding.GetEncodingを使用します。

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

ZipFile.ExtractToDirectory(
@"C:\work\sample.zip",
@"C:\work\extract",
Encoding.GetEncoding(932)
);

.NET Coreや.NET 5以降でShift_JISなどのコードページを使う場合は、必要に応じてSystem.Text.Encoding.CodePagesを追加し、Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);を実行します。

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

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

ZipFile.ExtractToDirectory(
@"C:\work\sample.zip",
@"C:\work\extract",
Encoding.GetEncoding("shift_jis")
);

2-2. System.IO.Compressionの参照・NuGetパッケージ

.NET FrameworkでZipFileが認識されない場合は、参照設定にSystem.IO.Compression.FileSystemを追加してください。ZipFileZipFileExtensionsは、このアセンブリに含まれる環境があります。Microsoft Learn+1

Visual Studioの場合は、プロジェクトの「参照」からSystem.IO.CompressionおよびSystem.IO.Compression.FileSystemを追加します。

SDKスタイルの.NETプロジェクトでNuGetパッケージが必要な場合は、次のように追加できます。

Bash
dotnet add package System.IO.Compression
dotnet add package System.IO.Compression.ZipFile

ただし、現在の.NETでは標準で利用できるケースも多いため、まずはusing System.IO.Compression;を追加してビルドし、ZipFileが見つからない場合に参照やパッケージを確認するとよいでしょう。

2-3. サンプルで使用するフォルダ構成

この記事では、次のようなフォルダ構成を例にします。

C:\zip-sample
├─ input
│ ├─ readme.txt
│ ├─ data.csv
│ └─ images
│ └─ logo.png
├─ output
└─ extract

各パスはC#コード内で次のように定義します。

C#
string baseDir = @"C:\zip-sample";
string sourceDir = Path.Combine(baseDir, "input");
string outputDir = Path.Combine(baseDir, "output");
string extractDir = Path.Combine(baseDir, "extract");
string zipPath = Path.Combine(outputDir, "sample.zip");

Path.Combineを使うと、ディレクトリ区切り文字を手動で連結する必要がなくなります。

C#
string zipPath = Path.Combine(outputDir, "sample.zip");

次のように文字列連結でパスを作ると、区切り文字の不足や重複が起きやすくなります。

C#
string zipPath = outputDir + "\\sample.zip";

C#でZIP処理を実装するときは、Path.CombinePath.GetFullPathDirectory.CreateDirectoryなどを組み合わせて、安全にパスを扱うことが大切です。

2-4. ZIPファイルの保存先とパス指定の注意点

ZIPファイルの保存先を指定するときは、保存先フォルダが存在しているかを確認します。ZipFile.CreateFromDirectoryはZIPファイルを作成しますが、保存先フォルダが存在しない場合は例外になります。

C#
string outputDir = @"C:\zip-sample\output";
string zipPath = Path.Combine(outputDir, "sample.zip");

Directory.CreateDirectory(outputDir);

ZipFile.CreateFromDirectory(@"C:\zip-sample\input", zipPath);

相対パスを使う場合は、現在の作業ディレクトリを基準に解釈される点に注意が必要です。ExtractToDirectoryExtractToFileのドキュメントでも、相対パスは現在の作業ディレクトリに対する相対パスとして解釈されることが示されています。Microsoft Learn+1

実行環境によってカレントディレクトリが変わると、想定外の場所にZIPファイルが作成されたり、解凍されたりすることがあります。業務アプリやバッチ処理では、できるだけ絶対パスを使うか、Path.GetFullPathで正規化してから処理するのがおすすめです。

C#
string zipPath = Path.GetFullPath(@"..\output\sample.zip");
Console.WriteLine(zipPath);

3. ZipFileでフォルダをZIP圧縮する方法

3-1. CreateFromDirectoryでフォルダをZIP化する基本コード

ZipFile.CreateFromDirectoryを使うと、指定したフォルダの中身をZIPファイルとして保存できます。CreateFromDirectoryは、指定したディレクトリ内のファイルとディレクトリを含むZIPアーカイブを作成するメソッドです。Microsoft Learn+1

基本コードは次のとおりです。

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

class Program
{
static void Main()
{
string sourceDir = @"C:\zip-sample\input";
string zipPath = @"C:\zip-sample\output\sample.zip";

Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!);

ZipFile.CreateFromDirectory(sourceDir, zipPath);

Console.WriteLine("ZIPファイルを作成しました。");
}
}

このコードを実行すると、C:\zip-sample\inputフォルダの中身がC:\zip-sample\output\sample.zipに圧縮されます。

注意点として、zipPathに指定したZIPファイルがすでに存在する場合、CreateFromDirectoryはそのまま上書きしません。上書きしたい場合は、事前に既存ファイルを削除してから作成します。

3-2. 圧縮レベルを指定してZIPファイルを作成する

CreateFromDirectoryでは、CompressionLevelを指定して圧縮レベルを調整できます。

C#
using System.IO.Compression;

string sourceDir = @"C:\zip-sample\input";
string zipPath = @"C:\zip-sample\output\sample.zip";

ZipFile.CreateFromDirectory(
sourceDir,
zipPath,
CompressionLevel.Optimal,
includeBaseDirectory: false
);

CompressionLevelには、主に次の値があります。

説明
CompressionLevel.Optimal圧縮率を重視する標準的な指定
CompressionLevel.Fastest圧縮速度を重視する指定
CompressionLevel.NoCompression圧縮せずに格納する指定
CompressionLevel.SmallestSizeより小さいサイズを重視する指定

一般的なZIP作成ではCompressionLevel.Optimalを使うことが多いです。処理速度を優先したい場合はFastestを検討します。Microsoftのドキュメントでも、Optimalはできるだけ圧縮したい場合、Fastestは圧縮処理が速く終わることを重視する場合に使う趣旨が説明されています。Microsoft Learn

3-3. includeBaseDirectoryで親フォルダを含めるか指定する

CreateFromDirectoryincludeBaseDirectory引数では、圧縮対象フォルダ自体をZIP内に含めるかどうかを指定できます。

C#
ZipFile.CreateFromDirectory(
sourceDirectoryName: @"C:\zip-sample\input",
destinationArchiveFileName: @"C:\zip-sample\output\sample.zip",
compressionLevel: CompressionLevel.Optimal,
includeBaseDirectory: false
);

includeBaseDirectory: falseの場合、ZIPを開くとinputフォルダの中身が直接入ります。

sample.zip
├─ readme.txt
├─ data.csv
└─ images
└─ logo.png

includeBaseDirectory: trueの場合、ZIP内にinputフォルダ自体が含まれます。

sample.zip
└─ input
├─ readme.txt
├─ data.csv
└─ images
└─ logo.png

配布用のZIPでは親フォルダを含めると、解凍時にファイルが散らばりにくくなります。一方、アプリケーション内で復元するためのZIPでは、展開先の構造を制御しやすいようにfalseを使うこともあります。

3-4. 既存のZIPファイルがある場合の上書き処理

ZipFile.CreateFromDirectoryで既存のZIPファイルを上書きしたい場合は、作成前にFile.Existsで存在確認し、File.Deleteで削除します。

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

string sourceDir = @"C:\zip-sample\input";
string zipPath = @"C:\zip-sample\output\sample.zip";

Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!);

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

ZipFile.CreateFromDirectory(
sourceDir,
zipPath,
CompressionLevel.Optimal,
includeBaseDirectory: false
);

実運用では、既存のZIPファイルをすぐ削除するのではなく、日時付きファイル名でバックアップを残す方法もあります。

C#
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string zipPath = $@"C:\zip-sample\output\backup_{timestamp}.zip";

ZipFile.CreateFromDirectory(
@"C:\zip-sample\input",
zipPath,
CompressionLevel.Optimal,
includeBaseDirectory: true
);

この方法なら、過去のバックアップを残しながらZIPファイルを作成できます。

3-5. ZIP圧縮時によくある例外と対処法

C#でZIP圧縮するときによく発生する例外には、次のようなものがあります。

例外主な原因対処法
DirectoryNotFoundException圧縮元フォルダや保存先フォルダが存在しないDirectory.ExistsDirectory.CreateDirectoryで確認する
IOExceptionZIPファイルが既に存在する、別プロセスが使用中事前削除、ファイル名変更、使用中プロセスの確認を行う
UnauthorizedAccessExceptionアクセス権限がない書き込み可能なフォルダを指定する
PathTooLongExceptionパスが長すぎるフォルダ階層やファイル名を短くする
NotSupportedExceptionパス形式が不正パス文字列を見直す

例外処理を入れる場合は、次のようにtry-catchで囲みます。

C#
try
{
ZipFile.CreateFromDirectory(
@"C:\zip-sample\input",
@"C:\zip-sample\output\sample.zip",
CompressionLevel.Optimal,
includeBaseDirectory: false
);

Console.WriteLine("ZIPファイルを作成しました。");
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine($"フォルダが見つかりません: {ex.Message}");
}
catch (IOException ex)
{
Console.WriteLine($"ファイル入出力エラーです: {ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"アクセス権限がありません: {ex.Message}");
}

ZIPファイル作成時は、圧縮元フォルダ、保存先フォルダ、既存ZIPファイル、アクセス権限の4点を確認すると、多くのエラーを防げます。

4. ZipFileでZIPファイルを解凍する方法

4-1. ExtractToDirectoryでZIPを解凍する基本コード

ZIPファイルを解凍するには、ZipFile.ExtractToDirectoryを使います。ExtractToDirectoryは、指定したZIPアーカイブ内のファイルを、ファイルシステム上の指定ディレクトリへ展開するメソッドです。Microsoft Learn+1

基本コードは次のとおりです。

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

class Program
{
static void Main()
{
string zipPath = @"C:\zip-sample\output\sample.zip";
string extractDir = @"C:\zip-sample\extract";

ZipFile.ExtractToDirectory(zipPath, extractDir);

Console.WriteLine("ZIPファイルを解凍しました。");
}
}

このコードを実行すると、sample.zipの内容がC:\zip-sample\extractに展開されます。

4-2. 解凍先フォルダを自動作成する方法

ExtractToDirectoryは解凍先ディレクトリを指定して展開しますが、親フォルダが存在しない場合に備えて、事前にDirectory.CreateDirectoryを呼び出しておくと安全です。

C#
string zipPath = @"C:\zip-sample\output\sample.zip";
string extractDir = @"C:\zip-sample\extract";

Directory.CreateDirectory(extractDir);

ZipFile.ExtractToDirectory(zipPath, extractDir);

Directory.CreateDirectoryは、既にフォルダが存在している場合でも例外にならず、そのまま処理を続けられます。そのため、解凍前に呼び出しておく定番の書き方です。

4-3. 既存ファイルを上書きして解凍する方法

.NET Core 2.0以降や.NET 5以降などでは、ExtractToDirectoryoverwriteFilesを指定できるオーバーロードがあります。overwriteFiles: trueを指定すると、解凍先に同名ファイルがある場合に上書きできます。Microsoft Learn+1

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\sample.zip";
string extractDir = @"C:\zip-sample\extract";

Directory.CreateDirectory(extractDir);

ZipFile.ExtractToDirectory(
sourceArchiveFileName: zipPath,
destinationDirectoryName: extractDir,
overwriteFiles: true
);

上書きに対応していない環境では、事前に解凍先フォルダを削除してから解凍する方法があります。

C#
string extractDir = @"C:\zip-sample\extract";

if (Directory.Exists(extractDir))
{
Directory.Delete(extractDir, recursive: true);
}

Directory.CreateDirectory(extractDir);

ZipFile.ExtractToDirectory(@"C:\zip-sample\output\sample.zip", extractDir);

ただし、フォルダごと削除すると既存データがすべて消えるため、業務データを扱う場合は削除対象を慎重に確認してください。

4-4. 文字化けするZIPファイルをEncoding指定で解凍する

ZIPファイル内の日本語ファイル名が文字化けする場合は、Encodingを指定して解凍します。ExtractToDirectoryにはエントリ名のエンコーディングを指定するオーバーロードがあります。エンコーディング指定時の動作として、UTF-8フラグが設定されているエントリはUTF-8で処理され、設定されていないエントリでは指定されたエンコーディングが使われます。Microsoft Learn+1

Shift_JISのZIPを解凍する例です。

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

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

string zipPath = @"C:\zip-sample\output\japanese.zip";
string extractDir = @"C:\zip-sample\extract";

ZipFile.ExtractToDirectory(
sourceArchiveFileName: zipPath,
destinationDirectoryName: extractDir,
entryNameEncoding: Encoding.GetEncoding("shift_jis")
);

既存ファイルを上書きしながら、文字コードも指定する場合は次のように書きます。

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

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

ZipFile.ExtractToDirectory(
sourceArchiveFileName: @"C:\zip-sample\output\japanese.zip",
destinationDirectoryName: @"C:\zip-sample\extract",
entryNameEncoding: Encoding.GetEncoding("shift_jis"),
overwriteFiles: true
);

最近のZIPファイルはUTF-8で作成されることも多いですが、古いツールやWindows環境で作成されたZIPではShift_JIS系の文字コードが使われていることがあります。日本語ファイル名が文字化けする場合は、Encoding指定を試してください。

4-5. ZIP解凍時によくある例外と対処法

ZIP解凍時によくある例外は次のとおりです。

例外主な原因対処法
FileNotFoundExceptionZIPファイルが存在しないFile.Existsで確認する
DirectoryNotFoundException解凍先の親フォルダが存在しないDirectory.CreateDirectoryで作成する
IOException同名ファイルが存在する、ファイルが使用中overwriteFiles: trueを使う、使用中ファイルを閉じる
InvalidDataExceptionZIPファイルが壊れている、ZIP形式ではないファイル形式や再ダウンロードを確認する
UnauthorizedAccessException書き込み権限がない権限のあるフォルダに変更する

例外処理を入れた解凍コードは次のとおりです。

C#
try
{
string zipPath = @"C:\zip-sample\output\sample.zip";
string extractDir = @"C:\zip-sample\extract";

if (!File.Exists(zipPath))
{
Console.WriteLine("ZIPファイルが存在しません。");
return;
}

Directory.CreateDirectory(extractDir);

ZipFile.ExtractToDirectory(zipPath, extractDir, overwriteFiles: true);

Console.WriteLine("解凍が完了しました。");
}
catch (InvalidDataException ex)
{
Console.WriteLine($"ZIPファイルが不正です: {ex.Message}");
}
catch (IOException ex)
{
Console.WriteLine($"入出力エラーです: {ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"アクセス権限がありません: {ex.Message}");
}

解凍処理では、ZIPファイルの存在確認、解凍先フォルダの作成、上書き可否、壊れたZIPへの対応を考慮しておくと安定します。

5. ZipArchiveでZIP内のファイルを個別に操作する方法

5-1. ZipArchiveを使うべきケース

ZipArchiveは、ZIPファイル全体ではなく、ZIP内のファイルを個別に操作したい場合に使います。Microsoftのドキュメントでも、ZIPアーカイブ内のファイル取得、全ファイル取得、エントリへのストリームアクセス、エントリ削除などにはZipArchiveZipArchiveEntryを使う構成が示されています。Microsoft Learn+1

具体的には、次のようなケースでZipArchiveが便利です。

やりたいこと使用する機能
ZIP内のファイル一覧を取得したいarchive.Entries
ZIPに1ファイルだけ追加したいCreateEntryFromFile
ZIP内の特定ファイルだけ解凍したいGetEntryExtractToFile
ZIP内のファイルを削除したいZipArchiveEntry.Delete
メモリ上でZIPを作りたいnew ZipArchive(stream, ZipArchiveMode.Create)
ASP.NETでZIPを返したいMemoryStreamZipArchive

ZipArchiveを開くときは、用途に応じてZipArchiveModeを指定します。

モード説明
ZipArchiveMode.Read読み取り専用
ZipArchiveMode.Create新しいZIPを作成
ZipArchiveMode.Update既存ZIPを更新

5-2. ZIPファイルに1つのファイルを追加する

既存のZIPファイルに1つのファイルを追加するには、ZipFile.OpenZipArchiveMode.Updateを指定し、CreateEntryFromFileを使います。CreateEntryFromFileは、ファイルシステム上のファイルを指定したエントリ名でZIPに追加する拡張メソッドです。Microsoft Learn+1

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\sample.zip";
string filePath = @"C:\zip-sample\input\readme.txt";

using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Update))
{
archive.CreateEntryFromFile(
sourceFileName: filePath,
entryName: "readme.txt",
compressionLevel: CompressionLevel.Optimal
);
}

ZIP内のフォルダに追加したい場合は、entryNameにスラッシュ区切りのパスを指定します。

C#
archive.CreateEntryFromFile(
@"C:\zip-sample\input\readme.txt",
"docs/readme.txt",
CompressionLevel.Optimal
);

ZIP内のパスでは、Windowsの\ではなく/を使うと扱いやすくなります。

5-3. ZIPファイルに複数ファイルを追加する

複数ファイルを選んでZIP化する場合も、ZipArchiveが便利です。

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\selected.zip";

string[] files =
{
@"C:\zip-sample\input\readme.txt",
@"C:\zip-sample\input\data.csv",
@"C:\zip-sample\input\images\logo.png"
};

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{
foreach (string file in files)
{
string entryName = Path.GetFileName(file);

archive.CreateEntryFromFile(
sourceFileName: file,
entryName: entryName,
compressionLevel: CompressionLevel.Optimal
);
}
}

元のフォルダ構成を保ったまま追加したい場合は、Path.GetRelativePathを使います。

C#
using System.IO.Compression;

string baseDir = @"C:\zip-sample\input";
string zipPath = @"C:\zip-sample\output\selected.zip";

string[] files = Directory.GetFiles(baseDir, "*.*", SearchOption.AllDirectories);

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{
foreach (string file in files)
{
string entryName = Path.GetRelativePath(baseDir, file)
.Replace(Path.DirectorySeparatorChar, '/');

archive.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
}
}

このコードでは、inputフォルダ以下の相対パスをZIP内のエントリ名として使用します。

5-4. ZIP内のファイル一覧を取得する

ZIP内のファイル一覧を取得するには、ZipArchive.Entriesを使います。Entriesは、現在ZIPアーカイブ内にあるエントリのコレクションを返します。Microsoft Learn+1

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

string zipPath = @"C:\zip-sample\output\sample.zip";

using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
Console.WriteLine($"{entry.FullName} / {entry.Length} bytes");
}
}

ZipArchiveEntryの主なプロパティは次のとおりです。

プロパティ説明
FullNameZIP内の相対パス
Nameファイル名
Length展開後のサイズ
CompressedLength圧縮後のサイズ
LastWriteTime最終更新日時

フォルダエントリを除外してファイルだけ表示したい場合は、Nameが空でないものを対象にします。

C#
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (!string.IsNullOrEmpty(entry.Name))
{
Console.WriteLine(entry.FullName);
}
}
}

5-5. ZIP内の特定ファイルだけを解凍する

ZIP内の特定ファイルだけを解凍したい場合は、GetEntryで対象エントリを取得し、ExtractToFileで保存します。ExtractToFileには、既存ファイルを上書きするためのオーバーロードがあります。Microsoft Learn+1

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\sample.zip";
string extractFilePath = @"C:\zip-sample\extract\readme.txt";

Directory.CreateDirectory(Path.GetDirectoryName(extractFilePath)!);

using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
ZipArchiveEntry? entry = archive.GetEntry("readme.txt");

if (entry != null)
{
entry.ExtractToFile(extractFilePath, overwrite: true);
}
}

ZIP内にdocs/readme.txtのようなパスで格納されている場合は、GetEntryにも同じパスを指定します。

C#
ZipArchiveEntry? entry = archive.GetEntry("docs/readme.txt");

拡張子が.txtのファイルだけを解凍することもできます。

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\sample.zip";
string extractDir = @"C:\zip-sample\extract";

Directory.CreateDirectory(extractDir);

using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
string destinationPath = Path.Combine(extractDir, entry.Name);
entry.ExtractToFile(destinationPath, overwrite: true);
}
}
}

ただし、ZIP内のパスをそのまま使って展開する場合は、後述するZip Slip対策を必ず行ってください。

5-6. ZIP内のファイルを削除・更新する

ZIP内のファイルを削除するには、ZipArchiveMode.UpdateでZIPを開き、対象のZipArchiveEntryに対してDeleteを呼び出します。ZipArchiveEntryは、エントリの情報確認、ストリームアクセス、削除などに使えるクラスです。Microsoft Learn

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\sample.zip";

using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Update))
{
ZipArchiveEntry? entry = archive.GetEntry("readme.txt");

if (entry != null)
{
entry.Delete();
}
}

ZIP内のファイルを更新したい場合は、既存エントリを削除してから追加し直す方法が分かりやすいです。

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\sample.zip";
string newFilePath = @"C:\zip-sample\input\readme.txt";
string entryName = "readme.txt";

using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Update))
{
ZipArchiveEntry? oldEntry = archive.GetEntry(entryName);

if (oldEntry != null)
{
oldEntry.Delete();
}

archive.CreateEntryFromFile(newFilePath, entryName, CompressionLevel.Optimal);
}

ZIP内のファイル名が重複しないように、追加前にGetEntryで確認しておくと安全です。

6. C#でZIP圧縮・解凍する実用サンプルコード

6-1. フォルダをZIP圧縮して保存するサンプル

次は、指定フォルダをZIP圧縮して保存する実用的なサンプルです。既存ZIPがある場合は削除し、保存先フォルダがない場合は自動作成します。

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

public static class ZipSample
{
public static void CompressFolder()
{
string sourceDir = @"C:\zip-sample\input";
string zipPath = @"C:\zip-sample\output\input.zip";

if (!Directory.Exists(sourceDir))
{
Console.WriteLine("圧縮元フォルダが存在しません。");
return;
}

string? outputDir = Path.GetDirectoryName(zipPath);
if (!string.IsNullOrEmpty(outputDir))
{
Directory.CreateDirectory(outputDir);
}

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

ZipFile.CreateFromDirectory(
sourceDirectoryName: sourceDir,
destinationArchiveFileName: zipPath,
compressionLevel: CompressionLevel.Optimal,
includeBaseDirectory: true
);

Console.WriteLine($"ZIPファイルを作成しました: {zipPath}");
}
}

includeBaseDirectory: trueにしているため、ZIP内にはinputフォルダ自体が含まれます。解凍時にフォルダ構成を保ちたい場合に便利です。

6-2. ZIPファイルを指定フォルダに解凍するサンプル

次は、ZIPファイルを指定フォルダに解凍するサンプルです。既存ファイルは上書きします。

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

public static class ZipSample
{
public static void ExtractZip()
{
string zipPath = @"C:\zip-sample\output\input.zip";
string extractDir = @"C:\zip-sample\extract";

if (!File.Exists(zipPath))
{
Console.WriteLine("ZIPファイルが存在しません。");
return;
}

Directory.CreateDirectory(extractDir);

ZipFile.ExtractToDirectory(
sourceArchiveFileName: zipPath,
destinationDirectoryName: extractDir,
overwriteFiles: true
);

Console.WriteLine($"ZIPファイルを解凍しました: {extractDir}");
}
}

overwriteFiles: trueを指定しているため、同名ファイルがある場合は上書きされます。既存ファイルを残したい場合はfalseにするか、別の解凍先フォルダを指定してください。

6-3. 複数ファイルだけを選んでZIP化するサンプル

フォルダ全体ではなく、選択した複数ファイルだけをZIP化する場合はZipArchiveを使います。

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

public static class ZipSample
{
public static void CompressSelectedFiles()
{
string zipPath = @"C:\zip-sample\output\selected.zip";

string[] files =
{
@"C:\zip-sample\input\readme.txt",
@"C:\zip-sample\input\data.csv"
};

Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!);

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{
foreach (string file in files)
{
if (!File.Exists(file))
{
Console.WriteLine($"ファイルが見つかりません: {file}");
continue;
}

string entryName = Path.GetFileName(file);

archive.CreateEntryFromFile(
sourceFileName: file,
entryName: entryName,
compressionLevel: CompressionLevel.Optimal
);
}
}

Console.WriteLine($"選択ファイルをZIP化しました: {zipPath}");
}
}

この方法なら、特定の拡張子だけ、更新日時が新しいファイルだけ、ユーザーが選択したファイルだけ、というような柔軟なZIP作成ができます。

6-4. ZIP内のファイル一覧を画面に表示するサンプル

ZIP内のファイル一覧を取得して画面に表示するサンプルです。

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

public static class ZipSample
{
public static void ShowZipEntries()
{
string zipPath = @"C:\zip-sample\output\selected.zip";

if (!File.Exists(zipPath))
{
Console.WriteLine("ZIPファイルが存在しません。");
return;
}

using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (string.IsNullOrEmpty(entry.Name))
{
continue;
}

Console.WriteLine($"ファイル名: {entry.FullName}");
Console.WriteLine($"サイズ: {entry.Length} bytes");
Console.WriteLine($"圧縮後: {entry.CompressedLength} bytes");
Console.WriteLine($"更新日時: {entry.LastWriteTime}");
Console.WriteLine();
}
}
}
}

FullNameを使うと、ZIP内のフォルダ階層を含むパスを取得できます。Nameはファイル名のみです。

6-5. MemoryStreamを使ってZIPをメモリ上で作成するサンプル

ASP.NETでZIPファイルをダウンロードさせる場合や、一時ファイルを作らずにZIPデータを生成したい場合は、MemoryStreamZipArchiveを使います。

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

public static class ZipSample
{
public static byte[] CreateZipInMemory()
{
using MemoryStream memoryStream = new MemoryStream();

using (ZipArchive archive = new ZipArchive(
memoryStream,
ZipArchiveMode.Create,
leaveOpen: true))
{
ZipArchiveEntry entry = archive.CreateEntry("message.txt");

using Stream entryStream = entry.Open();
using StreamWriter writer = new StreamWriter(entryStream, Encoding.UTF8);

writer.WriteLine("Hello ZIP");
writer.WriteLine("C#でメモリ上にZIPファイルを作成しています。");
}

return memoryStream.ToArray();
}
}

ASP.NET Coreのコントローラーで返す場合は、次のように使えます。

C#
public IActionResult DownloadZip()
{
byte[] zipBytes = ZipSample.CreateZipInMemory();

return File(
zipBytes,
"application/zip",
"sample.zip"
);
}

MemoryStreamを使うと一時ファイルを作らずに済みますが、大容量データではメモリ使用量が増えるため注意が必要です。大きなZIPを作る場合は、ファイルストリームやレスポンスストリームへ直接書き込む設計も検討してください。

7. C#でZIPを扱うときの注意点

7-1. パスワード付きZIPは標準機能だけでは作成・解凍できない

C#の標準ライブラリであるSystem.IO.Compressionは、基本的なZIPの圧縮・解凍には便利ですが、パスワード付きZIPの作成・解凍には対応していません。パスワード付きZIPを扱いたい場合は、SharpZipLib、DotNetZip、7-Zip連携などの外部ライブラリや外部ツールの利用を検討します。

たとえば、標準機能で次のような処理はできません。

C#
// 標準のSystem.IO.Compressionには、
// ZIPパスワードを指定する引数はありません。
ZipFile.CreateFromDirectory(sourceDir, zipPath);

セキュリティ要件がある場合は、単にZIPにパスワードを付けるだけでなく、暗号化方式、パスワード管理、鍵管理、通信経路の保護も含めて検討する必要があります。

7-2. 日本語ファイル名の文字化け対策

日本語ファイル名を含むZIPでは、作成したツールやOSによって文字コードの扱いが異なり、解凍時に文字化けすることがあります。ZipFileZipArchiveには、エントリ名のエンコーディングを指定できるオーバーロードがあります。Microsoft Learn+1

ZIP作成時にUTF-8を指定する例です。

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

string sourceDir = @"C:\zip-sample\input";
string zipPath = @"C:\zip-sample\output\utf8.zip";

ZipFile.CreateFromDirectory(
sourceDirectoryName: sourceDir,
destinationArchiveFileName: zipPath,
compressionLevel: CompressionLevel.Optimal,
includeBaseDirectory: false,
entryNameEncoding: Encoding.UTF8
);

解凍時にShift_JISを指定する例です。

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

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

ZipFile.ExtractToDirectory(
sourceArchiveFileName: @"C:\zip-sample\output\sjis.zip",
destinationDirectoryName: @"C:\zip-sample\extract",
entryNameEncoding: Encoding.GetEncoding("shift_jis"),
overwriteFiles: true
);

文字化けが発生した場合は、まずZIPを作成した環境を確認し、UTF-8、Shift_JIS、CP932などを試します。

7-3. Zip Slip脆弱性を防ぐ安全な解凍方法

ZIP解凍では、Zip Slipと呼ばれるパストラバーサル攻撃に注意が必要です。悪意のあるZIPファイルに../../のようなパスが含まれていると、解凍先フォルダの外にファイルを書き込もうとする可能性があります。Microsoftのベストプラクティスでも、ExtractToDirectoryはパストラバーサルを防ぐために各エントリのフルパスを解決し、展開先ディレクトリの外に出るものを拒否する一方で、サイズ制限やエントリ数制限までは強制しないと説明されています。Microsoft Learn

ZipArchiveで個別に解凍する場合は、解凍先パスを正規化し、指定フォルダの外へ出ないことを確認してから保存します。Microsoftのサンプルでも、Path.GetFullPathでパスを正規化し、展開先パスが解凍先ディレクトリ配下にあることを確認する例が示されています。Microsoft Learn+1

安全な個別解凍の例です。

C#
using System.IO.Compression;

public static void ExtractTextFilesSafely(string zipPath, string extractDir)
{
string fullExtractDir = Path.GetFullPath(extractDir);

if (!fullExtractDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullExtractDir += Path.DirectorySeparatorChar;
}

Directory.CreateDirectory(fullExtractDir);

using ZipArchive archive = ZipFile.OpenRead(zipPath);

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

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

if (!destinationPath.StartsWith(fullExtractDir, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"不正なパスを検出しました: {entry.FullName}"
);
}

Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);

entry.ExtractToFile(destinationPath, overwrite: true);
}
}

外部から受け取ったZIPファイルを解凍する場合は、Zip Slip対策に加えて、ファイル数、展開後サイズ、拡張子、保存先の権限もチェックすることが重要です。

7-4. 大容量ファイルをZIP化するときのメモリ・パフォーマンス対策

大容量ファイルや大量ファイルをZIP化する場合は、メモリ使用量と処理時間に注意します。特にMemoryStreamでZIP全体をメモリ上に作ると、ZIPサイズに応じてメモリ消費が大きくなります。

大容量ZIPでは、次のような対策を検討します。

対策内容
ファイルストリームを使うZIPをディスクへ直接書き込む
CompressionLevel.Fastestを使う圧縮率より速度を優先する
対象ファイルを絞る不要なログや一時ファイルを除外する
分割処理する大量ファイルを複数ZIPに分ける
サイズ制限を設ける展開後サイズやエントリ数を検証する

ファイルストリームを使ってZIPを作る例です。

C#
using System.IO.Compression;

string zipPath = @"C:\zip-sample\output\large.zip";
string sourceDir = @"C:\zip-sample\input";

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

ZipFile.CreateFromDirectory(
sourceDirectoryName: sourceDir,
destinationArchiveFileName: zipPath,
compressionLevel: CompressionLevel.Fastest,
includeBaseDirectory: true
);

大容量データを扱う場合は、圧縮率だけでなく、CPU負荷、ディスクI/O、メモリ使用量、処理時間を含めて設計しましょう。

7-5. 一時ファイルや不要なZIPファイルの削除処理

ZIP処理では、一時ファイルや古いZIPファイルが残り続けることがあります。特にバッチ処理やWebアプリでZIPを生成する場合は、不要になったファイルを削除する仕組みを用意しておきます。

一時ZIPを作成して最後に削除する例です。

C#
string tempZipPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.zip");

try
{
ZipFile.CreateFromDirectory(@"C:\zip-sample\input", tempZipPath);

// ここでアップロードや送信などの処理を行う
}
finally
{
if (File.Exists(tempZipPath))
{
File.Delete(tempZipPath);
}
}

古いZIPファイルを削除する例です。

C#
string outputDir = @"C:\zip-sample\output";
TimeSpan retention = TimeSpan.FromDays(7);

foreach (string file in Directory.GetFiles(outputDir, "*.zip"))
{
DateTime lastWriteTime = File.GetLastWriteTime(file);

if (DateTime.Now - lastWriteTime > retention)
{
File.Delete(file);
}
}

ZIPファイルはサイズが大きくなりやすいため、保存期間や削除タイミングを決めておくことが大切です。

8. ZipFile・ZipArchiveの使い分け

8-1. フォルダを丸ごと圧縮・解凍したい場合

フォルダを丸ごとZIP化したい場合は、ZipFile.CreateFromDirectoryを使います。指定ディレクトリのファイルとディレクトリを含むZIPアーカイブを作成できるため、バックアップ、ログ収集、配布ファイル作成などに向いています。Microsoft Learn+1

C#
ZipFile.CreateFromDirectory(
@"C:\zip-sample\input",
@"C:\zip-sample\output\backup.zip",
CompressionLevel.Optimal,
includeBaseDirectory: true
);

ZIPを丸ごと解凍したい場合は、ZipFile.ExtractToDirectoryを使います。

C#
ZipFile.ExtractToDirectory(
@"C:\zip-sample\output\backup.zip",
@"C:\zip-sample\extract",
overwriteFiles: true
);

「フォルダを圧縮する」「ZIPを解凍する」という単純な用途では、ZipFileが最も簡単です。

8-2. ZIP内のファイルを追加・削除・一覧取得したい場合

ZIP内のファイルを個別に操作したい場合は、ZipArchiveを使います。

一覧取得です。

C#
using ZipArchive archive = ZipFile.OpenRead(@"C:\zip-sample\output\sample.zip");

foreach (ZipArchiveEntry entry in archive.Entries)
{
Console.WriteLine(entry.FullName);
}

ファイル追加です。

C#
using ZipArchive archive = ZipFile.Open(
@"C:\zip-sample\output\sample.zip",
ZipArchiveMode.Update
);

archive.CreateEntryFromFile(
@"C:\zip-sample\input\readme.txt",
"readme.txt",
CompressionLevel.Optimal
);

ファイル削除です。

C#
using ZipArchive archive = ZipFile.Open(
@"C:\zip-sample\output\sample.zip",
ZipArchiveMode.Update
);

ZipArchiveEntry? entry = archive.GetEntry("readme.txt");
entry?.Delete();

ZipArchiveは、既存ZIPを編集したい場合や、ZIP内の一部だけを扱いたい場合に適しています。

8-3. StreamでZIPを扱いたい場合

ファイルパスではなくStreamでZIPを扱いたい場合も、ZipArchiveを使います。ZipArchiveはストリームを受け取ってZIPを作成・読み取りできるため、メモリ上のZIP作成、HTTPレスポンスへの書き込み、クラウドストレージとの連携などに向いています。

C#
using MemoryStream stream = new MemoryStream();

using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true))
{
ZipArchiveEntry entry = archive.CreateEntry("hello.txt");

using StreamWriter writer = new StreamWriter(entry.Open());
writer.Write("Hello");
}

byte[] zipBytes = stream.ToArray();

.NET 8では、ZipFile.CreateFromDirectoryZipFile.ExtractToDirectoryにストリーム対応のオーバーロードが追加され、ディレクトリ内容をZIP化してストリームへ格納したり、ZIPを含むストリームから展開したりできるようになっています。Microsoft Learn

8-4. ASP.NETでZIPをダウンロードさせたい場合

ASP.NET CoreでZIPをダウンロードさせたい場合は、MemoryStreamまたは一時ファイルにZIPを作成し、Fileで返します。

小さなZIPなら、メモリ上で作成する方法が簡単です。

C#
public IActionResult Download()
{
using MemoryStream memoryStream = new MemoryStream();

using (ZipArchive archive = new ZipArchive(
memoryStream,
ZipArchiveMode.Create,
leaveOpen: true))
{
ZipArchiveEntry entry = archive.CreateEntry("readme.txt");

using StreamWriter writer = new StreamWriter(entry.Open());
writer.Write("ダウンロード用ZIPです。");
}

return File(
memoryStream.ToArray(),
"application/zip",
"download.zip"
);
}

大きなZIPを生成する場合は、MemoryStreamではなく一時ファイルやレスポンスストリームへの直接書き込みを検討します。メモリ上にZIP全体を保持すると、同時アクセス時にメモリ使用量が急増する可能性があるためです。

9. C#のZIP処理でよくある質問

9-1. C#でZIPファイルを上書き作成できますか?

ZipFile.CreateFromDirectoryには、既存ZIPを直接上書きするための引数はありません。既存ファイルを上書きしたい場合は、作成前にFile.Deleteで削除します。

C#
string zipPath = @"C:\zip-sample\output\sample.zip";

if (File.Exists(zipPath))
{
File.Delete(zipPath);
}

ZipFile.CreateFromDirectory(
@"C:\zip-sample\input",
zipPath,
CompressionLevel.Optimal,
includeBaseDirectory: false
);

既存ファイルを消したくない場合は、日時付きファイル名にする方法がおすすめです。

C#
string zipPath = $@"C:\zip-sample\output\backup_{DateTime.Now:yyyyMMdd_HHmmss}.zip";

9-2. C#でZIPファイルにパスワードを設定できますか?

System.IO.Compressionの標準機能だけでは、パスワード付きZIPの作成や解凍はできません。パスワード付きZIPが必要な場合は、SharpZipLib、DotNetZip、7-Zipなどの外部ライブラリや外部ツールの利用を検討します。

ただし、パスワード付きZIPを使う場合でも、暗号化方式が弱いと十分なセキュリティにならないことがあります。機密情報を扱う場合は、ZIPパスワードだけに頼らず、通信経路、保存場所、アクセス権限、鍵管理まで含めて設計してください。

9-3. C#でZIP内の一部ファイルだけを解凍できますか?

できます。ZipArchiveでZIPを開き、GetEntryまたはEntriesで対象ファイルを探して、ExtractToFileで解凍します。ExtractToFileはZIP内のエントリをファイルへ抽出する拡張メソッドで、既存ファイルを上書きするオーバーロードも用意されています。Microsoft Learn+1

C#
using ZipArchive archive = ZipFile.OpenRead(@"C:\zip-sample\output\sample.zip");

ZipArchiveEntry? entry = archive.GetEntry("readme.txt");

if (entry != null)
{
entry.ExtractToFile(@"C:\zip-sample\extract\readme.txt", overwrite: true);
}

拡張子で絞り込むこともできます。

C#
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
{
string path = Path.Combine(@"C:\zip-sample\extract", entry.Name);
entry.ExtractToFile(path, overwrite: true);
}
}

9-4. C#で日本語ファイル名のZIPを正しく解凍できますか?

できます。日本語ファイル名が文字化けする場合は、ExtractToDirectoryZipArchiveのエンコーディング指定を使います。エントリ名のエンコーディングを指定するオーバーロードが用意されており、UTF-8フラグの有無に応じてエントリ名が処理されます。Microsoft Learn+1

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

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

ZipFile.ExtractToDirectory(
@"C:\zip-sample\output\japanese.zip",
@"C:\zip-sample\extract",
Encoding.GetEncoding("shift_jis"),
overwriteFiles: true
);

ZIPを作成する側を制御できる場合は、UTF-8で作成しておくと文字化けを避けやすくなります。

C#
ZipFile.CreateFromDirectory(
@"C:\zip-sample\input",
@"C:\zip-sample\output\utf8.zip",
CompressionLevel.Optimal,
includeBaseDirectory: false,
entryNameEncoding: Encoding.UTF8
);

9-5. ZipFileとZipArchiveはどちらを使えばよいですか?

フォルダを丸ごと圧縮・解凍するだけならZipFileを使うのがおすすめです。コードが短く、CreateFromDirectoryExtractToDirectoryだけで基本処理を実装できます。

ZIP内のファイル一覧を取得したい、特定ファイルだけを解凍したい、ファイルを追加・削除・更新したい、ストリームでZIPを作成したい場合はZipArchiveを使います。Microsoftのドキュメントでも、ディレクトリからZIPを作成する場合はZipFile.CreateFromDirectory、ZIP内容を展開する場合はZipFile.ExtractToDirectory、既存ZIPへの追加や一覧取得などはZipArchive関連の機能を使う形で整理されています。Microsoft Learn+1

使い分けをまとめると次のとおりです。

やりたいことおすすめ
フォルダを丸ごとZIP化したいZipFile
ZIPを丸ごと解凍したいZipFile
ZIP内の一覧を取得したいZipArchive
ZIPに一部ファイルを追加したいZipArchive
ZIP内の一部ファイルだけ解凍したいZipArchive
ZIP内のファイルを削除・更新したいZipArchive
メモリ上でZIPを作りたいZipArchive
ASP.NETでZIPをダウンロードさせたいZipArchiveまたはMemoryStream

まとめ

C#でZIPファイルを圧縮・解凍するには、標準ライブラリのSystem.IO.Compressionを使います。フォルダ全体をZIP化する場合はZipFile.CreateFromDirectory、ZIPファイルを解凍する場合はZipFile.ExtractToDirectoryを使うと、少ないコードで実装できます。

ZIP内のファイルを個別に操作したい場合は、ZipArchiveを使います。ZipArchive.Entriesで一覧を取得し、CreateEntryFromFileで追加し、ExtractToFileで特定ファイルを解凍し、Deleteで削除できます。

実装時は、既存ZIPの上書き、日本語ファイル名の文字化け、Zip Slip脆弱性、大容量ファイルのメモリ使用量、一時ファイルの削除に注意してください。特に外部から受け取ったZIPを解凍する場合は、パスの正規化、展開先チェック、ファイル数やサイズの制限を行うことが重要です。

C#のZIP処理では、単純な圧縮・解凍ならZipFile、細かいファイル操作やストリーム処理ならZipArchiveを選ぶのが基本です。用途に合わせて使い分ければ、バックアップ、ファイル配布、ログ収集、Webダウンロードなど、さまざまなZIP処理を効率よく実装できます。