連載
» 2015年02月03日 12時07分 UPDATE

.NET TIPS:ZIPファイルを解凍するには?(ZipArchive編)[C#、VB]

.NET Framework 4.5以降で提供されているZipArchiveクラスなどを使用して、ZIP形式のアーカイブファイルを手軽に扱う方法を解説する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Platform Development]
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

対象:.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ファイル
サンプルコードで使うZIPファイル サンプルコードで使うZIPファイル
上:拡張子が「.txt」のファイルを含めていくつかのファイルを用意し、それらをまとめた「SampleFiles.zip」という名前のZIPファイルを作成しておく。この画像は、作成したZIPファイルの内容をWindowsのエクスプローラーで表示したもの。
下:Visual Studioのプロジェクトに「Sample」というフォルダーを作り、そこに先ほどのZIPファイルをコピーし、プロジェクトに含める。Visual Studioのプロパティで、出力ディレクトリにコピーされるように設定しておく(赤枠内)。なお、Windowsランタイムアプリの場合は、出力ディレクトリにコピーせずに、[ビルドアクション]を[コンテンツ]にしておく。

ZipArchiveクラスとその関連クラス

 .NET 4.5で提供されたZIPアーカイブ関連のクラスには、次のものがある(いずれもSystem.IO.Compression名前空間)。

  • ZipArchiveクラス: ZIPアーカイブ形式の圧縮ファイルを扱うクラス。ZIPファイル内のエントリのコレクションを表すEntriesプロパティや、エントリを作成/取得するメソッドなどがある
  • ZipArchiveEntryクラス: ZIPアーカイブ形式の圧縮ファイルに格納されている個々のファイルを扱うクラス。ファイル名やファイルサイズなどのプロパティと、個々のファイルを開いてストリームを得るメソッドなどがある
  • ZipFileクラス: ZIPアーカイブ形式の圧縮ファイルを作成/展開するための静的メソッドが収められている(Windowsランタイムアプリでは利用不可)
  • ZipFileExtensionsクラス: ZIPアーカイブ形式の圧縮ファイルを作成/展開するための拡張メソッドが収められている(Windowsランタイムアプリでは利用不可)

 通常の.NET Frameworkのプログラムでは、ZipFileクラス/ZipFileExtensionsクラスのメソッドを使うと、簡潔にコードを記述できる。例えば、ZIPアーカイブ内の全ファイルを指定したディレクトリに展開するには、ZipFileExtensionsクラスのExtractToDirectory拡張メソッドを使うとよい。

ZIPファイルから特定の拡張子のファイルだけを展開するには?

 全てのファイルを展開するだけなら、上述のようにZipFileExtensionsクラスのExtractToDirectory拡張メソッドで簡単にできてしまう。本稿では、ZipArchiveEntryオブジェクトも扱いたいので、特定の拡張子のファイルだけを選択してディレクトリに書き出してみよう。

 処理の流れとしては、次のようになる。

  1. ZipFileクラスのOpenReadメソッドでZIPファイルを開いてZipArchiveオブジェクトを得る
  2. ZipArchiveオブジェクトのEntriesプロパティには、格納されているファイルの情報がZipArchiveEntryオブジェクトとして入っているので、特定の拡張子のものだけを選択する
  3. 選択したZipArchiveEntryオブジェクトのExtractToFileメソッド(ZipFileExtensionsクラスに定義されている拡張メソッド)を使って、フォルダーにファイルを書き出す

 コンソールプログラムとして作ったサンプルは次のコードのようになる。なお、プロジェクトの参照設定に、次の二つのファイルへの参照をあらかじめ追加しておいてほしい。

  • System.IO.Compression.dll
  • System.IO.Compression.FileSystem.dll

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

ZIPファイルから特定の拡張子のファイルだけを展開するコンソールプログラムの例(上:C#、下:VB)
このプログラムは、ファイルを書き出すフォルダーにすでに同名のファイルがあると例外が出る。繰り返して実行する前に、手動で削除しておいてほしい。
展開するファイルを選択する部分では、LINQのWhereメソッドOrderByメソッド(ともに、System.Linq名前空間のEnumerableクラスに定義された拡張メソッド)を使っている。その引数に与えているのはラムダ式だ*1
末尾には、Visual Studioからデバッグ実行したとき、コンソールがすぐに閉じてしまわないように「Console.ReadKey()」と記述してある。そこで何かキーを押すとプログラムは終了する。

 これを実行してみると、次のような結果になる(次の画像)。

実行結果
実行結果 実行結果
コンソールへの出力結果と(上)、展開先に指定したディレクトリをエクスプローラーで表示したもの(下)。 冒頭に掲げたZIPファイルの中身の画像と見比べてみてほしい。想定通りに、拡張子「.txt」のファイルだけが展開されている。また、ファイルのタイムスタンプも復元されている。

Windowsランタイムアプリの場合

 前述したように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

ZIPファイルから特定の拡張子のファイルだけを展開するWindowsランタイムアプリ用のメソッドの例(上:C#、下:VB)
このメソッドは、展開したファイル名のコレクションを返すようになっている。
処理の流れは先ほどのコンソールプログラムとほぼ同じなのだが、ファイルの読み書きを記述しなければならない分だけ複雑になる。

*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]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。