【C#】NPOIによるExcelセルデータの読み込みと型判別ロジックの実装

目次

概要

NPOIライブラリを活用し、Excelファイル(.xlsx)から任意のセルデータを抽出する実装です。

Excelのセルは「数値」「文字列」「日付」「数式」など多様な型を持ちますが、これらをプログラム側で適切に判別し、C#のネイティブな型として取り扱うための変換ロジックをカプセル化したクラスを提供します。

仕様(入出力)

  • 入力: ファイルパス、シートインデックス、取得したいセルの行・列番号
  • 出力: 適切な型に変換されたセルの値(object型)
  • 依存: NPOI (NuGetパッケージ)

実装メソッド一覧

メソッド名説明
Open指定パスのExcelファイルを読み込み、ワークブックオブジェクトを生成して返します。
SelectSheetインデックス(0始まり)を指定して、操作対象のワークシートをメモリ上に展開します。
GetValue指定された行・列のセルにアクセスし、値を取得します。行やセルが未定義の場合はnullを返します。
_CellValueセルの内部型(CellType)を判定し、数値・文字列・日付・論理値・数式結果へ振り分ける内部処理です。

基本の使い方

// ファイルを開く
var book = MyExcelBook.Open("data.xlsx");

// 1番目のシートを選択
book.SelectSheet(0);

// 3行目・1列目(B3)の値を取得
var value = book.GetValue(2, 1);

Console.WriteLine($"Value: {value}");

コード全文

using System;
using System.IO;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

class Program
{
    static void Main()
    {
        // 動作確認用パス(適宜変更してください)
        string path = "example.xlsx";

        if (!File.Exists(path))
        {
            Console.WriteLine("Target file not found.");
            return;
        }

        try
        {
            // 1. Excelファイルを開く
            var xls = MyExcelBook.Open(path);

            // 2. シートを選択(0番目)
            xls.SelectSheet(0);

            // 3. 値の取得テスト(例: 0~4行目のA列を取得)
            Console.WriteLine("--- Cell Values ---");
            for (int i = 0; i < 5; i++)
            {
                // GetValue(行, 列)
                var val = xls.GetValue(i, 0);
                
                // nullの場合は "(empty)" と表示
                Console.WriteLine($"Row {i}: {val?.ToString() ?? "(empty)"}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

/// <summary>
/// NPOIを使用したExcel読み込みラッパークラス
/// </summary>
public sealed class MyExcelBook
{
    private XSSFWorkbook _workbook;
    private ISheet _sheet;
    
    // 外部からのインスタンス生成を禁止
    private MyExcelBook() 
    {
    }

    /// <summary>
    /// Excelファイルを読み込みモードで開き、インスタンスを生成します。
    /// </summary>
    public static MyExcelBook Open(string filePath)
    {
        var obj = new MyExcelBook();
        
        // ファイルストリームを開き、Workbookにロードする
        // XSSFWorkbookはメモリ展開型のため、ロード後はストリームを閉じても動作します
        using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        obj._workbook = new XSSFWorkbook(stream);
        
        return obj;
    }

    /// <summary>
    /// 操作対象のシートをインデックスで選択します。
    /// </summary>
    public void SelectSheet(int index)
    {
        _sheet = _workbook.GetSheetAt(index);
    }

    /// <summary>
    /// 指定した行・列のセル値を取得します。
    /// </summary>
    public object GetValue(int rowIndex, int colIndex)
    {
        if (_sheet == null) return null;

        // 行を取得(存在しない場合はnull)
        var row = _sheet.GetRow(rowIndex);
        if (row == null) return null;

        // セルを取得(存在しない場合はnull)
        var cell = row.GetCell(colIndex);
        if (cell == null) return null;

        // 値の抽出と型変換を実行
        return _CellValue(cell, cell.CellType);
    }

    /// <summary>
    /// セルの型(CellType)に応じて適切な値を抽出する内部ロジック
    /// </summary>
    private object _CellValue(ICell cell, CellType type)
    {
        // 型がUnknownの場合は、セルの実際の型を参照
        var actualType = (type == CellType.Unknown) ? cell.CellType : type;

        switch (actualType)
        {
            case CellType.String:
                return cell.StringCellValue;

            case CellType.Boolean:
                return cell.BooleanCellValue;

            case CellType.Numeric:
                // 日付フォーマットかどうか判定
                if (DateUtil.IsCellDateFormatted(cell))
                {
                    return cell.DateCellValue; // DateTime型
                }
                return cell.NumericCellValue; // double型

            case CellType.Formula:
                // 数式の場合、計算結果(キャッシュ)の型に基づいて再帰的に値を取得
                return _CellValue(cell, cell.CachedFormulaResultType);

            case CellType.Blank:
                return string.Empty;

            case CellType.Error:
                return $"ERROR_CODE:{cell.ErrorCellValue}";

            default:
                return null;
        }
    }
}

カスタムポイント

  • 戻り値の型統一:現在は object を返していますが、利用側でキャストが煩雑な場合は、GetValue メソッド内で .ToString() を呼び出し、全て string として返す仕様に変更すると取り回しが良くなります。
  • シート名の指定:SelectSheet をオーバーロードし、_workbook.GetSheet(“SheetName”) を使ってシート名文字列で選択できるように拡張可能です。
  • 例外処理の追加:ファイルが開かれている場合の IOException や、パス不正時の例外ハンドリングを Open メソッド内に追加することで、堅牢性が向上します。

注意点

  1. 行とセルのNull安全性:NPOIではデータ未入力の行やセルはオブジェクト自体が null となる仕様です。GetRow や GetCell の戻り値チェックを省略すると、容易に NullReferenceException が発生するため、必ずnullチェックを行う必要があります。
  2. 日付型の内部表現:Excelの日付は内部的には数値(シリアル値)です。DateUtil.IsCellDateFormatted による判定を行わないと、日付が 45231.5 のような浮動小数点数として取得されてしまうため、型判定ロジックは必須です。
  3. 数式の計算:このコードは「最後にExcelが保存した際の計算結果(キャッシュ)」を取得します。NPOI上でセルの値を変更しても、再計算処理(HSSFFormulaEvaluator等)を明示的に行わない限り、数式セルの値は更新されません。

応用

全セルデータのテキストダンプ

シートに含まれる有効範囲のデータをすべて文字列として取得する拡張例です。

public void DumpSheetData()
{
    if (_sheet == null) return;

    // FirstRowNumからLastRowNumまで走査
    for (int i = _sheet.FirstRowNum; i <= _sheet.LastRowNum; i++)
    {
        var row = _sheet.GetRow(i);
        if (row == null) continue;

        for (int j = row.FirstCellNum; j < row.LastCellNum; j++)
        {
            var cell = row.GetCell(j);
            var val = (cell == null) ? "" : _CellValue(cell, cell.CellType);
            Console.Write($"{val}\t");
        }
        Console.WriteLine();
    }
}

まとめ

NPOIを用いたセル値の取得では、単なる値の読み出しだけでなく、CellType に基づいた厳密な型判定と、null オブジェクトへの防御的なプログラミングが不可欠です。本実装のように型判別ロジックをクラス内部に隠蔽することで、呼び出し側はExcel特有の内部仕様を意識せずにデータを安全に利用できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次