C#でExcel入出力する無料ライブラリ徹底比較:ClosedXMLとOpen XML SDKでOffice不要を実現

C#アプリでExcelファイルのエクスポート/インポートを実装したい一方で、EPPlusの商用ライセンスが気になり、Officeが入っていないサーバーやPCでも動く方法を探している方向けに、無料で使える主要ライブラリと選び方、実装の落とし穴をまとめます。

目次

結論:迷ったらClosedXML、要件が尖っているならOpen XML SDK

質問内容(Office不要/Excel入出力/セルコメント/列幅自動調整/商用でも無料で使いたい)をまとめて満たしやすいのは、まずClosedXMLです。ClosedXMLはMITライセンスで、Excel 2007+(.xlsx/.xlsm)を扱える高レベルAPIが揃っており、コメント付与や列幅調整までコードが短くなります。

一方で、Open XML SDKはMicrosoft系の公式ルートに近い立ち位置で、ファイル構造(Open XML)を直接操作できます。その分、実装は低レベルになり、コメントや自動列幅のような「Excelっぽい機能」を同じ手軽さで書くのは難しくなります。

観点ClosedXMLOpen XML SDK
ライセンスMIT(商用利用しやすい)MIT(商用利用しやすい)
Office不要不要(.xlsx/.xlsmを直接操作)不要(.xlsxのOpen XMLを直接操作)
実装難易度低(直感的なAPI)中〜高(XML構造の理解が必要)
セルコメントAPIで追加しやすい可能だが部品追加が多く複雑
列幅の自動調整AdjustToContentsで実装しやすい自前実装が基本(自動計測は作り込み)

前提:Office(Interop)に頼らないのが安全な理由

「Officeがインストールされていない環境でも動く必要がある」という条件がある場合、Microsoft.Office.Interop.Excel(Excelの自動操作)に依存する設計は避けるのが定石です。加えて、Microsoftは非対話・無人実行(サービス、IIS等)でのOffice自動化を推奨・サポートしていない旨を明記しています。サーバー用途での安定性(ハング、デッドロック等)を考えると、Open XML(.xlsx)を直接読み書きするライブラリが現実的です。

ClosedXMLやOpen XML SDKは、Excelアプリを起動せずに、Open XML形式(.xlsx)を操作します。これにより「Officeがない環境」でも実行できます。

EPPlusのライセンスが不安な理由(要点だけ)

EPPlusはバージョン5以降、コミュニティ向けはPolyform Noncommercial(非商用向け)を中心にしたモデルになっており、商用利用では商用ライセンスが必要になるケースがあります。過去バージョンはLGPLの範囲だった、という履歴もあります。ここが「商用で無料のまま使って良いのか?」という不安に直結します。

ライセンス判断はプロジェクトの利用形態で変わるため、最終確認は必須ですが、「最初から商用でも使いやすいOSSライセンス(MIT/Apache-2.0等)の代替を選ぶ」という方針が現場では事故が少ないです。

ClosedXMLを第一候補にする理由

ライセンスと対応形式

ClosedXMLはMITライセンスで公開されており、一般に商用利用でも採用しやすい条件です。

また、ClosedXMLはExcel 2007+のOpen XML形式を対象にしており、主に.xlsx / .xlsmを扱います。古い.xls(バイナリ形式)には原則対応しないため、要件に「.xlsも来る」がある場合は後述のNPOIやExcelDataReaderとの併用を検討してください。

ClosedXMLで「やりたいこと」が揃うポイント

  • ブック/シート作成、セルの読み書きが直感的
  • セルコメント(ノート)をコードで追加できる
  • 列幅を内容に合わせて調整(AutoFit相当)できる
  • Excelアプリ不要で.xlsxを生成・編集できる

コメント追加は、たとえば ws.Cell(row, col).Comment.AddText(...) のように書けます。

列幅の自動調整は ws.Columns().AdjustToContents() で実行できます。

インストール(NuGet)

開発環境ではNuGetから導入します。CLIなら次の通りです。

dotnet add package ClosedXML

エクスポート例:データ書き込み+コメント+列幅自動調整

「見出し行 → データ行 → コメント → 列幅調整 → 保存」の順番にすると、AutoFitが期待通り効きやすいです(後述の注意点も参照)。

using ClosedXML.Excel;

public static class ExcelExportSample
{
    public static void Export(string path)
    {
        using var wb = new XLWorkbook();
        var ws = wb.Worksheets.Add("Data");

        // 見出し
        ws.Cell(1, 1).Value = "ID";
        ws.Cell(1, 2).Value = "Name";
        ws.Cell(1, 3).Value = "Age";

        // 見出しの見た目(例)
        ws.Range(1, 1, 1, 3).Style.Font.Bold = true;
        ws.Range(1, 1, 1, 3).Style.Fill.BackgroundColor = XLColor.LightGray;

        // データ(例)
        var data = new[]
        {
            new { Id = 1, Name = "Alice", Age = 29 },
            new { Id = 2, Name = "Bob",   Age = 34 },
            new { Id = 3, Name = "Chika", Age = 41 },
        };

        var row = 2;
        foreach (var item in data)
        {
            ws.Cell(row, 1).Value = item.Id;
            ws.Cell(row, 2).Value = item.Name;
            ws.Cell(row, 3).Value = item.Age;
            row++;
        }

        // 任意セルにコメント(ノート)を付ける
        ws.Cell(2, 2).Comment.AddText("例:氏名は正式名称で入力してください");

        // 列幅を内容に合わせて調整(AutoFit相当)
        ws.Columns().AdjustToContents();

        // 保存
        wb.SaveAs(path);
    }
}

インポート例:Excel → アプリへ取り込む(行の読み取り)

インポートは「どこまでをデータ行と見なすか」「空行が混ざる」「数値・日付が型ブレする」といった実務の罠が多いので、RangeUsed(使用範囲)を軸にしつつ、ヘッダー行で列を確定してから読む設計が安定します。

using ClosedXML.Excel;

public sealed record PersonRow(int Id, string Name, int? Age);

public static class ExcelImportSample
{
    public static List<PersonRow> Import(string path)
    {
        using var wb = new XLWorkbook(path);
        var ws = wb.Worksheet("Data");

        var used = ws.RangeUsed();
        if (used is null) return new List<PersonRow>();

        // 1行目がヘッダー前提
        var rows = used.RowsUsed().Skip(1);

        var result = new List<PersonRow>();
        foreach (var r in rows)
        {
            // 空行スキップ例
            if (r.CellsUsed().All(c => c.IsEmpty())) continue;

            var id = r.Cell(1).GetValue<int>();
            var name = r.Cell(2).GetString();

            // Ageは空欄があり得る想定
            int? age = null;
            var ageCell = r.Cell(3);
            if (!ageCell.IsEmpty())
            {
                // 型が揺れても落ちないように、まず文字列で拾ってTryParseするのも手
                if (int.TryParse(ageCell.GetString(), out var tmp))
                    age = tmp;
                else
                    age = ageCell.GetValue<int>(); // 期待通りならこちらでOK
            }

            result.Add(new PersonRow(id, name, age));
        }

        return result;
    }
}

テンプレート方式が強い(見た目とロジックを分離)

帳票や定型レイアウトがある場合、最初からExcelで「テンプレート(.xlsx)」を作っておき、C#側はセル値だけ差し込む方式が最も運用が安定します。ClosedXMLは既存ファイルを開いて編集できるため、テンプレート運用と相性が良いです。

using ClosedXML.Excel;

public static class TemplateFillSample
{
    public static void Fill(string templatePath, string outputPath, string customerName, DateTime date)
    {
        using var wb = new XLWorkbook(templatePath);
        var ws = wb.Worksheet(1);

        ws.Cell("B2").Value = customerName;
        ws.Cell("B3").Value = date;

        wb.SaveAs(outputPath);
    }
}

列幅自動調整(AdjustToContents)を実運用で安定させるコツ

  • データを書き終えてからAdjustToContentsを呼ぶ(ヘッダー直後に呼ぶと期待通りにならないことがある)
  • 全列ではなく、必要な列だけにかける(例:ws.Columns(1, 10).AdjustToContents();)
  • 大規模データでは、測定行数を絞る(例:先頭数百行のみ)

AdjustToContentsは便利ですが、行数が多いと処理が重くなるケースが報告されています。大量データの全列AutoFitを毎回行うより、「主要列だけ」「先頭N行だけ」「最大幅を制限」などのルール化が現場向きです。

また、Linux環境でAdjustToContentsを使う場合は、フォント計測やグラフィックエンジンの前提(フォントの存在など)でエラーになることがあります。ClosedXML側はテキスト計測にフォントを使う設計があり、環境依存が出るため、サーバーOSにフォントを用意する・フォールバックフォント設定を検討する、といった対策が必要になる場合があります。

パフォーマンスとメモリの現実

ClosedXMLは「Excelをコードで扱いやすくする」ことに強い一方、巨大ファイル(数十万行など)ではメモリ消費や処理時間が課題になりやすい領域があります。操作をセル単位でループし続けるより、範囲操作を使う、不要な挿入削除を避けるなど、パフォーマンスガイドに沿った書き方が効きます。

Open XML SDK:公式寄りで「低レベルに確実」だが、実装量は増える

ライセンスと位置づけ

Open XML SDK(NuGet: DocumentFormat.OpenXml)はMITライセンスで提供されており、Office Open XML(.xlsx/.docx/.pptx)を直接操作できます。

Open XML SDKが向くケース

  • 「ClosedXMLの抽象化が邪魔」なくらい、細かくファイル構造を制御したい
  • 生成物のOpen XML構造(パーツ構成や最適化)を意識して作り込みたい
  • 特定の仕様・社内標準に合わせて厳密に出力したい

Open XML SDKの注意点(質問要件に照らした弱点)

  • コメント:実装は可能ですが、コメント用パーツ追加や関連付けなどが必要になり、学習コストが高くなりがちです。
  • 列幅の自動調整:ExcelのAutoFit相当をSDK単体で簡単に再現するAPIは基本的にありません。幅を固定で設定するか、別途「文字幅計測」の仕組みを自前で持つ必要があります。

Open XML SDKの最小サンプル(書き込みの雰囲気)

Open XML SDKは、WorkbookPart / WorksheetPart / SheetData…のように部品を組み立てていく流れになります。ClosedXMLに比べるとコードが長くなりやすい点は織り込んでおきましょう。

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

public static class OpenXmlMinimalExport
{
    public static void Export(string path)
    {
        using var doc = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook);

        var workbookPart = doc.AddWorkbookPart();
        workbookPart.Workbook = new Workbook();

        var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
        var sheetData = new SheetData();

        // 1行目(ヘッダー)
        sheetData.AppendChild(new Row(
            new Cell { DataType = CellValues.String, CellValue = new CellValue("ID") },
            new Cell { DataType = CellValues.String, CellValue = new CellValue("Name") },
            new Cell { DataType = CellValues.String, CellValue = new CellValue("Age") }
        ));

        // 2行目(データ例)
        sheetData.AppendChild(new Row(
            new Cell { DataType = CellValues.Number, CellValue = new CellValue("1") },
            new Cell { DataType = CellValues.String, CellValue = new CellValue("Alice") },
            new Cell { DataType = CellValues.Number, CellValue = new CellValue("29") }
        ));

        worksheetPart.Worksheet = new Worksheet(sheetData);

        var sheets = workbookPart.Workbook.AppendChild(new Sheets());
        sheets.Append(new Sheet
        {
            Id = workbookPart.GetIdOfPart(worksheetPart),
            SheetId = 1,
            Name = "Data"
        });

        workbookPart.Workbook.Save();
    }
}

用途別:無料で使える周辺候補(ClosedXMLだけで足りない時)

質問要件そのものはClosedXMLが最短ですが、現場では「相手が古い.xlsを送ってくる」「数十万行を高速に流し込みたい」など、要件が追加されがちです。そうした時の補欠も押さえておくと設計がブレません。

ライブラリ強み弱み/注意ライセンス
ExcelDataReader読み取り特化で軽量。xlsx/xls/xlsb/csvなど幅広く読める基本は「読むだけ」。フォーマットは直接扱わない(必要なら別ライブラリ併用)MIT
NPOIxls/xlsxを扱える。Office不要。機能カバー範囲が広いAPIはやや独特。ClosedXMLほど直感的ではないことがあるApache-2.0
MiniExcelストリーミング指向で低メモリ・大規模データに強い「帳票の見た目」や高度なExcel機能はClosedXMLほど得意ではないことが多いApache-2.0

ExcelDataReaderはMITライセンスで、対応フォーマット一覧(.xlsx/.xlsb/.xls/.csvなど)がREADMEに明記されています。また、フォーマット(表示形式)は直接サポートしない方針も説明されています。

NPOIは「Office不要」「xls/xlsx対応」などを特徴として掲げており、古い.xlsが混ざる現場では選択肢になりやすいです。

MiniExcelはApache-2.0ライセンスで、ストリーミング(行ごと処理)による低メモリ志向を前面に出しています。ClosedXMLでメモリや速度が厳しい局面の「割り切り解」として検討余地があります。

選定で失敗しないためのチェックリスト

ファイル形式と運用ルール

  • 受け取るExcelは.xlsx固定にできるか(.xls混在なら対策が必要)
  • マクロ付き(.xlsm)を扱う必要があるか
  • コメントは「ノート(旧コメント)」で良いか(Excelの新しいスレッドコメントは別物)

インポート側の品質要件

  • ヘッダー名で列をマッピングする(列順が変わっても壊れにくい)
  • 入力チェック(必須、型、範囲、桁数)をExcel任せにせず、アプリ側でも必ず行う
  • 式セルを許可するか(式の結果を読む/式そのものを禁止する等の方針を決める)

数式セルの扱い(ClosedXMLの場合)

ClosedXMLは数式を扱えますが、ファイル保存時に数式結果を保持するかどうか、計算をいつ走らせるかで挙動が変わります。たとえば、値取得で計算が走ることがあるため、インポート性能や「Excelで開いた時に初めて計算される」点を意識してください。

まとめ:要件を満たしつつ「運用で揉めない」おすすめ構成

  • Office不要・コメント・列幅自動調整・実装の簡単さまで含めて最初の一手はClosedXML
  • Open XMLの構造まで厳密に制御したい/生成物を最適化したいならOpen XML SDK
  • .xls混在や巨大データなど「追加要件」が来たら、NPOI/ExcelDataReader/MiniExcelの併用も検討

EPPlusのライセンス不安を避けつつ、商用でも無料で導入しやすいOSSライブラリに寄せるなら、まずClosedXML(必要に応じてOpen XML SDK)という選び方が、実装・運用の両面でバランスが取りやすいです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次