Dapperの使い方のメモ
まずはデータを用意しないと試すことができないので、MicrosoftがAdventureWorksという架空の会社を想定したデータを公開しているので
SQL Serverをつかいます
Dapperの使い方のメモ
まずはデータを用意しないと試すことができないので、MicrosoftがAdventureWorksという架空の会社を想定したデータを公開しているので
SQL Serverをつかいます
DataBaseは、SQL Serverで、データはAdventureWorks2022をいれています
→AdventureWorksについて
複数のSQLを1リクエストで投げて、結果を受け取るため
を使います
そこから得た結果に対し、それぞれのSQLで取得されるであろう結果のレコード数に応じて以下を使い分ける感じ
メソッド | 0件 | 1件 | 2件以上 |
---|---|---|---|
Read |
0件 | レコード | 複数レコード |
ReadSingleOrDefault |
null | レコード | 例外発生 |
ReadSingle |
null | レコード | 例外発生 |
ReadFirstOrDefault |
null | レコード | 最初のレコード |
ReadFirst |
例外発生 | レコード | 最初のレコード |
※Asyncをつければ非同期版
※Singleは結果が1行じゃないと例外発生、Firstは最初の1行だけ返す
サンプル:
1回のリクエストで男性女性わけてデータを取り出す例
Employee.cs
namespace SampleDapper.Models.HumanResources
{
public class Employee
{
// テーブルから取得したい列の情報を定義
public int BusinessEntityID { get; set; }
public string JobTitle { get; set; } = null!;
public DateTime BirthDate { get; set; }
public char Gender { get; set; }
public DateTime HireDate { get; set; }
}
}
Program.cs
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using SampleDapper.Models.HumanResources;
try
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@"appsettings.json")
.Build();
var connectStr = config["ConnectionStrings:DefaultConnection"];
using (var connection = new SqlConnection(connectStr))
{
connection.Open();
// 任意の件数、取得するSQL
var sql = @"
SELECT * FROM HumanResources.Employee WHERE Gender = @Gender1;
SELECT * FROM HumanResources.Employee WHERE Gender = @Gender2;
";
// SQL中のパラメータに渡す値を設定
var parameters = new { Gender1 = 'F', Gender2 = 'M' };
using (var multi = connection.QueryMultiple(sql, parameters))
{
var femaleEmployees = multi.Read<Employee>();
var maleEmployees = multi.Read<Employee>();
}
}
}
catch(Exception ex)
{
Console.Write(ex.ToString());
}
DataBaseは、SQL Serverで、データはAdventureWorks2022をいれています
→AdventureWorksについて
複数データが返ってくる場合は、以下メソッドを使います
サンプル:
Employee.cs
namespace SampleDapper.Models.HumanResources
{
public class Employee
{
// テーブルから取得したい列の情報を定義
public int BusinessEntityID { get; set; }
public string JobTitle { get; set; } = null!;
public DateTime BirthDate { get; set; }
public char Gender { get; set; }
public DateTime HireDate { get; set; }
}
}
Program.cs
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using SampleDapper.Models.HumanResources;
try
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@"appsettings.json")
.Build();
var connectStr = config["ConnectionStrings:DefaultConnection"];
using (var connection = new SqlConnection(connectStr))
{
connection.Open();
// 任意の件数、取得するSQL
var sql = "SELECT * FROM HumanResources.Employee WHERE Gender = @Gender AND JobTitle = @JobTitle";
// SQL中のパラメータに渡す値を設定
var parameters = new { Gender = 'F', JobTitle = "Design Engineer" };
var recordList = connection.Query<Employee>(sql, parameters);
}
}
catch(Exception ex)
{
Console.Write(ex.ToString());
}
DataBaseは、SQL Serverで、データはAdventureWorks2022をいれています
→AdventureWorksについて
主キーで絞り込みのように1行データを取得する場合
メソッド | 0件 | 1件 | 2件以上 |
---|---|---|---|
QuerySingle |
例外発生 | レコード | 例外発生 |
QuerySingleOrDefault |
null | レコード | 例外発生 |
QueryFirst |
null | レコード | 最初のレコード |
※Asyncをつければ非同期版
サンプル:
Employee.cs
namespace SampleDapper.Models.HumanResources
{
public class Employee
{
// テーブルから取得したい列の情報を定義
public int BusinessEntityID { get; set; }
public string JobTitle { get; set; } = null!;
public DateTime BirthDate { get; set; }
public char Gender { get; set; }
public DateTime HireDate { get; set; }
}
}
Program.cs
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using SampleDapper.Models.HumanResources;
try
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@"appsettings.json")
.Build();
var connectStr = config["ConnectionStrings:DefaultConnection"];
using (var connection = new SqlConnection(connectStr))
{
connection.Open();
// 主キーで1件取得するSQL
var sql = "SELECT * FROM HumanResources.Employee WHERE BusinessEntityID = @BusinessEntityID";
// SQL中のパラメータに渡す値を設定
var parameters = new { BusinessEntityID = 36 };
var record = connection.QuerySingle<Employee>(sql, parameters);
}
}
catch(Exception ex)
{
Console.Write(ex.ToString());
}
DataBaseは、SQL Serverで、データはAdventureWorks2022をいれています
→AdventureWorksについて
Count、Sum、Avg、Max、Minなどの1データ取得する場合に以下メソッドを使用します
サンプル:
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
try
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@"appsettings.json")
.Build();
var connectStr = config["ConnectionStrings:DefaultConnection"];
using (var connection = new SqlConnection(connectStr))
{
connection.Open();
var sql = "SELECT COUNT(*) FROM HumanResources.Employee";
var count = connection.ExecuteScalar<int>(sql);
}
}
catch(Exception ex)
{
Console.Write(ex.ToString());
}
アプリ起動後最初に表示される画面のことです
↓デフォルトだとこんな画面です
スプラッシュ画面は、プロジェクトファイルに設定があるとのこと
プロジェクト名で右クリック、プロジェクトファイルの編集を選択
MauiSplashScreenの項目がその設定
デフォルトは、Resourcesフォルダ内のSplashフォルダにあるsplash.svg(.NETの文字)が設定されていて
背景色は、Colorに設定してあるドットネットカラー(?)
背景を黒にするだけでもいい感じになりますね
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#000" BaseSize="128,128" />
試しに、デフォルトで入っているdotnet_bot.pngに差し替えてみると・・・
<MauiSplashScreen Include="Resources\Images\dotnet_bot.png" Color="#000" BaseSize="128,128" />
こんな感じです
ちょっとしたものならこれで十分ですね
・スプラッシュスクリーンの説明はこちら
https://learn.microsoft.com/en-us/dotnet/maui/user-interface/images/splashscreen?view=net-maui-9.0&tabs=android
.NET マルチプラットフォーム アプリ UI つまり、一つのプロジェクトでいろんなプラットフォーム向けのアプリを作成する仕組みになります
2022年5月に正式リリースされた新しい仕組みになります。
ひと昔前は、Xamarinがありましたが、サポート終了となり.NET MAUIがクロスプラットフォーム開発の仕組みになります
https://learn.microsoft.com/ja-jp/dotnet/maui/what-is-maui?view=net-maui-9.0
マルチプラットフォームとはいえ、iPhoneで個人用のアプリを作るにしても
Macが必要で Apple Developer Program で米99ドル/年払わないと個人で使い続けることができなかったりと
ちょっと試すにしても、いろいろハードルが高かったりするので、ひとまずAndroid中心で確認していきます
情報量はまだまだ少なめだとおもいますので、触ってみた内容をメモしていきます
少しずつ追加しておく
属性 | 内容 |
---|---|
AnchorX | X方向のアンカー位置(0.0(左側)~1.0(右側) |
AnchorY | X方向のアンカー位置(0.0(上端)~1.0(下端) |
BackgroundColor | 背景の色 |
BorderColor | ボーダーの色 |
BorderWidth | ボーダーの幅 |
CornerRadius | コーナーの半径 |
FontSize | フォントサイズ |
HorizontalOptions | 親に対する水平方向の配置(Start, Center, End, Fill) |
Margin | 外側余白 |
Padding | 内側余白 |
Text | 表示する文字列 |
TextColor | 表示する文字列の色 |
VerticalOptions | 垂直方向の位置(Start, Center, End, Justify) |
属性 | 内容 |
---|---|
AnchorX | X方向のアンカー位置(0.0(左側)~1.0(右側) |
AnchorY | X方向のアンカー位置(0.0(上端)~1.0(下端) |
BackgroundColor | 背景の色 |
CorsorPosition | 文字列におけるカーソル位置 |
FontSize | フォントサイズ |
HorizontalOptions | 親に対する水平方向の配置(Start, Center, End, Fill) |
HorizontalTextAlignment | 水平方向の位置(Start, Center, End, Justify) |
Keyboard | 入力文字の制約(Numeric, Plain(テキスト入力支援なし), Text(テキスト入力支援あり), Password, Url…) |
Margin | 外側余白 |
Padding | 内側余白 |
Text | 表示する文字列 |
TextColor | 表示する文字列の色 |
VerticalOptions | 親に対する垂直方向の配置(Start, Center, End, Fill) |
VerticalTextAlignment | 垂直方向の位置(Start, Center, End, Justify) |
属性 | 内容 |
---|---|
Margin | 外側余白 |
Padding | 内側余白 |
Spacing | コントロール同士の間隔 |
HorizontalOptions | 親に対する水平方向の配置(Start, Center, End, Fill) |
VerticalOptions | 親に対する垂直方向の配置(Start, Center, End, Fill) |
属性 | 内容 |
---|---|
AnchorX | X方向のアンカー位置(0.0(左側)~1.0(右側) |
AnchorY | X方向のアンカー位置(0.0(上端)~1.0(下端) |
BackgroundColor | 背景の色 |
FontSize | フォントサイズ |
HorizontalOptions | 親に対する水平方向の配置(Start, Center, End, Fill) |
HorizontalTextAlignment | 水平方向の位置(Start, Center, End, Justify) |
Margin | 外側余白 |
Padding | 内側余白 |
Text | 表示する文字列 |
TextColor | 表示する文字列の色 |
VerticalOptions | 親に対する垂直方向の配置(Start, Center, End, Fill) |
VerticalTextAlignment | 垂直方向の位置(Start, Center, End, Justify) |
属性 | 内容 |
---|---|
Margin | 外側余白 |
Padding | 内側余白 |
Spacing | コントロール同士の間隔 |
HorizontalOptions | 親に対する水平方向の配置(Start, Center, End, Fill) |
VerticalOptions | 親に対する垂直方向の配置(Start, Center, End, Fill) |
生成AIさん教えて
あまり有名じゃないパッケージの使い方を隅々まで調査するにはどうしたらよい?
NuGetで「面白そうだけど無名なパッケージ」に出会ったとき、使い勝手を隅々まで把握したいとなると、確かにドキュメントやサンプルが乏しい場合が多いです。
このようなケースで、効率よく深く使い勝手を探る方法をまとめます。✅ 段階的アプローチ:使い勝手を隅々まで調べる方法
1. 公式ソースコード・GitHub を探す
- NuGet.org のパッケージページには多くの場合、Project URL や Repository URL が載っています。
- そこから GitHub のリポジトリをたどり、以下を重点的に確認:
- README.md: サンプルコードがないか。
- tests/ フォルダ: 使い方の宝庫。作者が想定した使い方を把握できます。
- examples/ フォルダ: あれば超参考になります。
- issues: 他の人のつまずき方や、作者の設計意図を知るヒント。
- Pull Requests: 活発かどうか。過去の設計変更の理由も追える。
💡 小規模パッケージほど、READMEよりもテストコードが最良の使い方リファレンスになっていることが多いです。
2. パッケージをローカルに落として中身を読む
dotnet add package PackageName
- Visual Studio で Ctrl + Click や F12 でシンボルを辿る。
- Object Browser や ILSpy, dnSpy などを使って、内部実装・パブリックAPIの設計を読む。
- Visual Studio の「パッケージ マネージャーコンソール」から
Get-Package -ListAvailable -Filter PackageName
で説明文を見つける手もあります。
3. 自作サンドボックスアプリで試す
空の Console App や xUnit Test を用意し、自分で以下を意識して試すとよいです:
試したいこと 例 基本的な使い方 「最小構成」で動くか 想定外入力での動作 null, 空文字、異常系 複数APIの組み合わせ 内部状態が持続するかどうか パフォーマンス傾向 特にループや大量データ処理時の動作 拡張性 継承・DI・設定変更のしやすさ 4. JetBrains dotPeek や ILSpy で逆コンパイル
- NuGetパッケージの .dll を開いて、公開API以外の動作を逆算できます。
- 拡張ポイント(virtual, public, internal)を把握するのにも便利。
5. OSS レビューメディア or ブログを探す
- 日本語では少ないですが、英語だと以下で使い方が紹介されていることもあります:
- Dev.to
- Medium
- Stack Overflow
- GitHub Discussions
6. 自分で「お試しまとめ」をブログに書く or GitHubに上げる
- 試しがてら記録すると、他の人の検索に引っかかることもあり、二次的に作者に届いたりすることも。
- 自分の知識の棚卸し・再確認にも役立ちます。
githubのテストと F12でどんなメソッドが用意されているのか眺めるあたりなのかな
連続したメモリ領域への安全かつ効率的なアクセスを提供するための型
スタック上に必ず保持する(ヒープに置くような使い方はできない)
スライスで部分的にデータを取り出すことができる
ヒープにおけないので、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
参考になるページみつけたら貼っておく
dotnet / csharplang C# Language Design
タプルを使用すると複数の値を一度に受け取ることができますが
そのうちの一部の値は使わない場合、使いもしないのに変数に格納すると
この変数はどこで使うのだろうか?と疑問に思わせてしまうためよくないです
使う気がない値は明確に使わないという風に示せた方がよいのでその場合に _ をつかいます
例えば3つの値を返す関数があり、すべて使いたい場合と2つ名の値が不要な場合だと以下のように記載できます
var (a, b, c) = Hogehoge();
var (x, _, y) = Hogehoge();
static (string a, string b, string c) Hogehoge()
{
return ("a", "b", "c");
}
クラス名の識別のため名前空間を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;
例えば、実行ファイルのあるディレクトリを取得したい場合に以下のように書いたとします
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();
}
}
サーバーからちょっとしたメール送信をしたいときに何かしらのSMTPサーバー経由でメールを送りたいなと思ったので
ひとまず、簡単なメール通知をGoogleのSMTPサーバーを使って送る例をメモっておく
GoogleのSMTPを使ってメール送信する際には、アプリのパスワードを作成する必要がある
手順は、以下ページのとおり
Googleでアプリパスワードを生成
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);
}
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}")
予想外だったのでメモ
質問:
asp.netで公式が用意しているwebフレームワーク以外のフレームワークがnugetなどにあったりしますか?
rubyのSinatraみたいな薄いフレームワークってある
はい、そのような考え方はとても良い視点です。ASP.NETのような重厚なフレームワークに比べて、RubyのSinatraのような「薄い」Webフレームワークを.NET(C#)で探すことは、ミニマルで高速なWebアプリ開発を求める場合に有効です。
✔ ASP.NET以外の「軽量」Webフレームワーク
- Carter
URL: https://github.com/CarterCommunity/Carter
概要: ASP.NET Coreの上に構築された軽量マイクロフレームワーク。Sinatra風に書けます。
特徴:
モジュールベースでルーティング
ASP.NET CoreのミドルウェアやDIと互換性あり
非常にシンプルな構文
- NancyFx(非アクティブだが参考になる)
URL: https://github.com/NancyFx/Nancy
概要: Sinatraインスパイアの軽量フレームワーク。今は開発終了していますが、学習や小規模用途には依然として使えます。
- Minimal API (ASP.NET Core 6以降の機能)
公式ですが「薄い」アプローチ
.NET 6以降では Minimal API が公式サポートされており、Sinatra風の非常にシンプルな書き方が可能です
公式ではありますが、フルMVCやRazorなどと比べて極めて軽量です。
- EmbedIO
URL: https://github.com/unosquare/embedio
概要: Windows環境や軽量サーバ用途に向いた、自己ホスト型のWebフレームワーク。
特徴: .NETでIoTや小規模Web API用途に適しています。
- SimpleHttpServer / Watson Webserver
さらに低レベルのHTTPフレームワーク。WebAPIを提供するだけの超軽量用途向け。
~~公式で薄いフレームワーク出していたんですね ~~
→冷静に考えたら、初期状態で作っているだけね
チュートリアルはここらへん
https://learn.microsoft.com/ja-jp/aspnet/core/tutorials/min-web-api?view=aspnetcore-9.0&tabs=visual-studio
Sheet1・・・保護なし(保護に使用)
Sheet2・・・保護なし(保護に使用)
Sheet3・・・保護あり・パスなし(保護解除に使用)
Sheet4・・・保護あり・パスあり(保護解除に使用)
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
}
カレントの表示倍率を取得と設定
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
}
3つシートがあり、Sheet1とSheet2をIndexと名前を指定して削除してみます
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
}
シート名を変更する
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
}
安易に保存することはできないので、いろいろメモ。
■【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を使った書き込みをおさらいしてみる
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);
}
}
}
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上に書き込みを行うと、現在の位置から書き込み始めるので、おかしなことになる・・・
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 ファイルが壊れて開けなくなる
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のみなので、同じ対応が必要
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
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 でオブジェクトの解放について
ファイルパスでも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);
}
}
}
□ファイルパス
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);
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
}
シートが3つあり、Sheet3がアクティブな状態のExcelを使用
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
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 でオブジェクトの解放について
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);
}
}
}
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);
}
}
}
該当機能なし
□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("対象外の拡張子です")
};
}
}
}
Sheet1シートがあるファイルを開き、
シート名で参照する方法と、シート番号で参照する方法
また、存在しないシート名を指定するとどうなるか確認
シート番号と名前でアクセスする方法
シート番号は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
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 でオブジェクトの解放について
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);
}
}
}
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);
}
}
}
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);
}
}
}
□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("対象外の拡張子です")
};
}
}
}
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
配列の要素を指定する方法、後ろから何番目と指定するときに使用するの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])); // 範囲演算子の前は含む要素、後ろは指定した要素の一つ前まで取得
ログを出力する際に、 "3行14列"というよりは、"N3" といった方がわかりやすいだろうから、
アドレスの取得方法について
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
※ライブラリ側にはアドレスを出力する方法はないので、自作する
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}";
}
}
※ライブラリ側にはアドレスを出力する方法はないので、自作する
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("対象外の拡張子です")
};
}
}
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}";
}
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
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;
}
}
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("対象外の拡張子です")
};
}
}
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
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 でオブジェクトの解放について
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);
}
}
}
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}");
}
}
}
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;
}
}
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("対象外の拡張子です")
};
}
}
??の左側が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); // サンプル太郎
今まで、名前空間といえば、宣言してブロックで囲むものでしたが・・・
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!");
}
}
地味ですがいいですね
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
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!;
}
}
}
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);
}
}
}
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を空にしてからコピーをする手段をとっています
ライブラリ内部の設計の違いからくるもののそうです
読み取りのみのためなし
□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("対象外の拡張子です")
};
}
}
}
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) としても同じ結果
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}");
}
}
}
}
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}");
}
}
}
}
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}");
}
}
}
}
変数名やメソッド、クラス名等の名前が文字列として取得できる関数
ログやエラーメッセージなどに含めておくと、それらがリファクタリングで名前を変えた場合に追従できるのがメリット
また、特定のクラスだったら処理を分岐するといったケースでも直接文字列リテラルとして指定するのではなく
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}");
追従させれるので、よからぬ修正がはいらず安心ってことです
だいぶいろんな機能が増えたので自身の理解のためにも少しずつメモしていく
@"" と $"" が合体したような機能ですかねぇ
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文字になります
シートが3つ入っているExcelを開いて、シートの数を取得し出力します
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
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 でオブジェクトの解放について
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);
}
}
}
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);
}
}
}
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);
}
}
}
□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("対象外の拡張子です")
};
}
}
}
シートが3つ入っているExcelを開いて、各ワークシートを取得し名前を出力します
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
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 でオブジェクトの解放について
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);
}
}
}
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);
}
}
}
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);
}
}
}
□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("対象外の拡張子です")
};
}
}
}
シートが3つ入っているExcelを開いて、各ワークシートを取得し名前を出力します
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
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 でオブジェクトの解放について
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);
}
}
}
}
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);
}
}
}
}
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);
}
}
}
}
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("対象外の拡張子です")
};
}
}
}
もう一歩、シンプルな実装をするため解放ヘルパーなんてどう?と生成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関数が返す参照カウントの値をログにだせるようにすると解放漏れがあった場合にはすぐにわかってよいかも
以下コードは
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);
}
}
}
}
現実的で読みやすいレベルのソースコードになりました!
ただ、解放クラスは使いまわしをしない方針のため、セルを数万行チェックするといったようなケースの場合
大量にリストにオブジェクトを抱えた状態になるので、あまりおすすめできないとおもいます
それらは、一時的変数で管理してすぐに廃棄していくのがベストだと思うので、うまく組み合わせて使っていくことが重要だと思います
適切な解放がされないと、プロセスが消えずに残ることになります。
実際に確認をしてみます
以下のように、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);
}
}
}
}
ループして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さんこのコードで問題ない?
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チェックをしているのは良い実装です。
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
もし、ファイルが既にある場合に上書きしたくない場合は事前にファイル存在チェックをして処理を止めればよいでしょう
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!;
}
}
}
□ファイルパスで名前を付けて保存
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);
}
}
}
□ファイルパスで名前を付けて保存
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と同じなのでそちらを参照
読み取りのみのライブラリのため保存はありません
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("対象外の拡張子です")
};
}
}
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
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!;
}
}
}
未対応
未対応
未対応
未対応
以下リンク先に、マイクロソフトの説明があります。
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経由の操作は許可しているものの、ユーザーがログインしている状況化で、アプリを表示した状態で実行し意図しない動きの場合に対応ができることが前提という話になるので、サーバーでは技術的につかわないようにという話でした・・・
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)も同様です。
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!;
}
}
}
未対応
未対応
未対応
未対応
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
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!;
}
}
}
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);
}
}
}
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);
}
}
}
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);
}
}
}
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("対象外の拡張子です")
};
}
}
既存ファイルを開く
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
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 でオブジェクトの解放について
□ファイルパスを使って開く
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を使えば、ファイルへのアクセス方法(読み書き)や他プロセスへの共有方法も制御できるので細かい制御が必要であればこちらを使う
□ファイルパスを使って開く
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を使えば、ファイルへのアクセス方法(読み書き)や他プロセスへの共有方法も制御できるので細かい制御が必要であればこちらを使う
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);
}
}
}
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("対象外の拡張子です")
};
}
}
新規にブックを作成する
Sub Sample()
Dim bookObj As workbook
Set bookObj = Workbooks.Add
Set bookObj = Nothing
End Sub
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 でオブジェクトの解放について
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);
}
}
}
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);
}
}
}
読み取りのみのライブラリのためなし
□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("対象外の拡張子です")
};
}
}
参考になりそうなサイト
github
https://github.com/ClosedXML/ClosedXML
旧wiki
https://github.com/closedxml/closedxml/wiki
徐々に移行しているというwiki
https://docs.closedxml.io/en/latest/
github
https://github.com/ExcelDataReader/ExcelDataReader
ドキュメントっぽいものがみあたらなかったのでwikiくらいしかみるとこはないかも
github
https://github.com/EPPlusSoftware/EPPlus
公式サイト https://www.epplussoftware.com/ja ※いろいろ情報があるのでみるとよいかも
ドキュメント
https://www.epplussoftware.com/ja/Developers
EPPlus source code docsから使用するバージョンを選択するとそのドキュメントが見れます。
依存関係から右クリックでCOM参照の追加を選択
Microsoft.Excel XX.X ObjectLibrary を選択(要Excelインストール)
OKで閉じる
Nugetから ClosedXML をインストール
Nugetから ExcelDataReader をインストール
ExcelDataReader.DataSetもインストールしてください
AsDataSet() を使うのに必要になってきます
Nugetから EPPlus をインストール
Nugetから NPOIをインストール
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経由による自動操作について
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
めもめも。
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"));
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 読み: 品詞:名詞
文字列:. 読み: 品詞:名詞
マイグレーションの時に、列の文字数長さの設定でmaxの部分でエラーになるので、それぞれにどんな情報が入っていて、何を設定すべきか調べてみた。
Identityで生成されるテーブルとそれらの関係は以下のとおり
7つのテーブルがあり、Userとそれに関連するテーブルが4つと
Roleとそれに関するテーブルが2つと
UserとRoleを結ぶテーブルが1つの計7つで構成されている
AspNetUserテーブルは、ユーザーに関する主要な情報が格納
列名 | nvarchar(max) | 内容 |
---|---|---|
Id | ユーザーの一意の識別子。通常はGUID(ハイフンあり) | |
UserName | ユーザーのログイン名 | |
NormalizedUserName | 検索などの比較で効率的に行うための正規化(大文字) | |
メールアドレス | ||
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テーブルは、ーザーに関連付けられたクレーム情報が格納
列名 | nvarchar(max) | 内容 |
---|---|---|
Id | プライマリキーとして機能する一意の識別子 | |
UserId | クレームを設定するUserId | |
ClaimType | 〇 | クレームの種類や名前 |
ClaimValue | 〇 | クレームの値 |
クレームは、ユーザーはロールといった概念のほかに情報を追加し細かい認証の制御を行うために使用する
よって、クレームの種類や値を設定するのは開発者次第のためお好きな文字数を設定すればよい
長い名称を使うことなければ64文字とかで十分かなと
AspNetUserLoginsテーブルは、外部認証プロバイダーを使用してログインしたユーザーの情報が格納
列名 | nvarchar(max) | 内容 |
---|---|---|
LoginProvider | 外部認証プロバイダーの名前(例:Google、Facebook、Microsoft等) | |
ProviderKey | 認証プロバイダーがユーザーに割り当てた一意の識別子 | |
ProviderDisplayName | 〇 | 認証プロバイダーの表示名を格納 |
UserId | 該当するUserId |
ProviderDisplayNameは、例えばGoogleの認証だとGoogleが格納されています。そこまで長い名前は入るとおもえないので、64文字程度もあれば十分かなと
AspNetUserTokensテーブルは、ユーザーに関連する各種トークン情報が格納
列名 | nvarchar(max) | 内容 |
---|---|---|
UserId | AspNetUsersテーブルのIdに対応する、ユーザーの一意の識別子 | |
LoginProvider | トークンを生成したプロバイダーの名前を指定 | |
Name | トークンの名前や種類を格納 | |
Value | 〇 | トークンの実際の値を格納 |
どういうときにつかうのか?
パスワードリセット、メール確認、二要素認証、外部認証プロバイダー、リフレッシュトークン、カスタムトークンなどだそうです
AIの見解:
OAuth 2.0のアクセストークンは通常200〜400文字程度
JWTベースのトークンは500〜1000文字程度になることがあります
リフレッシュトークンは通常、アクセストークンよりも長くなる傾向があります
トークンのサイズが将来的に大きくなる可能性を考慮する必要があります
よって2000文字程度の長さが妥当では?
とのこと。
AspNetUserRolesテーブルは、ユーザーとロールの関連付けに関する情報が格納
列名 | nvarchar(max) | 内容 |
---|---|---|
UserId | UserIdを設定 | |
RoleId | RoleIdを設定 |
AspNetRolesテーブルは、アプリケーション内のロールに関する情報が格納
列名 | nvarchar(max) | 内容 |
---|---|---|
Id | UserIdを設定 | |
Name | RoleIdを設定 | |
NormalizedName | 検索などの比較で効率的に行うための正規化(大文字) | |
ConcurrencyStamp | 〇 | 同時実行制御に使用。GUIDが使われる |
・AIの見解
ConcurrencyStamp・・・同時実行制御に使用されるタイムスタンプです。通常GUIDが使用される。GUIDの文字列表現は通常36文字。よって36文字
AspNetRoleClaimsテーブルは、ロールに関連付けられた追加の情報(クレーム)が格納
AspNetUserClaimsのRole版
列名 | nvarchar(max) | 内容 |
---|---|---|
Id | プライマリキーとして機能する一意の識別子 | |
RoleId | クレームを設定するRoleId | |
ClaimType | 〇 | クレームの種類や名前 |
ClaimValue | 〇 | クレームの値 |
クレームは、ユーザーはロールといった概念のほかに情報を追加し細かい認証の制御を行うために使用する
よって、クレームの種類や値を設定するのは開発者次第のためお好きな文字数を設定すればよい
長い名称を使うことなければ64文字とかで十分かなと
同じレコードを複数のリクエストにより変更を行った際に、どちらのリクエストで変更したのか、判断するために、ConcurrencyStampにGUIDを設定し判断に使っています。
これにより、同時実行した場合、更新ができれば自分のリクエストにより更新ができたことになり
更新ができない場合(他のリクエストによりすでにConcurrencyStampの値が変わっていた場合)は更新が失敗することになります
該当のクラスデフォルトの説明あり
各説明とデフォルト値ですね
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);
}
}
}
一次ソースがみつからなかったので生成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は、期間が短いとちょくちょくログインしなおさないといけないのでほどほどのタイミングに設定しておく必要があると思います
ユーザー名の制約はこちら。
検証してみます
以下プログラムで、同一メールアドレスで複数のユーザー名が入るかテストしてみます
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になります
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");
}
}
}
これだと最初の設定しか入らない
この条件を変えたい場合は、
./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がデフォルトなのであえて設定するまでもなく
変更するとしたら文字列の長さくらいかなと
このページでは、下記ページの状態から開始しています
ASP.NET Core Webアプリで、SQLiteを使用して認証用のDBを作成
認証を使用したプロジェクトを作成し、そのまま起動するとデフォルトのUIが利用できます。
・認証は個別のアカウント
・デフォルトのまま起動すると、登録やログインのリンクあり。
・登録のページ
・ログインのページ
個人で利用するブログを作ったとすると、自分が利用するアカウントを登録してしまえば、登録画面は不要でログイン画面だけほしいという話になります。
そのため、起動時にSeedとして初期のアカウントを登録させて、不要なページは program.csファイル内で強制的にリダイレクトかけたりエラーにすることも可能ですが・・・
対応漏れがあると目もあてられないことになりかねないので、デフォルトのUIを無効とし、必要なページだけ設定していきます。
./Program.cs ファイル中でAddDefaultIdentityを利用していることで有効になります。(フレームワークのバージョンにより記載はことなるので要注意)
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
比較しやすいよう元のコードも残してあります。
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というページを生成していましたがこれらの設定に切り替えると設定されなくなります。
ここでは、起動時にアカウントが存在しなければ追加するようにします。
SeedDataクラスを作成し、追加する処理を記載します。
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に追加されません。
※いうまでもないですが、実際に使用するパスワードを記載してgithubにあげるようなことだけは・・・
次は、SeedDataクラスの呼び出しです。
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();
ログイン画面作っていきます
ログインやログアウトの処理はフレームワークに含まれているので、必要なモデルを作成しお作法にのっとって記述していきます。
まずはモデル
./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をコメントアウトで無効にする でもよいですね。
ASP.NET Core Webアプリ(Model-View-Controller)を選択
以下で作成
ソリューションのNugetパッケージの管理を開く
更新プログラムがあるので、更新を行う
Microsoft.EntityFrameworkCore.Sqliteをインストールする
以下ファイルを変更する
./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");
});
パッケージコンソールマネージャーを開きコマンドを実行
Update-Database -Context ApplicationDbContext
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.
作成できた。
一般的な値の交換は、一時退避の変数を用意し、値を退避してから
値の入れ替えを行う方法。
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;
var num1 = 5.0;
var num2 = 6.0;
num2 = num1 / num2;
num1 /= num2;
num2 *= num1;
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;
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
StyleCopで解析した結果大量のエラーが出ているものの、自動生成されたプログラム中で発生している場合は、
ファイル先頭に以下を追加し、解析ツールに自動生成されたことを伝える
// <auto-generated />
フロント側の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();