Sapnとは?

連続したメモリ領域への安全かつ効率的なアクセスを提供するための型

  • スタック上に必ず保持する(ヒープに置くような使い方はできない)

  • スライスで部分的にデータを取り出すことができる

  • ヒープにおけないので、GCの影響をうけない

実装例

1行コンマ区切りデータを受け取り分割する関数です

public static class SampleCode
{
    // Id(int), Name(string), Age(int), Date(DateTime)のCSV行を解析するメソッド
    public static (int Id, string Name, int Age, DateTime Date) ParseLine(string line)
    {
        // 文字列をReadOnlySpan<char>に変換
        ReadOnlySpan<char> span = line.AsSpan();

        // 1列目:Id
        int index1 = span.IndexOf(',');
        int id = int.Parse(span.Slice(0, index1));

        // 2列目:Name(sliceでコンマより後の文字列を参照)
        span = span.Slice(index1 + 1);
        int index2 = span.IndexOf(',');
        string name = span.Slice(0, index2).ToString();

        // 3列目:Age(sliceでコンマより後の文字列を参照)
        span = span.Slice(index2 + 1);
        int index3 = span.IndexOf(',');
        int age = int.Parse(span.Slice(0, index3));

        // 4列目:Date(sliceでコンマより後の文字列を参照)
        span = span.Slice(index3 + 1);
        DateTime date = DateTime.Parse(span);

        return (id, name, age, date);
    }
}
<code></pre>
ReadOnlySpan<T>は、スタックに積んだ値を参照するのみで、変更することができないことを意味します  
そのため、 span = span.Slice(index1 + 1); で参照する位置を変更していますが  
スタックに積んでいる値を変えているわけではないです  




### ベンチマーク

簡単な例で比較  

<pre class="line-numbers"><code class="language-csharp">using System;
using System.Globalization;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class CsvParsingBenchmark
{
    private const string CsvLine = "12345,John Doe,42,2024-06-24";

    [Benchmark]
    public (int, string, int, DateTime) Parse_WithSplit()
    {
        var parts = CsvLine.Split(',');
        return (
            int.Parse(parts[0]),
            parts[1],
            int.Parse(parts[2]),
            DateTime.Parse(parts[3], CultureInfo.InvariantCulture)
        );
    }

    [Benchmark]
    public (int, string, int, DateTime) Parse_WithSpan()
    {
        ReadOnlySpan<char> span = CsvLine.AsSpan();

        int index1 = span.IndexOf(',');
        int id = int.Parse(span.Slice(0, index1));

        span = span.Slice(index1 + 1);
        int index2 = span.IndexOf(',');
        string name = span.Slice(0, index2).ToString();

        span = span.Slice(index2 + 1);
        int index3 = span.IndexOf(',');
        int age = int.Parse(span.Slice(0, index3));

        span = span.Slice(index3 + 1);
        DateTime date = DateTime.Parse(span, CultureInfo.InvariantCulture);

        return (id, name, age, date);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<CsvParsingBenchmark>();
    }
}

※BenchmarkDotNetは、nugetで取得
※ベンチマークはリリースモードで実行

BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4351/24H2/2024Update/HudsonValley)
13th Gen Intel Core i5-13400 2.50GHz, 1 CPU, 16 logical and 10 physical cores
.NET SDK 9.0.300
[Host] : .NET 8.0.16 (8.0.1625.21506), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 8.0.16 (8.0.1625.21506), X64 RyuJIT AVX2

Method Mean Error StdDev Gen0 Allocated
Parse_WithSplit 152.32 ns 3.029 ns 5.144 ns 0.0198 208 B
Parse_WithSpan 96.56 ns 1.955 ns 3.858 ns 0.0038 40 B

およそ、1.5倍の差ですね

スタック領域

各スレッドごとに固定サイズが割り当てられたメモリ領域
高速で動作するが、領域は大きくない

そのため、大量に確保するとスタックオーバーフローになるので要注意

そのため、データ量が多い場合には、ArrayPool(ヒープ領域に配置するが、再利用することで、無駄な確保を減らしGCを減らす)を使う

投稿日時: 2025-06-25 15:19:25
更新日時: 2025-06-25 16:18:25

タプルを使用すると複数の値を一度に受け取ることができますが
そのうちの一部の値は使わない場合、使いもしないのに変数に格納すると
この変数はどこで使うのだろうか?と疑問に思わせてしまうためよくないです

使う気がない値は明確に使わないという風に示せた方がよいのでその場合に _ をつかいます

例えば3つの値を返す関数があり、すべて使いたい場合と2つ名の値が不要な場合だと以下のように記載できます

var (a, b, c) = Hogehoge();
var (x, _, y) = Hogehoge();

static (string a, string b, string c) Hogehoge()
{
    return ("a", "b", "c");
}
投稿日時: 2025-06-14 02:03:14

クラス名の識別のため名前空間をusingして利用する形になり、それはファイルごとに都度記載することになるが

using System.Reflection;

var directoryPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

他のファイルでも使う場合、globalをつけることで、全ファイルでその名前空間が参照されることになる

global using System.Reflection;

var directoryPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

※これなら他のファイルでもAssemblyクラスがそのまま使える

名前のエイリアスでも同様にglobalが使えます

global using Excel = Microsoft.Office.Interop.Excel;
投稿日時: 2025-06-14 01:27:14
更新日時: 2025-06-14 01:34:14

例えば、実行ファイルのあるディレクトリを取得したい場合に以下のように書いたとします

var directoryPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

そうすると、以下のように波線が表示され、 null 参照の可能性があるものの逆参照です。 という警告がでます

つまり、Assembly.GetEntryAssenbly()がnullを返す可能性があり、nullの時に .Location するとエラーになるという話です。

こういった場合に、Null条件演算子を記載しておくと、nullだった時その先のメソッドやプロパティを 実行せずにnullを返してくれエラーの回避をしてくれます

var directoryPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
↓
var directoryPath = Path.GetDirectoryName(null); // これと同等

※Path.GetDirectoryNameはnullを受け取った場合そのままnullを返してくれてエラーにはならないです

パスなのでnullを返されても・・・というのはあると思うので、Null合体演算子を使って空文字にしてしまうのでもよいですね

var directoryPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? "";

もうちょっと極端なケースをみるとその恩恵が強く感じられるのかもしれないのでそんな例を
たとえばあるクラスのメソッドをつなげて実行できる場合に、そのメソッドがnullを返す恐れがあると
それ以降のメソッドでエラーになるのでNull合体演算子を使うことになるのですが

var ret = new Hoge();
var val = ret.Foo()?.Bar()?.Baz();

これをNull合体演算子を使わないでかくと…
以下のようになり見づらくなってしまうって話しになります

var ret = new Hoge();
var val = ret.Foo();
if(val != null)
{
    val = val.Bar();
    if (val != null)
    {
        val = val.Baz();
    }
}
投稿日時: 2025-06-14 00:48:14

サーバーからちょっとしたメール送信をしたいときに何かしらのSMTPサーバー経由でメールを送りたいなと思ったので
ひとまず、簡単なメール通知をGoogleのSMTPサーバーを使って送る例をメモっておく

GoogleのSMTPを使ってメール送信する際には、アプリのパスワードを作成する必要がある
手順は、以下ページのとおり
Googleでアプリパスワードを生成

C#での実装
using System;
using System.Net;
using System.Net.Mail;

const string appPassword = "your-app-password"; // アプリのパスワード

var mailAddressFrom = "youraddress@gmail.com";  // 送信者
var mailAddressTo = "送信先メールアドレス";     // 受信者
var subject = "テストメール";                   // タイトル
var body = "プログラムから送信しています";      // 本文

// SMTP設定
var smtp = new SmtpClient("smtp.gmail.com", 587)
{
    EnableSsl = true,
    Credentials = new NetworkCredential(mailAddressFrom, appPassword)
};

var mail = new MailMessage(mailAddressFrom, mailAddressTo, subject, body);

try
{
    smtp.Send(mail);
    Console.WriteLine("メールを送信しました。");
}
catch (Exception ex)
{
    Console.WriteLine("送信エラー: " + ex.Message);
}
var mailAddressFrom = "youraddress@gmail.com"; // 送信者
var mailAddressTo = "送信先メールアドレス";    // 受信者
var subject = "テストメール";                  // タイトル
var body = "プログラムから送信しています";     // 本文

// SMTP設定
var smtp = new SmtpClient("smtp.gmail.com", 587)
{
    EnableSsl = true,
    Credentials = new NetworkCredential(mailAddressFrom, appPassword)
};

var mail = new MailMessage(mailAddressFrom, mailAddressTo, subject, body);

try
{
    smtp.Send(mail);
    Console.WriteLine("メールを送信しました。");
}
catch (Exception ex)
{
    Console.WriteLine("送信エラー: " + ex.Message);
}
Python
import smtplib
from email.mime.text import MIMEText

APP_PASSWORD = "your-app-password" # アプリのパスワード

mailAddressFrom = "youraddress@gmail.com" # 送信者
mailAddressTo   = "送信先メールアドレス" # 受信者
subject = "テストメール"
body = "プログラムから送信しています"

msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = mailAddressFrom
msg['To'] = mailAddressTo

try:
    smtp = smtplib.SMTP("smtp.gmail.com", 587)

    # SMTPでTLS通信をするためのお作法
    smtp.ehlo() 
    smtp.starttls()
    smtp.ehlo()

    smtp.login(mailAddressFrom, APP_PASSWORD)
    smtp.sendmail(mailAddressFrom, mailAddressTo, msg.as_string())
    smtp.close()
    print("メールを送信しました。")

except Exception as e:
    print(f"送信エラー: {e}")

投稿日時: 2025-06-07 16:02:07
更新日時: 2025-06-08 02:36:08
C#

予想外だったのでメモ

質問:
asp.netで公式が用意しているwebフレームワーク以外のフレームワークがnugetなどにあったりしますか?
rubyのSinatraみたいな薄いフレームワークってある

はい、そのような考え方はとても良い視点です。ASP.NETのような重厚なフレームワークに比べて、RubyのSinatraのような「薄い」Webフレームワークを.NET(C#)で探すことは、ミニマルで高速なWebアプリ開発を求める場合に有効です。

✔ ASP.NET以外の「軽量」Webフレームワーク

  1. Carter

URL: https://github.com/CarterCommunity/Carter

概要: ASP.NET Coreの上に構築された軽量マイクロフレームワーク。Sinatra風に書けます。

特徴:

モジュールベースでルーティング

ASP.NET CoreのミドルウェアやDIと互換性あり

非常にシンプルな構文

  1. NancyFx(非アクティブだが参考になる)

URL: https://github.com/NancyFx/Nancy

概要: Sinatraインスパイアの軽量フレームワーク。今は開発終了していますが、学習や小規模用途には依然として使えます。

  1. Minimal API (ASP.NET Core 6以降の機能)

公式ですが「薄い」アプローチ

.NET 6以降では Minimal API が公式サポートされており、Sinatra風の非常にシンプルな書き方が可能です

公式ではありますが、フルMVCやRazorなどと比べて極めて軽量です。

  1. EmbedIO

URL: https://github.com/unosquare/embedio

概要: Windows環境や軽量サーバ用途に向いた、自己ホスト型のWebフレームワーク。

特徴: .NETでIoTや小規模Web API用途に適しています。

  1. SimpleHttpServer / Watson Webserver

さらに低レベルのHTTPフレームワーク。WebAPIを提供するだけの超軽量用途向け。

~~公式で薄いフレームワーク出していたんですね ~~
→冷静に考えたら、初期状態で作っているだけね

チュートリアルはここらへん
https://learn.microsoft.com/ja-jp/aspnet/core/tutorials/min-web-api?view=aspnetcore-9.0&tabs=visual-studio

投稿日時: 2025-06-02 12:18:02
更新日時: 2025-06-12 14:23:12

Sheet1・・・保護なし(保護に使用)
Sheet2・・・保護なし(保護に使用)
Sheet3・・・保護あり・パスなし(保護解除に使用)
Sheet4・・・保護あり・パスあり(保護解除に使用)

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Worksheet
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'パスワード無し保護
    Set SheetObj = BookObj.Sheets(1)
    SheetObj.Protect ("")
    
    'パスワード有り保護
    Set SheetObj = BookObj.Sheets(2)
    SheetObj.Protect ("pass")
    
    'パスワード無し解除
    Set SheetObj = BookObj.Sheets(3)
    SheetObj.Unprotect ("")
    
    'パスワード有り解除
    Set SheetObj = BookObj.Sheets(4)
    SheetObj.Unprotect ("pass")
    
    '保存
    BookObj.Save
    
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using Microsoft.Office.Interop.Excel;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            // パスワード無し保護
            worksheet = worksheets[1];
            worksheet.Protect("");
            CleanUpComObject(ref worksheet);

            // パスワード有り保護
            worksheet = worksheets[2];
            worksheet.Protect("pass");
            CleanUpComObject(ref worksheet);

            // パスワード無し解除
            worksheet = worksheets[3];
            worksheet.Unprotect("");
            CleanUpComObject(ref worksheet);

            // パスワード有り保護
            worksheet = worksheets[4];
            worksheet.Unprotect("pass");
            CleanUpComObject(ref worksheet);

            // 保存
            workbook.Save();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using DocumentFormat.OpenXml.Spreadsheet;
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(fileStream))
            {
                // パスワード無し保護
                var sheet1 = workbook.Worksheet(1);
                sheet1.Protect(); 

                // パスワード有り保護
                var sheet2 = workbook.Worksheet(2);
                sheet2.Protect("pass"); 

                // パスワード無し解除
                var sheet3 = workbook.Worksheet(3);
                sheet3.Unprotect();

                // パスワード有り解除
                var sheet4 = workbook.Worksheet(4);
                sheet4.Unprotect("pass");

                // 保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                // パスワード無し保護
                var sheet1 = package.Workbook.Worksheets[0];
                sheet1.Protection.IsProtected = true;

                // パスワード有り保護
                var sheet2 = package.Workbook.Worksheets[1];
                sheet2.Protection.SetPassword("pass"); // IsProtectedは指定しなくてもパスがかかる

                // パスワード無し解除
                var sheet3 = package.Workbook.Worksheets[2];
                sheet3.Protection.IsProtected = false;

                // パスワード有り解除
                var sheet4 = package.Workbook.Worksheets[3];
                sheet4.Protection.IsProtected = false; // パスが設定されていてもこれで解除
                
                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // パスワード無し保護
                var sheet1 = workbook.GetSheetAt(0);
                sheet1.ProtectSheet("");

                // パスワード有り保護
                var sheet2 = workbook.GetSheetAt(1);
                sheet2.ProtectSheet("pass");

                // パスワード無し解除
                var sheet3 = workbook.GetSheetAt(2);
                sheet3.ProtectSheet(null); // パスワード無しなら解除できた

                // パスワード有り解除
                var sheet4 = workbook.GetSheetAt(3);
                // ※解除方法はなさそう

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // パスワード無し保護
                var sheet1 = workbook.GetSheetAt(0);
                sheet1.ProtectSheet("");

                // パスワード有り保護
                var sheet2 = workbook.GetSheetAt(1);
                sheet2.ProtectSheet("pass");

                // パスワード無し解除
                var sheet3 = workbook.GetSheetAt(2);
                sheet3.ProtectSheet(null); // パスワード無しなら解除できた

                // パスワード有り解除
                var sheet4 = workbook.GetSheetAt(3);
                // ※解除方法はなさそう

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合
Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // パスワード無し保護
                var sheet1 = workbook.GetSheetAt(0);
                sheet1.ProtectSheet("");

                // パスワード有り保護
                var sheet2 = workbook.GetSheetAt(1);
                sheet2.ProtectSheet("pass");

                // パスワード無し解除
                var sheet3 = workbook.GetSheetAt(2);
                sheet3.ProtectSheet(null); // パスワード無しなら解除できた

                // パスワード有り解除
                var sheet4 = workbook.GetSheetAt(3);
                // ※解除方法はなさそう

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-28 14:55:28

カレントの表示倍率を取得と設定

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    '表示倍率の取得
    Debug.Print ActiveWindow.Zoom

    '表示倍率の設定
    ActiveWindow.Zoom = 200
    
    '保存
    BookObj.Save
    
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using Microsoft.Office.Interop.Excel;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet1 = null;
        Excel.Worksheet? worksheet2 = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // 表示倍率の取得
            Console.WriteLine(application.ActiveWindow.Zoom);

            // 表示倍率の設定
            application.ActiveWindow.Zoom = 200;

            // 保存
            workbook.Save();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet1);
            CleanUpComObject(ref worksheet2);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(fileStream))
            {
                // 表示倍率の取得
                var activeSheet = workbook.Worksheets.First(ws => ws.TabActive);
                Console.WriteLine(activeSheet.SheetView.ZoomScale);

                // 表示倍率の設定
                activeSheet.SheetView.ZoomScale = 200;

                // 保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                // Activeなシートを取得
                var sheetNo = package.Workbook.View.ActiveTab;
                var sheet = package.Workbook.Worksheets[sheetNo];

                // 表示倍率の取得
                Console.WriteLine(sheet.View.ZoomScale);

                // 表示倍率の設定
                sheet.View.ZoomScale = 200;

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // Activeなシートを取得
                var sheetNo = workbook.ActiveSheetIndex;
                var sheet = workbook.GetSheetAt(sheetNo);

                // 表示倍率の取得(標準APIで提供されていない)

                // 表示倍率の設定
                sheet.SetZoom(200);

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // Activeなシートを取得
                var sheetNo = workbook.ActiveSheetIndex;
                var sheet = workbook.GetSheetAt(sheetNo);

                // 表示倍率の取得(標準APIで提供されていない)

                // 表示倍率の設定
                sheet.SetZoom(200);

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合
Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // Activeなシートを取得
                var sheetNo = workbook.ActiveSheetIndex;
                var sheet = workbook.GetSheetAt(sheetNo);

                // 表示倍率の取得(標準APIで提供されていない)

                // 表示倍率の設定
                sheet.SetZoom(200);

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-27 15:14:27

3つシートがあり、Sheet1とSheet2をIndexと名前を指定して削除してみます

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    
    Application.DisplayAlerts = False 'ダイアログ表示抑制
    
    'Index番号を指定して削除
    BookObj.Worksheets(1).Delete
    
    '名前を指定して削除
    BookObj.Sheets("Sheet2").Delete
    
    Application.DisplayAlerts = True 'ダイアログ表示抑制解除

    '閉じる
    BookObj.Save
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using Microsoft.Office.Interop.Excel;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet1 = null;
        Excel.Worksheet? worksheet2 = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            application.DisplayAlerts = false; // ダイアログ表示抑制

            // Index番号で取得して削除
            worksheet1 = worksheets[1];
            worksheet1.Delete();

            // 名前で取得して削除
            worksheet2 = worksheets["Sheet2"];
            worksheet2.Delete();

            application.DisplayAlerts = true; // ダイアログ表示抑制解除

            // 保存
            workbook.Save();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet1);
            CleanUpComObject(ref worksheet2);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(fileStream))
            {
                // Index番号で取得して削除
                workbook.Worksheet(1).Delete();

                // 名前で取得して削除
                workbook.Worksheet("Sheet2").Delete();

                // 保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                // Index番号で取得して削除
                package.Workbook.Worksheets.Delete(0);

                // 名前で取得して削除
                package.Workbook.Worksheets.Delete("Sheet2");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // Index番号を指定して削除
                workbook.RemoveSheetAt(0);

                // 名前を指定して直接削除はできないっぽい
                var index = workbook.GetSheetIndex("Sheet2");
                workbook.RemoveSheetAt(index); // 名前→Index番号→Index番号で削除

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // Index番号を指定して削除
                workbook.RemoveSheetAt(0);

                // 名前を指定して直接削除はできないっぽい
                var index = workbook.GetSheetIndex("Sheet2");
                workbook.RemoveSheetAt(index); // 名前→Index番号→Index番号で削除

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合
Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // Index番号を指定して削除
                workbook.RemoveSheetAt(0);

                // 名前を指定して直接削除はできないっぽい
                var index = workbook.GetSheetIndex("Sheet2");
                workbook.RemoveSheetAt(index); // 名前→Index番号→Index番号で削除

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-27 14:33:27

シート名を変更する

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Worksheet
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    '最初のシートを取得
    Set SheetObj = BookObj.Sheets(1)
    
    'シート名を変更
    SheetObj.Name = "abc"
    
    '閉じる
    BookObj.Save
    
    Set BookObj = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            // 最初のシートを取得
            worksheet = worksheets[1];

            // シート名変更
            worksheet.Name = "abc";

            // 保存
            workbook.Save();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                // 最初のシートを取得
                var worksheet = workbook.Worksheet(1);

                // シート名変更
                worksheet.Name = "abc";

                // 上書き保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                // シートを取得
                var worksheet = package.Workbook.Worksheets[0];
                worksheet.Name = "abc";

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // 最初のシートの名前を変える
                workbook.SetSheetName(0, "abc");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // 最初のシートの名前を変える
                workbook.SetSheetName(0, "abc");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合
Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // 最初のシートの名前を変える
                workbook.SetSheetName(0, "abc");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-27 11:57:27
更新日時: 2025-05-27 12:10:27

安易に保存することはできないので、いろいろメモ。

■【EPPlus】ファイルパス or FileInfo

□ファイルパスの場合Saveで保存

using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var package = new ExcelPackage(filePath))
            {
                // 何か書き込み
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                package.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileInfoも同様にSaveで保存

using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            var fileInfo = new FileInfo(filePath); // 何かしらのファイル情報を必要としている場合

            using (var package = new ExcelPackage(fileInfo))
            {
                // 何か書き込み
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                package.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ファイルパスや、FileInfoの場合、別プロセスからのアクセスを許容してしまうため、
同時に変更された場合、エラーになる
他からアクセスが想定される場合、Streamを使わざるを得ない
そこでStreamを使った書き込みをおさらいしてみる

[NG]StreamではSave

ClosedXMLはStreamで開いてもSaveで書き込み可能
同じように記載しても保存はされない

using Microsoft.Extensions.FileProviders;
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(fileStream))
            {
                // 何か書き込み
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                // ClosedXMLなら、Saveで書き込みできるが・・・
                package.Save(); //エラーも起きず、ファイルの更新日時が変わっていないことから保存はできていない
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

[NG]StreamでSaveAs

using Microsoft.Extensions.FileProviders;
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(fileStream))
            {
                // 何か書き込み
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                // エラーなく処理は終了する。更新日時は変わっているが・・・
                package.SaveAs(fileStream);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

開くとエラー

そもそも、同一stream上に書き込みを行うと、現在の位置から書き込み始めるので、おかしなことになる・・・

[NG]ファイル位置・内容を初期化

Seekの位置を先頭に戻し、内容を空っぽにしてから書き込みを行ってみる

using Microsoft.Extensions.FileProviders;
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // 読み取り専用で開く(Excelのデータを取り込むためのStream)
            using (var readStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            // メモリ上にデータを取り込む
            using (var package = new ExcelPackage(readStream))
            {
                // 何か書き込み
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                // Excelデータを取り込んだら閉じる
                readStream.Close();

                // 書き込み用のStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // Seekの位置を先頭にし、データを空にする
                    writeStream.Seek(0, SeekOrigin.Begin);
                    writeStream.SetLength(0);

                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

開いてみるとうまく書き込みている感じがします。

生成AIに確認すると・・・

「一見正しく見えるが、実は非推奨(危険)」なパターンです。

理由は次のとおりです。

🔍 問題の本質

💥 ExcelPackage の内部構造はストリームに依存

using (var readStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))

using (var package = new ExcelPackage(readStream))

{

readStream.Close(); // ← ここで物理的に閉じてしまう

...

package.SaveAs(writeStream); // ← 実はまだ読み取りストリームを使おうとする可能性あり

}

・ExcelPackage.Load(Stream) は ZIP アーカイブを内部的に展開して使いますが、

・一部の構成ファイルを「遅延読み込み」するため、ストリームを後からも参照しようとすることがあるんです。

・したがって、readStream.Close() したあとに SaveAs() を呼ぶと、未展開データにアクセスしようとして例外が起きる/壊れるリスクがあります。

🧪 実際に起きるエラー例

・IOException: Cannot access a closed stream

・System.InvalidOperationException: Cannot access Zip archive after stream is closed

・Excel ファイルが壊れて開けなくなる

[OK]メモリにExcelデータをすべて取り込んでから編集して保存する方法

using Microsoft.Extensions.FileProviders;
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);

            using( var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

NPOIはそもそもStreamのみなので、同じ対応が必要

投稿日時: 2025-05-25 12:13:25

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    '上書き保存
    BookObj.Save
    
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;
using System.Data;
using Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // 上書き保存
            workbook.Save();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

ファイルパスでもFileStreamでもSaveで上書きできる
□ファイルパス

using ClosedXML.Excel;
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var workbook = new XLWorkbook(filePath))
            {
                // 上書き保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileStream

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                // 上書き保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

□ファイルパス

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var package = new ExcelPackage(filePath))
            {
                // 上書き保存
                package.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileInfo

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            var fileInfo = new FileInfo(filePath);

             // 上書き保存
            using (var package = new ExcelPackage(fileInfo))
            {
                package.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileStream

using Microsoft.Extensions.FileProviders;
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using( var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                package.Workbook.Worksheets[0].Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // シートを取得
                var worksheet = workbook.GetSheetAt(0);

                var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                var cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(DateTime.Now.ToString("HH:mm:ss"));

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // シートを取得
                var worksheet = workbook.GetSheetAt(0);

                var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                var cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(DateTime.Now.ToString("HH:mm:ss"));

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版
Program.cs

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

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // シートを取得
                var worksheet = workbook.GetSheetAt(0);

                var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                var cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(DateTime.Now.ToString("HH:mm:ss"));

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-25 03:38:25
更新日時: 2025-05-26 14:27:26

シートが3つあり、Sheet3がアクティブな状態のExcelを使用

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Worksheet
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'Activeなシートを取得
    Set SheetObj = BookObj.ActiveSheet
    Debug.Print "Activeなシート名:" & SheetObj.Name ' Activeなシート名:Sheet3
    
    'Activeなシートを変更
    BookObj.Sheets(2).Activate
    
    'Activeなシートを取得
    Set SheetObj = BookObj.ActiveSheet
    Debug.Print "Activeなシート名:" & SheetObj.Name ' Activeなシート名:Sheet2
    
    BookObj.Close (False)
    
    Set BookObj = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;
using System.Data;
using Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // Activeなシートを取得
            worksheet = workbook.ActiveSheet;
            Console.WriteLine($"Activeなシート名:{worksheet.Name}"); // Activeなシート名:Sheet3
            CleanUpComObject(ref worksheet);

            // Activeなシートを変更
            worksheets = workbook.Sheets;
            worksheet = worksheets[2];
            worksheet.Activate();
            CleanUpComObject(ref worksheet);

            // Activeなシートを取得
            worksheet = workbook.ActiveSheet;
            Console.WriteLine($"Activeなシート名:{worksheet.Name}"); // Activeなシート名:Sheet2
            CleanUpComObject(ref worksheet);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(fileStream))
            {
                // Activeなシートを取得
                var sheet = workbook.Worksheets.First(ws => ws.TabActive);
                Console.WriteLine($"Activeなシート名:{sheet.Name}"); // Activeなシート名:Sheet3

                // Activeなシートを変更
                workbook.Worksheet("Sheet2").SetTabActive();

                // Activeなシートを取得
                sheet = workbook.Worksheets.First(ws => ws.TabActive);
                Console.WriteLine($"Activeなシート名:{sheet.Name}"); // Activeなシート名:Sheet2
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // Activeなシート番号を取得(シート番号は0から)
                var sheetNo = package.Workbook.View.ActiveTab; // 2

                // Activeなシートを取得
                var sheet = package.Workbook.Worksheets[sheetNo];
                Console.WriteLine($"Activeなシート名:{sheet.Name}"); // Activeなシート名:Sheet3

                // Activeなシートを変更(シート番号は0から)
                sheetNo = 1;
                package.Workbook.View.ActiveTab = sheetNo;

                // Activeなシートだけtrueにし、それ以外はfalseにする
                for(var n = 0; n < package.Workbook.Worksheets.Count; n++)
                {
                    package.Workbook.Worksheets[n].View.TabSelected = (n == sheetNo);
                }

                // Activeなシートを取得
                sheet = package.Workbook.Worksheets[sheetNo];
                Console.WriteLine($"Activeなシート名:{sheet.Name}"); // Activeなシート名:Sheet2
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

該当機能なし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xls");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new HSSFWorkbook(fileStream))
            {
                // ■Activeなシートの取得
                // Activeなシート番号を取得(シート番号は0から)
                var sheetNo = workbook.ActiveSheetIndex; // 2

                // Activeなシートを取得
                var sheet = workbook.GetSheetAt(sheetNo);
                Console.WriteLine($"Activeなシート名:{sheet.SheetName}");

                // ■Activeなシートの変更
                // Activeなシートを変更
                sheetNo = 1;
                workbook.SetActiveSheet(sheetNo); // Sheet2をActiveに設定する(これを変えただけじゃかわらない)
                workbook.SetSelectedTab(sheetNo); //こちらも変えるといいとのこと

                // 上記2つの設定で変わったが・・・
                // IsSelectedまで返ると完璧とのこと
                for(var n = 0; n < workbook.NumberOfSheets; n++)
                {
                    workbook.GetSheetAt(n).IsSelected = (n == sheetNo);
                }

                // Activeなシートを取得
                sheet = workbook.GetSheetAt(sheetNo);
                Console.WriteLine($"Activeなシート名:{sheet.SheetName}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XSSFWorkbook(fileStream))
            {
                // ■Activeなシートの取得
                // Activeなシート番号を取得(シート番号は0から)
                var sheetNo = workbook.ActiveSheetIndex; // 2

                // Activeなシートを取得
                var sheet = workbook.GetSheetAt(sheetNo);
                Console.WriteLine($"Activeなシート名:{sheet.SheetName}");

                // ■Activeなシートの変更
                // Activeなシートを変更
                sheetNo = 1;
                workbook.SetActiveSheet(sheetNo); // Sheet2をActiveに設定する(これを変えただけじゃかわらない)
                workbook.SetSelectedTab(sheetNo); //こちらも変えるといいとのこと

                // 上記2つの設定で変わったが・・・
                // IsSelectedまで返ると完璧とのこと
                for (var n = 0; n < workbook.NumberOfSheets; n++)
                {
                    workbook.GetSheetAt(n).IsSelected = (n == sheetNo);
                }

                // Activeなシートを取得
                sheet = workbook.GetSheetAt(sheetNo);
                Console.WriteLine($"Activeなシート名:{sheet.SheetName}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合
Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // ■Activeなシートの取得
                // Activeなシート番号を取得(シート番号は0から)
                var sheetNo = workbook.ActiveSheetIndex; // 2

                // Activeなシートを取得
                var sheet = workbook.GetSheetAt(sheetNo);
                Console.WriteLine($"Activeなシート名:{sheet.SheetName}");

                // ■Activeなシートの変更
                // Activeなシートを変更
                sheetNo = 1;
                workbook.SetActiveSheet(sheetNo); // Sheet2をActiveに設定する(これを変えただけじゃかわらない)
                workbook.SetSelectedTab(sheetNo); //こちらも変えるといいとのこと

                // 上記2つの設定で変わったが・・・
                // IsSelectedまで返ると完璧とのこと
                for (var n = 0; n < workbook.NumberOfSheets; n++)
                {
                    workbook.GetSheetAt(n).IsSelected = (n == sheetNo);
                }

                // Activeなシートを取得
                sheet = workbook.GetSheetAt(sheetNo);
                Console.WriteLine($"Activeなシート名:{sheet.SheetName}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-24 18:23:24
更新日時: 2025-05-27 15:13:27

Sheet1シートがあるファイルを開き、
シート名で参照する方法と、シート番号で参照する方法
また、存在しないシート名を指定するとどうなるか確認

ExcelVBA

シート番号と名前でアクセスする方法
シート番号は1から始まることに注意
存在しない場合はエラーとなるので例外の対応を行っておく

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Worksheet
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート番号で参照
    Set SheetObj = BookObj.Sheets(1)
    Debug.Print "シート番号で参照:" & SheetObj.Name
    
    '名前で参照
    Set SheetObj = BookObj.Sheets("Sheet1")
    Debug.Print "シート名で参照:" & SheetObj.Name
    
    On Error Resume Next
    '存在しない名前を指定して参照 => 例外が発生する
    Set SheetObj = BookObj.Sheets("AAA")
    
    If ERR.Number <> 0 Then
        Debug.Print ERR.Description 'インデックスが有効範囲にありません。
    End If
    
    On Error GoTo 0 'On Error Resume Next解除
    
    '保存せず閉じる
    BookObj.Close (False)
    
    Set BookObj = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;
using System.Data;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            // シート番号で参照
            worksheet = worksheets[1];
            Console.WriteLine($"シート番号で参照:{worksheet.Name}");
            CleanUpComObject(ref worksheet);

            // シート名で参照
            worksheet = worksheets["Sheet1"];
            Console.WriteLine($"シート名で参照:{worksheet.Name}");
            CleanUpComObject(ref worksheet);

            try
            {
                // 存在しない名前を指定して参照 => 例外が発生する
                worksheet = worksheets["AAA"];
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message); // インデックスが無効です。 (0x8002000B (DISP_E_BADINDEX))
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(fileStream))
            {
                // シート番号で参照
                var sheet = workbook.Worksheet(1);
                Console.WriteLine($"シート番号で参照:{sheet.Name}");

                // シート名で参照
                sheet = workbook.Worksheet("Sheet1");
                Console.WriteLine($"シート名で参照:{sheet.Name}");

                // 存在しない名前を指定して参照 => 例外発生する
                try
                {
                    sheet = workbook.Worksheet("AAA"); // There isn't a worksheet named 'AAA'.
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                // TryGetWorksheetを使うのも一つの手
                IXLWorksheet? worksheet = null;
                if (!workbook.TryGetWorksheet("AAA", out worksheet))
                {
                    Console.WriteLine("AAAシートないよ");
                }

                // 存在しないシートについては、Containsでtrue/falseを返してくれる
                if (!workbook.Worksheets.Contains("AAA"))
                {
                    Console.WriteLine("AAAシートないよ");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // シート番号で参照(シート番号は0から)
                var sheet = package.Workbook.Worksheets[0];
                Console.WriteLine($"シート番号で参照:{sheet.Name}");

                // シート名で参照
                sheet = package.Workbook.Worksheets["Sheet1"];
                Console.WriteLine($"シート名で参照:{sheet.Name}");

                // 存在しない名前を指定して参照 => nullを返す
                sheet = package.Workbook.Worksheets["AAA"];
                if(sheet == null)
                {
                    Console.WriteLine("AAAシートないよ");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ファイル開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
                // DataSetに変換(シート情報を取得)
                var result = reader.AsDataSet();

                // シート番号で参照(シート番号は0から)
                var sheet = result.Tables[0];
                Console.WriteLine($"シート番号で参照:{sheet.TableName}");

                // シート名で参照
                sheet = result.Tables["Sheet1"];
                Console.WriteLine($"シート名で参照:{sheet.TableName}");

                // 存在しない名前を指定して参照 => nullを返す
                sheet = result.Tables["AAA"];
                if (sheet == null)
                {
                    Console.WriteLine("AAAシートないよ");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xls");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new HSSFWorkbook(fileStream))
            {
                // シート番号で参照(シート番号は0から)
                var sheet = workbook.GetSheetAt(0);
                Console.WriteLine($"シート番号で参照:{sheet.SheetName}");

                // シート名で参照
                sheet = workbook.GetSheet("Sheet1");
                Console.WriteLine($"シート名で参照:{sheet.SheetName}");

                // 存在しない名前を指定して参照 => nullを返す
                sheet = workbook.GetSheet("AAA");
                if (sheet == null)
                {
                    Console.WriteLine("AAAシートないよ");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XSSFWorkbook(fileStream))
            {
                // シート番号で参照(シート番号は0から)
                var sheet = workbook.GetSheetAt(0);
                Console.WriteLine($"シート番号で参照:{sheet.SheetName}");

                // シート名で参照
                sheet = workbook.GetSheet("Sheet1");
                Console.WriteLine($"シート名で参照:{sheet.SheetName}");

                // 存在しない名前を指定して参照 => nullを返す
                sheet = workbook.GetSheet("AAA");
                if (sheet == null)
                {
                    Console.WriteLine("AAAシートないよ");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合
Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {   // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // シート番号で参照(シート番号は0から)
                var sheet = workbook.GetSheetAt(0);
                Console.WriteLine($"シート番号で参照:{sheet.SheetName}");

                // シート名で参照
                sheet = workbook.GetSheet("Sheet1");
                Console.WriteLine($"シート名で参照:{sheet.SheetName}");

                // 存在しない名前を指定して参照 => nullを返す
                sheet = workbook.GetSheet("AAA");
                if (sheet == null)
                {
                    Console.WriteLine("AAAシートないよ");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-24 16:47:24
更新日時: 2025-05-27 14:33:27

ExcelVBA

Sub Sample()
    Dim BookObj         As Workbook
    Dim c               As Long
    Dim FilePath        As String
    Dim r               As Long
    Dim SheetObj        As Worksheet
        
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート取得
    Set SheetObj = BookObj.Sheets("Sheet1")
    
    SheetObj.Range("A1").Value = 1
    SheetObj.Range("A2").Value = 1.1
    SheetObj.Range("A3").Value = "abc"
    SheetObj.Range("A4").Value = Now
        
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
    Set RangeData = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;
        Excel.Range? cellData = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;
            worksheet = worksheets["Sheet1"];

            cellData = worksheet.Range["A1"];
            cellData.Value = 1;
            CleanUpComObject(ref cellData);

            cellData = worksheet.Range["A2"];
            cellData.Value = 1.1;
            CleanUpComObject(ref cellData);

            cellData = worksheet.Range["A3"];
            cellData.Value = "abc";
            CleanUpComObject(ref cellData);

            cellData = worksheet.Range["A4"];
            cellData.Value = DateTime.Now;
            CleanUpComObject(ref cellData);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref cellData);
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook, true, true);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                IXLWorksheet? worksheet = null;

                if(!workbook.TryGetWorksheet("Sheet1", out worksheet))
                {
                    throw new Exception("Sheet1シートがありませんでした");
                }

                worksheet.Cell("A1").Value = 1;
                worksheet.Cell("A2").Value = 1.1;
                worksheet.Cell("A3").Value = "abc";
                worksheet.Cell("A4").Value = DateTime.Now;

                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                // シートを取得
                var worksheet = package.Workbook.Worksheets["Sheet1"];

                worksheet.Cells["A1"].Value = 1;
                worksheet.Cells["A2"].Value = 1.1;
                worksheet.Cells["A3"].Value = "abc";
                worksheet.Cells["A4"].Value = DateTime.Now.ToString("yyyy/MM/dd");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // xls,xlsx共通で該当セルの情報がなければnullになるとのこと
                // 空=nullではないとのこと

                // A1セル
                var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                var cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(1);

                // A2セル
                row = worksheet.GetRow(1) ?? worksheet.CreateRow(1);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(1.1);

                // A3セル
                row = worksheet.GetRow(2) ?? worksheet.CreateRow(2);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue("abc");

                // A4セル
                row = worksheet.GetRow(3) ?? worksheet.CreateRow(3);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(DateTime.Now);

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // xls,xlsx共通で該当セルの情報がなければnullになるとのこと
                // 空=nullではないとのこと

                // A1セル
                var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                var cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(1);

                // A2セル
                row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(1.1);

                // A3セル
                row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue("abc");

                // A4セル
                row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(DateTime.Now);

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // xls,xlsx共通で該当セルの情報がなければnullになるとのこと
                // 空=nullではないとのこと

                // A1セル
                var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                var cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(1);

                // A2セル
                row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(1.1);

                // A3セル
                row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue("abc");

                // A4セル
                row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
                cell = row.GetCell(0) ?? row.CreateCell(0);
                cell.SetCellValue(DateTime.Now);

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-23 23:56:23
更新日時: 2025-05-26 14:57:26

配列の要素を指定する方法、後ろから何番目と指定するときに使用するのIndex演算子

一番最後の要素が1であることに注意

var data = new[] { "a", "b", "c", "d", "e" };

// a
Console.WriteLine(data[0]);
Console.WriteLine(data[^5]); // 最後から5番目の要素

// b
Console.WriteLine(data[1]);
Console.WriteLine(data[^4]); // 最後から4番目の要素

// c
Console.WriteLine(data[2]);
Console.WriteLine(data[^3]); // 最後から3番目の要素

// d
Console.WriteLine(data[3]);
Console.WriteLine(data[^2]); // 最後から2番目の要素

// e
Console.WriteLine(data[4]);
Console.WriteLine(data[^1]); // 最後の要素

.. は範囲演算子
範囲演算子の前に指定するのは含む要素
範囲演算子の後ろに指定するのは、含まない要素を指定


var data = new[] { "日", "月", "火", "水", "木", "金", "土" };

// 含む要素番号..含まない要素番号
Console.WriteLine("平日は、{0}", string.Join(",", data[1..6])); // 月,火,水,木

// 同じ結果 含む要素番号..後ろから1番目の要素の前まで取得
Console.WriteLine("平日は、{0}", string.Join(",", data[1..^1])); // 月,火,水,木

// 木,金,土を出力
Console.WriteLine(string.Join(",", data[4..]));
Console.WriteLine(string.Join(",", data[^3..])); // 範囲演算子の前は含む要素、後ろは指定した要素の一つ前まで取得


// 日,月,火を出力
Console.WriteLine(string.Join(",", data[..3]));
Console.WriteLine(string.Join(",", data[..^4])); // 範囲演算子の前は含む要素、後ろは指定した要素の一つ前まで取得
投稿日時: 2025-05-22 12:12:22
更新日時: 2025-05-22 12:26:22

ログを出力する際に、 "3行14列"というよりは、"N3" といった方がわかりやすいだろうから、
アドレスの取得方法について

ExcelVBA

Sub Sample()
    Dim BookObj         As Workbook
    Dim c               As Long
    Dim FilePath        As String
    Dim r               As Long
    Dim SheetObj        As Worksheet
        
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート取得
    Set SheetObj = BookObj.Sheets("Sheet1")
    
    '2行、3列の各セルのアドレスを出力
    For r = 1 To 2
        For c = 1 To 3
            Debug.Print r & "行" & c & "列 => " & SheetObj.Cells(r, c).Address
        Next
    Next
        
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
    Set RangeData = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;
        Excel.Range? cellData = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;
            worksheet = worksheets["Sheet1"];

            // 2行、3列の各セルのアドレスを出力
            for (var r = 1; r <= 2; r++)
            {
                for(var c = 1; c <= 3; c++)
                {
                    cellData = worksheet.Cells[r, c];
                    Console.WriteLine($"{r}行{c}列 => {cellData.Address}");

                    CleanUpComObject(ref cellData);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref cellData);
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                IXLWorksheet? worksheet = null;

                if(!workbook.TryGetWorksheet("Sheet1", out worksheet))
                {
                    throw new Exception("Sheet1シートがありませんでした");
                }

                // 2行、3列の各セルのアドレスを出力
                for (var r = 1; r <= 2; r++)
                {
                    for (var c = 1; c <= 3; c++)
                    {
                        Console.WriteLine(worksheet.Cell(r, c).Address);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // シートを取得
                var worksheet = package.Workbook.Worksheets["Sheet1"];

                // 2行、3列の各セルのアドレスを出力
                for (var r = 1; r <= 2; r++)
                {
                    for (var c = 1; c <= 3; c++)
                    {
                        Console.WriteLine(worksheet.Cells[r, c].Address); // 行, 列
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

※ライブラリ側にはアドレスを出力する方法はないので、自作する

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ファイル開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
                // DataSetに変換(シート情報を取得)
                var result = reader.AsDataSet();
                
                // シート取得
                var worksheet = result.Tables["Sheet1"];

                if(worksheet == null)
                {
                    throw new Exception("Sheet1シートは存在しません");
                }

                // アドレスを出すような仕組みはなし。
                // もしログにA1形式で出力したければ、自分で作ること

                // 2行、3列の各セルのアドレスを出力
                for (var r = 1; r <= 2; r++)
                {
                    for (var c = 1; c <= 3; c++)
                    {
                        var cellAddress = CellAddress(r, c);
                        Console.WriteLine($"{r}行{c}列 => {cellAddress}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static string CellAddress(int row, int col)
    {
        string colStr = "";

        while (0 < col)
        {
            col--; // Aのアスキーコードに足すので1減らしておく
            int remainder = col % 26;
            colStr = (char)('A' + remainder) + colStr;
            col /= 26;
        }

        return $"{colStr}{row}";
    }
}

NPOI

※ライブラリ側にはアドレスを出力する方法はないので、自作する

xls

using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");
            
            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stRead = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = new HSSFWorkbook(stRead))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // アドレスを出すような仕組みはなし。
                // もしログにA1形式で出力したければ、自分で作ること

                // 2行、3列の各セルのアドレスを出力
                for (var r = 1; r <= 2; r++)
                {
                    for (var c = 1; c <= 3; c++)
                    {
                        var cellAddress = CellAddress(r, c);
                        Console.WriteLine($"{r}行{c}列 => {cellAddress}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static string CellAddress(int row, int col)
    {
        string colStr = "";

        while (0 < col)
        {
            col--; // Aのアスキーコードに足すので1減らしておく
            int remainder = col % 26;
            colStr = (char)('A' + remainder) + colStr;
            col /= 26;
        }

        return $"{colStr}{row}";
    }
}

xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stRead = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = new XSSFWorkbook(stRead))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // アドレスを出すような仕組みはなし。
                // もしログにA1形式で出力したければ、自分で作ること

                // 2行、3列の各セルのアドレスを出力
                for (var r = 1; r <= 2; r++)
                {
                    for (var c = 1; c <= 3; c++)
                    {
                        var cellAddress = CellAddress(r, c);
                        Console.WriteLine($"{r}行{c}列 => {cellAddress}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static string CellAddress(int row, int col)
    {
        string colStr = "";

        while (0 < col)
        {
            col--; // Aのアスキーコードに足すので1減らしておく
            int remainder = col % 26;
            colStr = (char)('A' + remainder) + colStr;
            col /= 26;
        }

        return $"{colStr}{row}";
    }
}

xls/xlsx対応版

Program.cs

using NPOI.SS.Util;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // アドレスを出すような仕組みはなし。
                // もしログにA1形式で出力したければ、自分で作ること

                // 2行、3列の各セルのアドレスを出力
                for (var r = 1; r <= 2; r++)
                {
                    for (var c = 1; c <= 3; c++)
                    {
                        var cellAddress = CellAddress(r, c);
                        Console.WriteLine($"{r}行{c}列 => {cellAddress}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static string CellAddress(int row, int col)
    {
        string colStr = "";

        while (0 < col)
        {
            col--; // Aのアスキーコードに足すので1減らしておく
            int remainder = col % 26;
            colStr = (char)('A' + remainder) + colStr;
            col /= 26;
        }

        return $"{colStr}{row}";
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-21 12:50:21
更新日時: 2025-05-21 12:57:21
C#
static string CellAddress(int row, int col)
{
    string colStr = "";

    while (0 < col)
    {
        col--; // Aのアスキーコードに足すので1減らしておく
        int remainder = col % 26;
        colStr = (char)('A' + remainder) + colStr;
        col /= 26;
    }

    return $"{colStr}{row}";
}
投稿日時: 2025-05-21 12:09:21
更新日時: 2025-05-21 12:56:21

ExcelVBA

Sub Sample()
    Dim BookObj         As Workbook
    Dim CellData        As Range
    Dim FilePath        As String
    Dim RangeData       As Range
    Dim SheetObj        As Worksheet
        
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート取得
    Set SheetObj = BookObj.Sheets("Sheet1")
    
    'A1形式
    Debug.Print SheetObj.Range("A1").Value
    
    'R1C1形式
    Debug.Print SheetObj.Cells(1, 1).Value
        
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
    Set CellData = Nothing
    Set RangeData = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;
        Excel.Range? cellData1 = null;
        Excel.Range? cellData2 = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;
            worksheet = worksheets["Sheet1"];

            // A1形式
            cellData1 = worksheet.Range["A1"];
            Console.WriteLine(cellData1.Value);

            // R1C1形式
            cellData2 = worksheet.Cells[1, 1];
            Console.WriteLine(cellData2.Value);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref cellData1);
            CleanUpComObject(ref cellData2);
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                IXLWorksheet? worksheet = null;

                if(!workbook.TryGetWorksheet("Sheet1", out worksheet))
                {
                    throw new Exception("Sheet1シートがありませんでした");
                }
                
                // A1形式
                Console.WriteLine(worksheet.Cell("A1").Value);

                // R1C1形式
                Console.WriteLine(worksheet.Cell(1, 1).Value);

            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // シートを取得
                var worksheet = package.Workbook.Worksheets["Sheet1"];

                // A1形式
                Console.WriteLine(worksheet.Cells["A1"].Value);

                // R1C1形式
                Console.WriteLine(worksheet.Cells[1, 1].Value); // 行, 列
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

using ExcelDataReader;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ファイル開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
                // DataSetに変換(シート情報を取得)
                var result = reader.AsDataSet();
                
                // シート取得
                var worksheet = result.Tables["Sheet1"];

                // A1形式は存在しない

                // R1C1形式(0から始まる)
                Console.WriteLine(worksheet.Rows[0][0]);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    // 列を表す数値から列を表す文字列に変換する
    static string ConvertColumnNumToStr(int colNum)
    {
        var colStr = string.Empty;

        while (colNum > 0)
        {
            int modulo = (colNum - 1) % 26;
            colStr = Convert.ToChar('A' + modulo) + colStr;
            colNum = (colNum - modulo) / 26;
        }

        return colStr;
    }
}

NPOI

xls

using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");
            
            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stRead = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = new HSSFWorkbook(stRead))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // A1形式は存在しない

                // R1C1形式(0から始まる)
                Console.WriteLine(worksheet.GetRow(0).GetCell(0).StringCellValue);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stRead = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = new XSSFWorkbook(stRead))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // A1形式は存在しない

                // R1C1形式(0から始まる)
                Console.WriteLine(worksheet.GetRow(0).GetCell(0).StringCellValue);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

xlsもxlsxも扱う場合

Program.cs

using NPOI.SS.Util;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                // A1形式は存在しない

                // R1C1形式(0から始まる)
                Console.WriteLine(worksheet.GetRow(0).GetCell(0).StringCellValue);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-19 16:19:19
更新日時: 2025-05-21 03:57:21

ExcelVBA

Sub Sample()
    Dim BookObj         As Workbook
    Dim CellData        As Range
    Dim FilePath        As String
    Dim RangeData       As Range
    Dim SheetObj        As Worksheet
        
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート取得
    Set SheetObj = BookObj.Sheets("Sheet1")
    
    '範囲セル取得
    Set RangeData = SheetObj.Range("A1:C2")
    
    '範囲のセルを一つずつ取り出す
    For Each CellData In RangeData
        Debug.Print "アドレス:" & CellData.Address & "/値:" & CellData.Value
    Next
        
    '閉じる
    BookObj.Close
    
    Set BookObj = Nothing
    Set CellData = Nothing
    Set RangeData = Nothing
    Set SheetObj = Nothing
End Sub

Excel(COM)

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;
        Excel.Range? rangeData = null;
        Excel.Range? cellData = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;
            worksheet = worksheets["Sheet1"];

            // 範囲セル取得
            rangeData = worksheet.Range["A1", "C2"];

            for (var n = 1; n <= rangeData.Count; n++)
            {
                cellData = rangeData[n];

                Console.WriteLine($"アドレス:{cellData.Address}/値:{cellData.Value}");

                CleanUpComObject(ref cellData);
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref cellData);
            CleanUpComObject(ref rangeData);
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                IXLWorksheet? worksheet = null;

                if(!workbook.TryGetWorksheet("Sheet1", out worksheet))
                {
                    throw new Exception("Sheet1シートがありませんでした");
                }

                var rangeData = worksheet.Range("A1:C2");

                foreach (var cellData in rangeData.Cells())
                {
                    Console.WriteLine($"アドレス:{cellData.Address}/値:{cellData.Value}");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // シートを取得
                var worksheet = package.Workbook.Worksheets["Sheet1"];

                // 範囲セルを取得
                var rangeData = worksheet.Cells["A1:C2"];

                foreach (var cellData in rangeData)
                {
                    Console.WriteLine($"アドレス:{cellData.Address}/値:{cellData.Value}");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ExcelDataReader

using ExcelDataReader;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ファイル開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
                // DataSetに変換(シート情報を取得)
                var result = reader.AsDataSet();
                
                // シート取得
                var worksheet = result.Tables["Sheet1"];
            
                for(var r = 0; r <= 1; r++)
                {
                    for(var c = 0; c <= 2; c++)
                    {
                        // セルの値
                        var value = worksheet.Rows[r][c];

                        // 列名
                        var colStr = ConvertColumnNumToStr(c + 1);
                        var address = $"{colStr}{r + 1}";

                        Console.WriteLine($"アドレス:{address}/値:{value}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    // 列を表す数値から列を表す文字列に変換する
    static string ConvertColumnNumToStr(int colNum)
    {
        var colStr = string.Empty;

        while (colNum > 0)
        {
            int modulo = (colNum - 1) % 26;
            colStr = Convert.ToChar('A' + modulo) + colStr;
            colNum = (colNum - modulo) / 26;
        }

        return colStr;
    }
}

NPOI

xls

using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");
            
            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stRead = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = new HSSFWorkbook(stRead))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                for(var r = 0; r <= 1; r++)
                {
                    for(var c = 0; c <= 2; c++)
                    {
                        // セルを取得
                        var cellData = worksheet.GetRow(r).GetCell(c);

                        // アドレスを取得
                        var address = new CellReference(r, c).FormatAsString();

                        // 値を取得
                        var value = cellData.StringCellValue;

                        Console.WriteLine($"アドレス:{address}/値:{value}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            
            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stRead = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = new XSSFWorkbook(stRead))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                for (var r = 0; r <= 1; r++)
                {
                    for (var c = 0; c <= 2; c++)
                    {
                        // セルを取得
                        var cellData = worksheet.GetRow(r).GetCell(c);

                        // アドレスを取得
                        var address = new CellReference(r, c).FormatAsString();

                        // 値を取得
                        var value = cellData.StringCellValue;

                        Console.WriteLine($"アドレス:{address}/値:{value}");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

xls/xlsx対応版

Program.cs

using NPOI.SS.Util;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // シートを取得
                var worksheet = workbook.GetSheet("Sheet1");

                for (var r = 0; r <= 1; r++)
                {
                    for (var c = 0; c <= 2; c++)
                    {
                        // セルを取得
                        var cellData = worksheet.GetRow(r).GetCell(c);

                        // アドレスを取得
                        var address = new CellReference(r, c).FormatAsString();

                        // 値を取得
                        var value = cellData.StringCellValue;

                        Console.WriteLine($"アドレス:{address}/値:{value}");
                    }
                }
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-19 14:33:19
更新日時: 2025-05-21 03:57:21

??の左側がnullだったら右側の値を設定するといった場合に使うもの

string user = null;

user = user ?? "サンプル太郎"; // userがnullならサンプル太郎が設定される

Console.WriteLine(user); // サンプル大老

user = user ?? "サンプル次郎"; // userがnullでなければuserの値が設定される

Console.WriteLine(user); // サンプル太郎

これは、C#2.0の機能

user = user ?? "サンプル太郎"; と書くと

??= と書きたくなるもので・・・

それがC#8.0で導入されましたという話

string user = null;

user ??= "サンプル太郎"; // userがnullならサンプル太郎が設定される

Console.WriteLine(user); // サンプル大老

user ??= "サンプル次郎"; // userがnullでなければuserの値が設定される

Console.WriteLine(user); // サンプル太郎
投稿日時: 2025-05-19 12:03:19

今まで、名前空間といえば、宣言してブロックで囲むものでしたが・・・

namespace SampleCode
{
    internal class Sample 
    {
        static void Main(string[] args) 
        {
            Console.WriteLine("Hello NameSpace!");
        }
    }
}

C#10からは、ブロック無しで記述できる
つまり、1個分ブロックが減ってすっきりするって話

namespace SampleCode;

internal class Sample 
{
    static void Main(string[] args) 
    {
        Console.WriteLine("Hello NameSpace!");
    }
}

地味ですがいいですね

投稿日時: 2025-05-18 11:57:18
更新日時: 2025-06-14 01:30:14

ExcelVBA

Sub Sample()
    Dim AddSheetObj     As Worksheet
    Dim BookObj         As Workbook
    Dim FilePath        As String
    Dim SheetCount      As Long
    Dim LastSheetObj    As Worksheet
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート数を取得
    SheetCount = BookObj.Sheets.Count
    
    '最後のシートを取得
    Set LastSheetObj = BookObj.Sheets(SheetCount)
    
    '渡し、その後ろに追加するよう指示する
    Set AddSheetObj = BookObj.Sheets.Add(After:=LastSheetObj)
    
    '追加されたシートの名前を変える
    AddSheetObj.Name = "sample"

    '保存
    BookObj.Save
        
    '閉じる
    BookObj.Close
    
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? addWorksheet = null;
        Excel.Worksheet? lastWorksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            // 最後のシートを取得
            var sheetCount = worksheets.Count;
            lastWorksheet = worksheets[sheetCount];

            // シート追加
            addWorksheet = worksheets.Add(After: lastWorksheet);

            // シート名変更
            addWorksheet.Name = "sample";

            // 保存
            workbook.Save();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref addWorksheet);
            CleanUpComObject(ref lastWorksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook, true, true);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                // 一番最後にsampleシート追加
                workbook.Worksheets.Add("sample");

                // 上書き保存
                workbook.Save();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var package = new ExcelPackage(readStream))
            {
                // シートを追加
                var worksheet = package.Workbook.Worksheets.Add("sample");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    package.SaveAs(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラー: {ex.Message}");
        }
    }
}

ClosedXMLだと、直接Streamを使って上書きしているのに対し、
EPPlusだと、別のStreamに保存し元のStreamを空にしてからコピーをする手段をとっています
ライブラリ内部の設計の違いからくるもののそうです

ExcelDataReader

読み取りのみのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new HSSFWorkbook(readStream))
            {
                // シートを追加
                workbook.CreateSheet("sample");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = new XSSFWorkbook(readStream))
            {
                // シートを追加
                workbook.CreateSheet("sample");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // メモリにすべて取り込む。これなら遅延でStreamにアクセスするような問題が発生しない
            byte[] fileBytes = File.ReadAllBytes(filePath);
            using (var readStream = new MemoryStream(fileBytes))
            using (var workbook = WorkbookFactory.Create(readStream, extension))
            {
                // シートを追加
                workbook.CreateSheet("sample");

                // あらためて書き込み用としてStreamを開く
                using (var writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // 書き込み
                    workbook.Write(writeStream);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-18 04:38:18
更新日時: 2025-05-26 14:47:26

作成

ファイルパスを指定

Excelデータをメモリ上で作って、別名保存する場合(SaveAs)

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                // Excelデータと紐づける
                using (var package = new ExcelPackage())
                {
                    // シートを追加
                    var worksheet =  package.Workbook.Worksheets.Add("sample");

                    // A1形式
                    worksheet.Cells["A1"].Value = "A1";
                    
                    // 行列形式
                    worksheet.Cells[1, 2].Value = "B1";

                    // 上書き保存
                    package.SaveAs(filePath);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}

※new FileInfo(filePath) としても同じ

保存先のファイルパスを含んだ状態で、Excelデータを作成し上書き保存する(Save)

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                // Excelデータと紐づける
                using (var package = new ExcelPackage(filePath))
                {
                    // シートを追加
                    var worksheet = package.Workbook.Worksheets.Add("sample");

                    // A1形式
                    worksheet.Cells["A1"].Value = "A1";

                    // 行列形式
                    worksheet.Cells[1, 2].Value = "B1";

                    // 上書き保存
                    package.Save();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}

※new FileInfo(filePath) としても同じ結果

Streamを指定
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
                using (var package = new ExcelPackage(stream))
                {
                    // シートを追加
                    var worksheet = package.Workbook.Worksheets.Add("sample");

                    // A1形式
                    worksheet.Cells["A1"].Value = "A1";

                    // 行列形式
                    worksheet.Cells[1, 2].Value = "B1";

                    // 上書き保存
                    package.Save();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}

式を記載する場合の注意点

sumを記載し値を確認
using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                using (var package = new ExcelPackage())
                {
                    // シートを追加
                    var worksheet =  package.Workbook.Worksheets.Add("sample");

                    for(var r = 1; r <=3; r++)
                    {
                        worksheet.Cells[r, 1].Value = 10 * r;
                    }

                    worksheet.Cells[4, 1].Formula = "sum(A1:A3)";

                    // 上書き保存
                    package.SaveAs(filePath);
                }

                // 開きなおす
                using (var package = new ExcelPackage(filePath))
                {
                    var worksheet = package.Workbook.Worksheets["sample"];

                    // 計算の結果を取得
                    var samValue = worksheet.Cells[4, 1].Value;
                    Console.WriteLine($"Sumの値は = {samValue}");

                    // 計算モードの取得
                    var calcMode = package.Workbook.CalcMode;
                    Console.WriteLine($"計算モード = {calcMode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}

実行結果は以下のとおりです。

Sumの値は =
計算モード = Automatic

60が返ってくることを期待していたのですが、ここでいう計算モードのAUtomaticは
Excelアプリケーションで開いたときの挙動であり、XMLを直接操作した場合の挙動とは無関係にないrます
そのため、プログラムを書く人が、明示的に

Workbook.Calculate() か Worksheet.Calculate()してあげる必要がある

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                using (var package = new ExcelPackage())
                {
                    // シートを追加
                    var worksheet =  package.Workbook.Worksheets.Add("sample");

                    for(var r = 1; r <=3; r++)
                    {
                        worksheet.Cells[r, 1].Value = 10 * r;
                    }

                    worksheet.Cells[4, 1].Formula = "sum(A1:A3)";

                    // 計算する
                    //package.Workbook.Calculate(); // ワークブック単位で計算
                    worksheet.Calculate();        // ワークシート単位で計算

                    // 上書き保存
                    package.SaveAs(filePath);
                }

                // 開きなおす
                using (var package = new ExcelPackage(filePath))
                {
                    var worksheet = package.Workbook.Worksheets["sample"];

                    // 計算の結果を取得
                    var samValue = worksheet.Cells[4, 1].Value;
                    Console.WriteLine($"Sumの値は = {samValue}");

                    // 計算モードの取得
                    var calcMode = package.Workbook.CalcMode;
                    Console.WriteLine($"計算モード = {calcMode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}

上書き保存

ファイルパスを指定
using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                // ファイルを開く
                using (var package = new ExcelPackage(filePath))
                {
                    // 最初のシートを取得
                    var worksheet = package.Workbook.Worksheets[0]; 
                    worksheet.Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");
                
                    // 上書き保存
                    package.Save();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}
streamを指定
using OfficeOpenXml;
using System;
using System.IO;
using System.IO.Pipes;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                // 読み書きモードでファイルオープン。ファイル共有は読み取りだけ可の状態
                using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
                using (var package = new ExcelPackage(filePath))
                {
                    // 最初のシートを取得
                    var worksheet = package.Workbook.Worksheets[0]; 
                    worksheet.Cells["A1"].Value = DateTime.Now.ToString("HH:mm:ss");

                    // 変更を一時的にMemoryStreamへ保存
                    using (var temp = new MemoryStream())
                    {
                        package.SaveAs(temp); // 変更内容をMemoryStreamに保存
                        temp.Position = 0;    // 書き込み位置を先頭へ

                        // streamを上書きするため、長さを0にリセット
                        stream.SetLength(0);
                        temp.CopyTo(stream); // MemoryStreamの内容をstreamへコピー
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー: {ex.Message}");
            }
        }
    }
}
投稿日時: 2025-05-18 04:32:18
更新日時: 2025-05-18 05:26:18

変数名やメソッド、クラス名等の名前が文字列として取得できる関数

ログやエラーメッセージなどに含めておくと、それらがリファクタリングで名前を変えた場合に追従できるのがメリット

また、特定のクラスだったら処理を分岐するといったケースでも直接文字列リテラルとして指定するのではなく

nameof演算子を使っておけば、のちのち変更された場合も動的に対応することができる

こんな感じで、変数名をべた書きしていた場合に、

var data = "abc";

data += DateTime.Now.ToString("yyyy-MM-dd");

Console.WriteLine($"data={data}");

dataって変数名はいくらなんでも抽象的だから名前を変えようってことで一括変換すると


var sampleData = "abc";

sampleData += DateTime.Now.ToString("yyyy-MM-dd");

Console.WriteLine($"data={sampleData}");

リテラルで書き込んでいた変数名が取り残されてしまう

そのため、nameof演算子をつかっておけば


var data = "abc";

data += DateTime.Now.ToString("yyyy-MM-dd");

Console.WriteLine($"{nameof(data)}={data}");

リファクタリングして変数名を変更したとしても


var sampleData = "abc";

sampleData += DateTime.Now.ToString("yyyy-MM-dd");

Console.WriteLine($"{nameof(sampleData)}={sampleData}");

追従させれるので、よからぬ修正がはいらず安心ってことです

投稿日時: 2025-05-17 17:23:17
更新日時: 2025-05-19 11:56:19

3つ以上のダブルクォートで囲むものを生文字列リテラルとする

@"" と $"" が合体したような機能ですかねぇ

Console.WriteLine("""生文字列リテラル""");
Console.WriteLine(""""これも生文字列リテラル"""");

\によるエスケープシーケンスは無効

Console.WriteLine("abc\ndef");
Console.WriteLine("""abc\ndef""");

実行結果

abc
def
abc\ndef

これは逐次的文字列リテラル(@"")と同じです

変数埋め込み

C#6.0から変数が埋め込めるようになりました
それのバージョンアップ版ですね

var x = 10;

// 生文字列リテラル
Console.WriteLine($"""x = {x}""");
Console.WriteLine($$"""x = {{x}}""");

// 変数埋め込み(C#6.0)
Console.WriteLine($"x = {x}");

開始のダブルクォートの前に$を何個記載するかに応じて{}の数が決まるようになっています

なかなか使う機会はないかもしれないですが・・・
以下のようなことができます

var x = 10;

// 生文字列リテラル
Console.WriteLine($$"""{x} = {{x}}"""); // {x} = 10

// 変数埋め込み(C#6.0)で同じことをするなら
Console.WriteLine($"{{x}} = {x}");      // {x} = 10

変数埋め込みだと { }が特別な文字なのでエスケープするためには、同じ文字で続けて書く必要がありました
生文字列リテラルの場合は、ダブルクォートの前の$の数で調整できるので、 {} をリテラルとして使いたい場合は、
$の数を調整すればよいので、直感的に記載ができるということになります

開始直後、終了直前の改行は無視。各行の前後空白は無視。

var str = """"
    abc
    def
    ghi
    """";

Console.WriteLine($"文字数:{str.Length}");
Console.WriteLine(str);
文字数:13
abc
def
ghi

13文字なので、各文字列の間にCRLFの2文字("abc\r\ndef\r\nghi")があるので
3文字(abc) + 2文字(\r\n) + 3文字(def) + 2文字(\r\n) + 3文字(ghi) 計13文字になります

投稿日時: 2025-05-17 16:20:17
更新日時: 2025-06-13 13:31:13

シートが3つ入っているExcelを開いて、シートの数を取得し出力します

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Variant
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    'シート数出力
    Debug.Print BookObj.Sheets.Count
        
    '保存せずに閉じる
    BookObj.Close (False)
    
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            // シート数出力
            Console.WriteLine(worksheets.Count);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                // シート数出力
                Console.WriteLine(workbook.Worksheets.Count);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // シート数出力
                Console.WriteLine(package.Workbook.Worksheets.Count);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

using ExcelDataReader;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ファイル開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
                // DataSetに変換(シート情報を取得)
                var result = reader.AsDataSet();

                // シート数出力
                Console.WriteLine(result.Tables.Count);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            {
                using (var workbook = new HSSFWorkbook(fileStream))
                {
                    // シート数出力
                    Console.WriteLine(workbook.NumberOfSheets);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            {
                using (var workbook = new XSSFWorkbook(fileStream))
                {
                    // シート数出力
                    Console.WriteLine(workbook.NumberOfSheets);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // シート数出力
                Console.WriteLine(workbook.NumberOfSheets);
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-17 05:31:17
更新日時: 2025-05-26 13:43:26

シートが3つ入っているExcelを開いて、各ワークシートを取得し名前を出力します

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Variant
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    '全シート取得し、それぞれの名前を出力する
    For Each SheetObj In BookObj.Sheets
        Debug.Print SheetObj.Name
    Next
    
    '保存せずに閉じる
    BookObj.Close (False)
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            // 各シートの名前を出力
            for (var i = 1; i <= worksheets.Count; i++)
            {
                // 各シートを取得して名前を表示
                worksheet = worksheets[i];
                Console.WriteLine(worksheet.Name);

                CleanUpComObject(ref worksheet);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(stream))
            {
                // ワークシートをループで処理
                foreach (var worksheet in workbook.Worksheets)
                {
                    // シート名を出力
                    Console.WriteLine(worksheet.Name);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(stream))
            {
                // ワークシートをループで処理
                foreach (var worksheet in package.Workbook.Worksheets)
                {
                    // シート名を出力
                    Console.WriteLine(worksheet.Name);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ファイル開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
                // DataSetに変換(シート情報を取得)
                var result = reader.AsDataSet();

                // DataTableCollectionからDataTable(シート情報)を一つずつ取り出す
                foreach (DataTable table in result.Tables)
                {
                    // シート名を出力
                    Console.WriteLine(table.TableName);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            {
                using (var workbook = new HSSFWorkbook(fileStream))
                {
                    // foreachはない。シート数からシート番号としてシートを受け取りシート名を表示
                    for (var i = 0; i < workbook.NumberOfSheets; i++)
                    {
                        var worksheet = workbook.GetSheetAt(i);

                        Console.WriteLine(worksheet.SheetName);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            {
                using (var workbook = new XSSFWorkbook(fileStream))
                {
                    // foreachはない。シート数からシート番号としてシートを受け取りシート名を表示
                    for (var i = 0; i < workbook.NumberOfSheets; i++)
                    {
                        var worksheet = workbook.GetSheetAt(i);

                        Console.WriteLine(worksheet.SheetName);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsもxlsxも扱う場合

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // 拡張子取得
            var extension = Path.GetExtension(filePath);

            // ブックを開く
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // foreachはない。シート数からシート番号としてシートを受け取りシート名を表示
                for (var i = 0; i < workbook.NumberOfSheets; i++)
                {
                    var worksheet = workbook.GetSheetAt(i);

                    Console.WriteLine(worksheet.SheetName);
                }
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-17 04:53:17
更新日時: 2025-05-26 14:21:26

シートが3つ入っているExcelを開いて、各ワークシートを取得し名前を出力します

ExcelVBA

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim SheetObj    As Variant
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    '全シート取得し、それぞれの名前を出力する
    For Each SheetObj In BookObj.Sheets
        Debug.Print SheetObj.Name
    Next
    
    '保存せずに閉じる
    BookObj.Close (False)
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode
{
    internal class SampleExcel
    {
        static void Main(string[] args)
        {
            Excel.Application? application = null;
            Excel.Workbooks? workbooks = null;
            Excel.Workbook? workbook = null;
            Excel.Sheets? worksheets = null;
            Excel.Worksheet? worksheet = null;

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");
                
                // Excelを開く
                application = new Excel.Application();
                application.Visible = true;

                // ブックを開く
                workbooks = application.Workbooks;
                workbook = workbooks.Open(filePath);

                // シートを取得
                worksheets = workbook.Sheets;

                // 各シートの名前を出力
                for (var i = 1; i <= worksheets.Count; i++)
                {
                    // 各シートを取得して名前を表示
                    worksheet = worksheets[i];
                    Console.WriteLine(worksheet.Name);

                    CleanUpComObject(ref worksheet);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                CleanUpComObject(ref worksheet);
                CleanUpComObject(ref worksheets);
                CleanUpComObject(ref workbook);
                CleanUpComObject(ref workbooks);
                CleanUpComObject(ref application);
            }
        }

        static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
        {
            // フラグによってWorkbookはClose / ApplicationはQuitする
            if (shouldClose && comObject != null)
            {
                // 型をチェックする
                if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
                {
                    // Workbookの場合
                    workbook.Close(saveChanges);
                }
                else if (comObject is Microsoft.Office.Interop.Excel.Application application)
                {
                    // Applicationの場合
                    application.Quit();
                }
            }

            // Objectを解放
            if (comObject != null && Marshal.IsComObject(comObject))
            {
                Marshal.ReleaseComObject(comObject);
                comObject = default!;
            }
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleClosedXML
    {
        static void Main(string[] args)
        {
            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");
                
                // ブックを開く、読み書きモード、共有なし(排他)
                using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                using (var workbook = new XLWorkbook(stream))
                {
                    // ワークシートをループで処理
                    foreach (var worksheet in workbook.Worksheets)
                    {
                        // シート名を出力
                        Console.WriteLine(worksheet.Name);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleEPPlus
    {
        static void Main(string[] args)
        {
            // Ver8.0のソースです
            // 非商用個人利用の場合 名前を設定
            ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");
                
                // ブックを開く、読み書きモード、共有なし(排他)
                using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                using (var package = new ExcelPackage(stream))
                {
                    // ワークシートをループで処理
                    foreach(var worksheet in package.Workbook.Worksheets)
                    {
                        // シート名を出力
                        Console.WriteLine(worksheet.Name);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

ExcelDataReader

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleExcelDataReader
    {
        static void Main(string[] args)
        {
            // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");
                filePath = @"c:\temp\sample.xlsx"; //////////////////////////////////////////////ここ消す
                // ファイル開く、読み書きモード、共有なし(排他)
                using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (var reader = ExcelReaderFactory.CreateReader(fileStream))
                {
                    // DataSetに変換(シート情報を取得)
                    var result = reader.AsDataSet();

                    // DataTableCollectionからDataTable(シート情報)を一つずつ取り出す
                    foreach (DataTable table in result.Tables)
                    {
                        // シート名を出力
                        Console.WriteLine(table.TableName);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

NPOI

xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleNPOI_xls
    {
        static void Main(string[] args)
        {
            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xls");
                
                // ブックを開く、読み書きモード、共有なし(排他)
                using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                {
                    using (var workbook = new HSSFWorkbook(fileStream))
                    {
                        // foreachはない。シート数からシート番号としてシートを受け取りシート名を表示
                        for (var i = 0; i < workbook.NumberOfSheets; i++)
                        {
                            var worksheet = workbook.GetSheetAt(i);

                            Console.WriteLine(worksheet.SheetName);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

xlsx

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

namespace SampleCode
{
    internal class SampleNPOI_xlsx
    {
        static void Main(string[] args)
        {
            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");
                
                // ブックを開く、読み書きモード、共有なし(排他)
                using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                {
                    using (var workbook = new XSSFWorkbook(fileStream))
                    {
                        // foreachはない。シート数からシート番号としてシートを受け取りシート名を表示
                        for (var i = 0; i < workbook.NumberOfSheets; i++)
                        {
                            var worksheet = workbook.GetSheetAt(i);

                            Console.WriteLine(worksheet.SheetName);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

xlsもxlsxも扱う場合

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode
{
    internal class SampleNPOI
    {
        static void Main(string[] args)
        {
            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");
                filePath = @"c:\temp\sample.xlsx";
                
                var extension = Path.GetExtension(filePath);

                using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                using (var workbook = WorkbookFactory.Create(fileStream, extension))
                {
                    for(var i = 0; i < workbook.NumberOfSheets; i++)
                    {
                        var worksheet = workbook.GetSheetAt(i);

                        Console.WriteLine(worksheet.SheetName);
                    }
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode
{
    public class WorkbookFactory
    {
        public static IWorkbook Create(Stream stream, string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(stream),
                ".xlsx" => new XSSFWorkbook(stream),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }

        public static IWorkbook Create(string extension)
        {
            extension = extension.ToLower();

            return extension switch
            {
                ".xls" => new HSSFWorkbook(),
                ".xlsx" => new XSSFWorkbook(),
                _ => throw new NotSupportedException("対象外の拡張子です")
            };
        }
    }
}
投稿日時: 2025-05-17 04:52:17

もう一歩、シンプルな実装をするため解放ヘルパーなんてどう?と生成AIにすすめられたので触れてみる
Idisposableのクラスに、object を格納するリストを作って、disposeで逆順に開放し、
GC.Collect();GC.WaitForPendingFinalizers();も最後にしっかり2度呼ぶという話

ついでに、workbookは、登録時に閉じるか否か、閉じる場合は保存するか否かと
applicationは、登録時に閉じるか否かを指定できるようにすると・・・

こんな感じのクラスになります

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleExcel
{
    internal class ComReleaseManager : IDisposable
    {
        /// <summary>
        /// COMオブジェクトデータ
        /// </summary>
        /// <param name="ComObject">COMオブジェクト</param>
        /// <param name="ShouldClose">WorkbookでCloseするか否か, ApplicationでQuitするか否か</param>
        /// <param name="SaveChanges">WorkbookでCloseする場合、保存するかしないか</param>
        private record ComObjectEntry(object ComObject, bool ShouldClose, bool SaveChanges);

        // new () は左辺の型で new するのを省略する書き方(C# 9.0)
        private readonly List<ComObjectEntry> _comObjects = new ();

        // where T : class で値型ではなく、参照型のみに限定している
        private T RegisterInternal<T>(T comObject, bool shouldClose, bool saveChanges) where T : class
        {
            // null ならエラー
            if (comObject == null)
            {
                throw new ArgumentNullException();
            }

            // COMオブジェクトデータとして登録
            _comObjects.Add(new ComObjectEntry(comObject, shouldClose, saveChanges));

            // COMオブジェクトを返却
            return comObject;
        }

        // 一般的なCOMオブジェクト
        public T Register<T>(T comObject) where T : class
        {
            return RegisterInternal(comObject, false, false);
        }

        // Application登録(shouldClose指定あり)
        public Excel.Application Register(Excel.Application app, bool shouldClose)
        {
            return RegisterInternal(app, shouldClose, false);
        }

        // Workbook登録(shouldCloseとsaveChangesを指定)
        public Excel.Workbook Register(Excel.Workbook workbook, bool shouldClose, bool saveChanges)
        {
            return RegisterInternal(workbook, shouldClose, saveChanges);
        }

        // 後処理
        public void Dispose()
        {
            for (int i = _comObjects.Count - 1; i >= 0; i--)
            {
                var entry = _comObjects[i];
                try
                {
                    if (entry.ShouldClose)
                    {
                        switch (entry.ComObject)
                        {
                            case Excel.Workbook workbook:
                                workbook.Close(entry.SaveChanges);
                                break;

                            case Excel.Application app:
                                app.Quit();
                                break;
                        }
                    }

                    Marshal.ReleaseComObject(entry.ComObject);
                }
                catch
                {
                    // ログなどに残してもよい(今回は無視)
                }
            }

            _comObjects.Clear();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

もう少し拡張するなら、debugビルドかreleaseビルドかによって
Marshal.ReleaseComObject関数が返す参照カウントの値をログにだせるようにすると解放漏れがあった場合にはすぐにわかってよいかも

補足:recordについて recordは、簡単に説明するとC#9.0に追加された機能 classでデータのみ保持する場合のシンタックスシュガーみたいなものになります

以下コードは

public record Person(string Name, int Age);

C# 9.0より前では以下と一緒

public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public override bool Equals(object obj) =>
        obj is Person other && Name == other.Name && Age == other.Age;

    public override int GetHashCode() => HashCode.Combine(Name, Age);

    public override string ToString() => $"Person {{ Name = {Name}, Age = {Age} }}";
}

以下ソースコードを解放ヘルパーを使って書き直してみます

Before:

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode
{
    internal class SampleExcel
    {
        static void Main(string[] args)
        {
            Excel.Application? application = null;
            Excel.Workbooks? workbooks = null;
            Excel.Workbook? workbook = null;
            Excel.Sheets? worksheets = null;
            Excel.Worksheet? worksheet = null;

            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

             
                
                // Excelを開く
                application = new Excel.Application();
                application.Visible = true;

                // ブックを開く
                workbooks = application.Workbooks;
                workbook = workbooks.Open(filePath);

                // シートを取得
                worksheets = workbook.Sheets;

                for (var i = 1; i <= worksheets.Count; i++)
                {
                    // 各シートを取得して名前を表示
                    worksheet = worksheets[i];
                    Console.WriteLine(worksheet.Name);

                    CleanUpComObject(ref worksheet);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                CleanUpComObject(ref worksheet);
                CleanUpComObject(ref worksheets);
                CleanUpComObject(ref workbook);
                CleanUpComObject(ref workbooks);
                CleanUpComObject(ref application);

                // RCW強制解放(残留プロセス対策)
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }

        static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
        {
            // フラグによってWorkbookはClose / ApplicationはQuitする
            if (shouldClose && comObject != null)
            {
                // 型をチェックする
                if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
                {
                    // Workbookの場合
                    workbook.Close(saveChanges);
                }
                else if (comObject is Microsoft.Office.Interop.Excel.Application application)
                {
                    // Applicationの場合
                    application.Quit();
                }
            }

            // Objectを解放
            if (comObject != null && Marshal.IsComObject(comObject))
            {
                Marshal.ReleaseComObject(comObject);
                comObject = default!;
            }
        }
    }
}

After:

using SampleExcel;
using System;
using System.IO;
using System.Reflection;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode
{
    internal class SampleExcel
    {
        static void Main(string[] args)
        {
            try
            {
                // 実行ファイルのあるフォルダパス取得
                var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
                if (folderPath == null) { return; }

                // Excelファイルパスを作成
                var filePath = Path.Combine(folderPath, "sample.xlsx");

                using (var comManager = new ComReleaseManager())
                {
                    // Excelを開く
                    var application = comManager.Register(new Excel.Application(), true);

                    // ブックを開く
                    var workbooks = comManager.Register(application.Workbooks);
                    var workbook = comManager.Register(workbooks.Open(filePath), true, false);
                    
                    // シートを取得
                    var worksheets = comManager.Register(workbook.Sheets);
   
                    for(var i = 1; i < worksheets.Count; i++)
                    {
                        var worksheet = comManager.Register(worksheets[i]);

                        Console.WriteLine(worksheet.Name);
                    }
                
                }
                // using をぬけると生成したComオブジェクトを逆順に解放し、必要に応じて、Close Quitし、2回GCを実行する
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

現実的で読みやすいレベルのソースコードになりました!

ただ、解放クラスは使いまわしをしない方針のため、セルを数万行チェックするといったようなケースの場合
大量にリストにオブジェクトを抱えた状態になるので、あまりおすすめできないとおもいます

それらは、一時的変数で管理してすぐに廃棄していくのがベストだと思うので、うまく組み合わせて使っていくことが重要だと思います

投稿日時: 2025-05-14 13:18:14
更新日時: 2025-05-14 13:51:14

解放をしない場合の挙動

適切な解放がされないと、プロセスが消えずに残ることになります。

実際に確認をしてみます
以下のように、applicationクラスのインスタンスを作成し、すぐ閉じます。

using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        application = new Excel.Application();
        application.Quit();
    }
}

これでおきることは、new Excel.Application()を実行すると

バックグラウンドにExcelのプロセス現れます

 

プログラムではQuitして終了したようにみえても、プロセスには残り続けています

もし、定時実行をし続けようならば、実行するたびにプロセスがのこっていきます・・・

残ったプロセスは手動で消してください・・・

アプリケーションを表示した際のプロセスの挙動

application.VisibleをtrueにするとExcelが表示されます

using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        application = new Excel.Application();
        application.Visible = true;
    }
}

この時の動きは、 new Excel.Application()で バックグラウンドとしてプロセスが作成され

その後、application.Visible = true でExcelが表示されアプリのプロセスに移動

↑バックグラウンドのプロセス一覧 ↑アプリのプロセス一覧

プログラムはここで終了となり、開かれているExcelを閉じると、再びバックグラウンドの方に移動します

パット見た感じアプリの一覧から消えるので、解放していなくても消えたようにみえるかもしれませんが、バックグラウンド側に残っています

一度でも参照したら解放する

たとえば、exeのあるフォルダに、3シートある sample.xlsxがあったとします
開いて、各シート名を出力してみます

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            for (var i = 1; i <= worksheets.Count; i++)
            {
                // 各シートを取得して名前を表示
                worksheet = worksheets[i];
                Console.WriteLine(worksheet.Name);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            if (worksheet != null) { Marshal.ReleaseComObject(worksheet); }
            if (worksheets != null) { Marshal.ReleaseComObject(worksheets); }
            if (workbook != null)
            {
                workbook.Close();
                Marshal.ReleaseComObject(workbook);
            }
            if (workbooks != null) { Marshal.ReleaseComObject(workbooks); }
            if (application != null)
            {
                application.Quit();
                Marshal.ReleaseComObject(application);
            }
        }
    }
}

これは、バックグラウンドにプロセスが残ります
一番最後のSheet3については、finallyで解放していますが、
Sheet1とSheet2は、参照しているのに解放していないので、プロセスが残ります

参照したら都度解放する必要があります

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            for (var i = 1; i <= worksheets.Count; i++)
            {
                // 各シートを取得して名前を表示
                worksheet = worksheets[i];
                Console.WriteLine(worksheet.Name);

                // 参照したら都度解放する
                Marshal.ReleaseComObject(worksheet);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            if (worksheet != null) { Marshal.ReleaseComObject(worksheet); }
            if (worksheets != null) { Marshal.ReleaseComObject(worksheets); }
            if (workbook != null)
            {
                workbook.Close();
                Marshal.ReleaseComObject(workbook);
            }
            if (workbooks != null) { Marshal.ReleaseComObject(workbooks); }
            if (application != null)
            {
                application.Quit();
                Marshal.ReleaseComObject(application);
            }
        }
    }
}

不要にReleaseComObjectを実行した場合

ループしてWorkSheetのオブジェクトを参照した場合、都度解放をしているのでループを抜けた場合、
参照カウントは0になっています。ただ、ReleaseComObjectを実行してもnullにはならないので、
finallyにきたら、さらにReleaseComObjectを実行することになります。

一般的には、不要にReleaseComObjectを実行するものではないといわれていますが、
それによって意図しない状態になったのを見たことがないのでなんともいえないです。

そのため、途中で開放した場合は、nullにしてしまうのがより良いと考えられているそうです

using System.IO;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            for (var i = 1; i <= worksheets.Count; i++)
            {
                // 各シートを取得して名前を表示
                worksheet = worksheets[i];
                Console.WriteLine(worksheet.Name);

                // 参照したら都度解放する
                Marshal.ReleaseComObject(worksheet);
                worksheet = null;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            if (worksheet != null) { Marshal.ReleaseComObject(worksheet); }
            if (worksheets != null) { Marshal.ReleaseComObject(worksheets); }
            if (workbook != null)
            {
                workbook.Close();
                Marshal.ReleaseComObject(workbook);
            }
            if (workbooks != null) { Marshal.ReleaseComObject(workbooks); }
            if (application != null)
            {
                application.Quit();
                Marshal.ReleaseComObject(application);
            }
        }
    }
}

とはいえこれはよいことではあるけども、少しめんどくさい
そこで、解放処理や、Close、Quitをする処理をまとめると・・・

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            for (var i = 1; i <= worksheets.Count; i++)
            {
                // 各シートを取得して名前を表示
                worksheet = worksheets[i];
                Console.WriteLine(worksheet.Name);

                CleanUpComObject(ref worksheet);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

もう少し、掘り下げておくとWorksheetのオブジェクトについては
for文のループ中でしかつかっていないので、その中で変数を作り格納しすぐ開放すればよいように思える

for (var i = 1; i <= worksheets.Count; i++)
{
    // 各シートを取得して名前を表示
    var worksheet = worksheets[i];
    Console.WriteLine(worksheet.Name);

    // 何かしらの処理

    CleanUpComObject(ref worksheet);
}

ただ、参照したあとに何かしらの処理で例外が発生したら、参照カウントが下げることができないので
オブジェクトを参照した直後から、解放するまではtry catchで囲わないといけないので、
あちことで変数を作って参照していたらtry catchだらけになることに注意

for (var i = 1; i <= worksheets.Count; i++)
{
    // 各シートを取得して名前を表示
    var worksheet = worksheets[i];
    try
    {
        Console.WriteLine(worksheet.Name);

        // 何かしらの処理

        CleanUpComObject(ref worksheet);
    }
    catch
    {
        throw;
    }
    finally
    {
        CleanUpComObject(ref worksheets);
    }
}

いろいろ調べてみるかぎり、より完全に解放するためには、GCを2度読んであげるとよいとされているそうです

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

2度呼ぶ理由についての生成AIの説明

✅ 結論から:なぜ2回 GC.Collect(); GC.WaitForPendingFinalizers(); が必要なのか?

これは、COMオブジェクトの参照がすべて解放されたことを確実にするために行われます。

📌 背景知識:COMオブジェクトとRCW(Runtime Callable Wrapper)

・.NET から COM オブジェクト(例:Excel)を使うとき、RCW という「仲介役」が自動的に生成されます。

・RCW は、.NET 側のガベージコレクションによって管理されますが、RCW自体がCOMオブジェクトの参照カウントを持っているため、RCWが解放されるまでCOMオブジェクトは解放されません。

🔄 なぜ2回GCするのか?

1回目の GC.Collect(); GC.WaitForPendingFinalizers(); で何が起きるか

・.NETでGC対象になったRCWオブジェクト(≒Excel COMオブジェクトのラッパー)を収集対象にする

・それにより、RCWのファイナライザ(最終処理)が実行される

・そのファイナライザの中で、COMオブジェクトへの参照が ReleaseComObject() などで解放される

🔁 しかし、ファイナライザが実行された直後にGCが実行されるとは限らないため、もう一度 GC をかけることで:

・ファイナライザによって解放されたRCW自体も収集され

・結果として RCWが保持していたCOMオブジェクトの参照カウントが0になり、COMオブジェクトも確実に解放される

⚠ よくある誤解ポイント

・RCWの解放自体は、明示的に Marshal.ReleaseComObject() を使うことでもできます。これを使えば、2回の GC は不要なこともあります。

・ただし、RCWを複数の変数で共有していたり、Excelオブジェクトの階層(Workbooks→Workbook→Worksheetなど)で参照が残っていたりすると、ReleaseComObjectだけでは不十分で、最終的にGCで回収させる必要が出るため、2回GCが有効になる。

とのこと

おまけ

生成AIさんこのコードで問題ない?

worksheetsをループ処理でシート名を表示するプログラム
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;
        Excel.Sheets? worksheets = null;
        Excel.Worksheet? worksheet = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            // ブックを開く
            workbooks = application.Workbooks;
            workbook = workbooks.Open(filePath);

            // シートを取得
            worksheets = workbook.Sheets;

            for (var i = 1; i <= worksheets.Count; i++)
            {
                // 各シートを取得して名前を表示
                worksheet = worksheets[i];
                Console.WriteLine(worksheet.Name);

                CleanUpComObject(ref worksheet);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref worksheet);
            CleanUpComObject(ref worksheets);
            CleanUpComObject(ref workbook);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

worksheetは、for文でしか使っていないので、一時変数に格納して不要になればすぐ解放することも考えられるけど、
オブジェクト参照から不要になり解放するまでに、処理があって例外が発生する可能性があると都度try~catchを
書く必要があるから、コードが読みにくくなるのであえて書いていないけどどう?

ご提示の設計・実装方針は現実的かつ推奨されるパターンです。

COMオブジェクト(特にExcel)は、取得したすべてのオブジェクトを明示的に解放することが重要であり、

finallyブロックでまとめて後始末をするやり方は、例外発生時も含めてリソースリークを防ぐ観点からも適切です

ポイント整理

・for文内でworksheetを都度解放するのは理想的ですが、例外処理や可読性の観点から冗長になりやすいです。

・あなたのように、finallyでworksheetも含めてまとめて解放する方法は、例外が発生しても確実にリソースを解放できるため、実運用でよく使われています

・ただし、for文内でCleanUpComObject(ref worksheet)を呼んでいるため、ループのたびに前の参照を解放し、finallyでも念のため解放する形になっています。この重複は問題ありませんが、二重解放を防ぐため、CleanUpComObject内でnullチェックをしているのは良い実装です。

投稿日時: 2025-05-13 03:43:13
更新日時: 2025-05-24 00:20:24

ExcelVBA

SaveAsでファイルパスを指定する

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    
    '新規ブック作成
    Set BookObj = Workbooks.Add
    
    '保存するファイルパス
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    '名前つけて保存
    BookObj.SaveAs Filename:=FilePath
    
    Set BookObj = Nothing
End Sub

ただし、ファイルが既にある場合は「この場所に 'XXXX'という名前のファイルが既にあります。置き換えますか?」
と表示されるため、ファイルがある場合も上書きするのであれば、以下のようにします

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    
    '新規ブック作成
    Set BookObj = Workbooks.Add
    
    '保存するファイルパス
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xls"
    
    '同一ファイルがあった場合の警告表示を無効とする
    Application.DisplayAlerts = False
    
    '名前つけて保存
    BookObj.SaveAs Filename:=FilePath
    
    '警告表示を有効に戻す
    Application.DisplayAlerts = True
    
    Set BookObj = Nothing
End Sub

もし、ファイルが既にある場合に上書きしたくない場合は事前にファイル存在チェックをして処理を止めればよいでしょう

Excel(COM)

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;

            workbooks = application.Workbooks;
            workbook = workbooks.Add();

            // 警告表示を無効にする
            application.DisplayAlerts = false;

            // 名前つけて保存
            workbook.SaveAs2(filePath);

            // 警告表示を有効に戻す
            application.DisplayAlerts = false;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

ClosedXML

ファイルパスと、Streamを使った方法

□ファイルパスで名前を付けて保存

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var workbook = new XLWorkbook())
            {
                // シートが一つもないブックは保存でエラーになるので追加
                var sheet = workbook.AddWorksheet("Sheet1");

                // 名前をつけて保存
                workbook.SaveAs(filePath);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileStreamで名前を付けて保存

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        var filePath = string.Empty;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            // ファイル出力用のストリームを作成(まだ中身は空のものが作られる)
            using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
            // メモリ上にワークブックを作成(この時点ではストリームとは未接続)
            using (var workbook = new XLWorkbook())
            {
                // シートが一つもないブックは保存でエラーになるので追加
                var sheet = workbook.AddWorksheet("Sheet1");

                // 作成したワークブックをストリームに保存(初めてファイルと紐づく)
                workbook.SaveAs(stream);
            }
        }
        catch (Exception ex)
        {
            // エラーが発生し、保存ができていなければ、空ファイルを作っただけなので、紛らわしいので消しておく
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

□ファイルパスで名前を付けて保存

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var package = new ExcelPackage())
            {
                // シートが一つもないブックは保存でエラーになるので追加
                var worksheet = package.Workbook.Worksheets.Add("sample");

                // 保存
                package.SaveAs(filePath);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

FileStreamを使った場合は、ClosedXMLと同じなのでそちらを参照

ExcelDataReader

読み取りのみのライブラリのため保存はありません

NPOI

NPOIは、ファイルパスをうけとれないので、FileStreamで処理する

□xls

using NPOI.HSSF.UserModel;
using System.Reflection;

var filePath = "";
try
{
    // 実行ファイルのあるフォルダパス取得
    var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
    if (folderPath == null) { return; }

    // Excelファイルパスを作成
    filePath = Path.Combine(folderPath, "sample.xls");
    
    using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
    using (var workbook = new HSSFWorkbook())
    {
        // シートが一つもないブックは保存でエラーになるので追加
        workbook.CreateSheet("Sheet1");

        // 作成したワークブックをストリームに保存
        workbook.Write(stream);
    }
}
catch (Exception ex)
{
    // エラーが発生し、保存ができていなければ、空ファイルを作っただけなので、紛らわしいので消しておく
    if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
    {
        File.Delete(filePath);
    }

    Console.WriteLine(ex.Message);
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XSSFWorkbook())
            {
                // シートなしでも保存はできてしまう
                // sample.xlsxの一部の内容に問題が見つかりました。と出るのでシートは追加すること
                workbook.CreateSheet("Sheet1");

                // 作成したワークブックをストリームに保存
                workbook.Write(stream);
            }
        }
        catch (Exception ex)
        {
            // エラーが発生し、保存ができていなければ、空ファイルを作っただけなので、紛らわしいので消しておく
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        var filePath = "";

        try
        {   // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            {
                // シートなしでも保存はできてしまう
                // sample.xlsxの一部の内容に問題が見つかりました。と出るのでシートは追加すること
                workbook.CreateSheet("Sheet1");

                // 作成したワークブックをストリームに保存
                workbook.Write(fileStream);
            }
        }
        catch (Exception ex)
        {
            // エラーが発生し、保存ができていなければ、空ファイルを作っただけなので、紛らわしいので消しておく
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-11 10:19:11
更新日時: 2025-05-24 11:45:24

ExcelVBA

Open関数のPasswordに設定

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim ReadPass    As String: ReadPass = "test"
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'パスワード付きExcelを開く
    Set BookObj = Workbooks.Open(FilePath, Password:=ReadPass)
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;
            workbooks = application.Workbooks;

            // 書き込みパスワード
            var pass = "test";

            // 書き込みパスワードでブックを開く
            workbook = workbooks.Open(Filename: filePath, Password: pass);  // 名前付き引数がおすすめ
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

ClosedXML

未対応

EPPlus

未対応

ExcelDataReader

未対応

NPOI

未対応

参考:
ClosedXML, ExcelDataReader, EPPlus, NPOIがパスワード解除について

投稿日時: 2025-05-11 07:00:11
更新日時: 2025-05-24 09:47:24

以下リンク先に、マイクロソフトの説明があります。
Office のサーバーサイド オートメーションについて

開発者は Microsoft Office のオートメーションを使用して、Office 製品に組み込まれている能力や機能を使用するカスタムソリューションを構築できます。 このようなプログラムによる開発は、比較的簡単にクライアント システムに実装できますが、Microsoft Active Server Pages (ASP)、ASP.NET、DCOM、Windows NT サービスなどのサーバー側のコードから自動化が行われると、多くの問題が発生する可能性があります。

まずは問題が起きるか否かの話
クライアントだと問題はないが、サーバーでの実行は問題が発生する可能性ありと言っています

Microsoft Office のすべての現行バージョンは、クライアント ワークステーション上のエンドユーザー製品として実行されるように設計、テスト、および構成されました。 また、対話型デスクトップとユーザー プロファイルが想定されています。

対話型デスクトップとユーザープロファイルといっているので、ユーザーがログインしGUI上でユーザーが操作することを想定し、ダイアログ等が出た場合には対処できることが前提とされている。ユーザープロファイルを想定しているということは、サーバー上のようにログイン無しのバックグラウンド実行は想定していないことを暗に意味しています。そのことは次の行にはっきりと記されています

Microsoft では、無人の非対話型クライアント アプリケーションまたはコンポーネント (ASP、ASP.NET、DCOM、NT Services を含む) からの Microsoft Office アプリケーションの自動化は現在推奨されておらず、サポートされていません。この環境で Office を実行すると、Office が不安定な動作やデッドロックを発生する可能性があるためです。

推奨されていないとまで言っていますね

続いて、5つの問題について話が続きます。

ユーザー ID: オートメーションによって Office アプリケーションが起動する場合でも、アプリケーション実行時にユーザー ID が想定されています。 アプリケーションを起動したユーザーのユーザー レジストリ ハイブの設定に基づいて、ツールバー、メニュー、オプション、プリンター、一部のアドインの初期化が試行されます。 多くのサービスは、ユーザー プロファイルがないアカウント (SYSTEM アカウント、IWAM_[サービス名] アカウントなど) で実行されています。 そのため、Office が起動時に正しく初期化されないことがあります。 この場合、CreateObject 関数または CoCreateInstance 関数のエラーが Office から返されます。 Office アプリケーションを起動できても、ユーザー プロファイルが存在しない場合、他の関数が正常に動作しない可能性があります。

この件についてGPTに確認してみました

自分:この問題については、一度でも実行するアカウントでログインしてOfficeを扱ったのであれば、初期化はされているの認識であっていますか?またユーザープロファイルに依存するような関数を使っていなければ、この点については問題ないといえるか?

GPT:はい、あなたの理解はほぼ正しいです。以下、Microsoftの説明を技術的に整理しつつ、あなたの質問に回答します。
✅ 回答1:一度でもログインしてOfficeを扱ったアカウントなら、初期化は済んでいるか?
→ 原則として「はい」です。
・ Officeは、初回起動時にそのユーザーの**ユーザープロファイル(レジストリ、テンプレート、アドインの情報など)**を構成します。

・ そのため、一度でも対象アカウントでWindowsにインタラクティブにログインしてOfficeを起動していれば、そのユーザーのプロファイルは存在し、初期化も完了しています。

・ こうしたプロファイルがあることで、CreateObject や CoCreateInstance を使ってOfficeを起動する際にエラーが出にくくなります。

✅ 回答2:ユーザープロファイルに依存しない関数だけを使うなら問題ない?
→ 「ある程度までは」その通りですが、油断は禁物です。

Officeアプリケーション(特にExcelやWord)は、内部的に次のような処理でユーザープロファイルに依存する可能性があります:

・ 初期プリンタの取得

・ テンプレートファイル(Normal.dotmなど)の読み込み

・ アドインやレジストリ設定(セキュリティ設定など)の確認

・ UI要素(ツールバー、メニュー)の構成

もしあなたのバッチ処理が以下のような限定的な処理であれば、問題が起きる可能性はかなり低くなります:

・ Excelファイルの読み書き

・ セルの値の取得・設定

・ ファイルの保存や印刷指示(プリンタ依存に注意)

・ 複雑なマクロやアドイン、テンプレートを使用しない

プログラムを実行するアカウントでログインし、Office操作して初期化をすませたうえで、
ファイルの中身の読み書きぐらいだと可能性はかなり低いという言い方になっています。

次の問題が一番大きい気がします

デスクトップとの対話機能: Office アプリケーションは、対話型デスクトップで実行されることを想定しています。 状況によっては、特定のオートメーション機能が正常に動作するために、アプリケーションを可視化する必要があります。 予期しないエラーが発生した場合、または関数を完了するために不明なパラメーターが必要な場合、Office はモーダル ダイアログ ボックスを表示し、実行する動作をユーザーに確認するように設計されています。 対話型ではないデスクトップのモーダル ダイアログ ボックスを閉じることはできません。 そのため、そのスレッドは永久に応答しなくなります (ハングします)。 コーディング方法によってこの問題が発生する可能性を軽減できる場合もありますが、このような方法で問題全体を回避することはできません。 そのため、サーバーサイド環境から Office アプリケーションを実行することは危険であり、サポートされません。

ログインして人が操作することを想定しているので、場合によってはダイアログが表示されたり、ハングしてとまっているということを人が判断しとめたりそういったことを前提しているから、よくないという話ですね
これはさすがに自動化している以上、一定の時間がたつとプログラム側でエラーをはいて終了とかそういった対応を想定しておかないといけないとかそういう話になると思いますが、そもそもサポートしていないと念を押しています・・・。

次の問題

再入機能とスケーラビリティ: サーバーサイド コンポーネントは、複数のクライアントに対応するために最小限のオーバーヘッドで高いスループットを持つ、再入性が高いマルチスレッドの COM コンポーネントである必要があります。 Office アプリケーションは、ほぼすべての点でその反対のアプリケーションです。 Office アプリケーションは、再入機能がない STA ベースのオートメーション サーバーであり、1 つのクライアントに対して多様でリソースを大量に消費する機能を提供するように設計されています。 また、サーバーサイド ソリューションとしてのスケーラビリティをほとんど提供していません。 さらに、メモリなどの重要な要素に対して固定の制限があります。 これらの制限は構成で変更できません。 さらに重要な点は、Office アプリケーションが、メモリ マップ ファイル、グローバル アドインまたはテンプレート、共有オートメーション サーバーなどのグローバル リソースを使用していることです。 そのため、複数クライアント環境で Office アプリケーションが構成されると、同時に実行できるインスタンス数が制限され、競合条件が発生する可能性があります。 同時に複数インスタンスの Office アプリケーションを実行する予定の場合、開発者は、"プーリング" または Office アプリケーションへのアクセスをシリアル化して、デッドロックやデータ破損の可能性を回避することを検討する必要があります。

定時実行のバッチ処理等でマルチスレッドによる実行がなければこの話は問題ないことになります

次の問題

回復性と安定性: Office 2000、Office XP、Office 2003、Office 2007 では、Microsoft Windows インストーラー (MSI) テクノロジを使用して、エンド ユーザーのインストールと自己修復を容易にします。 MSI では、"初回使用時にインストールする" という概念が導入されています。 これにより、システムの実行時に機能を動的にインストールまたは構成したり、特定のユーザーに対してより頻繁に機能をインストールしたり構成したりできます。 サーバー側環境では、パフォーマンスが低下し、ユーザーにインストールの承認またはインストール ディスクの提供を求めるダイアログ ボックスが表示される可能性が高くなります。 これは、エンド ユーザー製品としての Office の回復性を高めるために設計されていますが、Office の MSI 機能の実装は、サーバー側環境では逆効果です。 さらに、この種類の使用のために設計またはテストされていないため、Office がサーバー側で実行されている場合、一般的な Office の安定性は保証されません。 ネットワーク サーバーで Office をサービス コンポーネントとして使用すると、そのコンピューターの安定性が低下し、ネットワーク全体の安定性が低下する可能性があります。

GPTとの会話をのせておきます

自分:動的にインストールする話がかかれてますが、そういったことをしないのであれば、この話は関係ないということでしょうか?

GPT:はい、その認識は基本的に正しいです。

この部分に書かれている「回復性と安定性」に関する注意は、特に以下のような**“動的な機能インストール”や“自己修復”が発動するような場面”**に対しての警告です:

該当する「危険なシナリオ」 Officeには以下のようなMSIベースの仕組みがあります:

初回使用時にインストールされる機能(Install on first use)

特定の機能(たとえば数式エディター、図表描画ツールなど)は、Officeの最初のインストール時にはインストールされず、使おうとしたときに動的にインストールされる設定になっていることがあります。

自己修復機能(self-repair)

Officeの構成に何か欠損があると、起動時に自動的に再構成や修復が走ることがあります。

ユーザー単位で異なる構成が適用されることがある

特にマルチユーザー環境で、ユーザーごとにOfficeの状態が異なる場合があります。

🚨 サーバー側でこれが発動すると何が起きるか? インストールメディアの要求(=ダイアログボックスが表示されてハング)

インタラクティブな操作ができず失敗

サーバーの安定性が下がる(特にIISやバッチジョブ実行中にOfficeが待ち状態になる)

✅ バッチ処理のような用途で問題が出るか? → 原則として「いいえ」、問題にならないケースが多いです。 なぜなら:

バッチ処理で使用するOffice機能を事前にすべてフルインストールしておけば、「初回使用時インストール」や「自己修復」は原則発動しません。

事前に実行アカウントでOfficeを起動し、必要な機能が問題なく動作することを確認しておけば、突発的なインストールや修復の可能性はかなり低くなります。

さらに、Office 2016以降(特にC2R版)では、このMSIの挙動はかなり簡素化・抑制されています。

最後

サーバーサイドのセキュリティ: Office アプリケーションは、サーバーサイドで使用するように設計されていません。 そのため、Office アプリケーションは、配布されたコンポーネントで発生するセキュリティの問題を考慮していません。 Office は受信要求を認証しません。 また、サーバーサイド コードから、予期しないマクロの実行や、マクロを実行する可能性がある他のサーバーの起動を阻止する機能もありません。 匿名 Web サイトからサーバーにアップロードされたファイルを開かないでください。 サーバーは、最後に設定されたセキュリティ設定に基づいて、完全な特権を持つ管理者またはシステム コンテキストでマクロを実行できるため、ネットワークが侵害される可能性があります。 また、Office は処理を高速にするために、クライアントの認証情報をキャッシュできる多くのクライアント側コンポーネント (Simple MAPI、WinInet、MSDAIPP など) を使用しています。 Office がサーバーサイドで自動化されている場合、1 つのインスタンスで複数のクライアントにサービスを提供する可能性があります。 そのセッションで認証情報がキャッシュされると、あるクライアントが別のクライアントのキャッシュ済み資格情報を使用する可能性があります。 結果として、そのクライアントは他のユーザーになりすまして許可されていないアクセス許可を得る可能性があります。

この話では、素性のわからないファイルを実行してはいけないという話なので、プログラム自身が生成するような用途だとこの問題は該当しないと考えています

いろんなリスクはあるものの状況によっては回避し問題が起きる可能性は下げれるものの、ただサポート外の動作であるため、やはり使わないことがベストという話になります

続いてライセンスの話

技術的な問題とは別に、ライセンスの問題も考慮する必要があります。 現在のライセンス ガイドラインでは、クライアントにライセンス認証済みの Office がインストールされている場合を除き、クライアント要求を処理するサーバー上で Office アプリケーションを使用することは禁じられています。 サーバーサイド オートメーションを使用して、ライセンス認証されていないワークステーションに Office 機能を提供することは、ソフトウェア ライセンス条項 (EULA) では認められていません。

サーバー側にライセンス無しでの実行がNGなのは言うまでもなくだと思います・・

最終的に・・・

Microsoft では、サーバー側ソリューションを開発する必要がある場合は、開発者が Office の Automation の代替手段を見つけることを強くお勧めします。

別手段で実行することを推奨しています。そのため、何かしらのライブラリを使用しての対応が必要になってくるという話ですね・・・

なので、COM経由の操作は許可しているものの、ユーザーがログインしている状況化で、アプリを表示した状態で実行し意図しない動きの場合に対応ができることが前提という話になるので、サーバーでは技術的につかわないようにという話でした・・・

投稿日時: 2025-05-11 03:50:11

ExcelVBA

VBAだとWritePasswordにパスを設定しておけば、開いた際にパスを求められるようになります

Sub Sample()
    Dim BookObj     As Workbook
    Dim FilePath    As String
    Dim WritePass   As String: Pass = "test"
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    '書き込みパスワード付きExcelを開く
    Set BookObj = Workbooks.Open(FilePath, WriteResPassword:=WritePass)
    
    Set BookObj = Nothing
End Sub

Excel(COM)

Excel(COM)も同様です。

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");
            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;
            workbooks = application.Workbooks;

            // 書き込みパスワード
            var writePass = "test";

            // 書き込みパスワードでブックを開く
            workbook = workbooks.Open(Filename: filePath, WriteResPassword: writePass);  // 名前付き引数がおすすめ
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

ClosedXML

未対応

EPPlus

未対応

ExcelDataReader

未対応

NPOI

未対応

参考:
ClosedXML, ExcelDataReader, EPPlus, NPOIがパスワード解除について

投稿日時: 2025-05-10 18:18:10
更新日時: 2025-05-24 09:25:24

ExcelVBA

Open時、ReadOnlyをTrueにすることで読み取り専用で開く

Sub Sample()
    Dim BookObj As Workbook
    Dim FilePath    As String
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath, ReadOnly:=True)
    
    Set BookObj = Nothing
End Sub

Excel(COM)

VBA同様 ReadOnlyにTrueを指定して、読み取り専用で開く

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;
            workbooks = application.Workbooks;
            // ブックを開く(ReadOnlyの項目をtrue)
            workbook = workbooks.Open(Filename: filePath, ReadOnly: true); // 名前付き引数がおすすめ
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

ClosedXML

FileStreamで読み取り専用で開く

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み取りモード、共有なし(排他)
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var workbook = new XLWorkbook(stream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

FileStreamで読み取り専用で開く

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var package = new ExcelPackage(stream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

FileAccessがReadであろうがなかろうが、読み取りのみのライブラリのため書き込む機能なし

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

NPOI

FileStreamで読み取り専用で開く
□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var workbook = new HSSFWorkbook(fileStream);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var workbook = new XSSFWorkbook(fileStream);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版

Program.cs

using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {   // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            var extension = Path.GetExtension(filePath);

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var workbook = WorkbookFactory.Create(fileStream, extension))
            { 
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-10 16:10:10
更新日時: 2025-05-24 09:06:24

既存ファイルを開く

ExcelVBA

Sub Sample()
    Dim BookObj As Workbook
    Dim FilePath    As String
    
    'ファイルパス作成
    FilePath = ThisWorkbook.Path & "\sample.xlsx"
    
    'ブックを開く
    Set BookObj = Workbooks.Open(FilePath)
    
    Set BookObj = Nothing
End Sub

Excel(COM)

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // Excelを開く
            application = new Excel.Application();
            application.Visible = true;
            workbooks = application.Workbooks;
            // ブックを開く
            workbook = workbooks.Open(filePath);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

ファイルパス、FileStreamを使った開き方

□ファイルパスを使って開く

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            using (var workbook = new XLWorkbook(filePath))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileStreamを使って開く

using ClosedXML.Excel;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XLWorkbook(fileStream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

fileStreamを使えば、ファイルへのアクセス方法(読み書き)や他プロセスへの共有方法も制御できるので細かい制御が必要であればこちらを使う

EPPlus

ファイルパス、FileInfo、FileStreamを使った開き方

□ファイルパスを使って開く

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ファイルパスで開く
            using (var package = new ExcelPackage(filePath))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□FileInfoを使って開く

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ファイルの情報を使う予定があるならfleinfoで開く
            var fileInfo = new FileInfo(filePath);

            // ファイルパスで開く
            using (var package = new ExcelPackage(fileInfo))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

更新日時とかファイルの情報を使う予定ならFileInfoを使って開く


□FileStreamを使って開く

using OfficeOpenXml;
using System;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var package = new ExcelPackage(fileStream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

fileStreamを使えば、ファイルへのアクセス方法(読み書き)や他プロセスへの共有方法も制御できるので細かい制御が必要であればこちらを使う

ExcelDataReader

Streamのみ

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Reflection;

namespace SampleCode; // C#10~

internal class SampleExcelDataReader
{
    static void Main(string[] args)
    {
        // デフォルトでは、エンコード(CP1252)がサポートされておらずエラーになるのでこれが必要
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み取りモード、共有は読み取りのみOK
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = ExcelReaderFactory.CreateReader(fileStream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

NPOI

Streamのみ

□xls

using NPOI.HSSF.UserModel;
using System;
using System.IO;
using System.Reflection;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xls");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new HSSFWorkbook(fileStream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

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

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // 実行ファイルのあるフォルダパス取得
            var folderPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
            if (folderPath == null) { return; }

            // Excelファイルパスを作成
            var filePath = Path.Combine(folderPath, "sample.xlsx");

            // ブックを開く、読み書きモード、共有なし(排他)
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            using (var workbook = new XSSFWorkbook(fileStream))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版

Program.cs

using System;
namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {   // xls
            using (var workbook_xls = WorkbookFactory.Create(".xls"))
            {
            }

            // xlsx
            using (var workbook_xlsx = WorkbookFactory.Create(".xlsx"))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-08 16:15:08
更新日時: 2025-05-24 11:51:24

新規にブックを作成する

ExcelVBA

Sub Sample()
    Dim bookObj As workbook
    
    Set bookObj = Workbooks.Add
    
    Set bookObj = Nothing
End Sub

Excel(COM)

using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace SampleCode; // C#10~

internal class SampleExcel
{
    static void Main(string[] args)
    {
        Excel.Application? application = null;
        Excel.Workbooks? workbooks = null;
        Excel.Workbook? workbook = null;

        try
        {
            // Excelが起動
            application = new Excel.Application();

            // 表示
            application.Visible = true; // 処理を確認したいときだけtrueにする

            // ブックを追加
            workbooks = application.Workbooks;
            workbook = workbooks.Add();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            CleanUpComObject(ref workbook, true, false);
            CleanUpComObject(ref workbooks);
            CleanUpComObject(ref application);

            // RCW強制解放(残留プロセス対策)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    static void CleanUpComObject<T>(ref T comObject, bool shouldClose = true, bool saveChanges = false)
    {
        // フラグによってWorkbookはClose / ApplicationはQuitする
        if (shouldClose && comObject != null)
        {
            // 型をチェックする
            if (comObject is Microsoft.Office.Interop.Excel.Workbook workbook)
            {
                // Workbookの場合
                workbook.Close(saveChanges);
            }
            else if (comObject is Microsoft.Office.Interop.Excel.Application application)
            {
                // Applicationの場合
                application.Quit();
            }
        }

        // Objectを解放
        if (comObject != null && Marshal.IsComObject(comObject))
        {
            Marshal.ReleaseComObject(comObject);
            comObject = default!;
        }
    }
}

補足:Microsoft.Office.Interop.Excel でオブジェクトの解放について

ClosedXML

using ClosedXML.Excel;
using System;

namespace SampleCode; // C#10~

internal class SampleClosedXML
{
    static void Main(string[] args)
    {
        try
        {
            using (var workbook = new XLWorkbook())
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

EPPlus

using OfficeOpenXml;
using System;
namespace SampleCode; // C#10~

internal class SampleEPPlus
{
    static void Main(string[] args)
    {
        // Ver8.0のソースです
        // 非商用個人利用の場合 名前を設定
        ExcelPackage.License.SetNonCommercialPersonal("SampleTarou");

        try
        {
            using (var package = new ExcelPackage())
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

ExcelDataReader

読み取りのみのライブラリのためなし

NPOI

□xls

using NPOI.HSSF.UserModel;
using System;


namespace SampleCode; // C#10~

internal class SampleNPOI_xls
{
    static void Main(string[] args)
    {
        try
        {
            using (var workbook = new HSSFWorkbook())
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xlsx

using NPOI.XSSF.UserModel;
using System;

namespace SampleCode; // C#10~

internal class SampleNPOI_xlsx
{
    static void Main(string[] args)
    {
        try
        {
            // xlsx形式
            using (var workbook = new XSSFWorkbook())
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

□xls/xlsx対応版

Program.cs

using System;
namespace SampleCode; // C#10~

internal class SampleNPOI
{
    static void Main(string[] args)
    {
        try
        {   // xls
            using (var workbook_xls = WorkbookFactory.Create(".xls"))
            {
            }

            // xlsx
            using (var workbook_xlsx = WorkbookFactory.Create(".xlsx"))
            {
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

WorkbookFactory.cs

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

namespace SampleCode; // C#10

public class WorkbookFactory
{
    public static IWorkbook Create(Stream stream, string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(stream),
            ".xlsx" => new XSSFWorkbook(stream),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }

    public static IWorkbook Create(string extension)
    {
        extension = extension.ToLower();

        return extension switch
        {
            ".xls" => new HSSFWorkbook(),
            ".xlsx" => new XSSFWorkbook(),
            _ => throw new NotSupportedException("対象外の拡張子です")
        };
    }
}
投稿日時: 2025-05-08 15:05:08
更新日時: 2025-05-24 01:57:24

参考になりそうなサイト

ClosedXML

github
https://github.com/ClosedXML/ClosedXML

旧wiki
https://github.com/closedxml/closedxml/wiki

徐々に移行しているというwiki
https://docs.closedxml.io/en/latest/

ExcelDataReader

github
https://github.com/ExcelDataReader/ExcelDataReader

ドキュメントっぽいものがみあたらなかったのでwikiくらいしかみるとこはないかも

EPPlus

github
https://github.com/EPPlusSoftware/EPPlus

公式サイト https://www.epplussoftware.com/ja ※いろいろ情報があるのでみるとよいかも

ドキュメント
https://www.epplussoftware.com/ja/Developers

EPPlus source code docsから使用するバージョンを選択するとそのドキュメントが見れます。

NPOI

github
https://github.com/nissl-lab/npoi

チュートリアル
https://github.com/nissl-lab/npoi-tutorial

投稿日時: 2025-05-08 13:02:08

COM経由でExcelを操作するための準備

依存関係から右クリックでCOM参照の追加を選択

Microsoft.Excel XX.X ObjectLibrary を選択(要Excelインストール)

OKで閉じる

ClosedXMLでExcelを操作するための準備

Nugetから ClosedXML をインストール

ExcelDataReaderでExcelを操作するための準備

Nugetから ExcelDataReader をインストール

ExcelDataReader.DataSetもインストールしてください
AsDataSet() を使うのに必要になってきます

EPPlusでExcelを操作するための準備

Nugetから EPPlus をインストール

NPOIでExcelを操作するための準備

Nugetから NPOIをインストール

投稿日時: 2025-05-07 13:02:07
更新日時: 2025-05-17 04:16:17

C#からExcel操作方法のメモ

ライブラリ .xls .xlsx 読み 書き ライセンス
ClosedXML × MIT
ExcelDataReader × MIT
NPOI Apache 2.0
EPPlus × Polyform Noncommercial License 1.0.0

ClosedXML、ExcelDataReader、EPPlus、NPOIのドキュメント・チュートリアル

Excel(COM)、ClosedXML、ExcelDataReader、NPOI、EPPlusを使えるようにする


早見表

項目 Excel(COM) ClosedXML EPPlus ExcelDataReader NPOI
ファイルパスオープン × ×
FileInfoオープン × × × ×
FileStreamオープン ×
行列番号 1~ 1~ 1~ 0~ 0~
シート番号 1~ 1~ 0~ 0~ 0~
シート取得 Sheets変数[ ] Worksheet() Worksheets() - GetSheetAt()/GetSheet()
存在しないシート 例外 例外 null null null

ワークブック

処理 Excel(COM) ClosedXML EPPlus ExcelDataReader NPOI
ブックを作成 ×
ブックを開く
ブックを開く(読み取り専用)
ブックを開く(書き込みパスワード付き) × × × ×
ブックを開く(開くパスワード付き) × × × ×
ブックを保存(名前をつけて保存) ×
上書き保存 ×

ワークシート

処理 Excel(COM) ClosedXML EPPlus ExcelDataReader NPOI
シートの数を取得する
全てのシートを取得しその名前を出力する
シート名を変更 ×
シートを追加する(一番最後) ×
シートの削除 ×
ワークシートの参照
アクティブなシートを取得/設定 ×
表示倍率の取得 × ×
表示倍率の設定 ×
シートの保護 ×
シートの解除 ×

(補足)シートの解除は、NPOIだとパス無し保護は解除できても、パス有り保護は解除できなさそう


セル

処理 Excel(COM) ClosedXML EPPlus ExcelDataReader NPOI
A1形式でセルの値取得 × ×
R1C1形式でセルの値取得
複数のセルを範囲で取得し、1つずつ取り出し値を出力
セルのアドレス × ×
セルに値を設定する ×

参考

ClosedXML, ExcelDataReader, EPPlus, NPOIがパスワード解除について

Officeのサーバー上でのCOM経由による自動操作について

Microsoft.Office.Interop.Excel でオブジェクトの解放について

解放ヘルパークラスを使ったオブジェクトの解放

R1C1情報からA1形式への変換

EPPlus/NPOIで既存ファイルを開き、保存する方法

EPPlusでファイル作成、保存方法

投稿日時: 2025-05-07 12:05:07
更新日時: 2025-05-28 14:58:28

Nuget

ClosedXMLをインストール

ファイルを開いてシート名をすべて表示

using ClosedXML.Excel;

try
{
    var filePath = @"c:\temp\Sample.xlsx";
    using (var workbook = new XLWorkbook(filePath))
    {
        Console.WriteLine($"シート数: {workbook.Worksheets.Count}");

        foreach (var sheet in workbook.Worksheets)
        {
            Console.WriteLine($"シート名: {sheet.Name}");
        }
    }
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

Excelを開いていると、ファイルオープン時にエラーになるので・・・

読み取り専用で開く場合は、ClosedXML自体には機能がないので

FileStreamで読み取り専用で開いてそれを使って開くとよい

using ClosedXML.Excel;

try
{
    var filePath = @"c:\temp\Sample.xlsx";

    // 読み取りで開き、他ファイルは読み書きOKとする
    using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        using (var workbook = new XLWorkbook(fileStream))
        {
            Console.WriteLine($"シート数: {workbook.Worksheets.Count}");

            foreach (var sheet in workbook.Worksheets)
            {
                Console.WriteLine($"シート名: {sheet.Name}");
            }
        }
    }

}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

Excelで開いている間にClosedXMLで開くとエラーになるが、
CLosedXMLで開いている間にExcelは開くことが可能なので、処理して保存する際にエラーとなる可能性はある。
そういうケースを気にするのであれば、FileStreamで排他制御する必要あり。

using ClosedXML.Excel;

try
{
    var filePath = @"c:\temp\Sample.xlsx";

    // 読み書き開き、他ファイルは読み書き禁止とする
    using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
    {
        using (var workbook = new XLWorkbook(fileStream))
        {
            Console.WriteLine($"シート数: {workbook.Worksheets.Count}");

            foreach (var sheet in workbook.Worksheets)
            {
                Console.WriteLine($"シート名: {sheet.Name}");
            }
        }
    }

}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

セルの操作

A1形式でアクセス

worksheet.Cell(2, "C").value

行列でアクセス

worksheet.Cell(2, 3).value
投稿日時: 2025-04-24 13:23:24
更新日時: 2025-04-24 13:43:24
C#, ML

めもめも。

using Microsoft.ML;
using Microsoft.ML.Data;

public class CommentData
{
    // このカラム名は「Label」として指定
    [ColumnName("Label")]
    public bool IsSpam { get; set; }

    public string Comment { get; set; }
}

public class CommentPrediction
{
    [ColumnName("PredictedLabel")]
    public bool IsSpam { get; set; }
}

public class SpamFilter
{
    private static MLContext context = new MLContext();

    public static void Main(string[] args)
    {
        // MLContextの作成
        var context = new MLContext();

        // 学習データの準備
        var data = context.Data.LoadFromEnumerable(new List<CommentData>
        {
            // スパムコメント
            new CommentData { IsSpam = true, Comment = "今すぐ報酬を手に入れるチャンス!" },
            new CommentData { IsSpam = true, Comment = "無料で今すぐ登録して!" },
            new CommentData { IsSpam = true, Comment = "お金を稼ぐチャンスを見逃すな!" },
            new CommentData { IsSpam = true, Comment = "クリックして簡単に儲けよう!" },

            // 非スパムコメント
            new CommentData { IsSpam = false, Comment = "この会議は素晴らしかったです。" },
            new CommentData { IsSpam = false, Comment = "最近観た映画はとても面白かったです。" },
            new CommentData { IsSpam = false, Comment = "今日のランチはとても美味しかったです。" },
            new CommentData { IsSpam = false, Comment = "新しい本を読んでみたけど面白かった。" }
        });

        // テキストの前処理と特徴量化(フィーチャー化)
        var pipeline = context.Transforms.Text.FeaturizeText("Features", "Comment")
            .Append(context.BinaryClassification.Trainers.SdcaLogisticRegression("Label", "Features"));

        // モデルのトレーニング
        var model = pipeline.Fit(data);

        var modelPath = @"spam_model.zip"; //お好きなパス
        context.Model.Save(model, data.Schema, modelPath);

        // 既存のモデルをロード


        var newComment = new CommentData { Comment = "あなたにだけ、報酬をあげます!" };

        ITransformer newModel = LoadModel(modelPath);

        // モデルを使って予測
        var prediction = Predict(newModel, newComment);

        Console.WriteLine($"コメント: {newComment.Comment}");
        Console.WriteLine($"スパム予測: {prediction.IsSpam}");
    }

    // モデルをロードする
    private static ITransformer LoadModel(string modelPath)
    {
        return context.Model.Load(modelPath, out var modelInputSchema);
    }

    // モデルを使って新しいデータを予測する
    private static CommentPrediction Predict(ITransformer model, CommentData newComments)
    {
        // 新しいデータをロード
        var predictionEngine = context.Model.CreatePredictionEngine<CommentData, CommentPrediction>(model);
        var predictions = predictionEngine.Predict(newComments);
        return predictions;
    }

    // 新しいデータを使ってモデルを再学習する
    private static ITransformer RetrainModel(ITransformer existingModel, List<CommentData> updatedData)
    {
        var data = context.Data.LoadFromEnumerable(updatedData);

        // パイプラインを作成(フィーチャーの生成とトレーニング)
        var pipeline = context.Transforms.Text.FeaturizeText("Features", "Comment")
            .Append(context.BinaryClassification.Trainers.SdcaLogisticRegression("Label", "Features"));

        // モデルを再学習
        var retrainedModel = pipeline.Fit(data);
        return retrainedModel;
    }

    // 再学習したモデルを保存する
    private static void SaveModel(ITransformer model, string modelPath)
    {
        context.Model.Save(model, null, modelPath);
    }
}

複数のデータから判定したい場合は

public class CommentData
{
    // このカラム名は「Label」として指定
    [ColumnName("Label")]
    public bool IsSpam { get; set; }

    public string Comment { get; set; }

    public string DataA{ get; set; }

    public string DataB{ get; set; }

    public string DataC{ get; set; }
}

として、特徴量の定義を増やす。

var pipeline = context.Transforms.Text.FeaturizeText("CommentFeatures", "Comment")
    .Append(context.Transforms.Text.FeaturizeText("DataAFeatures", "DataA"))
    .Append(context.Transforms.Text.FeaturizeText("DataBFeatures", "DataB"))
    .Append(context.Transforms.Text.FeaturizeText("DataCFeatures", "DataC"))
    .Append(context.Transforms.Concatenate("Features", "CommentFeatures", "DataAFeatures", "DataBFeatures", "DataCFeatures"))
    .Append(context.BinaryClassification.Trainers.SdcaLogisticRegression("Label", "Features"));
投稿日時: 2024-12-30 16:10:30
更新日時: 2025-05-12 06:29:12
C#

C#で利用可能な形態素解析は、Mecabになります。

NMeCabか MeCab.DotNetの2択になります

経緯については、以下の2つを見るとよくわかります

リポジトリ移転について 2010年からOSDNで開発し公開してきたNMeCabですが、バージョン0.10.0からは、こちらGitHubで開発し公開していきます。 NMeCab MeCab.DotNet

これは何? NOTE: 将来的に、MeCab.DotNetとNMeCabを統合する作業をしています。 詳しくはこのissueを参照して下さい。 "MeCab" は、日本語形態素解析エンジンのプロジェクトです。 "NMeCab" は、上記MeCabを、.NET Framework 2.0のマネージライブラリとして実装し直したものです。ただ、もう更新されていないようです... --> GitHubで復活しました 2010年からOSDNで開発し公開してきたNMeCabですが、バージョン0.10.0からは、こちらGitHubで開発し公開していきます。 MeCab.DotNet

NMeCabは、もともとOSDNで開発されていて、.NET Frameworkが要件となっていた
その後、 別の人が .NETでも使えるように作ったのが MeCab.DotNetになります
その後、 NMeCabが OSDNから GitHubに移行し、.NETの対応を行ったため、

結論としては、 NMeCabを使えばよいという話になります

Nugetに至っては少しややこしい状況になっています。
単純にNMeCabを検索すると、作者とは別の方の名前でヒットします。
これは、説明にもありますが"An unofficial package of NMecab. "
作成者の作ったものではなく別の方が作ったものになります
ずいぶん古いものがそのまま残っているのでこちらは見なかったことにしましょう・・・

では、どれをつかえばよいかという話ですが、GitHubのページの説明では
LibNMeCabが該当するライブラリとのこと
辞書パッケージを選択してインストールすると依存関係からLibNMeCabも入るよとのこと

辞書リソースは、Disposeして解放すればよいとのこと。

Taggerインスタンスが確保している辞書リソースへのハンドルを解放するのが、この Dispose() メソッドです。(.NETプログラミングに慣れない方は注意して下さい) もちろん、一度確保した辞書リソースを再利用したい場合には、usingステートメントを記述せず、Taggerインスタンスをスコープの広い変数に保持して使い回すこともできます。アプリケーション終了時など任意のタイミングでDisposeしてください。

using NMeCab.Specialized;
using System;

class Program
{
    static void Main(string[] args)
    {
        var sentence = "「これはサンプルテキストです。」 This is a sample text.\n";

        using(var tagger = MeCabIpaDicTagger.Create())
        {
            foreach (var node in tagger.Parse(sentence))
            {
                Console.WriteLine($"文字列:{node.Surface}\t読み:{node.Reading}\t品詞:{node.PartsOfSpeech}");
            }
        }
    }
}

出力:

文字列:「      読み:「        品詞:記号
文字列:これ    読み:コレ      品詞:名詞
文字列:は      読み:ハ        品詞:助詞
文字列:サンプル        読み:サンプル  品詞:名詞
文字列:テキスト        読み:テキスト  品詞:名詞
文字列:です    読み:デス      品詞:助動詞
文字列:。      読み:。        品詞:記号
文字列:」      読み:」        品詞:記号
文字列:       読み:         品詞:記号
文字列:This    読み:  品詞:名詞
文字列:is      読み:  品詞:名詞
文字列:a       読み:  品詞:名詞
文字列:sample  読み:  品詞:名詞
文字列:text    読み:  品詞:名詞
文字列:.       読み:  品詞:名詞
投稿日時: 2024-12-30 12:08:30
更新日時: 2024-12-30 16:11:30

マイグレーションの時に、列の文字数長さの設定でmaxの部分でエラーになるので、それぞれにどんな情報が入っていて、何を設定すべきか調べてみた。

Identityで生成されるテーブルとそれらの関係は以下のとおり

7つのテーブルがあり、Userとそれに関連するテーブルが4つと
Roleとそれに関するテーブルが2つと
UserとRoleを結ぶテーブルが1つの計7つで構成されている

AspNetUsersテーブル

AspNetUserテーブルは、ユーザーに関する主要な情報が格納

列名 nvarchar(max) 内容
Id ユーザーの一意の識別子。通常はGUID(ハイフンあり)
UserName ユーザーのログイン名
NormalizedUserName 検索などの比較で効率的に行うための正規化(大文字)
Email メールアドレス
NormalizedEmail 検索などの比較で効率的に行うための正規化(大文字)
EmailConfirmed メールアドレスによる確認ができたかを示すブール値
PasswordHash ユーザーパスワード(Hash値)
SecurityStamp セキュリティ目的で使用される一意の値
ConcurrencyStamp 同時実行制御に使用。GUIDが使われる
PhoneNumber 電話番号
PhoneNumberConfirmed 電話番号による確認ができたかを示すブール値
TwoFactorEnabled 二要素認証が有効かどうかを示すブール値
LockoutEnd アカウントロックアウトが終了する日時
LockoutEnabled アカウントのロックアウトが有効かどうかのブール値
AccessFailedCount ログイン失敗回数

・AIの見解
PasswordHash・・・一般的なハッシュアルゴリズム(例:SHA256)の出力は64文字程度です。将来的なハッシュアルゴリズムの変更に対応できる余裕を持たせて128文字程度が妥当ではないか

SecurityStamp・・・セキュリティスタンプはGUIDベースで生成されることが多いです。GUIDの文字列表現は通常36文字です。よって36文字

ConcurrencyStamp・・・同時実行制御に使用されるタイムスタンプです。通常GUIDが使用される。GUIDの文字列表現は通常36文字。よって36文字

PhoneNumber・・・国際電話番号を考慮すると15桁程度。中継電話会社経由の番号だと16桁になることもある。電話番号の桁数はいつかわってもおかしくないので多少余裕をもたせるとよさそう。よって20文字

AspNetUserClaimsテーブル

AspNetUserClaimsテーブルは、ーザーに関連付けられたクレーム情報が格納

列名 nvarchar(max) 内容
Id プライマリキーとして機能する一意の識別子
UserId クレームを設定するUserId
ClaimType クレームの種類や名前
ClaimValue クレームの値

クレームは、ユーザーはロールといった概念のほかに情報を追加し細かい認証の制御を行うために使用する
よって、クレームの種類や値を設定するのは開発者次第のためお好きな文字数を設定すればよい
長い名称を使うことなければ64文字とかで十分かなと

AspNetUserLoginsテーブル

AspNetUserLoginsテーブルは、外部認証プロバイダーを使用してログインしたユーザーの情報が格納

列名 nvarchar(max) 内容
LoginProvider 外部認証プロバイダーの名前(例:Google、Facebook、Microsoft等)
ProviderKey 認証プロバイダーがユーザーに割り当てた一意の識別子
ProviderDisplayName 認証プロバイダーの表示名を格納
UserId 該当するUserId

ProviderDisplayNameは、例えばGoogleの認証だとGoogleが格納されています。そこまで長い名前は入るとおもえないので、64文字程度もあれば十分かなと

AspNetUserTokensテーブル

AspNetUserTokensテーブルは、ユーザーに関連する各種トークン情報が格納

列名 nvarchar(max) 内容
UserId AspNetUsersテーブルのIdに対応する、ユーザーの一意の識別子
LoginProvider トークンを生成したプロバイダーの名前を指定
Name トークンの名前や種類を格納
Value トークンの実際の値を格納

どういうときにつかうのか?
パスワードリセット、メール確認、二要素認証、外部認証プロバイダー、リフレッシュトークン、カスタムトークンなどだそうです

AIの見解: OAuth 2.0のアクセストークンは通常200〜400文字程度
JWTベースのトークンは500〜1000文字程度になることがあります
リフレッシュトークンは通常、アクセストークンよりも長くなる傾向があります
トークンのサイズが将来的に大きくなる可能性を考慮する必要があります
よって2000文字程度の長さが妥当では?
とのこと。

AspNetUserRolesテーブル

AspNetUserRolesテーブルは、ユーザーとロールの関連付けに関する情報が格納

列名 nvarchar(max) 内容
UserId UserIdを設定
RoleId RoleIdを設定

AspNetRolesテーブル

AspNetRolesテーブルは、アプリケーション内のロールに関する情報が格納

列名 nvarchar(max) 内容
Id UserIdを設定
Name RoleIdを設定
NormalizedName 検索などの比較で効率的に行うための正規化(大文字)
ConcurrencyStamp 同時実行制御に使用。GUIDが使われる

・AIの見解
ConcurrencyStamp・・・同時実行制御に使用されるタイムスタンプです。通常GUIDが使用される。GUIDの文字列表現は通常36文字。よって36文字

AspNetRoleClaimsテーブル

AspNetRoleClaimsテーブルは、ロールに関連付けられた追加の情報(クレーム)が格納
AspNetUserClaimsのRole版

列名 nvarchar(max) 内容
Id プライマリキーとして機能する一意の識別子
RoleId クレームを設定するRoleId
ClaimType クレームの種類や名前
ClaimValue クレームの値

クレームは、ユーザーはロールといった概念のほかに情報を追加し細かい認証の制御を行うために使用する
よって、クレームの種類や値を設定するのは開発者次第のためお好きな文字数を設定すればよい
長い名称を使うことなければ64文字とかで十分かなと


同時実行制御について

同じレコードを複数のリクエストにより変更を行った際に、どちらのリクエストで変更したのか、判断するために、ConcurrencyStampにGUIDを設定し判断に使っています。

  1. Selectで対象のレコードの情報を取得する
  2. 新しいGUIDを取得する
  3. 対象レコードを取得する際に、1で取得したConcurrencyStampも条件に含めレコードをしぼり更新を行う。その際に新しいGUIDを設定する

これにより、同時実行した場合、更新ができれば自分のリクエストにより更新ができたことになり
更新ができない場合(他のリクエストによりすでにConcurrencyStampの値が変わっていた場合)は更新が失敗することになります

投稿日時: 2024-10-12 06:24:12
更新日時: 2024-10-12 12:13:12

該当のクラスデフォルトの説明あり

各説明とデフォルト値ですね

builder.Services.Configure<IdentityOptions>(options =>
{
    // ロックアウトの設定
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // ロックアウトの期間
    options.Lockout.MaxFailedAccessAttempts = 5; // ロックアウトになる前の最大失敗試行回数
    options.Lockout.AllowedForNewUsers = true; // 新しいユーザーに対してロックアウトを許可するかどうか
});

AllowedForNewUsersは、アカウント作ったあとの初回ログインでロックアウトを許容するかどうかという設定とのこと
作ったばかりなので間違える可能性を考慮してということだろうか。。


ただ、上記設定を利用するかどうかは、Loginする際のメソッドに渡す引数できまる
具体的には、以下のようにコントローラーのLoginのメソッドのところで、 PasswordSignInAsyncメソッドに渡すlockoutFailureがtrueだと
MaxFailedAccessAttemptsの回数に応じてロックアウトされる

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using SampleWeb.Models;

namespace SampleWeb.Controllers
{
    public class AccountController : Controller
    {
        private readonly SignInManager<IdentityUser> _signInManager;

        public AccountController(SignInManager<IdentityUser> signInManager)
        {
            _signInManager = signInManager;
        }

        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginModel model)
        {
            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
                if (result.Succeeded)
                {
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                }
            }
            return View(model);
        }
    }
}
投稿日時: 2024-08-31 19:01:31
更新日時: 2024-09-01 10:33:01

一次ソースがみつからなかったので生成AIの情報をそのままペタッと
./program.csに以下項目でidentityに関するcookieの挙動を設定ができるそうです

builder.Services.ConfigureApplicationCookie(options =>
{
    // Cookie settings
    options.Cookie.HttpOnly = true;
    options.ExpireTimeSpan = TimeSpan.FromDays(14);

    options.LoginPath = "/Account/Login";
    options.AccessDeniedPath = "/Account/AccessDenied";
    options.SlidingExpiration = false;
});

HttpOnly・・・クッキーが HTTP リクエストでのみ送信され、JavaScript からはアクセスできないようにします
補足:セキュリティ: HttpOnly フラグが設定されたクッキーは、クロスサイトスクリプティング(XSS)攻撃から保護されるため、セキュリティが向上します。JavaScript からクッキーの値を読み取ることができなくなり、クッキーに保存されている情報(例えば認証トークン)が盗まれるリスクが低くなります

ExpireTimeSpan・・・クッキーの有効期限を設定します。ここでは、クッキーが 14日後に期限切れとなります

LoginPath・・・ 認証が必要なリソースにアクセスしようとしたときに、リダイレクトされるログインページのパスを指定します

AccessDeniedPath・・・ユーザーがアクセス権のないリソースにアクセスしようとしたときにリダイレクトされるページのパスを指定します

SlidingExpiration ・・・クッキーの有効期限が、ユーザーのアクティビティがあるたびに延長されるかどうかを指定します


HttpOnlyはセキュリティの観点からtrue
SlidingExpiration もセキュリティの観点からfalse(時間がきたら強制的に切れる状態のほうが安全)
パス関係は、必要に応じてカスタムページを設定した場合は、その設定を行う必要アリだと思います
ExpireTimeSpanは、期間が短いとちょくちょくログインしなおさないといけないのでほどほどのタイミングに設定しておく必要があると思います

投稿日時: 2024-08-31 15:38:31
更新日時: 2024-09-01 05:33:01

ユーザー名の制約はこちら。

ユーザー名は、AllowedUserNameCharctersに設定されている文字しかつかえないです。つまり日本語はNGです。 RequireUniqueEmailは、ユーザー名とEmailの関係の設定になります false…ユーザー名は一意となる。同一メールアドレスでも異なるユーザー名が設定可能 true... デフォルトはfalseです

検証してみます
以下プログラムで、同一メールアドレスで複数のユーザー名が入るかテストしてみます

参考_同一メールアドレスで異なるユーザー名
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace SampleWeb.Data
{
    public class SeedData
    {
        public static async Task Initialize(IServiceProvider serviceProvider)
        {
            var userManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();

            var email1 = "username@example.com";
            var user1 = new IdentityUser()
            {
                UserName = "username1",
                Email = email1,
                EmailConfirmed = true
            };
            await userManager.CreateAsync(user1, "Password@12345");

            var email2 = "username@example.com";
            var user2 = new IdentityUser()
            {
                UserName = "username2",
                Email = email2,
                EmailConfirmed = true
            };
            await userManager.CreateAsync(user2, "Password@12345");
        }
    }
}

雑なプログラムですが・・・→2つとも入ります


ユーザー名は一意として扱われ、異なるEmailであってもNGになります

参考_同一ユーザー名異なるemailで登録
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace SampleWeb.Data
{
    public class SeedData
    {
        public static async Task Initialize(IServiceProvider serviceProvider)
        {
            var userManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();

            var email1 = "username1@example.com";
            var user1 = new IdentityUser()
            {
                UserName = "username",
                Email = email1,
                EmailConfirmed = true
            };
            await userManager.CreateAsync(user1, "Password@12345");

            var email2 = "username2@example.com";
            var user2 = new IdentityUser()
            {
                UserName = "username",
                Email = email2,
                EmailConfirmed = true
            };
            await userManager.CreateAsync(user2, "Password@12345");
        }
    }
}

これだと最初の設定しか入らない

投稿日時: 2024-08-31 13:05:31
更新日時: 2024-08-31 16:40:31
デフォルトの条件は、6文字以上 数字、大文字、小文字、英数字以外をそれぞれ最低1文字含むことが条件

この条件を変えたい場合は、

./program.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SampleWeb03.Data;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();

// パスワードの条件設定
builder.Services.Configure<IdentityOptions>(options =>
{
    options.Password.RequiredLength = 10;           // 長さ

    options.Password.RequireUppercase = true;       // 大文字
    options.Password.RequireLowercase = true;       // 小文字
    options.Password.RequireDigit = true;           // 数字
    options.Password.RequireNonAlphanumeric = true; // 英数以外の文字
    options.Password.RequiredUniqueChars = 1;       // 必須項目がそれぞれ最低いくつ必要か?
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

パスワードの条件設定のように、それぞれを設定する
必須な種類はすべてtrueがデフォルトなのであえて設定するまでもなく
変更するとしたら文字列の長さくらいかなと

投稿日時: 2024-08-31 11:06:31
更新日時: 2024-08-31 13:22:31

このページでは、下記ページの状態から開始しています
ASP.NET Core Webアプリで、SQLiteを使用して認証用のDBを作成


認証を使用したプロジェクトを作成し、そのまま起動するとデフォルトのUIが利用できます。

・認証は個別のアカウント


・デフォルトのまま起動すると、登録やログインのリンクあり。


・登録のページ


・ログインのページ


個人で利用するブログを作ったとすると、自分が利用するアカウントを登録してしまえば、登録画面は不要でログイン画面だけほしいという話になります。
そのため、起動時にSeedとして初期のアカウントを登録させて、不要なページは program.csファイル内で強制的にリダイレクトかけたりエラーにすることも可能ですが・・・
対応漏れがあると目もあてられないことになりかねないので、デフォルトのUIを無効とし、必要なページだけ設定していきます。

デフォルトのUI

./Program.cs ファイル中でAddDefaultIdentityを利用していることで有効になります。(フレームワークのバージョンにより記載はことなるので要注意)

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

### デフォルトのUIを無効にする

比較しやすいよう元のコードも残してあります。

./program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SampleWeb.Data;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

//builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
//    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => { options.SignIn.RequireConfirmedAccount = true; })
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication(); //add
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

//app.MapRazorPages(); //del

app.Run();

AddDefaultIdentityからAddIdentityに変更しています。
app.MapRazorPages()は、自動でデフォルトページへのマップをするもので、デフォルトページを使わないため不要になります。
また、カスタムページからの認証をおこなうために、app.UseAuthentication()を追加しています。

./Views/Shared/_LoginPartial.cshtml

    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>

asp-areaとasp-pageから /Identity/Account/Register や /Identity/Account/Loginというページを生成していましたがこれらの設定に切り替えると設定されなくなります。

before: https://localhost:{ポート番号}/Identity/Account/Login after: https://localhost:{ポート番号}/?area=Identity&page=%2FAccount%2FLogin
### アカウントの追加

ここでは、起動時にアカウントが存在しなければ追加するようにします。

SeedDataクラスを作成し、追加する処理を記載します。

./Data/SeedData.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace SampleWeb.Data
{
    public class SeedData
    {
        public static async Task Initialize(IServiceProvider serviceProvider)
        {
            var userManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();

            var email = "username@example.com";
            var user = await userManager.FindByEmailAsync(email);

            if (user == null)
            {
                user = new IdentityUser()
                {
                    UserName = email,
                    Email = email,
                    EmailConfirmed = true
                };
                await userManager.CreateAsync(user, "Password@12345");
            }
        }
    }
}

メールは、 username@example.com パスワードは Password@12345にしてみました。
制約をみたすものでないとDBに追加されません。

  • ユーザー名はデフォルトだと日本語はつかえません
  • パスワードは、6文字以上数字、大小アルファベット、英数字以外を含む必要があります。

※いうまでもないですが、実際に使用するパスワードを記載してgithubにあげるようなことだけは・・・


次は、SeedDataクラスの呼び出しです。

./program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SampleWeb.Data;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

//builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
//    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => { options.SignIn.RequireConfirmedAccount = true; })
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddControllersWithViews();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication(); //add
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

// 初期アカウントの追加
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    await SeedData.Initialize(services);
}
//app.MapRazorPages(); //del

app.Run();
最後のほうで、初期アカウントの追加として記載してあります。 これで、ルールにのっとった、email、username、passwordを指定してあればAspNetUsersテーブルに追加されます。 ※SQLiteのクライアントソフトは、DB Browser for SQLiteを使用

ログイン画面

ログイン画面作っていきます
ログインやログアウトの処理はフレームワークに含まれているので、必要なモデルを作成しお作法にのっとって記述していきます。


まずはモデル
./Models/LoginModel.cs

using System.ComponentModel.DataAnnotations;

namespace SampleWeb.Models
{
    public class LoginModel
    {
        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; } = string.Empty;

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; } = false;
    }
}

コントローラー
./Controllers/AccountController.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using SampleWeb.Models;

namespace SampleWeb.Controllers
{
    public class AccountController : Controller
    {
        private readonly SignInManager<IdentityUser> _signInManager;

        public AccountController(SignInManager<IdentityUser> signInManager)
        {
            _signInManager = signInManager;
        }

        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginModel model)
        {
            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
                if (result.Succeeded)
                {
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                }
            }
            return View(model);
        }
    }
}

Accountコントローラーを追加
ログイン、ログアウトはSignInManagerを扱うのでコンストラクタで追加して保持。
Loginのページ表示(GET)とログイン処理を行う(POST)を作成


ビュー
./Views/Account/Login.cshtml

@model SampleWeb.Models.LoginModel

@{
    ViewData["Title"] = "ログイン画面";
}

<div class="row">
    <div class="col-6">
        <form id="account" asp-controller="Account" asp-action="Login" method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-floating mb-3">
                <input asp-for="Email" class="form-control" autocomplete="username" aria-required="true" />
                <label asp-for="Email" class="form-label">Email</label>
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>

            <div class="form-floating mb-3">
                <input asp-for="Password" class="form-control" autocomplete="current-password" aria-required="true" />
                <label asp-for="Password" class="form-label"></label>
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>

            <div>
                <div class="checkbox">
                    <label asp-for="RememberMe" class="form-label">
                        <input class="form-check-input" asp-for="RememberMe" />
                        @Html.DisplayNameFor(m => m.RememberMe)
                    </label>
                </div>
            </div>

            <div>
                <button id="login-submit" type="submit" class="btn btn-primary">Log in</button>
            </div>
        </form>
    </div>
</div>

HTMLはテンプレートとして組み込まれているもののうち、ログインフォーム部分だけとってきています
もともとRazorPageだったので手直ししてあります


呼び出しもとのリンクのURLを直していきます。
./Views/Shared/_LoginPartial.cshtml

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity?.Name!</a>
    </li>
    <li class="nav-item">
        <form  class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
@*     <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li> *@

        <li class="nav-item">
            <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">Login</a>
        </li>

}
</ul>

こちらも比較しやすいように元のソース残してあります。
asp-area、asp-pageを削除して、controllerとactionを指定しただけです
確認のため起動します


Registerは消えて、Loginのみ。デフォルトのUIは無効にしているので直接URLを入力してもたどりつけません。

ログインのURLは、/Account/Loginになっています。

見た目はほぼいじっていないので、質素な感じですが、最低限のものは表示できています

EmailとPasswordを入力して送信するとログインできます


ログアウト

ログアウトの処理をControllerに追加します
./Controllers/AccountController.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using SampleWeb.Models;

namespace SampleWeb.Controllers
{
    public class AccountController : Controller
    {
        private readonly SignInManager<IdentityUser> _signInManager;

        public AccountController(SignInManager<IdentityUser> signInManager)
        {
            _signInManager = signInManager;
        }

        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginModel model)
        {
            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
                if (result.Succeeded)
                {
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                }
            }
            return View(model);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }
    }
}

Logout(Post)を追加しました

ログアウトのリンクを修正していきます
./Views/Shared/_LoginPartial.cshtml

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity?.Name!</a>
    </li>
    <li class="nav-item">
            @*         <form  class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
        <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form> *@

            <form class="form-inline" asp-controller="Account" asp-action="Logout" method="post">
                <button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
            </form>
    </li>
}
else
{
@*     <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li> *@

        <li class="nav-item">
            <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">Login</a>
        </li>

}
</ul>

Login同様に、 asp-area、asp-pageを削除して、controllerとactionを追加
postメソッドに飛ばしたいので、postを追加
起動して、ログイン→ログアウトができています


おまけ

個人利用のwebアプリとはいえ、パスワードはソースに書きたくない・・・
という場合は、パスワードを入れる部分はデフォルトのUIを一時的に有効にして追加し入れ終わったら無効にするでもよいかもしれないですね。 ./program.csは以下の状態にします。

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SampleWeb.Data;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

//builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => { options.SignIn.RequireConfirmedAccount = true; })
//    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication(); //add
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");


app.MapRazorPages(); //パスワード登録したらコメントアウト

app.Run();

プログラム実行し、直接登録のURLを実行します
https://localhost:{ポート番号}/Identity/Account/Register


必要な項目を追加して、Registerを実行


以下ページが表示されます

これは、program.csで登録時の認証がデフォルトはtrueになっているものの、メール送信の方法がないから表示されています
./program.cs

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

※RequireConfirmedAccount = true の部分。falseならメールによる確認を行わない設定

最初アカウントを登録すると AspNetUsersテーブルのEmailConfirmedが0になっています。この状態だとログインできません

登録とどうじにこのメールアドレスにメールを飛ばすことになるのですが、その処理が追加されていないため、このような画面を表示しています

実運用だとメールを飛ばして、そこからリンククリックしてもらい認証完了というところですがそうなっていないためアカウントを有効にすることができないです
ただし、英文をよくよむと

This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. Normally this would be emailed: Click here to confirm your account

メール送信の設定内からこのドキュメントみてねって話と
メールないなら有効にする手段ないから、ここクリックしてねと書いてあります
よって、今回の目的では、リンクをクリックして認証の代わりをするか、設定をfalseにするかのどちらかの対応が必要です
(直接テーブルの値を1に変えても一緒ですが・・・)


リンクをクリックすると以下画面に遷移し、AspNetUsersテーブルのEmailConfirmedの値が1に代わっていることが確認できます


ここまでできたら、デフォルトのUIをコメントアウトで無効にする でもよいですね。

投稿日時: 2024-08-31 02:41:31
更新日時: 2024-08-31 16:40:31

プロジェクトの作成

ASP.NET Core Webアプリ(Model-View-Controller)を選択

以下で作成


パッケージの更新及び追加

ソリューションのNugetパッケージの管理を開く
更新プログラムがあるので、更新を行う

Microsoft.EntityFrameworkCore.Sqliteをインストールする


認証関係のDBを作成

以下ファイルを変更する
./appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=Identity.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

SQLiteの接続文字列を設定。

./Program.cs

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));

UseSqlServerをUseSqliteに変更する。

パッケージマネージャーコンソールを起動

Update-Database -Context ApplicationDbContext

Update-Databaseコマンドを実行しDBを作成する。 ※複数のプロジェクトがある場合、パッケージマネージャーコンソールの規定のプロジェクトは、対象のプロジェクトを選択しておくこと。

エラー発生
PM> Update-Database -Context ApplicationDbContext
Build started...
Build succeeded.
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (7ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT "MigrationId", "ProductVersion"
      FROM "__EFMigrationsHistory"
      ORDER BY "MigrationId";
Microsoft.EntityFrameworkCore.Migrations[20402]
      Applying migration '00000000000000_CreateIdentitySchema'.
Applying migration '00000000000000_CreateIdentitySchema'.
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
      Failed executing DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetRoles" (
          "Id" nvarchar(450) NOT NULL CONSTRAINT "PK_AspNetRoles" PRIMARY KEY,
          "Name" nvarchar(256) NULL,
          "NormalizedName" nvarchar(256) NULL,
          "ConcurrencyStamp" nvarchar(max) NULL
      );
Failed executing DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "AspNetRoles" (
    "Id" nvarchar(450) NOT NULL CONSTRAINT "PK_AspNetRoles" PRIMARY KEY,
    "Name" nvarchar(256) NULL,
    "NormalizedName" nvarchar(256) NULL,
    "ConcurrencyStamp" nvarchar(max) NULL
);
Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 1: 'near "max": syntax error'.
   at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
   at Microsoft.Data.Sqlite.SqliteCommand.PrepareAndEnumerateStatements()+MoveNext()
   at Microsoft.Data.Sqlite.SqliteCommand.GetStatements()+MoveNext()
   at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader()
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteNonQuery()
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
   at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String connectionString, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String connectionString, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
SQLite Error 1: 'near "max": syntax error'.

エラー対応

SQLiteでは、 .HasColumnType("nvarchar(max)"); がエラーとなるので、コメントアウトする。

./Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs(修正の一例)

            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
                {
                    b.Property<string>("Id")
                        .HasColumnType("nvarchar(450)");

                    b.Property<string>("ConcurrencyStamp")
                        .IsConcurrencyToken();
                        //.HasColumnType("nvarchar(max)"); // コメントアウトする

                    b.Property<string>("Name")
                        .HasColumnType("nvarchar(256)")
                        .HasMaxLength(256);

                    b.Property<string>("NormalizedName")
                        .HasColumnType("nvarchar(256)")
                        .HasMaxLength(256);

                    b.HasKey("Id");

                    b.HasIndex("NormalizedName")
                        .IsUnique()
                        .HasName("RoleNameIndex")
                        .HasFilter("[NormalizedName] IS NOT NULL");

                    b.ToTable("AspNetRoles");
                });

再度DB作成

パッケージコンソールマネージャーを開きコマンドを実行

Update-Database -Context ApplicationDbContext
Done.
PM> Update-Database -Context ApplicationDbContext
Build started...
Build succeeded.
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (6ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT "MigrationId", "ProductVersion"
      FROM "__EFMigrationsHistory"
      ORDER BY "MigrationId";
Microsoft.EntityFrameworkCore.Migrations[20402]
      Applying migration '00000000000000_CreateIdentitySchema'.
Applying migration '00000000000000_CreateIdentitySchema'.
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetRoles" (
          "Id" nvarchar(450) NOT NULL CONSTRAINT "PK_AspNetRoles" PRIMARY KEY,
          "Name" nvarchar(256) NULL,
          "NormalizedName" nvarchar(256) NULL,
          "ConcurrencyStamp" TEXT NULL
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetUsers" (
          "Id" nvarchar(450) NOT NULL CONSTRAINT "PK_AspNetUsers" PRIMARY KEY,
          "UserName" nvarchar(256) NULL,
          "NormalizedUserName" nvarchar(256) NULL,
          "Email" nvarchar(256) NULL,
          "NormalizedEmail" nvarchar(256) NULL,
          "EmailConfirmed" bit NOT NULL,
          "PasswordHash" TEXT NULL,
          "SecurityStamp" TEXT NULL,
          "ConcurrencyStamp" TEXT NULL,
          "PhoneNumber" TEXT NULL,
          "PhoneNumberConfirmed" bit NOT NULL,
          "TwoFactorEnabled" bit NOT NULL,
          "LockoutEnd" datetimeoffset NULL,
          "LockoutEnabled" bit NOT NULL,
          "AccessFailedCount" int NOT NULL
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetRoleClaims" (
          "Id" int NOT NULL CONSTRAINT "PK_AspNetRoleClaims" PRIMARY KEY,
          "RoleId" nvarchar(450) NOT NULL,
          "ClaimType" TEXT NULL,
          "ClaimValue" TEXT NULL,
          CONSTRAINT "FK_AspNetRoleClaims_AspNetRoles_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AspNetRoles" ("Id") ON DELETE CASCADE
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetUserClaims" (
          "Id" int NOT NULL CONSTRAINT "PK_AspNetUserClaims" PRIMARY KEY,
          "UserId" nvarchar(450) NOT NULL,
          "ClaimType" TEXT NULL,
          "ClaimValue" TEXT NULL,
          CONSTRAINT "FK_AspNetUserClaims_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetUserLogins" (
          "LoginProvider" nvarchar(128) NOT NULL,
          "ProviderKey" nvarchar(128) NOT NULL,
          "ProviderDisplayName" TEXT NULL,
          "UserId" nvarchar(450) NOT NULL,
          CONSTRAINT "PK_AspNetUserLogins" PRIMARY KEY ("LoginProvider", "ProviderKey"),
          CONSTRAINT "FK_AspNetUserLogins_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetUserRoles" (
          "UserId" nvarchar(450) NOT NULL,
          "RoleId" nvarchar(450) NOT NULL,
          CONSTRAINT "PK_AspNetUserRoles" PRIMARY KEY ("UserId", "RoleId"),
          CONSTRAINT "FK_AspNetUserRoles_AspNetRoles_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AspNetRoles" ("Id") ON DELETE CASCADE,
          CONSTRAINT "FK_AspNetUserRoles_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "AspNetUserTokens" (
          "UserId" nvarchar(450) NOT NULL,
          "LoginProvider" nvarchar(128) NOT NULL,
          "Name" nvarchar(128) NOT NULL,
          "Value" TEXT NULL,
          CONSTRAINT "PK_AspNetUserTokens" PRIMARY KEY ("UserId", "LoginProvider", "Name"),
          CONSTRAINT "FK_AspNetUserTokens_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX "IX_AspNetRoleClaims_RoleId" ON "AspNetRoleClaims" ("RoleId");
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE UNIQUE INDEX "RoleNameIndex" ON "AspNetRoles" ("NormalizedName") WHERE [NormalizedName] IS NOT NULL;
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX "IX_AspNetUserClaims_UserId" ON "AspNetUserClaims" ("UserId");
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX "IX_AspNetUserLogins_UserId" ON "AspNetUserLogins" ("UserId");
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX "IX_AspNetUserRoles_RoleId" ON "AspNetUserRoles" ("RoleId");
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX "EmailIndex" ON "AspNetUsers" ("NormalizedEmail");
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE UNIQUE INDEX "UserNameIndex" ON "AspNetUsers" ("NormalizedUserName") WHERE [NormalizedUserName] IS NOT NULL;
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
      VALUES ('00000000000000_CreateIdentitySchema', '8.0.8');
Done.

作成できた。

投稿日時: 2024-08-27 13:23:27

一般的な値の交換は、一時退避の変数を用意し、値を退避してから
値の入れ替えを行う方法。

var num1 = 5;
var num2 = 6;

var temp = num1; // 値を退避させる。
num1 = num2;
num2 = temp;

変数を増やさずに入れ替えを行う方法として、排他的論理和や加減、乗除による入れ替えもある。 排他的論理和
var num1 = 5; // 101
var num2 = 6; // 110

num2 ^= num1; // 110 => 011 num1 ^= num2; // 101 => 110 num2 ^= num1; // 011 => 101


加減による方法

var num1 = 5;
var num2 = 6;

num2 = num1 - num2;
num1 -= num2;
num2 += num1;

乗除による方法(分母の数字は0以外であること)
var num1 = 5.0;
var num2 = 6.0;

num2 = num1 / num2; num1 /= num2; num2 *= num1;

投稿日時: 2024-07-28 08:54:28
更新日時: 2024-07-28 08:55:28
C#

列挙体を宣言

enum Fruit { Apple, Orange, Kiwi, Banana }

列挙体の文字列を表示

ToString()で設定した文字列を表示

enum Fruit { Apple, Orange, Kiwi, Banana }

Fruit f = Fruit.Apple;
Console.WriteLine(f.ToString()); // Apple

列挙体を配列に変換

GetValuesを使って配列に変換

enum Fruit { Apple, Orange, Kiwi, Banana }
var fruits = (Fruit[])Enum.GetValues(typeof(Fruit));

列挙体の個数

配列に変換してからlengthで取得する方法か・・・

enum Fruit { Apple, Orange, Kiwi, Banana }
var fruits = (Fruit[])Enum.GetValues(typeof(Fruit));
var count = fruits.Length;

列挙体の最後に数を知るための要素を追加しておくか・・・

enum Fruit { Apple, Orange, Kiwi, Banana, Count }
var count = (int)Fruit.Count;

foreachでループ処理

enum Fruit { Apple, Orange, Kiwi, Banana }

var ary = Enum.GetValues(typeof(Fruit));

foreach(var val in ary)
{
    Console.WriteLine("{0}->{1}", (int)val, val.ToString());
}
0->Apple
1->Orange
2->Kiwi
3->Banana
投稿日時: 2024-07-28 07:23:28

StyleCopで解析した結果大量のエラーが出ているものの、自動生成されたプログラム中で発生している場合は、
ファイル先頭に以下を追加し、解析ツールに自動生成されたことを伝える

// <auto-generated />
投稿日時: 2024-07-22 11:14:22
C#

フロント側のWebサーバーで受けたリクエストを
ASP.NET CoreのKestrel Webサーバーで受け取った際に
フロント側でX-Forwarded-For、X-Forwarded-Protoを設定をしておけば
以下で取得できるものかと思っていたのですが、残念ながらフロント側のIPアドレスが取れるだけだったので


this.HttpContext.Connection.RemoteIpAddress.ToString();

フロント側で受けた際のIPアドレスをとるなら以下で取得。


this.HttpContext.Request.Headers["X-Forwarded-For"].ToString();
投稿日時: 2024-07-06 00:15:06
更新日時: 2024-07-06 00:16:06

最近の投稿

最近のコメント

タグ

アーカイブ

その他