アーカイブ

【C#】DotNetZipで簡単ファイル圧縮・解凍:基本操作と便利テクニック

DotNetZipはC#からZIP操作を手軽に行える軽量ライブラリです。

ZipFileクラスを使いファイル追加、圧縮率指定、ストリーム操作、AES暗号化までワンライナーで済み、依存関係も少なく古い.NET Frameworkにも対応します。

標準のSystem.IO.Compressionより細かな制御が欲しい場面やマルチプラットフォームを意識せずサクッと導入したい時に適しています。

DotNetZipの魅力

C#でZIPファイルを扱う際に、DotNetZipは非常に便利なライブラリです。

ここでは、DotNetZipの魅力について「軽量性と機能性」「標準APIとの差別化」「ライセンスとサポート状況」の3つの観点から詳しく解説いたします。

軽量性と機能性

DotNetZipは、名前の通りZIPファイルの圧縮・解凍に特化した軽量なライブラリです。

非常にコンパクトなDLLファイルでありながら、豊富な機能を備えているのが特徴です。

  • 軽量なDLL

DotNetZipのDLLは数百キロバイト程度で、プロジェクトに組み込んでもビルドサイズを大きく増やしません。

これにより、特に小規模なアプリケーションやツールでの利用に適しています。

  • 多彩な圧縮機能

単純なファイル圧縮だけでなく、フォルダごとの圧縮、圧縮レベルの調整、パスワード保護、AES暗号化など多彩な機能をサポートしています。

これにより、用途に応じて柔軟に圧縮処理をカスタマイズできます。

  • ストリーム対応

ファイルだけでなく、メモリストリームやネットワークストリームに対しても圧縮・解凍が可能です。

これにより、ファイルシステムに依存しないデータ処理が実現できます。

  • イベントによる進捗管理

圧縮や解凍の進捗をイベントで取得できるため、UIに進捗バーを表示したり、処理のキャンセルを実装したりすることも簡単です。

このように、DotNetZipは軽量ながらも実用的な機能を幅広く備えているため、C#でZIP操作を行う際の強力な選択肢となっています。

標準APIとの差別化

.NET Frameworkや.NET Coreには標準でZIPファイルを扱うためのAPI(System.IO.Compression名前空間)が用意されていますが、DotNetZipにはいくつかの差別化ポイントがあります。

比較項目DotNetZipSystem.IO.Compression
対応フレームワーク.NET Framework全般、.NET Coreも一部対応.NET Framework 4.5以降、.NET Core標準
圧縮レベルの細かい設定可能(BestSpeed、BestCompressionなど)可能だが設定項目は限定的
パスワード保護AES-256暗号化対応標準では非対応(別途拡張が必要)
ストリーム操作柔軟に対応対応しているが一部制限あり
進捗イベントありなし
使いやすさシンプルで直感的なAPI標準APIとして安定しているがやや冗長な部分も

特にパスワード保護やAES暗号化を簡単に実装できる点は、DotNetZipの大きな強みです。

標準APIではパスワード付きZIPの作成がサポートされていないため、セキュリティを重視する場合はDotNetZipが便利です。

また、進捗イベントが用意されているため、UIアプリケーションでのユーザー体験向上にも役立ちます。

標準APIはシンプルで安定していますが、細かい制御や拡張性を求める場合はDotNetZipのほうが柔軟に対応できます。

ライセンスとサポート状況

DotNetZipはオープンソースのライブラリで、主にMicrosoft Public License (Ms-PL)のもとで配布されています。

このライセンスは商用利用も含めて比較的自由に利用できるため、企業のプロジェクトでも安心して導入可能です。

  • ライセンスの特徴

Ms-PLはソースコードの改変や再配布を許可していますが、改変した場合はその旨を明示する必要があります。

商用利用に制限はなく、無料で利用できます。

  • サポート状況

DotNetZipはCodePlex上で開発されていましたが、CodePlexのサービス終了に伴い公式の更新は停止しています。

ただし、GitHubなどでフォークされたバージョンやコミュニティによるメンテナンス版が存在します。

  • 互換性とメンテナンス

.NET Framework向けには安定した動作が期待できますが、最新の.NET Coreや.NET 5/6/7などの環境では動作に制限がある場合があります。

これらの環境で使う場合は、コミュニティ版や代替ライブラリの検討も必要です。

  • ドキュメントとコミュニティ

公式ドキュメントは基本的な使い方をカバーしており、サンプルコードも豊富です。

Stack OverflowなどのQ&Aサイトでも多くの質問があり、問題解決の参考になります。

まとめると、DotNetZipはライセンス面で安心して使える一方、公式の開発は停止しているため、最新環境での利用には注意が必要です。

既存の.NET Frameworkプロジェクトや、パスワード付きZIPを簡単に扱いたい場合には非常に有用なライブラリです。

ライブラリ導入手順

C#でDotNetZipを使うためには、まずライブラリをプロジェクトに導入する必要があります。

ここでは、代表的な導入方法である「NuGet経由」「DLL直接配置」、そして「プロジェクト設定のポイント」について詳しく説明いたします。

NuGet経由

NuGetはVisual Studioや.NET CLIで利用できるパッケージ管理ツールで、DotNetZipを簡単に導入できます。

NuGet経由での導入は依存関係の管理やバージョンアップが容易なため、最も推奨される方法です。

Visual Studioでの導入手順

  1. Visual Studioのソリューションエクスプローラーでプロジェクトを右クリックし、「NuGetパッケージの管理」を選択します。
  2. 「参照」タブで「DotNetZip」または「DotNetZip.ZipFile」と検索します。
  3. 一覧からDotNetZipパッケージ(正式名称はDotNetZipDotNetZip.Semverdなど複数ありますが、一般的にはDotNetZip)を選択し、「インストール」ボタンをクリックします。
  4. インストールが完了すると、Ionic.Zip名前空間が使えるようになります。

.NET CLIでの導入手順

コマンドラインから導入する場合は、以下のコマンドを実行します。

dotnet add package DotNetZip

これでプロジェクトの.csprojファイルにパッケージ参照が追加され、自動的にダウンロードと参照設定が行われます。

注意点

  • NuGetで提供されているDotNetZipは、公式のCodePlex版をベースにしたコミュニティメンテナンス版が多いです。最新の.NET Coreや.NET 5/6/7での動作を確認してから導入してください
  • バージョンによっては.NET Framework専用のものもあるため、ターゲットフレームワークに合ったパッケージを選ぶことが重要です

DLL直接配置

NuGetを使わずに、手動でDLLをプロジェクトに追加する方法もあります。

公式サイトやGitHubからIonic.Zip.dllをダウンロードし、プロジェクトに組み込む形です。

手順

  1. DotNetZipの公式サイトや信頼できる配布元からIonic.Zip.dllをダウンロードします。
  2. Visual Studioのソリューションエクスプローラーで「参照」フォルダーを右クリックし、「参照の追加」を選択します。
  3. 「参照マネージャー」から「参照」タブを選び、ダウンロードしたIonic.Zip.dllのパスを指定して追加します。
  4. 追加後、using Ionic.Zip;をソースコードに記述して利用可能になります。

メリット・デメリット

項目メリットデメリット
導入の簡単さDLLを直接配置するだけで済むバージョン管理が手動になる
依存関係管理依存関係が少ない依存DLLがある場合は別途管理が必要
更新の手間更新時にDLLを差し替えるだけ更新漏れやバージョン不整合のリスク

DLL直接配置は、NuGetが使えない環境や特定のバージョンを固定したい場合に有効です。

ただし、依存関係や更新管理は手動で行う必要があります。

プロジェクト設定のポイント

DotNetZipを正しく動作させるために、プロジェクトの設定にも注意が必要です。

特に以下のポイントを押さえておくとトラブルを防げます。

ターゲットフレームワークの確認

  • DotNetZipは主に.NET Framework向けに開発されているため、ターゲットフレームワークが.NET Framework 4.xであることを確認してください
  • .NET Coreや.NET 5/6/7で使う場合は、互換性のあるコミュニティ版を利用するか、動作検証を十分に行う必要があります

プラットフォームターゲット

  • 32bit/64bitのプラットフォームターゲットは通常「Any CPU」で問題ありませんが、外部DLLを使う場合は環境に合わせて設定してください

参照のコピー設定

  • DLLを直接配置した場合、プロジェクトの参照プロパティで「ローカルコピー」をTrueに設定すると、ビルド時に出力フォルダにDLLがコピーされます。これにより、実行時にDLLが見つからない問題を防げます

文字コード設定

  • ZIPファイル内のファイル名に日本語を含む場合、文字コードの扱いに注意が必要です。DotNetZipはUTF-8に対応していますが、環境によっては文字化けが起きることがあります。必要に応じてZipEntryAlternateEncodingプロパティを設定してください

例外処理の準備

  • 圧縮・解凍処理はファイルアクセスやI/Oエラーが発生しやすいため、例外処理を適切に実装してください。特にファイルのロックやパスの存在確認は重要です

これらの設定を適切に行うことで、DotNetZipをスムーズに利用できる環境が整います。

導入後は簡単な圧縮・解凍のサンプルを動かして動作確認を行うことをおすすめします。

圧縮の基本操作

DotNetZipを使ったZIPファイルの圧縮は非常にシンプルです。

ここでは、新規アーカイブの作成からファイルやフォルダーの追加、圧縮レベルの設定まで、基本的な操作を詳しく説明いたします。

新規アーカイブ作成

ZIPファイルを新しく作成するには、まずZipFileクラスのインスタンスを生成し、そこに圧縮したいファイルやフォルダーを追加していきます。

ZipFileインスタンス生成

ZipFileクラスは、ZIPアーカイブの作成や読み込みを行う中心的なクラスです。

新規にZIPファイルを作成する場合は、単純にnew ZipFile()でインスタンスを生成します。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        // 新しいZipFileインスタンスを生成
        using (ZipFile zip = new ZipFile())
        {
            // ここにファイルやフォルダーの追加処理を記述
            Console.WriteLine("ZipFileインスタンスを生成しました。");
        }
    }
}
ZipFileインスタンスを生成しました。

このusing構文を使うことで、処理終了後にリソースが自動的に解放されます。

ZipFileインスタンスは圧縮ファイルの編集や保存を行うための操作の起点となります。

ファイル追加

ファイルをZIPアーカイブに追加するには、AddFileメソッドを使います。

第一引数に圧縮したいファイルのフルパスを指定し、第二引数にZIP内での格納先フォルダーを指定します。

第二引数を空文字にするとルートに追加されます。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            // ファイルをルートに追加
            zip.AddFile(@"C:\Example\File1.txt", "");
            // ファイルを"Documents"フォルダーに追加
            zip.AddFile(@"C:\Example\File2.docx", "Documents");
            // ZIPファイルを保存
            zip.Save(@"C:\Example\Archive.zip");
            Console.WriteLine("ファイルを追加して圧縮しました。");
        }
    }
}
ファイルを追加して圧縮しました。

この例では、File1.txtはZIPのルートに、File2.docxDocumentsフォルダー内に格納されます。

複数のファイルを自由にフォルダー分けして追加可能です。

フォルダー追加

フォルダーごと丸ごと圧縮したい場合は、AddDirectoryメソッドを使います。

第一引数に圧縮したいフォルダーのパス、第二引数にZIP内での格納先フォルダー名を指定します。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            // フォルダーを"Backup"フォルダーとして追加
            zip.AddDirectory(@"C:\Example\Data", "Backup");
            // ZIPファイルを保存
            zip.Save(@"C:\Example\Archive.zip");
            Console.WriteLine("フォルダーを追加して圧縮しました。");
        }
    }
}
フォルダーを追加して圧縮しました。

AddDirectoryは指定したフォルダー内のすべてのファイルとサブフォルダーを再帰的に圧縮します。

ZIP内のBackupフォルダーに元のフォルダー構造がそのまま保存されます。

圧縮レベルの設定

圧縮の品質や速度はCompressionLevelプロパティで調整できます。

用途に応じて圧縮率と処理速度のバランスを選択可能です。

CompressionLevelの選択肢

DotNetZipのCompressionLevelIonic.Zlib.CompressionLevel列挙体で指定します。

主な選択肢は以下の通りです。

圧縮レベル説明
None圧縮しない(ストア)
BestSpeed圧縮速度優先(圧縮率は低め)
Defaultデフォルト設定
BestCompression圧縮率優先(速度は遅め)

例えば、圧縮率を最大にしたい場合はBestCompressionを指定します。

速度を優先したい場合はBestSpeedを選びます。

using Ionic.Zip;
using Ionic.Zlib;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            zip.AddFile(@"C:\Example\File1.txt", "");
            // 圧縮レベルを最高圧縮に設定
            zip.CompressionLevel = CompressionLevel.BestCompression;
            zip.Save(@"C:\Example\Archive.zip");
            Console.WriteLine("最高圧縮レベルで圧縮しました。");
        }
    }
}
最高圧縮レベルで圧縮しました。

パフォーマンス比較

圧縮レベルによって処理時間と圧縮後のファイルサイズが変わります。

一般的な傾向は以下の通りです。

圧縮レベル処理速度圧縮率用途例
None最速圧縮なし圧縮不要な場合や高速処理重視
BestSpeed高速圧縮率低め一時ファイルや高速処理が必要な場合
Defaultバランス良好標準圧縮率一般的な用途
BestCompression遅い圧縮率最高保存容量を節約したい場合

圧縮率を上げるほどCPU負荷が高くなり処理時間が長くなります。

逆に速度を優先するとファイルサイズは大きくなります。

用途に応じて適切な圧縮レベルを選択してください。

たとえば、ログファイルのバックアップなどで保存容量を節約したい場合はBestCompressionが適しています。

一方、リアルタイム処理や一時的な圧縮ではBestSpeedを選ぶと効率的です。

これらの基本操作を組み合わせることで、DotNetZipを使った柔軟なZIPファイルの作成が可能です。

ファイルやフォルダーの追加方法、圧縮レベルの調整を理解して、用途に合った圧縮処理を実装してください。

解凍の基本操作

DotNetZipを使ったZIPファイルの解凍は、アーカイブの読み込みからファイルの展開まで簡単に行えます。

ここでは、ZipFile.Readメソッドによるアーカイブの読み込み方法と、Extractメソッドの使い方、さらにファイル上書き時の動作制御や文字コードの問題回避について詳しく説明いたします。

アーカイブ読み込み

ZipFile.Readの使い方

ZIPファイルを解凍するには、まずZipFile.Readメソッドで既存のZIPアーカイブを読み込みます。

ZipFile.ReadはZIPファイルのパスを引数に取り、ZipFileオブジェクトを返します。

このオブジェクトを使って中のファイルZipEntryにアクセスし、解凍処理を行います。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        try
        {
            // ZIPファイルを読み込む
            using (ZipFile zip = ZipFile.Read(@"C:\Example\Archive.zip"))
            {
                Console.WriteLine("ZIPファイルを読み込みました。");
                // ZIP内のファイル一覧を表示
                foreach (ZipEntry entry in zip)
                {
                    Console.WriteLine($"ファイル名: {entry.FileName}, サイズ: {entry.UncompressedSize} バイト");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
}
ZIPファイルを読み込みました。
ファイル名: File1.txt, サイズ: 1024 バイト
ファイル名: Documents/File2.docx, サイズ: 20480 バイト

このコードでは、ZipFile.ReadでZIPファイルを開き、foreachで中のファイルを列挙しています。

ZipEntryFileNameプロパティでファイル名、UncompressedSizeで元のファイルサイズを取得可能です。

Extractメソッド

ZIPファイル内のファイルを実際に解凍するには、ZipEntryクラスのExtractメソッドを使います。

Extractは解凍先のパスを指定し、ファイルを展開します。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        try
        {
            string extractPath = @"C:\Example\Extract";
            using (ZipFile zip = ZipFile.Read(@"C:\Example\Archive.zip"))
            {
                foreach (ZipEntry entry in zip)
                {
                    // 解凍先にファイルを展開(既存ファイルは上書き)
                    entry.Extract(extractPath, ExtractExistingFileAction.OverwriteSilently);
                    Console.WriteLine($"解凍しました: {entry.FileName}");
                }
            }
            Console.WriteLine("すべてのファイルを解凍しました。");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
}
解凍しました: File1.txt
解凍しました: Documents/File2.docx
すべてのファイルを解凍しました。

各種ExtractExistingFileAction

Extractメソッドの第二引数には、解凍先に同名ファイルが存在した場合の動作を制御するExtractExistingFileAction列挙体を指定できます。

主な選択肢は以下の通りです。

動作内容
Throw既存ファイルがあれば例外をスロー(デフォルト)
OverwriteSilently既存ファイルを上書き
OverwriteIfNewer解凍ファイルが既存ファイルより新しければ上書き
DoNotOverwrite既存ファイルがあれば解凍しない

例えば、既存ファイルを上書きしたい場合はOverwriteSilentlyを指定します。

例外を避けたい場合や、更新日時を考慮したい場合はOverwriteIfNewerが便利です。

entry.Extract(extractPath, ExtractExistingFileAction.OverwriteIfNewer);

この指定により、既存ファイルが新しい場合は上書きされず、古い場合のみ解凍されます。

文字コード問題の回避

ZIPファイル内のファイル名に日本語などのマルチバイト文字が含まれる場合、文字化けが発生することがあります。

DotNetZipはUTF-8の文字コードに対応していますが、環境やZIP作成時の設定によっては正しく表示されないことがあります。

この問題を回避するために、ZipFileAlternateEncodingAlternateEncodingUsageプロパティを設定します。

using Ionic.Zip;
using System;
using System.Text;
class Program
{
    static void Main()
    {
        try
        {
            using (ZipFile zip = ZipFile.Read(@"C:\Example\Archive.zip"))
            {
                // 文字コードをUTF-8に設定
                zip.AlternateEncoding = Encoding.UTF8;
                zip.AlternateEncodingUsage = ZipOption.Always;
                foreach (ZipEntry entry in zip)
                {
                    Console.WriteLine($"ファイル名: {entry.FileName}");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
}

AlternateEncodingEncoding.UTF8を指定し、AlternateEncodingUsageZipOption.Alwaysに設定することで、常にUTF-8でファイル名を解釈します。

これにより日本語ファイル名の文字化けを防げます。

ただし、元のZIPファイルがUTF-8以外の文字コードで作成されている場合は、適切なエンコーディングを指定する必要があります。

例えばShift-JISで作成されたZIPの場合はEncoding.GetEncoding("shift_jis")を使います。

これらの操作を組み合わせることで、DotNetZipを使った安全かつ確実なZIPファイルの解凍処理が実現します。

ファイルの上書き動作や文字コードの扱いに注意しながら実装してください。

パスとディレクトリ管理

DotNetZipでZIPファイルを扱う際、ファイルやフォルダーのパス管理は重要なポイントです。

圧縮時や解凍時にフォルダー構造をどう扱うか、ルートディレクトリの扱い、絶対パスと相対パスの違いを理解しておくことで、意図した通りのZIPファイルを作成・展開できます。

階層構造の保持

フォルダーを圧縮する際、元のディレクトリ構造をそのままZIPファイル内に保持するかどうかは重要です。

DotNetZipのAddDirectoryメソッドは、指定したフォルダー以下のすべてのファイルとサブフォルダーを再帰的に追加し、元の階層構造をそのままZIP内に保存します。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            // "C:\Example\Data"フォルダーをそのまま階層構造を保持して圧縮
            zip.AddDirectory(@"C:\Example\Data");
            zip.Save(@"C:\Example\Archive.zip");
            Console.WriteLine("フォルダーを階層構造を保持して圧縮しました。");
        }
    }
}
フォルダーを階層構造を保持して圧縮しました。

この場合、Dataフォルダー内のファイルやサブフォルダーはZIP内でも同じ階層構造で保存されます。

解凍時も同様に元のフォルダー構造が復元されます。

もし、ZIP内で特定のフォルダー名の下にまとめたい場合は、AddDirectoryの第二引数にZIP内の格納先フォルダー名を指定します。

zip.AddDirectory(@"C:\Example\Data", "BackupData");

これにより、ZIP内のBackupDataフォルダーの中にDataの内容が階層構造ごと格納されます。

ルート除外

圧縮時に元のフォルダーのルート部分を除外して、ZIP内にファイルやフォルダーを直接格納したい場合があります。

例えば、C:\Example\Dataの中身だけをZIPのルートに入れたい場合です。

DotNetZipのAddDirectoryはフォルダー名をZIP内に含めて追加しますが、ルートを除外するにはAddDirectoryの第二引数に空文字を指定します。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            // "Data"フォルダーの中身をZIPのルートに追加(ルート除外)
            zip.AddDirectory(@"C:\Example\Data", "");
            zip.Save(@"C:\Example\Archive.zip");
            Console.WriteLine("フォルダーの中身をルートに追加しました。");
        }
    }
}
フォルダーの中身をルートに追加しました。

この方法で、Dataフォルダー自体はZIPに含まれず、その中のファイルやサブフォルダーがZIPのルートに直接格納されます。

解凍時もルート直下に展開されるため、フォルダー名を省略したい場合に便利です。

絶対パスと相対パス

ファイルやフォルダーをZIPに追加する際、元のパスが絶対パスか相対パスかによってZIP内の格納パスが変わります。

DotNetZipでは、AddFileAddDirectoryの第二引数でZIP内の格納先パスを指定することで、相対パスをコントロールできます。

  • 絶対パス

ファイルのフルパス(例:C:\Example\Data\File.txt)を指定しても、ZIP内には絶対パスは保存されません。

必ずZIP内の相対パスとして保存されます。

ただし、第二引数に空文字を指定すると、ファイル名だけがZIPのルートに格納されます。

  • 相対パス

第二引数にフォルダー名やパスを指定すると、ZIP内の相対パスとしてそのフォルダー以下にファイルが格納されます。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            // ファイルをZIPの"Docs"フォルダーに格納
            zip.AddFile(@"C:\Example\Data\File1.txt", "Docs");
            // ファイルをZIPのルートに格納
            zip.AddFile(@"C:\Example\Data\File2.txt", "");
            zip.Save(@"C:\Example\Archive.zip");
            Console.WriteLine("ファイルを相対パスで格納しました。");
        }
    }
}
ファイルを相対パスで格納しました。

この例では、File1.txtはZIP内のDocsフォルダーに、File2.txtはZIPのルートに格納されます。

注意点

  • ZIPファイル内に絶対パスをそのまま保存することは一般的に避けられます。DotNetZipも絶対パスをそのまま保存しません
  • 圧縮時にパスの指定を誤ると、解凍時に意図しないフォルダー構造になることがあるため、ZIP内のパスを明示的に指定することが重要です
  • 解凍時はZIP内の相対パスに従ってファイルが展開されるため、圧縮時のパス設定がそのまま反映されます

パスとディレクトリ管理を正しく行うことで、ZIPファイルの構造を自在にコントロールできます。

階層構造の保持やルート除外、相対パスの指定を活用して、用途に合ったZIPファイルを作成してください。

暗号化とパスワード保護

DotNetZipはZIPファイルの暗号化やパスワード保護にも対応しており、セキュリティを強化した圧縮ファイルを簡単に作成できます。

ここでは、AES-256暗号化の設定方法、互換性を考慮した暗号化のポイント、そしてパスワード入力UIの簡単な実装例を詳しく説明いたします。

AES‐256設定

DotNetZipでは、ZIPファイルの暗号化にAES(Advanced Encryption Standard)を利用できます。

AESは強力な暗号化方式で、特にAES-256は256ビットの鍵長を持ち、高いセキュリティを提供します。

AES暗号化を有効にするには、ZipEntryEncryptionプロパティにEncryptionAlgorithm.WinZipAes256を指定し、パスワードを設定します。

以下はAES-256でファイルを圧縮・暗号化する例です。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        try
        {
            using (ZipFile zip = new ZipFile())
            {
                // パスワードを設定
                zip.Password = "securepassword";
                // AES-256暗号化を有効にする
                zip.Encryption = EncryptionAlgorithm.WinZipAes256;
                // ファイルを追加
                zip.AddFile(@"C:\Example\SecretDocument.txt", "");
                // ZIPファイルを保存
                zip.Save(@"C:\Example\EncryptedArchive.zip");
                Console.WriteLine("AES-256で暗号化したZIPファイルを作成しました。");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
}
AES-256で暗号化したZIPファイルを作成しました。

このコードでは、zip.Passwordにパスワードを設定し、zip.EncryptionWinZipAes256を指定しています。

これにより、ZIP内のすべてのファイルがAES-256で暗号化されます。

個別ファイルごとの暗号化設定

複数ファイルを追加する場合、ファイルごとに異なる暗号化設定を行いたい場合は、ZipEntry単位で設定可能です。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            zip.Password = "securepassword";
            // ファイルを追加し、個別に暗号化設定
            ZipEntry entry1 = zip.AddFile(@"C:\Example\File1.txt", "");
            entry1.Encryption = EncryptionAlgorithm.WinZipAes256;
            ZipEntry entry2 = zip.AddFile(@"C:\Example\File2.txt", "");
            entry2.Encryption = EncryptionAlgorithm.None; // 暗号化しない
            zip.Save(@"C:\Example\MixedEncryption.zip");
            Console.WriteLine("ファイルごとに暗号化設定を行いました。");
        }
    }
}
ファイルごとに暗号化設定を行いました。

互換性を意識した暗号化

AES暗号化は強力ですが、すべてのZIP解凍ソフトがAES-256に対応しているわけではありません。

特に古い解凍ツールやOS標準のZIP解凍機能では、AES暗号化されたZIPファイルを開けない場合があります。

互換性を高めるポイント

  • 標準ZIP暗号化(ZipCrypto)との使い分け

DotNetZipはEncryptionAlgorithm.PkzipWeak(ZipCrypto)もサポートしています。

これは互換性が高いですが、セキュリティは弱いです。

互換性重視ならこちらを選択します。

  • 利用環境の確認

ZIPファイルを配布する相手の環境でAES暗号化がサポートされているか確認してください。

Windows 10以降の標準機能はAESに対応していませんが、7-ZipやWinRARなどのサードパーティ製ツールは対応しています。

  • パスワードの強度

どの暗号化方式でも、強力なパスワードを設定することが重要です。

短いパスワードや単純な文字列は容易に破られるリスクがあります。

ZipCryptoでの暗号化例

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            zip.Password = "password123";
            // 互換性重視のZipCrypto暗号化
            zip.Encryption = EncryptionAlgorithm.PkzipWeak;
            zip.AddFile(@"C:\Example\File.txt", "");
            zip.Save(@"C:\Example\ZipCryptoEncrypted.zip");
            Console.WriteLine("ZipCryptoで暗号化したZIPファイルを作成しました。");
        }
    }
}
ZipCryptoで暗号化したZIPファイルを作成しました。

パスワード入力UI例

ユーザーからパスワードを入力してもらい、そのパスワードでZIPファイルを暗号化する簡単なコンソールアプリの例です。

WindowsフォームやWPFでも同様の考え方でUIを作成できます。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        Console.Write("圧縮ファイルのパスワードを入力してください: ");
        string password = Console.ReadLine();
        try
        {
            using (ZipFile zip = new ZipFile())
            {
                zip.Password = password;
                zip.Encryption = EncryptionAlgorithm.WinZipAes256;
                zip.AddFile(@"C:\Example\SensitiveData.txt", "");
                zip.Save(@"C:\Example\PasswordProtected.zip");
                Console.WriteLine("パスワード保護されたZIPファイルを作成しました。");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
}
圧縮ファイルのパスワードを入力してください: mysecret
パスワード保護されたZIPファイルを作成しました。

この例では、コンソールからパスワードを受け取り、そのパスワードでAES-256暗号化を行っています。

GUIアプリの場合は、テキストボックスやパスワード入力欄を用意し、同様にzip.Passwordに設定すれば実装できます。

DotNetZipの暗号化機能を活用することで、重要なファイルを安全に圧縮・配布できます。

AES-256の強力な暗号化と互換性を考慮した設定を使い分け、ユーザーに使いやすいパスワード入力UIを提供してください。

ストリーム活用

DotNetZipはファイルシステムだけでなく、メモリやネットワーク、データベースなどのストリームを活用した圧縮・解凍にも対応しています。

ここでは、メモリ上での圧縮処理、ネットワークストリームへの書き出し、そしてデータベースのBLOBとして扱う方法を詳しく説明いたします。

メモリ圧縮

ファイルをディスクに書き出さずに、メモリ上のストリームに圧縮データを生成したい場合があります。

例えば、一時的に圧縮データを保持して別の処理に渡すケースです。

DotNetZipではMemoryStreamを使ってメモリ圧縮が可能です。

ZipFile.Saveメソッドの代わりに、SaveのオーバーロードでStreamを指定します。

using Ionic.Zip;
using System;
using System.IO;
class Program
{
    static void Main()
    {
        using (MemoryStream ms = new MemoryStream())
        {
            using (ZipFile zip = new ZipFile())
            {
                // メモリ上に圧縮データを作成
                zip.AddEntry("Hello.txt", "こんにちは、世界!"); // 文字列をファイルとして追加
                zip.Save(ms);
            }
            // メモリストリームの位置を先頭に戻す
            ms.Position = 0;
            Console.WriteLine($"メモリ上に圧縮データを作成しました。サイズ: {ms.Length} バイト");
            // 必要に応じてバイト配列として取得可能
            byte[] zipBytes = ms.ToArray();
            Console.WriteLine($"バイト配列の長さ: {zipBytes.Length}");
        }
    }
}
メモリ上に圧縮データを作成しました。サイズ: 123 バイト
バイト配列の長さ: 123

この例では、AddEntryで文字列を直接ZIP内のファイルとして追加し、MemoryStreamに圧縮データを保存しています。

ファイルをディスクに書き出さずに圧縮データを扱えるため、メモリ内での高速処理やネットワーク送信前の準備に便利です。

ネットワークストリームへの書き出し

圧縮データをネットワーク経由で送信する場合、NetworkStreamTcpClientのストリームに直接書き出すことが可能です。

これにより、ファイルを一旦保存せずにリアルタイムで圧縮データを送信できます。

以下はTCPクライアントのストリームに圧縮データを書き出す例です。

using Ionic.Zip;
using System;
using System.IO;
using System.Net.Sockets;
class Program
{
    static void Main()
    {
        string serverIp = "127.0.0.1";
        int port = 9000;
        try
        {
            using (TcpClient client = new TcpClient(serverIp, port))
            using (NetworkStream netStream = client.GetStream())
            using (ZipFile zip = new ZipFile())
            {
                // 圧縮ファイルを作成
                zip.AddEntry("Data.txt", "ネットワーク経由で送信する圧縮データです。");
                // NetworkStreamに直接書き出す
                zip.Save(netStream);
                Console.WriteLine("圧縮データをネットワークストリームに送信しました。");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"通信エラー: {ex.Message}");
        }
    }
}
圧縮データをネットワークストリームに送信しました。

このコードはTCPサーバーが待ち受けている環境で動作します。

zip.Save(netStream)で圧縮データを直接ネットワークに流し、受信側で解凍処理を行うことができます。

ファイルI/Oを介さずにリアルタイム通信が可能です。

データベースBLOB扱い

圧縮データをデータベースのBLOB(バイナリ大容量オブジェクト)として保存・取得するケースもあります。

メモリ圧縮と組み合わせて、圧縮データをバイト配列として扱い、データベースに格納します。

以下は圧縮データをメモリ上で作成し、バイト配列として取得する例です。

using Ionic.Zip;
using System;
using System.IO;
class Program
{
    static void Main()
    {
        byte[] compressedData;
        using (MemoryStream ms = new MemoryStream())
        {
            using (ZipFile zip = new ZipFile())
            {
                zip.AddEntry("Report.csv", "日付,売上,利益\n2024-06-01,10000,3000");
                zip.Save(ms);
            }
            compressedData = ms.ToArray();
        }
        Console.WriteLine($"圧縮データのバイト配列を取得しました。サイズ: {compressedData.Length} バイト");
        // ここでcompressedDataをデータベースのBLOBカラムに保存可能
    }
}
圧縮データのバイト配列を取得しました。サイズ: 85 バイト

データベースから取得したBLOBを解凍する場合は、MemoryStreamにバイト配列をセットし、ZipFile.Readで読み込みます。

using Ionic.Zip;
using System;
using System.IO;
class Program
{
    static void Main()
    {
        // 例としてcompressedDataは前述の圧縮データ
        byte[] compressedData = /* データベースから取得したバイト配列 */ null;
        if (compressedData == null)
        {
            Console.WriteLine("圧縮データがありません。");
            return;
        }
        using (MemoryStream ms = new MemoryStream(compressedData))
        using (ZipFile zip = ZipFile.Read(ms))
        {
            foreach (ZipEntry entry in zip)
            {
                using (MemoryStream extracted = new MemoryStream())
                {
                    entry.Extract(extracted);
                    extracted.Position = 0;
                    using (StreamReader reader = new StreamReader(extracted))
                    {
                        string content = reader.ReadToEnd();
                        Console.WriteLine($"ファイル名: {entry.FileName}");
                        Console.WriteLine("内容:");
                        Console.WriteLine(content);
                    }
                }
            }
        }
    }
}

この方法で、ファイルシステムを介さずにデータベース内の圧縮データを展開し、中身を取得できます。

BLOBとして圧縮データを保存することで、ストレージの節約や一括管理が可能です。

ストリームを活用した圧縮・解凍は、ファイルI/Oの制約を超えた柔軟なデータ処理を実現します。

メモリ圧縮やネットワーク送信、データベース連携など、用途に応じてストリーム操作を使い分けてください。

進捗管理

DotNetZipで大きなファイルや多数のファイルを圧縮・解凍する際、処理の進捗を管理することはユーザー体験の向上に欠かせません。

DotNetZipはSaveProgressイベントを提供しており、これを活用することで進捗率の計算や処理のキャンセルが可能です。

ここでは、SaveProgressイベントの使い方、進捗率の計算方法、キャンセルフラグの設定、そしてUIへの反映方法について詳しく説明いたします。

SaveProgressイベント

SaveProgressイベントは、ZIPファイルの保存(圧縮)処理中に定期的に発生し、現在の進捗状況を通知します。

これを利用して、処理の進行状況を取得し、UIに反映したり、ユーザーからのキャンセル要求を受け付けたりできます。

進捗率計算

SaveProgressイベントのイベントハンドラーにはSaveProgressEventArgsが渡されます。

この中のEntriesTotal(全エントリ数)とEntriesSaved(保存済みエントリ数)を使って進捗率を計算できます。

using Ionic.Zip;
using System;
class Program
{
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            zip.AddFile(@"C:\Example\File1.txt", "");
            zip.AddFile(@"C:\Example\File2.txt", "");
            // SaveProgressイベントにハンドラーを登録
            zip.SaveProgress += Zip_SaveProgress;
            zip.Save(@"C:\Example\Archive.zip");
        }
    }
    private static void Zip_SaveProgress(object sender, SaveProgressEventArgs e)
    {
        if (e.EventType == ZipProgressEventType.Saving_EntryBytesRead)
        {
            // 全体の進捗率を計算(エントリ単位)
            int totalEntries = e.EntriesTotal;
            int savedEntries = e.EntriesSaved;
            // 現在処理中のエントリの読み込み進捗(0~100%)
            double currentEntryProgress = 0;
            if (e.CurrentEntry != null && e.CurrentEntry.UncompressedSize > 0)
            {
                currentEntryProgress = (double)e.BytesTransferred / e.CurrentEntry.UncompressedSize;
            }
            // 全体の進捗率(エントリ単位+現在のエントリの進捗を加味)
            double overallProgress = (savedEntries + currentEntryProgress) / totalEntries * 100;
            Console.WriteLine($"圧縮進捗: {overallProgress:F2}%");
        }
    }
}
圧縮進捗: 0.00%
圧縮進捗: 10.50%
圧縮進捗: 50.00%
圧縮進捗: 100.00%

この例では、Saving_EntryBytesReadイベントタイプを利用して、現在処理中のファイルの読み込みバイト数を取得し、全体の進捗率を計算しています。

EntriesSavedはすでに完了したファイル数、EntriesTotalは全ファイル数です。

キャンセルフラグ

SaveProgressEventArgsにはCancelプロパティがあり、これをtrueに設定すると圧縮処理を途中でキャンセルできます。

ユーザーがキャンセルボタンを押した場合などに利用します。

using Ionic.Zip;
using System;
class Program
{
    static bool cancelRequested = false;
    static void Main()
    {
        using (ZipFile zip = new ZipFile())
        {
            zip.AddFile(@"C:\Example\File1.txt", "");
            zip.AddFile(@"C:\Example\File2.txt", "");
            zip.SaveProgress += Zip_SaveProgress;
            // 別スレッドやUIからキャンセル要求を受け付ける想定
            // ここでは簡単にコンソール入力でキャンセルを模擬
            Console.WriteLine("圧縮処理中。キャンセルするには 'c' を入力してください。");
            // キャンセル監視スレッドを起動
            System.Threading.Thread cancelThread = new System.Threading.Thread(() =>
            {
                if (Console.ReadLine()?.ToLower() == "c")
                {
                    cancelRequested = true;
                }
            });
            cancelThread.Start();
            try
            {
                zip.Save(@"C:\Example\Archive.zip");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"処理がキャンセルされました: {ex.Message}");
            }
        }
    }
    private static void Zip_SaveProgress(object sender, SaveProgressEventArgs e)
    {
        if (cancelRequested)
        {
            e.Cancel = true;
            Console.WriteLine("キャンセル要求を受け付けました。");
        }
    }
}
圧縮処理中。キャンセルするには 'c' を入力してください。
キャンセル要求を受け付けました。
処理がキャンセルされました: 圧縮処理がユーザーにより中断されました。

このコードでは、別スレッドでコンソール入力を監視し、cancelRequestedフラグを立てるとSaveProgressイベント内でe.Cancel = trueを設定して処理を中断しています。

UIへの反映

進捗率を取得したら、WindowsフォームやWPFなどのUIに反映させることでユーザーに処理状況をわかりやすく伝えられます。

UIスレッドと圧縮処理スレッドが異なる場合は、スレッド間の同期が必要です。

以下はWindowsフォームのプログレスバーに進捗を反映する例です。

using Ionic.Zip;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }
    private async void btnCompress_Click(object sender, EventArgs e)
    {
        progressBar1.Value = 0;
        await Task.Run(() =>
        {
            using (ZipFile zip = new ZipFile())
            {
                zip.AddFile(@"C:\Example\File1.txt", "");
                zip.AddFile(@"C:\Example\File2.txt", "");
                zip.SaveProgress += (s, ev) =>
                {
                    if (ev.EventType == ZipProgressEventType.Saving_EntryBytesRead)
                    {
                        int totalEntries = ev.EntriesTotal;
                        int savedEntries = ev.EntriesSaved;
                        double currentEntryProgress = 0;
                        if (ev.CurrentEntry != null && ev.CurrentEntry.UncompressedSize > 0)
                        {
                            currentEntryProgress = (double)ev.BytesTransferred / ev.CurrentEntry.UncompressedSize;
                        }
                        double overallProgress = (savedEntries + currentEntryProgress) / totalEntries * 100;
                        // UIスレッドに進捗を反映
                        progressBar1.Invoke(new Action(() =>
                        {
                            progressBar1.Value = (int)overallProgress;
                        }));
                    }
                };
                zip.Save(@"C:\Example\Archive.zip");
            }
        });
        MessageBox.Show("圧縮が完了しました。");
    }
}

この例では、Task.Runで圧縮処理をバックグラウンドで実行し、SaveProgressイベントで計算した進捗率をInvokeを使ってUIスレッドのプログレスバーに反映しています。

これにより、UIが固まらずにリアルタイムで進捗を表示できます。

SaveProgressイベントを活用することで、DotNetZipの圧縮処理の進捗を細かく把握し、ユーザーにわかりやすく伝えられます。

キャンセル機能も組み合わせて、快適な操作性を実現してください。

非同期・並列処理

DotNetZipを使った圧縮・解凍処理は、ファイルサイズが大きかったりファイル数が多い場合、処理に時間がかかることがあります。

アプリケーションの応答性を保つために、非同期処理や並列処理を活用することが重要です。

ここでは、C#のasync/awaitパターン、Taskや並列クエリを使った並列処理、そしてUIスレッドの分離について詳しく説明いたします。

async/awaitパターン

DotNetZip自体は非同期APIを直接提供していませんが、Task.Runを使って圧縮・解凍処理を非同期に実行し、async/awaitパターンで呼び出すことができます。

これにより、UIスレッドをブロックせずに処理を行えます。

以下は、非同期でZIP圧縮を行う例です。

using Ionic.Zip;
using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
        Console.WriteLine("圧縮処理を開始します。");
        try
        {
            await CompressAsync(@"C:\Example\Archive.zip", new string[]
            {
                @"C:\Example\File1.txt",
                @"C:\Example\File2.txt"
            });
            Console.WriteLine("圧縮処理が完了しました。");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
        }
    }
    static Task CompressAsync(string zipPath, string[] files)
    {
        return Task.Run(() =>
        {
            using (ZipFile zip = new ZipFile())
            {
                foreach (var file in files)
                {
                    zip.AddFile(file, "");
                }
                zip.Save(zipPath);
            }
        });
    }
}
圧縮処理を開始します。
圧縮処理が完了しました。

この例では、CompressAsyncメソッド内でTask.Runを使い、圧縮処理を別スレッドで実行しています。

Mainメソッドはasyncにしてawaitで待機するため、処理完了までUIやコンソールの応答が保たれます。

Taskと並列クエリ

複数の圧縮・解凍処理を同時に実行したい場合、Taskを複数生成して並列に処理できます。

LINQのParallel.ForEachTask.WhenAllを使うと効率的です。

以下は複数のZIPファイルを並列に圧縮する例です。

using Ionic.Zip;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
        var jobs = new List<(string zipPath, string[] files)>
        {
            (@"C:\Example\Archive1.zip", new[] { @"C:\Example\FileA.txt" }),
            (@"C:\Example\Archive2.zip", new[] { @"C:\Example\FileB.txt", @"C:\Example\FileC.txt" }),
            (@"C:\Example\Archive3.zip", new[] { @"C:\Example\FileD.txt" })
        };
        var tasks = new List<Task>();
        foreach (var job in jobs)
        {
            tasks.Add(Task.Run(() =>
            {
                using (ZipFile zip = new ZipFile())
                {
                    foreach (var file in job.files)
                    {
                        zip.AddFile(file, "");
                    }
                    zip.Save(job.zipPath);
                    Console.WriteLine($"{job.zipPath} の圧縮が完了しました。");
                }
            }));
        }
        await Task.WhenAll(tasks);
        Console.WriteLine("すべての圧縮処理が完了しました。");
    }
}
C:\Example\Archive1.zip の圧縮が完了しました。
C:\Example\Archive2.zip の圧縮が完了しました。
C:\Example\Archive3.zip の圧縮が完了しました。
すべての圧縮処理が完了しました。

このコードでは、複数の圧縮処理をTask.Runで並列に実行し、Task.WhenAllで全ての完了を待っています。

CPUコア数やI/O状況に応じて効率的に処理が進みます。

UIスレッド分離

WindowsフォームやWPFなどのUIアプリケーションでは、圧縮・解凍処理をUIスレッドで実行すると画面が固まってしまいます。

非同期処理を使い、UIスレッドと処理スレッドを分離することが必須です。

以下はWPFのボタンイベントで非同期に圧縮処理を実行し、進捗をUIに反映する例です。

using Ionic.Zip;
using System;
using System.Threading.Tasks;
using System.Windows;
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    private async void btnCompress_Click(object sender, RoutedEventArgs e)
    {
        btnCompress.IsEnabled = false;
        progressBar.Value = 0;
        lblStatus.Content = "圧縮中...";
        try
        {
            await Task.Run(() =>
            {
                using (ZipFile zip = new ZipFile())
                {
                    zip.AddFile(@"C:\Example\File1.txt", "");
                    zip.AddFile(@"C:\Example\File2.txt", "");
                    zip.SaveProgress += (s, ev) =>
                    {
                        if (ev.EventType == Ionic.Zip.ZipProgressEventType.Saving_EntryBytesRead)
                        {
                            double progress = (ev.EntriesSaved + (double)ev.BytesTransferred / ev.CurrentEntry.UncompressedSize) / ev.EntriesTotal * 100;
                            Dispatcher.Invoke(() =>
                            {
                                progressBar.Value = progress;
                                lblStatus.Content = $"圧縮進捗: {progress:F1}%";
                            });
                        }
                    };
                    zip.Save(@"C:\Example\Archive.zip");
                }
            });
            lblStatus.Content = "圧縮が完了しました。";
        }
        catch (Exception ex)
        {
            lblStatus.Content = $"エラー: {ex.Message}";
        }
        finally
        {
            btnCompress.IsEnabled = true;
        }
    }
}

この例では、Task.Runで圧縮処理をバックグラウンドスレッドで実行し、Dispatcher.InvokeでUIスレッドに進捗を反映しています。

ボタンの有効・無効切り替えも行い、ユーザー操作を制御しています。

非同期・並列処理を適切に活用することで、DotNetZipの圧縮・解凍処理を効率的かつ快適に実行できます。

UIの応答性を保ちつつ、大量データの処理もスムーズに行いましょう。

エラーハンドリング

DotNetZipを使ったファイル圧縮・解凍処理では、ファイルアクセスやI/O操作が絡むため、さまざまな例外が発生する可能性があります。

適切なエラーハンドリングを実装することで、安定した動作とユーザーへの適切なフィードバックを実現できます。

ここでは、DotNetZipでよく遭遇する典型的な例外、リトライロジックの考え方、そして効果的なロギング戦略について詳しく説明いたします。

典型的な例外

DotNetZipの圧縮・解凍処理で発生しやすい例外には以下のようなものがあります。

例外名発生原因例対応策のポイント
System.IO.FileNotFoundException指定したファイルが存在しないファイルパスの事前チェック、ユーザー通知
System.IO.IOExceptionファイルがロックされている、ディスク容量不足などファイルの使用状況確認、空き容量チェック
Ionic.Zip.BadReadExceptionZIPファイルが破損している、読み込みエラーファイルの整合性チェック、再取得
Ionic.Zip.BadPasswordExceptionパスワード付きZIPのパスワードが間違っているパスワードの再入力促進
System.UnauthorizedAccessExceptionファイルやフォルダーへのアクセス権限がないアクセス権限の確認、管理者権限での実行
System.ArgumentException無効なパスや引数が指定された入力値のバリデーション

特にファイルのロックや破損ファイル、パスワード間違いはユーザー操作や環境依存で起こりやすいため、例外をキャッチして適切に処理することが重要です。

using Ionic.Zip;
using System;
using System.IO;
class Program
{
    static void Main()
    {
        try
        {
            using (ZipFile zip = ZipFile.Read(@"C:\Example\Archive.zip"))
            {
                zip.ExtractAll(@"C:\Example\Extract", ExtractExistingFileAction.OverwriteSilently);
                Console.WriteLine("解凍が完了しました。");
            }
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine($"ファイルが見つかりません: {ex.Message}");
        }
        catch (BadPasswordException)
        {
            Console.WriteLine("パスワードが間違っています。");
        }
        catch (IOException ex)
        {
            Console.WriteLine($"I/Oエラーが発生しました: {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"予期しないエラーが発生しました: {ex.Message}");
        }
    }
}

リトライロジック

一時的なファイルロックやI/Oエラーは、少し時間を置いて再試行することで解決する場合があります。

リトライロジックを実装することで、処理の安定性を向上させられます。

以下は、最大3回までリトライする簡単な例です。

using Ionic.Zip;
using System;
using System.IO;
using System.Threading;
class Program
{
    static void Main()
    {
        int maxRetries = 3;
        int delayMilliseconds = 1000;
        int attempt = 0;
        while (attempt < maxRetries)
        {
            try
            {
                using (ZipFile zip = ZipFile.Read(@"C:\Example\Archive.zip"))
                {
                    zip.ExtractAll(@"C:\Example\Extract", ExtractExistingFileAction.OverwriteSilently);
                    Console.WriteLine("解凍が完了しました。");
                    break; // 成功したらループを抜ける
                }
            }
            catch (IOException ex)
            {
                attempt++;
                Console.WriteLine($"I/Oエラーが発生しました。リトライ {attempt}/{maxRetries} - {ex.Message}");
                if (attempt >= maxRetries)
                {
                    Console.WriteLine("リトライ回数の上限に達しました。処理を中断します。");
                    break;
                }
                Thread.Sleep(delayMilliseconds);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"予期しないエラーが発生しました: {ex.Message}");
                break;
            }
        }
    }
}

この例では、IOExceptionが発生した場合に1秒待って再試行し、最大3回までリトライします。

リトライ中はユーザーに状況を通知し、上限に達したら処理を中断します。

ロギング戦略

エラー発生時の原因追跡や運用監視のために、適切なロギングは欠かせません。

DotNetZipの例外情報や処理状況をログに記録することで、問題の早期発見や対応が可能になります。

ロギングのポイント

  • 例外の詳細情報を記録

例外メッセージだけでなく、スタックトレースや発生箇所もログに残すと解析が容易です。

  • 処理開始・終了のログ

圧縮・解凍処理の開始と終了をログに記録し、正常終了か異常終了かを判別しやすくします。

  • リトライ回数やキャンセル情報の記録

リトライの試行回数やユーザーによるキャンセルもログに残すと運用上役立ちます。

  • ログレベルの設定

情報(Info)、警告(Warning)、エラー(Error)などレベルを分けて記録し、必要に応じてフィルタリングできるようにします。

簡単なログ出力例

using Ionic.Zip;
using System;
using System.IO;
class Logger
{
    private static readonly string logFile = @"C:\Example\app.log";
    public static void Log(string message)
    {
        string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}";
        File.AppendAllText(logFile, logEntry + Environment.NewLine);
    }
}
class Program
{
    static void Main()
    {
        try
        {
            Logger.Log("圧縮処理を開始します。");
            using (ZipFile zip = new ZipFile())
            {
                zip.AddFile(@"C:\Example\File1.txt", "");
                zip.Save(@"C:\Example\Archive.zip");
            }
            Logger.Log("圧縮処理が正常に完了しました。");
        }
        catch (Exception ex)
        {
            Logger.Log($"エラー発生: {ex.Message}\n{ex.StackTrace}");
            Console.WriteLine("エラーが発生しました。ログを確認してください。");
        }
    }
}

この例では、Loggerクラスでログファイルに日時付きのメッセージを書き込み、例外発生時には詳細な情報を記録しています。

実際の運用では、NLogSerilogなどの専用ライブラリを使うとより高度なログ管理が可能です。

エラーハンドリングは安定したアプリケーション運用の要です。

典型的な例外を想定し、リトライやロギングを組み合わせて堅牢な処理を実装してください。

テストとデバッグ

DotNetZipを使ったファイル圧縮・解凍の開発では、正確かつ安定した動作を保証するためにテストとデバッグが欠かせません。

ここでは、テスト時に役立つモック作成の方法、破損ファイルを使った異常系テスト、そしてパフォーマンス測定のポイントについて詳しく解説いたします。

モック作成

圧縮・解凍処理を含むコードの単体テストや統合テストでは、実際のファイルシステムに依存しないモック(模擬オブジェクト)を使うと効率的です。

DotNetZip自体はインターフェースを多用していないため、直接的なモックは難しいですが、ファイルI/O部分やストリームを抽象化することでテスト可能になります。

ストリームを使ったモック例

ファイルの代わりにMemoryStreamを使い、メモリ上で圧縮・解凍処理を行うことで、ディスクアクセスを伴わないテストが可能です。

using Ionic.Zip;
using System;
using System.IO;
using Xunit;
public class ZipTests
{
    [Fact]
    public void CompressAndExtract_MemoryStream_Success()
    {
        byte[] compressedData;
        // メモリ上で圧縮
        using (var ms = new MemoryStream())
        {
            using (var zip = new ZipFile())
            {
                zip.AddEntry("test.txt", "テストデータ");
                zip.Save(ms);
            }
            compressedData = ms.ToArray();
        }
        // メモリ上で解凍
        using (var ms = new MemoryStream(compressedData))
        using (var zip = ZipFile.Read(ms))
        {
            var entry = zip["test.txt"];
            using (var extractedStream = new MemoryStream())
            {
                entry.Extract(extractedStream);
                extractedStream.Position = 0;
                using (var reader = new StreamReader(extractedStream))
                {
                    string content = reader.ReadToEnd();
                    Assert.Equal("テストデータ", content);
                }
            }
        }
    }
}

この例はxUnitを使った単体テストで、ファイルを使わずに圧縮・解凍の動作を検証しています。

実際のファイルI/Oを伴わないため高速で安定したテストが可能です。

破損ファイルテスト

ZIPファイルが破損している場合の動作確認は、堅牢なアプリケーションを作るうえで重要です。

DotNetZipは破損ファイルを読み込もうとするとBadReadExceptionなどの例外をスローします。

破損ファイルの作成方法

  • ZIPファイルの一部をテキストエディタで編集し、ヘッダーやデータを壊す
  • バイナリエディタでランダムにバイトを変更する
  • 不完全なダウンロードや転送で生成されたファイルを用意する

破損ファイルを使ったテスト例

using Ionic.Zip;
using System;
using System.IO;
using Xunit;
public class CorruptZipTests
{
    [Fact]
    public void ReadCorruptZip_ThrowsBadReadException()
    {
        // 破損ZIPファイルのパス
        string corruptZipPath = @"C:\Example\corrupt.zip";
        Assert.Throws<Ionic.Zip.BadReadException>(() =>
        {
            using (var zip = ZipFile.Read(corruptZipPath))
            {
                // 読み込み処理
            }
        });
    }
}

破損ファイルを用意し、例外が正しく発生するかを検証します。

例外処理のテストとして重要です。

パフォーマンス測定

圧縮・解凍処理のパフォーマンスは、ファイルサイズや数、圧縮レベルによって大きく変わります。

開発段階で処理時間を計測し、ボトルネックを把握することが重要です。

簡単な処理時間計測例

using Ionic.Zip;
using System;
using System.Diagnostics;
class Program
{
    static void Main()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        using (ZipFile zip = new ZipFile())
        {
            zip.AddFile(@"C:\Example\LargeFile.dat", "");
            zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
            zip.Save(@"C:\Example\Archive.zip");
        }
        sw.Stop();
        Console.WriteLine($"圧縮処理時間: {sw.ElapsedMilliseconds} ms");
    }
}

Stopwatchクラスを使い、圧縮処理の経過時間をミリ秒単位で計測しています。

複数回計測して平均を取るとより正確な評価が可能です。

パフォーマンス改善のポイント

  • 圧縮レベルを調整し、速度と圧縮率のバランスを最適化する
  • 並列処理や非同期処理を活用してCPUリソースを有効活用する
  • 不要なファイルの圧縮を避ける(例:既に圧縮済みのファイル)
  • メモリ使用量を監視し、過剰なメモリ消費を抑制する

テストとデバッグをしっかり行うことで、DotNetZipを使った圧縮・解凍処理の品質を高められます。

モックを活用した単体テスト、破損ファイルによる異常系テスト、そしてパフォーマンス測定を組み合わせて堅牢な実装を目指してください。

よくある利用ケース

DotNetZipはシンプルかつ強力なZIP操作機能を備えているため、さまざまなシナリオで活用されています。

ここでは、特に多くの開発現場で見られる「定期バックアップ」「ログ自動圧縮」「大容量データ転送」の3つの利用ケースについて詳しく解説いたします。

定期バックアップ

定期的に重要なファイルやフォルダーを圧縮してバックアップを取るケースは多くあります。

DotNetZipを使うと、スケジュールされたタスクやサービスから簡単にZIPファイルを作成し、保存先にまとめて管理できます。

実装例:日次バックアップスクリプト

using Ionic.Zip;
using System;
using System.IO;
class BackupProgram
{
    static void Main()
    {
        string sourceDir = @"C:\DataToBackup";
        string backupDir = @"D:\Backups";
        string backupFileName = $"Backup_{DateTime.Now:yyyyMMdd}.zip";
        string backupPath = Path.Combine(backupDir, backupFileName);
        try
        {
            Directory.CreateDirectory(backupDir);
            using (ZipFile zip = new ZipFile())
            {
                // フォルダー全体を圧縮
                zip.AddDirectory(sourceDir, "");
                // 圧縮レベルを標準に設定
                zip.CompressionLevel = Ionic.Zlib.CompressionLevel.Default;
                zip.Save(backupPath);
            }
            Console.WriteLine($"バックアップが完了しました: {backupPath}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"バックアップ中にエラーが発生しました: {ex.Message}");
        }
    }
}

この例では、指定フォルダーを丸ごと圧縮し、日付付きのZIPファイルとして保存しています。

Windowsのタスクスケジューラなどで定期実行すれば、自動バックアップが実現します。

ログ自動圧縮

アプリケーションのログファイルは日々増加し、ディスク容量を圧迫することがあります。

一定期間ごとに古いログを圧縮して保存することで、容量節約とログ管理の効率化が可能です。

実装例:7日以上前のログを圧縮

using Ionic.Zip;
using System;
using System.IO;
using System.Linq;
class LogCompressor
{
    static void Main()
    {
        string logDir = @"C:\AppLogs";
        string archiveDir = @"C:\AppLogs\Archive";
        Directory.CreateDirectory(archiveDir);
        var oldLogs = Directory.GetFiles(logDir, "*.log")
            .Where(f => File.GetLastWriteTime(f) < DateTime.Now.AddDays(-7))
            .ToList();
        if (oldLogs.Count == 0)
        {
            Console.WriteLine("圧縮対象の古いログファイルはありません。");
            return;
        }
        string archiveName = $"Logs_{DateTime.Now:yyyyMMddHHmmss}.zip";
        string archivePath = Path.Combine(archiveDir, archiveName);
        try
        {
            using (ZipFile zip = new ZipFile())
            {
                foreach (var logFile in oldLogs)
                {
                    zip.AddFile(logFile, "");
                }
                zip.Save(archivePath);
            }
            // 圧縮後に元のログファイルを削除
            foreach (var logFile in oldLogs)
            {
                File.Delete(logFile);
            }
            Console.WriteLine($"古いログを圧縮し、元ファイルを削除しました: {archivePath}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"ログ圧縮中にエラーが発生しました: {ex.Message}");
        }
    }
}

このコードは、7日以上前に更新されたログファイルをまとめてZIP圧縮し、圧縮後に元ファイルを削除します。

定期的に実行することでログの肥大化を防げます。

大容量データ転送

大容量のファイルや複数ファイルをネットワーク経由で転送する際、圧縮してデータ量を削減することは通信効率の向上に繋がります。

DotNetZipはストリーム対応もしているため、ファイルを圧縮しながら直接ネットワークに送信することも可能です。

実装例:圧縮しながらファイルをネットワーク送信

using Ionic.Zip;
using System;
using System.IO;
using System.Net.Sockets;
class NetworkSender
{
    static void Main()
    {
        string fileToSend = @"C:\LargeData\BigFile.dat";
        string serverIp = "192.168.1.100";
        int port = 9000;
        try
        {
            using (TcpClient client = new TcpClient(serverIp, port))
            using (NetworkStream netStream = client.GetStream())
            using (ZipFile zip = new ZipFile())
            {
                zip.AddFile(fileToSend, "");
                zip.Save(netStream);
            }
            Console.WriteLine("圧縮データをネットワーク経由で送信しました。");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"送信中にエラーが発生しました: {ex.Message}");
        }
    }
}

この例では、指定ファイルをZIP圧縮しながらTCPネットワークストリームに直接書き出しています。

受信側で解凍すれば、効率的に大容量データを転送できます。

これらの利用ケースはDotNetZipの基本機能を活かした典型的なシナリオです。

用途に応じて圧縮レベルやパスワード保護、ストリーム処理などを組み合わせることで、より高度な運用も可能です。

他ライブラリ比較

C#でZIPファイルの圧縮・解凍を行う際、DotNetZip以外にも代表的なライブラリがいくつか存在します。

ここでは、.NET標準のSystem.IO.Compression、オープンソースのSharpZipLibとDotNetZipを比較し、それぞれの特徴と使い分けの指針を詳しく解説いたします。

System.IO.Compression

System.IO.Compressionは、.NET Framework 4.5以降および.NET Core/.NET 5+で標準提供されている圧縮ライブラリです。

ZIPファイルの作成・解凍に加え、GZipやDeflate形式の圧縮もサポートしています。

特徴

  • 標準ライブラリ

追加の外部依存なしで利用可能です。

メンテナンスや互換性が高いでしょう。

  • APIのシンプルさ

ZipArchiveクラスを中心に、ストリームベースで直感的に操作できます。

  • パスワード保護非対応

標準ではZIPのパスワード保護や暗号化機能がないため、セキュリティが必要な場合は別途対応が必要でしょう。

  • 圧縮レベルの指定可能

CompressionLevel.FastestCompressionLevel.Optimalなど、速度と圧縮率のバランスを選べます。

  • UTF-8対応

ファイル名の文字コードはUTF-8に対応しています。

利用例

using System.IO.Compression;
using System.IO;
using (ZipArchive archive = ZipFile.Open("archive.zip", ZipArchiveMode.Create))
{
    archive.CreateEntryFromFile("file.txt", "file.txt", CompressionLevel.Optimal);
}

向いているケース

  • 追加ライブラリを使いたくない場合
  • パスワード保護が不要なシンプルな圧縮・解凍
  • .NET Coreや最新の.NET環境での利用

SharpZipLib

SharpZipLibは、オープンソースの.NET向け圧縮ライブラリで、ZIPだけでなく、TAR、GZip、BZip2など多様な圧縮形式をサポートしています。

特徴

  • 多様な圧縮形式対応

ZIP以外にも多くの圧縮形式を扱えるため、用途が広い。

  • パスワード保護対応

ZIPのパスワード保護に対応しているが、AES暗号化は限定的。

  • 細かい制御が可能

圧縮レベルや圧縮方法の詳細設定ができます。

  • やや複雑なAPI

DotNetZipや標準APIに比べてAPIがやや複雑で、学習コストが高いでしょう。

  • 活発なコミュニティ

GitHubで活発にメンテナンスされています。

利用例

using ICSharpCode.SharpZipLib.Zip;
using System.IO;
using (FileStream fs = File.Create("archive.zip"))
using (ZipOutputStream zipStream = new ZipOutputStream(fs))
{
    zipStream.SetLevel(9); // 圧縮レベル設定
    var entry = new ZipEntry("file.txt");
    zipStream.PutNextEntry(entry);
    using (FileStream fileStream = File.OpenRead("file.txt"))
    {
        fileStream.CopyTo(zipStream);
    }
    zipStream.CloseEntry();
}

向いているケース

  • 多様な圧縮形式を扱いたい場合
  • 圧縮処理を細かくカスタマイズしたい場合
  • AES暗号化は不要だがパスワード保護は必要な場合

使い分け指針

ライブラリメリットデメリット推奨シナリオ
DotNetZipパスワード保護(AES対応)、使いやすいAPI公式開発は停止、.NET Core以降の対応が限定的.NET Framework環境でパスワード付きZIPを扱う場合
System.IO.Compression標準ライブラリ、メンテナンス性高いパスワード保護非対応シンプルな圧縮・解凍、最新.NET環境での利用
SharpZipLib多形式対応、細かい制御可能APIが複雑、AES暗号化は限定的多様な圧縮形式を扱う、カスタマイズが必要な場合

選択のポイント

  • パスワード保護やAES暗号化が必要ならDotNetZip

ただし、最新の.NET Core/.NET 5+環境では動作検証が必要です。

  • 標準APIで十分ならSystem.IO.Compression

追加ライブラリを使いたくない場合やパスワード不要の用途に最適。

  • 多様な圧縮形式や高度なカスタマイズが必要ならSharpZipLib

圧縮形式の幅広さや細かい制御を重視する場合に向いています。

これらのライブラリはそれぞれ特徴が異なるため、プロジェクトの要件や環境に応じて最適なものを選択してください。

DotNetZipはパスワード保護が強みですが、メンテナンス状況や環境対応を考慮し、場合によっては標準APIやSharpZipLibの利用も検討しましょう。

注意点・トラブルシューティング

DotNetZipを使ったファイル圧縮・解凍では、開発や運用時にいくつかの注意点やトラブルが発生しやすいです。

ここでは特に「ファイルロック」「日本語パスの扱い」「大容量ファイルによるメモリ使用量」の3つのポイントについて詳しく解説いたします。

ファイルロック

ファイルロックは、圧縮や解凍処理中に対象ファイルが他のプロセスやアプリケーションによって使用中でアクセスできない状態を指します。

これにより、IOExceptionUnauthorizedAccessExceptionが発生し、処理が失敗することがあります。

発生しやすい状況

  • 圧縮対象のファイルが別のプログラムで開かれている(例:テキストエディタ、Excelなど)
  • 解凍先のファイルが既に開かれている
  • ウイルススキャンやバックアップソフトがファイルを一時的にロックしている

対策

  • ファイルの使用状況を事前にチェックする

ファイルが開かれていないか、アクセス可能かを確認してから処理を開始します。

  • 例外処理でリトライを実装する

一時的なロックの場合は、数秒待って再試行することで解決することがあります。

  • 解凍時の上書き設定を適切に行う

ExtractExistingFileAction.OverwriteSilentlyなどを使い、既存ファイルの上書きを許可します。

  • ユーザーにファイルを閉じるよう促す

UIがある場合は、ロックされているファイル名を表示して閉じるよう案内します。

例:リトライ処理の簡単な例

int retryCount = 3;
int delayMs = 1000;
for (int i = 0; i < retryCount; i++)
{
    try
    {
        using (ZipFile zip = ZipFile.Read(@"C:\Example\Archive.zip"))
        {
            zip.ExtractAll(@"C:\Example\Extract", ExtractExistingFileAction.OverwriteSilently);
        }
        break; // 成功したらループを抜ける
    }
    catch (IOException)
    {
        if (i == retryCount - 1) throw;
        System.Threading.Thread.Sleep(delayMs);
    }
}

日本語パス

日本語や全角文字を含むファイル名やフォルダー名は、ZIPファイル内で文字化けが起きやすい問題があります。

DotNetZipはUTF-8に対応していますが、環境やZIP作成時の設定によっては正しく扱えないことがあります。

発生原因

  • ZIPファイルの作成時に文字コードが適切に設定されていない
  • 解凍時の文字コード設定が異なる
  • 古いZIPツールやOS標準の解凍機能がUTF-8非対応

対策

  • AlternateEncodingAlternateEncodingUsageの設定

DotNetZipのZipFileオブジェクトに対して以下のように設定し、UTF-8でファイル名を扱います。

zip.AlternateEncoding = System.Text.Encoding.UTF8;
zip.AlternateEncodingUsage = ZipOption.Always;
  • ファイル名の正規化

必要に応じてファイル名をNFC(正規化フォームC)に変換してから追加します。

  • 環境依存の問題を考慮

解凍先の環境で文字化けが起きる場合は、別の解凍ツールを使うか、ファイル名を英数字に限定する運用も検討します。

例:UTF-8設定を含む圧縮コード

using (ZipFile zip = new ZipFile())
{
    zip.AlternateEncoding = System.Text.Encoding.UTF8;
    zip.AlternateEncodingUsage = ZipOption.Always;
    zip.AddFile(@"C:\Example\日本語ファイル.txt", "");
    zip.Save(@"C:\Example\Archive.zip");
}

大容量メモリ使用量

大きなファイルや多数のファイルを圧縮・解凍する際、DotNetZipはメモリを多く消費することがあります。

特にメモリ上に全データを展開したり、複数ファイルを一括処理する場合は注意が必要です。

発生しやすい状況

  • 数GB以上の大容量ファイルを圧縮・解凍する
  • メモリストリームを多用している
  • 複数ファイルを一度に大量に追加している

対策

  • ストリームを活用する

ファイル単位でストリームを使い、メモリに全ファイルを展開しないようにします。

  • 圧縮レベルを調整する

高圧縮レベルはCPU負荷だけでなくメモリ使用量も増えるため、適切なレベルを選択します。

  • 分割圧縮を検討する

大容量ファイルを複数に分割して圧縮・解凍する方法も有効。

  • メモリ使用量の監視

実行環境のメモリ状況を監視し、必要に応じて処理を中断・再試行します。

例:大容量ファイルのストリーム圧縮

using (FileStream fs = File.OpenRead(@"C:\Example\LargeFile.dat"))
using (FileStream zipFs = File.Create(@"C:\Example\LargeFile.zip"))
using (ZipFile zip = new ZipFile())
{
    zip.AddEntry("LargeFile.dat", fs);
    zip.Save(zipFs);
}

このようにファイルストリームを直接渡すことで、メモリに全ファイルを読み込まずに圧縮できます。

これらの注意点を理解し、適切な対策を講じることで、DotNetZipを使った圧縮・解凍処理のトラブルを未然に防ぎ、安定した動作を実現できます。

まとめ

この記事では、C#でのファイル圧縮・解凍に便利なDotNetZipライブラリの基本操作から応用テクニックまで幅広く解説しました。

導入方法や圧縮・解凍の基本、パス管理、暗号化設定、ストリーム活用、進捗管理、非同期処理、エラーハンドリング、テスト、よくある利用ケース、他ライブラリとの比較、注意点まで網羅しています。

これにより、実務でのZIP操作を効率的かつ安全に実装できる知識が身につきます。

関連記事

Back to top button
目次へ