.NET Framework 4.5以降で提供されているZipArchiveクラスなどを使用して、ZIP形式のアーカイブファイルを手軽に扱う方法を解説する。
対象:.NET 4.5以降
ZIPアーカイブ形式の圧縮ファイルを簡単に扱う手段は、これまで.NET Frameworkに提供されてこなかった。そのため、サードパーティー製のライブラリや、Visual J#の再頒布可能パッケージなどを苦労して利用してきた。それが.NET Framework 4.5で提供されたZipArchiveクラス(System.IO.Compression名前空間)でサポートされたのである。標準のライブラリに入ったため、配布の心配をすることなく安心して使える。本稿では、ZipArchiveクラスを使ってZIPアーカイブからファイルを展開する方法を説明する。
適当なZIPファイルを「SampleFiles.zip」という名前で作成し、Visual Studioのプロジェクトに「Sample」というフォルダーを作ってその中に配置しておく。また、配置したZIPファイルは、Visual Studioの出力ディレクトリにコピーされるように設定しておく(次の画像)。
サンプルコードで使うZIPファイル.NET 4.5で提供されたZIPアーカイブ関連のクラスには、次のものがある(いずれもSystem.IO.Compression名前空間)。
通常の.NET Frameworkのプログラムでは、ZipFileクラス/ZipFileExtensionsクラスのメソッドを使うと、簡潔にコードを記述できる。例えば、ZIPアーカイブ内の全ファイルを指定したディレクトリに展開するには、ZipFileExtensionsクラスのExtractToDirectory拡張メソッドを使うとよい。
全てのファイルを展開するだけなら、上述のようにZipFileExtensionsクラスのExtractToDirectory拡張メソッドで簡単にできてしまう。本稿では、ZipArchiveEntryオブジェクトも扱いたいので、特定の拡張子のファイルだけを選択してディレクトリに書き出してみよう。
処理の流れとしては、次のようになる。
コンソールプログラムとして作ったサンプルは次のコードのようになる。なお、プロジェクトの参照設定に、次の二つのファイルへの参照をあらかじめ追加しておいてほしい。
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
class Program
{
  static void Main(string[] args)
  {
    // ZIPファイルのパス
    const string ZipPath = @".\Sample\SampleFiles.zip";
    // ファイルを書き出すフォルダーを作成する
    const string ExtractPath = @".\Extract";
    var directoryInfo = Directory.CreateDirectory(ExtractPath);
    // ZIPファイルを開いてZipArchiveオブジェクトを作る
    using (ZipArchive archive = ZipFile.OpenRead(ZipPath))
    {
      // 展開するファイルを選択する(ここでは、拡張子が".txt"のものとする)
      var allTextFiles
        = archive.Entries
          .Where(e => e.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
          .OrderBy(e => e.FullName);
      Console.WriteLine("全{0}ファイル(txtファイルは{1})",
                        archive.Entries.Count, allTextFiles.Count());
      // 選択したファイルを指定したフォルダーに書き出す
      foreach (ZipArchiveEntry entry in allTextFiles)
      {
        // ZipArchiveEntryオブジェクトのExtractToFileメソッドにフルパスを渡す
        entry.ExtractToFile(Path.Combine(ExtractPath, entry.FullName));
        Console.WriteLine("展開: {0}", entry.FullName);
      }
    }
#if DEBUG
    Console.ReadKey();
#endif
  }
}
Imports System.IO
Imports System.IO.Compression
Module Module1
  Sub Main()
    ' ZIPファイルのパス
    Const ZipPath As String = ".\Sample\SampleFiles.zip"
    ' ファイルを書き出すフォルダーを作成する
    Const ExtractPath As String = ".\Extract"
    Dim directoryInfo = Directory.CreateDirectory(ExtractPath)
    ' ZIPファイルを開いてZipArchiveオブジェクトを作る
    Using archive As ZipArchive = ZipFile.OpenRead(ZipPath)
      ' 展開するファイルを選択する(ここでは、拡張子が".txt"のものとする)
      Dim allTextFiles _
        = archive.Entries _
          .Where(Function(e) e.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase)) _
          .OrderBy(Function(e) e.FullName)
      Console.WriteLine("全{0}ファイル(txtファイルは{1})",
                        archive.Entries.Count, allTextFiles.Count())
      ' 選択したファイルを指定したフォルダーに書き出す
      For Each entry As ZipArchiveEntry In allTextFiles
        ' ZipArchiveEntryオブジェクトのExtractToFileメソッドにフルパスを渡す
        entry.ExtractToFile(Path.Combine(ExtractPath, entry.FullName))
        Console.WriteLine("展開: {0}", entry.FullName)
      Next
    End Using
#If DEBUG Then
    Console.ReadKey()
#End If
  End Sub
End Module
これを実行してみると、次のような結果になる(次の画像)。
実行結果前述したようにWindowsランタイムアプリではZipFileクラスとZipFileExtensionsクラスが利用できない。そのため、ファイルの読み書きは全てアプリ側で面倒を見なければならない。上のコンソールプログラムと同様な処理を行うWindowsランタイムアプリ用のメソッドは、次のコードのように書ける。
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
namespace dotNetTips1096VS2013.Pcl
{
  public class Class1
  {
    public static async Task<IEnumerable<string>> ExtractZipAsync()
    {
      // ZIPファイル
      const string ZipPath = @"Sample\SampleFiles.zip";
      var zipFile = await Windows.ApplicationModel.Package.Current
                          .InstalledLocation.GetFileAsync(ZipPath);
      // ファイルを書き出すフォルダーを作成する
      const string ExtractPath = @"Extract";
      var destFolder = await Windows.Storage.ApplicationData.Current
                             .LocalFolder.CreateFolderAsync(
                                ExtractPath,
                                Windows.Storage.CreationCollisionOption.OpenIfExists
                              );
 
      // ZIPファイルを開いてストリームを得る
      using (var zipStream = await zipFile.OpenReadAsync())
      // ZIPファイルのストリームからZipArchiveオブジェクトを作る
      using (var archive = new ZipArchive(zipStream.AsStream()))
      {
        // 展開するファイルを選択する(ここでは、拡張子が".txt"のものとする)
        var allTextFiles
              = archive.Entries
                .Where(e => e.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
                .OrderBy(e => e.FullName);
        // 選択したファイルを指定したフォルダーに書き出す
        foreach (var archiveEntry in allTextFiles)
        {
          // 書き込み先のファイルを用意し、
          var destFile = await destFolder.CreateFileAsync(
                                            archiveEntry.FullName,
                                            Windows.Storage.CreationCollisionOption.ReplaceExisting
                                          );
          // ZipArchiveEntryオブジェクトからストリームを取り出し、
          using (var archiveEntryStream = archiveEntry.Open())
          // 書き込み先のファイルからもストリームを開き、
          using (var destStream = await destFile.OpenStreamForWriteAsync())
          {
            // ストリームからストリームへデータをコピーする
            await archiveEntryStream.CopyToAsync(destStream);
          }
        }
        return allTextFiles.Select(e => e.FullName);
      }
    }
  }
}
Imports System.IO.Compression
Public Class Class1
  Public Shared Async Function ExtractZipAsync() As Task(Of IEnumerable(Of String))
    ' ZIPファイル
    Const ZipPath As String = "Sample\SampleFiles.zip"
    Dim zipFile = Await Windows.ApplicationModel.Package.Current _
                        .InstalledLocation.GetFileAsync(ZipPath)
    ' ファイルを書き出すフォルダーを作成する
    Const ExtractPath As String = "Extract"
    Dim destFolder = Await Windows.Storage.ApplicationData.Current _
                           .LocalFolder.CreateFolderAsync( _
                             ExtractPath, _
                             Windows.Storage.CreationCollisionOption.OpenIfExists _
                           )
    ' ZIPファイルを開いてストリームを得る
    Using zipStream = Await zipFile.OpenReadAsync()
      ' ZIPファイルのストリームからZipArchiveオブジェクトを作る
      Using archive = New ZipArchive(zipStream.AsStream())
        ' 展開するファイルを選択する(ここでは、拡張子が".txt"のものとする)
        Dim allTextFiles _
            = archive.Entries _
              .Where(Function(e) e.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase)) _
              .OrderBy(Function(e) e.FullName)
        ' 選択したファイルを指定したフォルダーに書き出す
        For Each archiveEntry In allTextFiles
          ' 書き込み先のファイルを用意し、
          Dim destFile = Await destFolder.CreateFileAsync( _
                                            archiveEntry.FullName, _
                                            Windows.Storage.CreationCollisionOption.ReplaceExisting _
                                          )
          ' ZipArchiveEntryオブジェクトからストリームを取り出し、
          Using archiveEntryStream = archiveEntry.Open()
            ' 書き込み先のファイルからもストリームを開き、
            Using destStream = Await destFile.OpenStreamForWriteAsync()
              ' ストリームからストリームへデータをコピーする
              Await archiveEntryStream.CopyToAsync(destStream)
            End Using
          End Using
        Next
        Return allTextFiles.Select(Function(e) e.FullName)
      End Using
    End Using
  End Function
End Class
*1 Whereメソッド/OrderByメソッドの引数には、ラムダ式を与える。ラムダ式について詳しくは、次のMSDNのドキュメントを参照していただきたい。
利用可能バージョン:.NET Framework 4.5以降
カテゴリ:クラスライブラリ 処理対象:ディレクトリ&ファイル
使用ライブラリ:ZipArchiveクラス(System.IO.Compression名前空間)
使用ライブラリ:ZipArchiveEntryクラス(System.IO.Compression名前空間)
使用ライブラリ:ZipFileクラス(System.IO.Compression名前空間)
使用ライブラリ:ZipFileExtensionsクラス(System.IO.Compression名前空間)
関連TIPS:[ASP.NET]動的に圧縮ファイルを生成するには?
関連TIPS:LINQ:文字列コレクションで「LIKE検索」(部分一致検索)をするには?[C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.