【C#でエクセル解読】
ExcelDataReaderの実装とパフォーマンス測定


(最終更新日:

記事イメージ

C#でエクセルファイルを扱うライブラリは、ClosedXMLEPPlusなど完成度の高いものが存在していますが、XLS形式(エクセル2003以前の形式)には非対応のものが少なくありません。他方、ExcelDataReaderライブラリは書き込みができないという制約の反面、XLS形式にも対応している上に読み込み速度も高速です。

エクセル2007以降のXLSX形式登場により、XLSファイルを新たに作成することは減りました。他方、昔のファイルを解読しないといけないといったタスクはまだまだ現役です。また、後述する通り読み込み速度は速い方なので、読み取りのみであれば有力な選択肢と言えます。

本記事では、ExcelDataReaderライブラリの実装とパフォーマンス測定を紹介します。尚、C#でエクセル操作関連については以下関連記事もご参考ください。

関連記事:【C#でエクセル操作】ClosedXMLの特徴と操作まとめ
関連記事:【C#でエクセル操作】EPPlus操作まとめ
関連記事:【C#×Excelライブラリ選定】及びEPPlus vs ClosedXML 処理速度・概要比較

ExcelDataReaderの導入時に検討するべきこと。書き込み不可、書式読み取り不可。

ExcelDataReaderはエクセルのセル値読み取りに特化したライブラリです。ClosedXMLEPPlusとの大きな違いは、メリットとしてXLS形式(Excel2003以前フォーマット)に対応可能、デメリットとして書き込み・編集ができないことが挙げられます。

また、前2者のライブラリはセルの書式(背景カラー、罫線等)も読み取り可能でしたが、ExcelDataReaderはあくまでセルの値のみ読み取り可能です。書式読み取りまで視野に入れるなら、他のライブラリにするべきでしょう。(本件、引き続き調査中。参考:Read excel sheet internal formatting./GitHub

ExcelDataReaderはMITライセンスで提供されている

ExcelDataReaderはMITライセンスで提供されています。MITライセンスは、著作権表示等を前提として商用利用等も可能なライセンス体系です。

詳しくはGitHubにライセンス本文が公開されていますのでご確認ください。

【社内PR】チーム・ウォーク

IExcelDataReader.AsDataSetメソッドを利用した解読の実装

他ライブラリとの文法が近く、直感的にも理解しやすいAsDataSetメソッドを使用してエクセルファイルのセル値を読み込みます。NuGetからの導入とコード記述の2段階で解説します。

NuGetからの導入

下図のように、ExcelDataReaderとExcelDataReader.DataSetをプロジェクトにインストールします。

尚、今回はAsDataSetメソッドを使用するためExcelDataReader.DataSetも併せてインストールしていますが、ExcelDataReaderにはAsDataSetメソッドのほかもう1通りのデータ取り出し方法(Readerメソッド)が用意されており、そちらを利用する場合は不要となります。

ExcelDataReaderとExcelDataReader.DataSetをインストール
ExcelDataReaderとExcelDataReader.DataSetをインストール

ExcelDataReaderだけインストールすると、AsDataSetメソッドが存在しない状態に

上述の通り、AsDataSetメソッドを利用するにはNuGetからExcelDataReader.DataSetも併せてインストールする必要があります。ExcelDataReaderだけの場合、以下のエラーによりコンパイルすることが出来ません。

'IExcelDataReader' に 'AsDataSet' の定義が含まれておらず、型 'IExcelDataReader' の最初の引数を受け付けるアクセス可能な拡張メソッド 'AsDataSet' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください

'IExcelDataReader' does not contain a definition for 'AsDataSet'

シート内の全セルの値取得をするサンプルコード記述

例として、先頭のシートを取得して、シート内の全セルの値取得を行います。尚、セルの値はobject型で取得されます。

C#
using ExcelDataReader;

//エンコードを登録
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
ExcelReaderConfiguration config = new() {
    FallbackEncoding = Encoding.GetEncoding("Shift_JIS")
};

//ファイル読み込み→データセット生成
using FileStream stream = File.Open(@"C:\users\sakai\desktop\test.xls", FileMode.Open, FileAccess.Read);
IExcelDataReader reader = ExcelReaderFactory.CreateReader(stream, config);
var dataset = reader.AsDataSet();

//先頭のワークシート取得
var worksheet = dataset.Tables[dataset.Tables.Count - 1];

//セルの値を逐次読み込み
for (var row = 0; row < worksheet.Rows.Count; row++) {
    for (var col = 0; col < worksheet.Columns.Count; col++) {
        object? cellValue = worksheet.Rows[row][col];
    }
}

reader.Close();

尚、上記におけるdataset.TablesプロパティはDataTable型のコレクションとして取得され、ICollectionを承継しているためForeach等が使用可能です。また、シート名はDataTable.TableNameに格納されます。

セル解読速度:EPPlusよりは遅いが、無償ライセンスの範囲であれば十分有力となる速度

ExcelDataReaderは、XLS形式のほか、CSVやXLSX形式にも対応しています。同じくC#で利用可能なClosedXMLEPPlusと解読速度を比較してみます。下図のように、A~E列に一番下の行まで値を敷き詰めたシート(1048576×5セル)を含むファイル(容量19.2 MB)を解読させてみます。

尚、比較に使用する各ライブラリのバージョンは次の通りです。

ExcelDataReader3.6.0
ClosedXML0.100.3
EPPlus4.5.3.3
使用するエクセルファイル。A~E列に一番下の行まで値を敷き詰めたシートを使用します。
使用するエクセルファイル

結果:EPPlus>ExcelDataReader>>ClosedXMLの順位。MITライセンス限定かつ読み込みのみのタスクなら有力

下表のとおり、EPPlusの2倍、ClosedXMLの5分の1の速度という結果となりました。ClosedXMLは0.100.X系以降Valueプロパティがobject型ではなく独自のクラスを使用する形となったため、読み込み速度にマイナス影響が出ているのかもしれません。

EPPlusは最新バージョンは商用有償ライセンスになっているため、MITライセンスの中で選ぶのであれば有力候補となると考えられます。

ファイルオープン→1048576×5セル解読に要した時間(3回実行平均値)
ExcelDataReader10,967ミリ秒
ClosedXML53,442ミリ秒
EPPlus6,144ミリ秒

測定に使用したコード

以下のコードで測定を行いました。尚、ClosedXMLとEPPlusには、セルの値が存在する範囲については既知情報として与えています。

C#
using ExcelDataReader;
using System.Diagnostics;
using System.Text;
using OfficeOpenXml;
using ClosedXML.Excel;

var filepath = @"C:\users\sakai\desktop\1.xlsx";

//ウォーミングアップ
for (int i = 0; i < 100000; i++) {
    var a = 1;
}

//測定(連続実行はしないで、個別にコメントアウトを解除して実測します)
//ExcelDataReader(filepath);
ClosedXML(filepath);
//EPplus(filepath);

static void ExcelDataReader(string filepath) {

    //測定開始
    var stopwatch = new Stopwatch();
    stopwatch.Start();

    //エンコードを登録
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    ExcelReaderConfiguration config = new() {
        FallbackEncoding = Encoding.GetEncoding("Shift_JIS")
    };

    //ファイル読み込み→データセット生成
    using FileStream stream = File.Open(filepath, FileMode.Open, FileAccess.Read);
    using IExcelDataReader reader = ExcelReaderFactory.CreateReader(stream, config);
    var dataset = reader.AsDataSet();

    //先頭のワークシート取得
    var worksheet = dataset.Tables[dataset.Tables.Count - 1];

    //セルの値を逐次読み込み
    var readCount = 0;
    for (var row = 0; row < worksheet.Rows.Count; row++) {
        for (var col = 0; col < worksheet.Columns.Count; col++) {
            var val = worksheet.Rows[row][col];
            readCount++;
        }
    }

    //測定終了
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds.ToString());
    Console.WriteLine($"ReadCount:{readCount}");
}

static void ClosedXML(string filepath) {
    //測定開始
    var stopwatch = new Stopwatch();
    stopwatch.Start();

    var wb = new XLWorkbook(filepath);
    var ws = wb.Worksheets.First();
    var readCount = 0;
    for (int rowNumber = 1; rowNumber <= 1048576; rowNumber++) {
        for (int colNumber = 1; colNumber <= 5; colNumber++) {
            var val = ws.Cell(rowNumber, colNumber).Value;
            readCount++;
        }
    }

    //測定終了
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds.ToString());
    Console.WriteLine($"ReadCount:{readCount}");
}

static void EPplus(string filepath) {
    //測定開始
    var stopwatch = new Stopwatch();
    stopwatch.Start();

    FileInfo fileInfo = new FileInfo(filepath);
    using ExcelWorkbook wb = new ExcelPackage(fileInfo).Workbook;
    var ws = wb.Worksheets.First();
    var readCount = 0;
    for (int rowNumber = 1; rowNumber <= 1048576; rowNumber++) {
        for (int colNumber = 1; colNumber <= 5; colNumber++) {
            var val = ws.Cells[rowNumber, colNumber];
            readCount++;
        }
    }

    //測定終了
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds.ToString());
    Console.WriteLine($"ReadCount:{readCount}");
}

記事筆者へのお問い合わせ、仕事のご依頼

当社では、IT活用をはじめ、業務効率化やM&A、管理会計など幅広い分野でコンサルティング事業・IT開発事業を行っております。

この記事をご覧になり、もし相談してみたい点などがあれば、ぜひ問い合わせフォームまでご連絡ください。

皆様のご投稿をお待ちしております。

記事筆者へ問い合わせする

※ご相談は無料でお受けいたします。

この記事へのコメント

ニックネーム(任意)

返信通知先Emailアドレス(任意)

本文


* 感想やご意見等、お気軽にコメントください。但し、公序良俗に反するコメントはお控えください。
* 管理者が承認したコメントはこの箇所に公開させていただく可能性がございます。
* 返信通知先Emailアドレスは、筆者のみに通知され、公開されることはありません。返信通知先Emailを入力された場合は、コメントへの返信をこちらに掲載した際に通知させていただきます。その他の目的には使用いたしません。
* スパム対策のため、コメントは日本語のみ受け付けております。

堺財経電算合同会社 小規模IT構築サービス