C#でエクセルファイルを扱うライブラリは、ClosedXMLやEPPlusなど完成度の高いものが存在していますが、XLS形式(エクセル2003以前の形式)には非対応のものが少なくありません。他方、ExcelDataReaderライブラリは書き込みができないという制約の反面、XLS形式にも対応している上に読み込み速度も高速です。
エクセル2007以降のXLSX形式登場により、XLSファイルを新たに作成することは減りました。他方、昔のファイルを解読しないといけないといったタスクはまだまだ現役です。また、後述する通り読み込み速度は速い方なので、読み取りのみであれば有力な選択肢と言えます。
本記事では、ExcelDataReaderライブラリの実装とパフォーマンス測定を紹介します。尚、C#でエクセル操作関連については以下関連記事もご参考ください。
◇目次
ExcelDataReaderの導入時に検討するべきこと。書き込み不可、書式読み取り不可。
ExcelDataReaderはエクセルのセル値読み取りに特化したライブラリです。ClosedXMLやEPPlusとの大きな違いは、メリットとしてXLS形式(Excel2003以前フォーマット)に対応可能、デメリットとして書き込み・編集ができないことが挙げられます。
また、前2者のライブラリはセルの書式(背景カラー、罫線等)も読み取り可能でしたが、ExcelDataReaderはあくまでセルの値のみ読み取り可能です。書式読み取りまで視野に入れるなら、他のライブラリにするべきでしょう。(本件、引き続き調査中。参考:Read excel sheet internal formatting./GitHub)
ExcelDataReaderはMITライセンスで提供されている
ExcelDataReaderはMITライセンスで提供されています。MITライセンスは、著作権表示等を前提として商用利用等も可能なライセンス体系です。
詳しくはGitHubにライセンス本文が公開されていますのでご確認ください。
IExcelDataReader.AsDataSetメソッドを利用した解読の実装
他ライブラリとの文法が近く、直感的にも理解しやすいAsDataSetメソッドを使用してエクセルファイルのセル値を読み込みます。NuGetからの導入とコード記述の2段階で解説します。
NuGetからの導入
下図のように、ExcelDataReaderとExcelDataReader.DataSetをプロジェクトにインストールします。
尚、今回はAsDataSetメソッドを使用するためExcelDataReader.DataSetも併せてインストールしていますが、ExcelDataReaderにはAsDataSetメソッドのほかもう1通りのデータ取り出し方法(Readerメソッド)が用意されており、そちらを利用する場合は不要となります。
ExcelDataReaderだけインストールすると、AsDataSetメソッドが存在しない状態に
上述の通り、AsDataSetメソッドを利用するにはNuGetからExcelDataReader.DataSetも併せてインストールする必要があります。ExcelDataReaderだけの場合、以下のエラーによりコンパイルすることが出来ません。
'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#で利用可能なClosedXML・EPPlusと解読速度を比較してみます。下図のように、A~E列に一番下の行まで値を敷き詰めたシート(1048576×5セル)を含むファイル(容量19.2 MB)を解読させてみます。
尚、比較に使用する各ライブラリのバージョンは次の通りです。
結果:EPPlus>ExcelDataReader>>ClosedXMLの順位。MITライセンス限定かつ読み込みのみのタスクなら有力
下表のとおり、EPPlusの2倍、ClosedXMLの5分の1の速度という結果となりました。ClosedXMLは0.100.X系以降Valueプロパティがobject型ではなく独自のクラスを使用する形となったため、読み込み速度にマイナス影響が出ているのかもしれません。
EPPlusは最新バージョンは商用有償ライセンスになっているため、MITライセンスの中で選ぶのであれば有力候補となると考えられます。
ExcelDataReader | 10,967ミリ秒 |
ClosedXML | 53,442ミリ秒 |
EPPlus | 6,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開発事業を行っております。
この記事をご覧になり、もし相談してみたい点などがあれば、ぜひ問い合わせフォームまでご連絡ください。
皆様のご投稿をお待ちしております。