sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

C# Excel ファイルからセルのデータを読み込む(ClosedXML)

C#Excel ファイルを読み込んで利用する方法は、ライブラリーを利用したいくつかのやり方があると思います。おそらくよく検討に挙がりそうなのは以下のあたりだと思います。

個人的な感想だと、開発が比較的に盛んなものは NPOI で、コードが比較的に読みやすいものは ClosedXML だと思いました。MIT ライセンスで機能の充実している SpreadsheetLight も存在感があり、海外だと EPPlus も盛んな印象ですが、ライセンスは他と違う点で留意しましょう。(商用の場合はライセンス購入要と思われます)速度の面では、ExcelDataReader が優れるといったデータもありました。

古くからあるライブラリーは .xls 形式(バイナリ形式)に対応しています。が、注意点として COM を利用している場合があります。C# だと COM オブジェクト解放をしないと、メモリー使用の上で問題が生じることになる上、EXCEL 自体を参照することがあるので、そもそも EXCEL のインストールが必要になります。

比較的新しいライブラリーは、.xlsx(OpenXML 形式)にのみ対応していることがあります。(ClosedXML など)オープン規格に対応する形なので、COM オブジェクト問題のようなことが発生しませんし、EXCEL のインストールも必須になりません。

これ以外に、.xls/.xlsx 形式に対応して COM 利用が無い、EXCEL インストールも必須ではないし、グラフの扱いもできるといったケースも存在するみたいです。(SpreadsheetLight など)

まとめると 2022 年現在だと、さほど複雑ではない Excel ファイルを利用する際は、NPOI, ClosedXML, ExcelDataReader あたりの選択が個人的にはベターだと思いました。(開発頻度と、人気などから)

すこし高度な EXCEL の扱いを検討する場合、SpreadsheetLight はよいかもしれませんが、開発は個人で、更新頻度も低い点は気になりました。(最終更新も .NET Standard 2 への対応なので)

2020 年ごろで更新が止まっているプロジェクトが多く、ある程度成熟した分野になって手を加えることがあまりないのかもしれませんが、どのライブラリーも非同期処理 (async/await) をサポートしていないと思います……。

ClosedXML を使った読み込み

ClosedXML は、利用してみたところ比較的に用意されているメソッドがわかりやすいと思いました。

特定セルのデータを取得するサンプルは以下のとおり。

var result = Read(@"C:\sample.xlsx", "A1");
private string Read(string filePath, string address)
{
    using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using var book = new XLWorkbook(stream);

    var sheet = book.Worksheets.FirstOrDefault();

    if (sheet == null) return "";

    var cell = sheet.Cell(address);

    return cell?.GetValue<string>() ?? "";
}

細かいポイントだけど new XLWorkbook(filePath) でもいいのに stream で渡しています。ファイルパスを直接渡してしまうと、EXCEL を開きながらセルのデータを取得できないからですね。以下の例外が発生してしまうと面倒なので。

別のプロセスで使用されているため、プロセスはファイル“sample.xlsx”にアクセスできません。

それから、この処理は File I/O です。現在だと ReadAsync のような非同期処理にしたいことがあるかも。ざっくり対応するなら:

private async Task<string> ReadAsync(string filePath, string address)
{
    return await Task.Run(() => Read(filePath, address));
}

とりあえず、Read メソッド自体を書き換えて、Task.Run() を盛り込まないほうがいいと思います。(可能なら使いたくないし)ネストも深くなってしまう。もともとライブラリーで対応していないのだから、仕方なくオプションとして Async メソッドを追加しておく。そのぐらいでいいんじゃないかと思います。(例外処理に注意)

サンプル

GitHub に今回のサンプルを公開しています。

参考