C#でZipファイルを解凍・圧縮する

C#でZipファイルを扱う方法を紹介したいと思います。この機能は、結構最近追加されたため.NET4.5以上で利用可能です。

簡単なファイル解凍・圧縮

.NET Framework ではまずプロジェクトの参照の追加より、以下のアセンブリを参照に追加します。

.NET Core 以降は、最初から使用可能なので追加の指定は必要ありません。

System.IO.Compression.FileSystem.dll

圧縮・解凍を単純にするだけであれば、System.IO.Compression の参照は必要ありません。

Zipの解凍

以下のような中身になってるファイルがあった場合、まるごと回答する方法です。

Test.zip/
  /Test_001.txt
  /Test_002.txt
  /Text_003.txt
  /Dir_001
    /Sub_Text_001.txt
    /Sut_Test_002.txt
    /Sut_Text_003.txt

Zip の回答は1行で記述することができます。第3引数にエンコードを指定することもできます。

解凍時にファイルにマルチバイト文字が含まれていて結果が文字化けするため、エンコードを指定したほうが安全です。

using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            ZipFile.ExtractToDirectory(@"E:\Test.zip", @"E:\Test";
            //ZipFile.ExtractToDirectory(@"E:\Test.zip", @"E:\Test", Encoding.UTF8);
        }
    }
}

実行後のE:\Testの内容はzipの内容がそのまま展開されます。

E:\TEST/
  /Test_001.txt
  /Test_002.txt
  /Text_003.txt
  /Dir_001
    /Sub_Text_001.txt
    /Sut_Test_002.txt
    /Sut_Text_003.txt

フォルダの圧縮

あるフォルダを Zip ファイルに圧縮します。こちらも ZipFile.CreateFromDirectory というメソッド1行で記述できます。

using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            ZipFile.CreateFromDirectory(@"E:\Test", @"E:\Test.zip");

            // (2) ZipFile.CreateFromDirectory(@"E:\Test", @"E:\Test.zip",
            //           CompressionLevel.Fastest, true);

            // (3) ZipFile.CreateFromDirectory(@"E:\Test", @"E:\Test.zip",
            //           CompressionLevel.Fastest, true, Encoding.UTF8);
        }
    }
}

実行後は以下構造のZipファイルが作成されます。

Test.zip/
  /Test_001.txt
  /Test_002.txt
  /Text_003.txt
  /Dir_001
    /Sub_Text_001.txt
    /Sut_Test_002.txt
    /Sut_Text_003.txt

ちなみに、既に圧縮先にファイルがあった場合、以下例外が発生します。

System.IO.IOException: 'ファイル 'E:\Test.zip' は既に存在します。'

また、コード中の(2)のオーバーロードにて、第3引数にて圧縮レベルを以下の3種類から選べます。

識別子 意味
Optimal 高い圧縮率
Fastest 低い圧縮率
NoCompression 無圧縮

また、第4引数で、ルートディレクトリをアーカイブに含めるかどうかを指定できます。指定しない場合はfalseとなっていて、trueを指定すると以下のようにzipにルートフォルダが作成されます。

Test.zip/
  /Test // ★この階層を含むzipになる
    /Test_001.txt
    /Test_002.txt
    /Text_003.txt
    /Dir_001
      /Sub_Text_001.txt
      /Sut_Test_002.txt
      /Sut_Text_003.txt

パスワード指定などはコノライブラリではできません。

その他のZip操作

こちらでは、zipファイルの内容を1つ1つ表示したり、ファイルを別々の場所から集めてzipにしたりなど、アーカイブに対する自由な操作を行います。

今回は、System.IO.Compressionを参照に追加します。

Zip内のファイルを列挙する

ZipFile.Openを呼び出すとZipArchiveクラスを得られそこからZipFileに対する細かい操作を行えるようになります。

以下例では、ZipArchive.Entriesより得られるZipArchiveEntry.FullNameによって圧縮ファイル内のファイルをすべて列挙します。

using System;
using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            //ZipArchive archive = ZipFile.Open(@"E:\Test.zip", ZipArchiveMode.Read);
            using (ZipArchive archive = ZipFile.OpenRead(@"E:\Test.zip"))
            {
                foreach(ZipArchiveEntry entry in archive.Entries)
                {
                    Console.WriteLine(entry.FullName);
                }
            }
        }
    }
}

上記コードを実行すると以下出力になります。

> Dir_001/
> Dir_001/Sub_Text_001.txt
> Dir_001/Sut_Test_002.txt
> Dir_001/Sut_Text_003.txt
> Test_001.txt
> Test_002.txt
> Text_003.txt

1つだけファイルを取り出す

これまで取り扱っていたアーカイブ内から"Dir_001/Sut_Text_003.txt"を取り出してHDDに保存します。

先ほどとコードはほとんど同じで、ZipArchiveEntryのExtractToFileをファイルパス付で呼び出します。

using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            using (ZipArchive archive = ZipFile.OpenRead(@"E:\Test.zip"))
            {
                foreach(ZipArchiveEntry entry in archive.Entries)
                {
                    if(entry.FullName == "Dir_001/Sut_Text_003.txt")
                    {
                        // ここでこのファイルの保存先を指定する
                        entry.ExtractToFile(@"E:\" + entry.Name);
                    }
                }
            }
        }
    }
}

既存のZipにファイルを追加する

アーカイブ内に1つファイルを追加するには、ZipFile.Open時に書き込みモード = ZipArchiveMode.Update を指定してアーカイブを開きます。そのあとに、ZipArchive.CreateEntryを用いてStreamを取得しそこへ書き込みたいテキストを書き込んでいきます。

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

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            using (ZipArchive archive = ZipFile.Open(@"E:\Test.zip", ZipArchiveMode.Update))
            //using (ZipArchive archive = ZipFile.OpenRead(@"E:\Test.zip"))
            {
                ZipArchiveEntry item = 
                    archive.CreateEntry("Dir_001/Sub_Text_004.txt",
                        CompressionLevel.NoCompression);

                // (1) 自分でStreamに書き込む
                using (Stream stream = item.Open())
                using (var sw = new StreamWriter(stream))
                {
                    sw.WriteLine("New Text");
                }

                // (2) ファイルを直接指定してファイルを追加
                archive.CreateEntryFromFile(@"E:\Sub_Text_005.txt",
                    "Dir_002/Sub_Text_005.txt", CompressionLevel.NoCompression);

                archive.CreateEntryFromFile(@"E:\Sub_Text_006.txt",
                    "Dir_002/Sub_Text_006.txt", CompressionLevel.NoCompression);
            }
        }
    }
}

(1)では自分でStreamを操作してふぃあるを追加しています。(2)ではディスク上の既存のファイルを指定してアーカイブに追加しています。(1)のほうが自由度が高いですが、(2)のほうが操作が簡単です。

C#でZipファイルを操作する方法は以上です。

関連記事

このライブラリでフォルダを追加するには、「フォルダをZip内にエントリーとして追加する」→「そのエントリーの中にファイルを追加する」という操作が必要です。なので、このフォルダを指定して中身を全部追加するという処理を書くのは結構大変だったりします。

なのでフォルダを一括で追加する方法は別の記事にまとめました。

takap-tech.com