ZipArchiveクラスはZIPアーカイブの作成・展開・編集を行うためのクラス。 .NET Framework 4.5以降で使用することができる。 ZipArchiveクラスを使う場合は、System.IO.Compression.dllを参照に追加する必要がある。

§1 ZipArchiveクラス

ZipArchiveクラスはインスタンスを作成する際にStreamZipArchiveModeを指定する。 ZipArchiveクラスでできる操作と動作は指定したZipArchiveModeによって次のように変わる。

ZipArchiveModeとZipArchiveクラスの動作
ZipArchiveMode ZipArchiveクラスの動作
ZipArchiveMode.Read StreamからZIPアーカイブを読み込み、展開する
ZipArchiveMode.Create ZIPアーカイブを作成し、エントリを圧縮してStreamに書き込む
ZipArchiveMode.Update ZipArchiveMode.ReadとZipArchiveMode.Createの操作の両方を行う
StreamからZIPアーカイブを読み込んで展開し、ZIPアーカイブの内容を変更した結果を再び圧縮してStreamに書き込む

ZipArchiveクラスのコンストラクタでは、エントリ名(ZIPアーカイブ内のファイル名)で使用するエンコーディングをEncodingで指定することができる。

§1.1 ZIPアーカイブの展開

ZipArchiveクラスを使ったZIPアーカイブの展開は次の順序で行う。

  1. ZIPアーカイブのStreamを開く
  2. 開いたStreamとZipArchiveMode.Readを指定してZipArchiveを作成する
  3. ZipArchive.EntriesプロパティもしくはZipArchive.GetEntryメソッドで展開したいファイルのZipArchiveEntryを取得する
  4. 取得したZipArchiveEntryのOpenメソッドメソッドを呼び出してエントリのStreamを取得する
  5. 取得したエントリのStreamから読み込むことで内容が展開される

次の例は、ZIPアーカイブを展開し、フォルダ構造を維持したまますべてのエントリを展開するもの。 なお、ZipFile.ExtractToDirectoryメソッドを使うと、以下の例と同等のコードをメソッド呼び出し1つで実現できる。 また、ファイルへの展開には拡張メソッドExtractToFileを使うこともできる。

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

class Sample {
  static void Main()
  {
    // ZIPアーカイブから展開したファイルを保存するディレクトリ
    const string extractDirectory = "extract";

    // sample.zipを開く
    using (var zipStream = File.OpenRead("sample.zip")) {
      // ストリームを読み込みZIPアーカイブを展開するためのZipArchiveを作成
      using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Read)) {
        // ZIPアーカイブ内の全エントリを列挙
        foreach (var entry in archive.Entries) {
          // エントリのアーカイブ内での相対パス名、サイズ、最終更新日を表示
          Console.Write("{0} ({1:N0} bytes, {2})", entry.FullName, entry.Length, entry.LastWriteTime);

          // エントリの展開先となるファイル名を作成
          var extractToFileName = Path.Combine(extractDirectory, entry.FullName);

          // サブディレクトリが存在しない場合は、先に作成しておく
          var extractToSubDirectory = Path.GetDirectoryName(extractToFileName);

          if (!Directory.Exists(extractToSubDirectory))
            Directory.CreateDirectory(extractToSubDirectory);

          // エントリを展開してファイルに保存する
          using (var entryStream = entry.Open()) {
            using (var extractToStream = File.Create(extractToFileName)) {
              entryStream.CopyTo(extractToStream);
            }
          }

          Console.WriteLine(" -> extracted to '{0}'", extractToFileName);
        }
      }
    }
  }
}
実行結果例
sample.txt (13 bytes, 2013/03/22 0:29:52 +09:00) -> extracted to 'extract\sample.txt'
サンプル.txt (21 bytes, 2013/03/22 0:29:52 +09:00) -> extracted to 'extract\サンプル.txt'
フォルダ1\a.txt (1,024 bytes, 2013/03/22 0:29:52 +09:00) -> extracted to 'extract\フォルダ1\a.txt'
フォルダ1\b.txt (1,024 bytes, 2013/03/22 0:29:52 +09:00) -> extracted to 'extract\フォルダ1\b.txt'
フォルダ1\c.txt (1,024 bytes, 2013/03/22 0:29:52 +09:00) -> extracted to 'extract\フォルダ1\c.txt'

§1.2 ZIPアーカイブの作成

ZipArchiveクラスを使ったZIPアーカイブの作成は次の順序で行う。

  1. ZIPアーカイブを書き込むためのStreamを開く
  2. 開いたStreamとZipArchiveMode.Createを指定してZipArchiveを作成する
  3. ZipArchive.CreateEntryメソッドでZipArchiveEntryを作成する
  4. 作成したZipArchiveEntryのOpenメソッドメソッドを呼び出してエントリのStreamを取得する
  5. 取得したエントリのStreamに書き込む事で圧縮して格納される

次の例は、フォルダ構造を維持したままフォルダ内のファイルを圧縮してZIPアーカイブを作成するもの。 なお、ZipFile.CreateFromDirectoryメソッドを使うと、以下の例と同等のコードをメソッド呼び出し1つで実現できる。 また、ファイルからZipArchiveEntryを作成するには拡張メソッドCreateEntryFromFileを使うこともできる。

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

class Sample {
  static void Main()
  {
    // ZIPアーカイブとして圧縮したいファイルが存在するディレクトリ
    const string sourceDirectory = "source";

    // ZIPアーカイブとして圧縮するテスト用のファイルを用意する
    CreateTestFiles(sourceDirectory);

    // sample.zipを作成して開く
    using (var zipStream = File.Create("sample.zip")) {
      // ZIPアーカイブを作成してストリームに書き込むためのZipArchiveを作成
      using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) {
        // 相対パスを取得するために、ディレクトリのフルパスを区切り文字付きで取得しておく
        var directory = Path.GetFullPath(sourceDirectory) + Path.DirectorySeparatorChar;

        // ディレクトリ内にある全ての階層のファイルを列挙
        foreach (var file in Directory.EnumerateFiles(sourceDirectory, "*", SearchOption.AllDirectories)) {
          // エントリ名として使用するためにルートディレクトリからの相対パスを取得
          var entryName = Path.GetFullPath(file).Replace(directory, string.Empty);

          // エントリを作成
          var entry = archive.CreateEntry(entryName);

          // ファイルを開いてエントリに書き込む (圧縮する)
          using (var sourceStream = File.OpenRead(file)) {
            using (var entryStream = entry.Open()) {
              sourceStream.CopyTo(entryStream);
            }
          }

          Console.WriteLine("{0} -> {1}", file, entryName);
        }
      }
    }
  }

  private static void CreateTestFiles(string dir)
  {
    if (Directory.Exists(dir))
      Directory.Delete(dir, true);

    Directory.CreateDirectory(dir);

    File.WriteAllText(Path.Combine(dir, "sample.txt"), "Hello, world!");
    File.WriteAllText(Path.Combine(dir, "サンプル.txt"), "こんにちは世界");

    var subdir = Path.Combine(dir, "フォルダ1");

    Directory.CreateDirectory(subdir);

    File.WriteAllText(Path.Combine(subdir, "a.txt"), new string('a', 1024));
    File.WriteAllText(Path.Combine(subdir, "b.txt"), new string('b', 1024));
    File.WriteAllText(Path.Combine(subdir, "c.txt"), new string('c', 1024));
  }
}
実行結果
source\sample.txt -> sample.txt
source\サンプル.txt -> サンプル.txt
source\フォルダ1\a.txt -> フォルダ1\a.txt
source\フォルダ1\b.txt -> フォルダ1\b.txt
source\フォルダ1\c.txt -> フォルダ1\c.txt

§1.2.1 圧縮レベルの指定

ZipArchive.CreateEntryメソッドでZipArchiveEntryを作成する際、同時に引数で圧縮レベルを指定することができる。 圧縮レベルの指定に使うCompressionLevel列挙体の値には次のようなものがある。

CompressionLevel
CompressionLevel 圧縮レベル
CompressionLevel.Optimal 圧縮率優先
CompressionLevel.Fastest 圧縮速度優先
CompressionLevel.NoCompression 無圧縮

§1.3 ZIPアーカイブの変更・更新

ZipArchiveクラスを使ったZIPアーカイブの変更・更新は次の順序で行う。

  1. ZIPアーカイブを読み込むためのStreamを開く (このStreamは読み書き可能である必要がある)
  2. 開いたStreamとZipArchiveMode.Updateを指定してZipArchiveを作成する
  3. ZIPアーカイブの作成の場合と同様の手順でZipArchiveEntryを作成してファイルを追加する
  4. もしくはZIPアーカイブの展開の場合と同様の手順でZipArchiveEntryを取得し、Deleteメソッドで不要なエントリを削除する
  5. 変更した内容はStreamに書き込まれてZIPアーカイブは更新される

次の例は、ZIPアーカイブを開いて、ZIPアーカイブ内に格納されているフォルダを削除するもの。

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

class Sample {
  static void Main()
  {
    // 既存のZIPアーカイブsample.zipを開く
    using (var zipStream = File.Open("sample.zip", FileMode.Open)) {
      // ストリームを読み込みZIPアーカイブを変更・更新するためのZipArchiveを作成
      using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Update)) {
        // ZIPアーカイブ内の全エントリを列挙
        //   ZipArchiveEntry.Deleteメソッドでエントリを削除するとZipArchive.Entriesからも
        //   削除されるが、foreach内でDeleteすると列挙中にコレクションを変更することになる。
        //   そのため、ToArray()メソッドで配列に変換するなどして直接ZipArchive.Entriesの
        //   コレクションを列挙しないようにする。
        foreach (var entry in archive.Entries.ToArray()) {
          // エントリ名(相対パス)が"フォルダ1\"で始まるエントリを削除
          // (アーカイブ内のフォルダを削除する)
          if (entry.FullName.StartsWith("フォルダ1\\")) {
            entry.Delete();

            Console.WriteLine("deleted '{0}'", entry.FullName);
          }
        }
      }
    }
  }
}
実行結果
deleted 'フォルダ1\a.txt'
deleted 'フォルダ1\b.txt'
deleted 'フォルダ1\c.txt'

§2 ZipFileクラス

ZipFileクラスには、ZIPアーカイブを扱う際によく行われる操作に対応するユーティリティメソッドが用意されている。 フォルダをまるごとZIPアーカイブとして圧縮したり、逆にZIPアーカイブをフォルダに展開するといった単純な操作は、ZipFileクラスのメソッドを呼び出すだけで行うことができる。

アセンブリSystem.IO.Compression.dllに含まれるZipArchiveクラスに対して、ZipFileクラスはSystem.IO.Compression.FileSystem.dllに含まれるので、使う場合にはこのアセンブリも参照に含める必要がある。

§2.1 ZIPアーカイブの展開

ZipFile.ExtractToDirectoryメソッドを使うと、ZIPアーカイブを任意のフォルダに展開できる。

// csc /r:System.IO.Compression.dll /r:System.IO.Compression.FileSystem.dll Sample.cs
using System;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    // sample.zipを展開し、展開したファイルをフォルダ"extract"以下に格納する
    ZipFile.ExtractToDirectory("sample.zip", "extract");
  }
}

上記の例と同等の動作をするコードをZipArchiveクラスを使って記述した例

§2.2 ZIPアーカイブの作成

ZipFile.CreateFromDirectoryメソッドを使うと、指定したフォルダの内容を圧縮してZIPアーカイブを作成できる。

引数includeBaseDirectoryにtrueを指定した場合、アーカイブされるファイルにはディレクトリ名も含まれる(フォルダ構造が維持される)。 falseを指定した場合は、ファイルのみとなる(フラットな構造でアーカイブされる)。 falseを指定した場合、既に同名のファイルがアーカイブ内に存在する場合はIOExceptionがスローされる。

// csc /r:System.IO.Compression.dll /r:System.IO.Compression.FileSystem.dll Sample.cs
using System;
using System.IO;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    // ZIPアーカイブとして圧縮したいファイルが存在するディレクトリ
    const string sourceDirectory = "source";

    // ZIPアーカイブとして圧縮するテスト用のファイルを用意する
    CreateTestFiles(sourceDirectory);

    // フォルダ"source"のすべてのファイルを、フォルダ構造を維持したままsample.zipとして圧縮する
    ZipFile.CreateFromDirectory(sourceDirectory, "sample.zip", CompressionLevel.Fastest, true);
  }

  private static void CreateTestFiles(string dir)
  {
    if (Directory.Exists(dir))
      Directory.Delete(dir, true);

    Directory.CreateDirectory(dir);

    File.WriteAllText(Path.Combine(dir, "sample.txt"), "Hello, world!");
    File.WriteAllText(Path.Combine(dir, "サンプル.txt"), "こんにちは世界");

    var subdir = Path.Combine(dir, "フォルダ1");

    Directory.CreateDirectory(subdir);

    File.WriteAllText(Path.Combine(subdir, "a.txt"), new string('a', 1024));
    File.WriteAllText(Path.Combine(subdir, "b.txt"), new string('b', 1024));
    File.WriteAllText(Path.Combine(subdir, "c.txt"), new string('c', 1024));
  }
}

上記の例と同等の動作をするコードをZipArchiveクラスを使って記述した例

§3 拡張メソッド

System.IO.Compression.FileSystem.dllを参照に追加すると、ZipArchiveクラスおよびZipArchiveEntryクラスに拡張メソッドが追加される。 拡張メソッドを使うことにより、ZIPアーカイブの操作をよりシンプルに記述できる。

§3.1 ExtractToDirectory

ExtractToDirectoryメソッドはZipArchiveクラスに対して追加される拡張メソッドで、ZipArchiveに含まれるすべてのファイルを指定したフォルダに展開して保存する。  従って、ExtractToDirectoryはZipFile.ExtractToDirectoryと同等の動作を行うメソッドとなる。

// csc /r:System.IO.Compression.dll /r:System.IO.Compression.FileSystem.dll Sample.cs
using System;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    // sample.zipを開いてZIPアーカイブを展開するためのZipArchiveを作成
    using (var archive = ZipFile.Open("sample.zip", ZipArchiveMode.Read)) {
      // ZipArchiveからファイルを展開してフォルダ"extract"以下に格納する
      archive.ExtractToDirectory("extract");
    }
  }
}

§3.2 ExtractToFile

ExtractToFileメソッドはZipArchiveEntryクラス対して追加される拡張メソッドで、ZipArchiveEntryの内容を展開して指定したファイル名で保存することができる。 このメソッドは展開する際のファイル名を変更したり、個別に指定したりしたい場合などに使うことができる。

// csc /r:System.IO.Compression.dll /r:System.IO.Compression.FileSystem.dll Sample.cs
using System;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    // sample.zipを開いてZIPアーカイブを展開するためのZipArchiveを作成
    using (var archive = ZipFile.Open("sample.zip", ZipArchiveMode.Read)) {
      // ZIPアーカイブ内の全エントリを列挙
      for (int index = 0; index < archive.Entries.Count; index++) {
        var entry = archive.Entries[index];

        // エントリを展開し、インデックスをファイル名に使用して保存
        entry.ExtractToFile(string.Format("{0}.tmp", index));
      }
    }
  }
}

§3.3 CreateEntryFromFile

CreateEntryFromFileメソッドはZipArchiveクラス対して追加される拡張メソッドで、指定したファイルの内容でZipArchiveEntryを作成し、ZipArchiveに追加することができる。

// csc /r:System.IO.Compression.dll /r:System.IO.Compression.FileSystem.dll Sample.cs
using System;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    // ZIPアーカイブを作成してsample.zipに書き込むためのZipArchiveを作成
    using (var archive = ZipFile.Open("sample.zip", ZipArchiveMode.Update)) {
      // ファイル"a.txt"をエントリ名"1"でZipArchiveに追加
      var entry = archive.CreateEntryFromFile("a.txt", "1");

      // 追加したエントリの変更日時に別の値を設定
      // (ZipArchiveMode.CreateでZipArchiveを作成した場合は、プロパティを変更するとIOExceptionとなる)
      entry.LastWriteTime = new DateTimeOffset(2000, 1, 23, 4, 56, 7, new TimeSpan(8, 0, 0));

      // さらに別のファイルをZipArchiveに追加
      archive.CreateEntryFromFile("b.txt", "2");
      archive.CreateEntryFromFile("c.txt", "3");
    }
  }
}