はじめに 「ZIP を扱える」と業務ツールの格が一段上がる
業務でよくあるシーンです。
複数ファイルを ZIP にまとめてメールで送りたい。
バックアップを ZIP で固めて日付ごとに保存したい。
外部システムから渡された ZIP を展開して中身を処理したい。
こういうときに、C# から ZIP をサクッと扱えると、一気に「業務ユーティリティ感」が増します。
しかも、.NET には標準で ZIP 用のクラスが用意されているので、実はそんなに難しくありません。
ここでは、プログラミング初心者向けに、
ZIP の基本(どのクラスを使うか)
フォルダを丸ごと ZIP にする
特定ファイルだけを選んで ZIP にする
ZIP を展開する(解凍)
実務ユーティリティとしての設計ポイント
を、例題付きでかみ砕いて説明していきます。
C# で ZIP を扱うための基本クラス
System.IO.Compression と ZipFile / ZipArchive
C#(.NET)で ZIP を扱うときに使う名前空間は、System.IO.Compression です。
ここに、ZIP 圧縮・解凍のためのクラスが用意されています。
一番よく使うのは ZipFile クラスです。
これは「フォルダを ZIP にする」「ZIP をフォルダに展開する」といった、
“ざっくり操作”を簡単にやるためのユーティリティ的なクラスです。
もう少し細かく制御したいときは、ZipArchive クラスを使います。
こちらは「ZIP の中のエントリ(ファイル)を 1 つずつ追加・削除・読み書きする」ためのクラスです。
まずは ZipFile から入り、必要になったら ZipArchive に進む、という順番が分かりやすいです。
フォルダを丸ごと ZIP に圧縮する
最も簡単な「フォルダ → ZIP」圧縮
「このフォルダをそのまま ZIP にしたい」というケースはとても多いです。
その場合、ZipFile.CreateFromDirectory を使うと一発でできます。
事前に、参照に System.IO.Compression.FileSystem を追加しておく必要があります(.NET Framework の場合)。
.NET 6 以降などでは、通常の using だけで使えることが多いです。
using System;
using System.IO.Compression;
class Program
{
static void Main()
{
string sourceDirectory = @"C:\work\backup_source";
string zipPath = @"C:\work\backup.zip";
ZipFile.CreateFromDirectory(
sourceDirectory,
zipPath,
CompressionLevel.Optimal,
includeBaseDirectory: false);
Console.WriteLine("圧縮完了");
}
}
C#ここでの引数の意味をしっかり押さえておきましょう。
sourceDirectory
圧縮元のフォルダです。このフォルダの中身が ZIP に入ります。
zipPath
出力する ZIP ファイルのパスです。既に存在している場合は上書きされます。
CompressionLevel
圧縮レベルです。Optimal(そこそこ圧縮)、Fastest(速さ優先)、NoCompression(圧縮しない)などがあります。
業務では、よほどの理由がなければ Optimal で問題ありません。
includeBaseDirectory
true にすると、「フォルダ自体」も ZIP の中に含めます。
false だと、「フォルダの中身だけ」が ZIP のルートに入ります。
どちらが正しいかは、相手が期待する構造によります。
例えば、C:\work\backup_source に a.txt と b.txt があるとします。
includeBaseDirectory: false の場合、ZIP の中身はこうなります。
a.txt
b.txt
includeBaseDirectory: true の場合はこうです。
backup_source\a.txt
backup_source\b.txt
外部システムに渡す ZIP の場合、「ルートに何があるべきか」を仕様で確認しておくのが大事です。
特定のファイルだけを選んで ZIP にする(ZipArchive)
「フォルダ全部」ではなく「選んだファイルだけ」圧縮したい
ZipFile.CreateFromDirectory は便利ですが、「この拡張子だけ」「このリストにあるファイルだけ」といった柔軟な指定はできません。
そういうときは、ZipArchive を使って、自分でエントリを追加していきます。
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
public static class ZipUtil
{
public static void CreateZipFromFiles(
string zipPath,
IEnumerable<string> filePaths,
string? baseDirectory = null)
{
string? dir = Path.GetDirectoryName(zipPath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
using var zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write, FileShare.None);
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
foreach (var filePath in filePaths)
{
if (!File.Exists(filePath))
{
continue;
}
string entryName = GetEntryName(filePath, baseDirectory);
var entry = archive.CreateEntry(entryName, CompressionLevel.Optimal);
using var entryStream = entry.Open();
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
fileStream.CopyTo(entryStream);
}
}
private static string GetEntryName(string filePath, string? baseDirectory)
{
if (string.IsNullOrEmpty(baseDirectory))
{
return Path.GetFileName(filePath);
}
string fullBase = Path.GetFullPath(baseDirectory);
string fullPath = Path.GetFullPath(filePath);
if (fullPath.StartsWith(fullBase, StringComparison.OrdinalIgnoreCase))
{
string relative = fullPath.Substring(fullBase.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return relative.Replace(Path.DirectorySeparatorChar, '/');
}
return Path.GetFileName(filePath);
}
}
C#使い方の例です。
var files = Directory.GetFiles(
@"C:\work\data",
"*.csv",
SearchOption.TopDirectoryOnly);
ZipUtil.CreateZipFromFiles(
@"C:\work\csv_only.zip",
files,
baseDirectory: @"C:\work");
C#ここでの重要ポイントを整理します。
ZIP の中の「パス(エントリ名)」を自分で決めているCreateEntry(entryName, ...) の entryName が、ZIP 内でのファイルパスになります。baseDirectory を指定しておくと、「そのフォルダからの相対パス」で ZIP に入れられます。
FileStream.CopyTo でストリーム同士をコピーしているfileStream(元ファイル)から entryStream(ZIP 内のエントリ)へ、CopyTo で一気にコピーしています。
これが「ファイルを ZIP に書き込む」基本パターンです。
存在しないファイルはスキップしている
業務ユーティリティでは、「リストにあるけど実際には無いファイル」が混ざることもあります。
その場合にどうするか(例外にするか、スキップするか)は設計ポイントです。
ここではシンプルにスキップしています。
ZIP を展開する(解凍)
フォルダに丸ごと展開する
ZIP を解凍するのも、ZipFile を使うと簡単です。
using System;
using System.IO;
using System.IO.Compression;
class Program
{
static void Main()
{
string zipPath = @"C:\work\backup.zip";
string extractDirectory = @"C:\work\restore";
if (!Directory.Exists(extractDirectory))
{
Directory.CreateDirectory(extractDirectory);
}
ZipFile.ExtractToDirectory(
zipPath,
extractDirectory,
overwriteFiles: true);
Console.WriteLine("展開完了");
}
}
C#ExtractToDirectory の overwriteFiles を true にすると、
展開先に同名ファイルがあっても上書きします。
false にすると、既に存在するファイルがある場合に例外が出ます。
業務で使うときは、「既存ファイルを上書きしてよいか」を仕様で決めておく必要があります。
ZIP の中身を見ながら、必要なファイルだけ展開する
「ZIP の中にたくさんファイルがあるけれど、そのうち一部だけ使いたい」というケースもあります。
その場合は、ZipArchive を使って中身を列挙し、条件に合うものだけ取り出します。
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#使い方です。
ZipExtractUtil.ExtractCsvOnly(
@"C:\work\mixed.zip",
@"C:\work\csv_out");
C#ここでのポイントは、「ZIP 内のパス(entry.FullName)をそのまま展開先のパスに反映している」ことです。
サブフォルダ構造もそのまま再現されます。
実務ユーティリティとしての設計ポイント
圧縮・解凍の「単位」を決める
業務で ZIP を扱うときは、「何を 1 単位として圧縮するか」を最初に決めておくと設計が楽になります。
例えば、次のような単位があります。
1 日分のログを 1 ZIP にする(logs_2025-01-28.zip)
1 件の取引データを構成する複数ファイルを 1 ZIP にする
バックアップ対象フォルダ全体を 1 ZIP にする
この「単位」が決まると、ファイル名のルールやフォルダ構成も自然に決まってきます。
エンコーディングとファイル名
ZIP 自体はバイナリ形式ですが、中に入っているファイルはテキストかもしれません。
圧縮・解凍のときに「中身のエンコーディング」は変わりません。
つまり、「中の CSV を Shift_JIS で書いたら、解凍しても Shift_JIS のまま」です。
一方で、「ZIP 内のファイル名の文字コード」が絡む問題もありますが、
.NET の ZipArchive / ZipFile を使っている限り、通常はあまり意識しなくて大丈夫です。
ただし、他言語・他環境との連携では、「日本語ファイル名が文字化けする」問題が起きることもあるので、
可能なら英数字だけのファイル名にしておくと安全です。
例外処理とログ
圧縮・解凍は、ファイル I/O の塊です。
次のような理由で例外が出ることがあります。
ZIP ファイルが壊れている
展開先に書き込み権限がない
ディスク容量が足りない
業務ユーティリティとしては、「どの ZIP で」「どのファイルで」「何が起きたか」をログに残しておくと、
運用時のトラブルシュートがかなり楽になります。
まとめ 「ZIP ユーティリティ」は“ファイルをまとめて扱う”ための基本ツール
ZIP 圧縮・解凍は、業務システムと外部世界をつなぐときの定番フォーマットです。
C# では、標準ライブラリだけでかなり実用的なことができます。
押さえておきたいポイントを整理すると、こうなります。
フォルダ丸ごとなら ZipFile.CreateFromDirectory が一番簡単。
個別ファイルを選んで圧縮したいときは ZipArchive を使い、CreateEntry と CopyTo の組み合わせで書き込む。
解凍は ZipFile.ExtractToDirectory で一括、ZipArchive で中身を見ながら部分展開もできる。
ZIP 内のパス(エントリ名)をどうするか(ルート構造、相対パス)は、相手の期待する構造から逆算して決める。
例外や失敗ケースを想定し、「どの ZIP をどう処理しようとして失敗したか」をログに残す。
ここまで理解できれば、「ファイルを ZIP で固めてやり取りする」という業務の定番パターンを、
自分の C# ユーティリティでしっかり支えられるようになります。
