Visual Studio 2022 の WPF(C# / .NET 6)で Excel ファイルを生成しようとすると、「Worksheet が見つからない(CS0246)」や「Interop の参照はあるのに実行時に読み込めない」といったトラブルに遭遇しがちです。原因を整理し、Interop で確実に動かすための要点と、Office 不要で .xlsx を出力できる現実的な選択肢までまとめます。
まず把握したい:起きている現象は「コンパイルの問題」か「実行環境の問題」か
Excel 連携のトラブルは、見た目が似ていても原因がまったく違うことがあります。最初に「どの段階で落ちているか」を切り分けると、無駄な遠回りを減らせます。
| 症状 | 発生タイミング | よくある原因 | 最初にやること |
|---|---|---|---|
| Worksheet が見つからない(CS0246) | コンパイル時 | 型名の書き方(別名 using)/ using 不足 / 参照不足 | Excel.Worksheet を明示し、参照に Interop が入っているか確認 |
| 参照は通るが、実行時に Interop を読み込めない | 実行時 | 参照 DLL のバージョン固定・不整合 / 配布形態の問題 | Embed Interop Types を有効化、参照経路を整理 |
| Class not registered / COM クラスファクトリ取得に失敗 | 実行時 | Excel が未インストール / Office の 32/64 bit 不一致 / COM 登録不整合 | Office の有無と 32/64 を確認、PlatformTarget を揃える |
| 処理後に Excel.exe が残り続ける | 実行後 | COM オブジェクト解放漏れ(Range/Worksheet など) | ReleaseComObject/FinalReleaseComObject を逆順に実行 |
| 配布先で Office が入っているか不明で運用できない | 運用時 | Interop は Excel 本体が前提(Office 非依存ではない) | ClosedXML 等の「Excel 非依存」ライブラリへ切り替え |
前提を押さえる:Interop は「Excel をインストールして操作する」ための仕組み
Microsoft.Office.Interop.Excel は、Excel 本体(COM サーバー)を外部から操作するための橋渡しです。つまり、プロジェクトに Interop を追加しても、それだけで Excel 機能がアプリに内蔵されるわけではありません。
- Interop を使う=配布先で Excel が動作できる状態であることが前提(インストール・ライセンス・COM 登録)
- 「参照があるのに実行できない」は、参照(ビルド時)と Excel 本体(実行時)が噛み合っていないケースが多い
- 「Office 365(Microsoft 365 Apps)」でも、Interop のアセンブリや登録状況が環境によって揺れることがある
この構造を理解すると、次の判断がブレません。
| やりたいこと | Interop が向く | ClosedXML 等が向く |
|---|---|---|
| Excel を起動して操作(表示、印刷、マクロ実行、既存ブックの高度な操作) | ◎(Excel を直接操作できる) | △(操作そのものは不可) |
| データを .xlsx に出力できればOK(他システム取り込み、帳票添付、配布) | △(Excel 必須、配布で詰まりやすい) | ◎(Office 不要、配布が安定) |
| 配布先が不特定/Office の有無が保証できない | × | ◎ |
CS0246「Worksheet が見つからない」を直す:型名を明示してコンパイルを通す
WPF(.NET 6)で Interop を書くとき、using Excel = Microsoft.Office.Interop.Excel; のように別名を使うのは一般的です。このとき、Worksheet を単独で書くと型解決できず CS0246 になりやすいです。
ポイントはシンプルで、Worksheet を曖昧に書かず Excel.Worksheet を明示し、ActiveSheet は object なのでキャストすることです。
| 書き方 | 結果 | 理由 |
|---|---|---|
Worksheet ws = workbook.ActiveSheet; | ×(CS0246 になりやすい) | Worksheet が現在の using では解決できない |
Excel.Worksheet ws = (Excel.Worksheet)workbook.ActiveSheet; | ◎ | 別名 Excel で明示し、object をキャスト |
var ws = (Excel.Worksheet)workbook.ActiveSheet; | ◎ | 型推論+明示キャストで安全 |
最小構成のサンプル(コンパイル時の迷いを消す書き方)は次の形です。
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
public static void CreateXlsxWithInterop(string path)
{
Excel.Application? app = null;
Excel.Workbooks? workbooks = null;
Excel.Workbook? workbook = null;
Excel.Worksheet? worksheet = null;
try
{
app = new Excel.Application
{
Visible = false,
DisplayAlerts = false
};
workbooks = app.Workbooks;
workbook = workbooks.Add();
worksheet = (Excel.Worksheet)workbook.ActiveSheet;
worksheet.Name = "Sheet1";
worksheet.Cells[1, 1] = "Hello";
worksheet.Cells[1, 2] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
workbook.SaveAs(path);
}
finally
{
// COM は「子 → 親」の逆順で解放するのが基本
if (worksheet != null) Marshal.FinalReleaseComObject(worksheet);
if (workbook != null)
{
workbook.Close(false);
Marshal.FinalReleaseComObject(workbook);
}
if (workbooks != null) Marshal.FinalReleaseComObject(workbooks);
if (app != null)
{
app.Quit();
Marshal.FinalReleaseComObject(app);
}
// Excel.exe 残り対策(Interop では定番)
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
ここまで書いても CS0246 が消えない場合は、そもそもプロジェクトが Interop を参照できていない可能性が高いです。次の章で「参照の入れ方」を整理します。
参照の入れ方で差が出る:COM 参照と NuGet 参照は役割が違う
Interop の導入方法は大きく2つあります。どちらを選ぶかで「動く/動かない」「配布で詰む/詰まない」が変わるので、意図して使い分けるのが重要です。
| 導入方法 | 特徴 | メリット | 落とし穴 |
|---|---|---|---|
| COM 参照(Excel Object Library) | PC に入っている Excel の COM 定義を参照 | 環境に近い参照になる、IDE 補完が安定 | 配布先の Excel とズレると実行時に失敗する |
| NuGet(Microsoft.Office.Interop.Excel 等) | Interop 用アセンブリをパッケージとして参照 | ビルドは通しやすい | Excel 本体は含まれないため、配布先に Office がないと必ず失敗 |
重要なのは、NuGet を入れたからといって Office 不要にはならないという点です。Interop はあくまで Excel を操作するための窓口であり、Excel そのものを置き換えるものではありません。
また .NET 6 の WPF プロジェクトでは、プロジェクトのターゲットを Windows 向けにしておくのが基本です。WPF は通常 net6.0-windows になっていますが、万一ずれている場合は次のような形になっているか確認してください。
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
最頻出の実行時トラブル:Office の 32bit/64bit とビルド設定が合っていない
Interop の実行時エラーで最も多いのが、Office のビット数とアプリのビット数が一致していないケースです。特に「Office 365 を入れているはず」「でも参照が 15.0 に見える」など、バージョンが混乱している環境ほど、ビット数の不一致が見落とされます。
- Office(Excel)が 32bit なのに、アプリが x64 ビルド → COM の生成で失敗しやすい
- Office(Excel)が 64bit なのに、アプリが x86 ビルド → 同様に失敗しやすい
Excel のビット数は、Excel の「アカウント」画面の「Excel のバージョン情報」などで確認できることが多いです。企業環境では 32bit が採用されているケースもまだあり、「Any CPU にしておけば大丈夫」ではないのが COM 連携の難しさです。
| 配布先の Office | 推奨するビルド(PlatformTarget) | 補足 |
|---|---|---|
| Excel 32bit | x86 | 互換性優先。WPF アプリ側は 32bit で動かす |
| Excel 64bit | x64 | メモリ面では有利だが、配布先が限定される |
| 配布先が不特定(32/64 不明) | Interop を採用しない | Office 依存のままでは運用コストが跳ね上がる |
「参照はあるのに動かない」を減らす:Embed Interop Types を有効にする
Interop の参照には、Visual Studio の参照プロパティにある 「相互運用型の埋め込み(Embed Interop Types)」が効きます。これを有効にすると、アプリ側に必要な型情報が埋め込まれ、参照アセンブリのバージョン差で詰まりにくくなることがあります。
| 設定 | 期待できる効果 | 注意点 |
|---|---|---|
| Embed Interop Types = True | 参照 DLL のバージョン差による影響を受けにくくなる | Excel 本体が不要になるわけではない(COM サーバーは必要) |
| Embed Interop Types = False | 参照に強く依存(環境差が出やすい) | 配布先が限定されるほど影響が大きい |
「Office 365 を入れているのに GAC 上の Interop が 15.0 に見える」といった状況では、過去の Office が残っていたり、参照が古い側に寄っていたり、複数の参照経路が混ざっていることがあります。Embed Interop Types は万能ではありませんが、“参照はあるのに実行時に Version=xx.x が違うと言われる”系の事故を減らせることがあります。
よく出る実行時エラーと対処の考え方
実行時エラーは文言が似ていても意味が違います。代表例と「次に確認する場所」をセットで覚えると切り分けが速いです。
| エラー例(要約) | 疑うポイント | 対処の方向性 |
|---|---|---|
| Could not load file or assembly ‘Microsoft.Office.Interop.Excel, Version=…’ | 参照 DLL が固定されている/埋め込みされていない | Embed Interop Types を True、参照の重複・古い参照を整理 |
| Retrieving the COM class factory… / Class not registered | Excel 未インストール/COM 登録不整合/ビット数不一致 | Office の有無・32/64 を確認、PlatformTarget を揃える |
| アプリでは動くが配布先でだけ落ちる | 配布先の Office 構成が違う | Interop 前提をやめて ClosedXML 等へ切り替える検討 |
| Excel.exe が残ってファイルがロックされる | COM 解放漏れ(Range/Cells/Worksheet 等) | COM を逆順に解放、Range を極力使い捨てにする |
Interop を使うなら避けられない:COM 解放と Excel.exe 残りの対策
Interop が「動いた」としても、次に起きがちなのが Excel.exe がバックグラウンドに残る問題です。原因は多くの場合、COM オブジェクト(Worksheet/Range など)への参照が残っていることです。
実務で事故を減らすための鉄板ルールは次のとおりです。
- COM は「子 → 親」の逆順で解放(Range → Worksheet → Workbook → Application)
- Cells/Range を変数に保持しない(保持するなら必ず解放する)
- 大量書き込みは セルを1つずつ叩かず、2次元配列を Range に一括代入(高速化+参照管理が楽)
一括書き込みの例です(大量データでも速度が出やすく、COM 参照が散らかりにくい形)。
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
public static void WriteBulk(Excel.Worksheet ws, string[,] data)
{
Excel.Range? start = null;
Excel.Range? end = null;
Excel.Range? range = null;
try
{
int rows = data.GetLength(0);
int cols = data.GetLength(1);
// 1-based
start = (Excel.Range)ws.Cells[1, 1];
end = (Excel.Range)ws.Cells[rows, cols];
range = ws.Range[start, end];
// Value2 は Date/通貨などの自動変換が少なく扱いやすいことが多い
var values = new object[rows, cols];
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
values[r, c] = data[r, c];
range.Value2 = values;
}
finally
{
if (range != null) Marshal.FinalReleaseComObject(range);
if (end != null) Marshal.FinalReleaseComObject(end);
if (start != null) Marshal.FinalReleaseComObject(start);
}
}
WPF では「重い処理を Task.Run で逃がす」ことも多いですが、Interop は COM の性質上、スレッド(Apartment)で事故ることがあります。WPF の UI スレッドは STA ですが、Task.Run は通常 MTA になりやすい点に注意が必要です。
- Interop を UI スレッドで実行する(短時間なら最も簡単)
- 重い場合は STA スレッドを自前で立てる(Interop をそのスレッドに閉じ込める)
using System.Threading;
public static void RunInteropOnSta(Action action)
{
Exception? ex = null;
var t = new Thread(() =>
{
try { action(); }
catch (Exception e) { ex = e; }
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
if (ex != null) throw ex;
}
純粋に .xlsx を生成するだけなら、この苦労を避ける方法があります。次の章がその「現実解」です。
配布先に Office が入っているか不明なら、Interop を避けるのが正攻法
結論から言うと、Office の有無・バージョン・32/64 をコントロールできない配布形態(一般配布、社外 PC、現場端末など)では、Interop は運用で破綻しやすいです。
その場合は、Office 非依存で .xlsx を生成できるライブラリに切り替える方が、結果として早く安定します。代表的なのが ClosedXML です。
ClosedXML を選ぶメリット
- Excel(Office)のインストール不要で .xlsx を作成できる
- WPF(.NET 6)でも扱いやすく、セル書き込みや書式設定が直感的
- 配布先の環境差(Office のバージョン、ビット数)に引っ張られにくい
ClosedXML の基本例:シート作成・書式・オートフィルタ・保存
using ClosedXML.Excel;
public static void CreateXlsxWithClosedXml(string path)
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet("Report");
// ヘッダー
ws.Cell(1, 1).Value = "ID";
ws.Cell(1, 2).Value = "氏名";
ws.Cell(1, 3).Value = "金額";
ws.Cell(1, 4).Value = "更新日";
var header = ws.Range(1, 1, 1, 4);
header.Style.Font.Bold = true;
header.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
header.Style.Fill.BackgroundColor = XLColor.LightGray;
// データ
var rows = new[]
{
new { Id = 1, Name = "山田 太郎", Amount = 12000, Updated = DateTime.Today },
new { Id = 2, Name = "佐藤 花子", Amount = 8500, Updated = DateTime.Today.AddDays(-1) },
new { Id = 3, Name = "鈴木 一郎", Amount = 4300, Updated = DateTime.Today.AddDays(-7) },
};
int r = 2;
foreach (var row in rows)
{
ws.Cell(r, 1).Value = row.Id;
ws.Cell(r, 2).Value = row.Name;
ws.Cell(r, 3).Value = row.Amount;
ws.Cell(r, 4).Value = row.Updated;
// 表示形式(例)
ws.Cell(r, 3).Style.NumberFormat.Format = "#,##0";
ws.Cell(r, 4).Style.DateFormat.Format = "yyyy-mm-dd";
r++;
}
// 使い勝手
ws.Range(1, 1, r - 1, 4).SetAutoFilter();
ws.Columns().AdjustToContents();
wb.SaveAs(path);
}
ClosedXML は「Excel を操作する」のではなく「Excel が読める .xlsx を生成する」方式なので、Interop 特有の参照不整合・COM 登録・Excel.exe 残りといった悩みから解放されます。
ライブラリ選定の目安:ClosedXML / EPPlus / Open XML SDK
.xlsx を作る選択肢は ClosedXML 以外にもあります。重要なのは「何を優先するか」です。
| 選択肢 | Office 必須 | 扱いやすさ | 得意分野 | 注意点 |
|---|---|---|---|---|
| ClosedXML | 不要 | 高い | 帳票出力、一覧表、書式、フィルタなどを直感的に実装 | 複雑な Excel 操作(マクロ実行等)はできない |
| EPPlus | 不要 | 高い | Excel 出力全般 | 利用形態によりライセンス条件の確認が必要な場合がある |
| Open XML SDK | 不要 | 低め | 低レベルで厳密に制御、生成物の細部まで最適化 | 実装が冗長になりやすく、保守コストが上がる |
| Interop(Microsoft.Office.Interop.Excel) | 必要 | 中 | Excel を起動して実操作(マクロ/印刷/UI 連携) | 環境依存が強い(Office 有無、32/64、COM) |
Interop から ClosedXML へ置き換えるときの考え方
Interop をやめると決めたとき、実装の置き換えで迷いやすいポイントを整理します。
セルを直接叩く発想から「データ構造を流し込む」へ
- Interop:
worksheet.Cells[row, col] = value;を繰り返しがち - ClosedXML:
ws.Cell(row, col).Value = value;でも書けるが、範囲指定やスタイル適用が楽
たとえば「ヘッダー行+データ行」という典型的な帳票なら、次の順で作ると実装が安定します。
- シート名・ヘッダー固定(見た目を先に決める)
- データを行単位で流し込む
- 数値/日付の表示形式を列単位で整える
- 列幅調整・フィルタ・罫線などの仕上げ
「数式を計算したい」は要件を分解する
Interop は Excel を起動するため、数式の再計算や表示上の結果をすぐに得られることがあります。一方、ClosedXML などは .xlsx に数式を「書く」ことはできても、計算結果をその場で保証するのは別問題です(開いた Excel 側で再計算される前提になりやすい)。
この違いが要件に影響する場合は、次を検討すると整理が進みます。
- 計算結果が必要なら、アプリ側で計算して「値」として書き込む(最も堅牢)
- Excel で再計算されればよいなら、数式をセルに入れておく
- 印刷や PDF 化まで自動化したいなら、Interop を採用する理由が残る
どうしても Interop を続ける場合の実務的チェックリスト
「Excel 操作が必須で、Interop を捨てられない」状況もあります。その場合は、最初から運用前提で条件を固めるのが大切です。
| チェック項目 | 確認場所の例 | 揃っていないと起きやすいこと |
|---|---|---|
| 配布先に Excel がインストール済み | アプリ配布要件/インストール手順 | 起動時に COM 生成で失敗 |
| Office の 32/64 bit が統一されている | Excel のバージョン情報/管理台帳 | x86/x64 不一致で実行時エラー |
| アプリの PlatformTarget が配布先と一致 | Visual Studio の構成マネージャー | 特定環境だけ動かない |
| Embed Interop Types を True | 参照のプロパティ | 参照バージョン固定で読み込み失敗 |
| COM 解放が徹底されている | finally で逆順解放 | Excel.exe が残る/ファイルロック |
| Interop を STA スレッドで実行 | Task.Run を避ける/STA スレッド作成 | 不定期に落ちる、COM 例外が出る |
このチェックリストを満たすのが難しい(=配布先が自由・端末が多い・Office 管理ができない)なら、Interop にこだわるほど運用コストが上がります。「Excel を操作したい」のか「Excel 形式で出したいだけ」なのかを再確認し、後者なら ClosedXML 等へ寄せるのが結果的に堅牢です。
まとめ:最短で安定させるための結論
- CS0246(Worksheet が見つからない)は、Excel.Worksheet を明示し、ActiveSheet を キャストすれば解消しやすい
- 参照不整合・実行時エラーは、Office の有無と32/64 bit、そして Embed Interop Types の有効化が鍵
- 配布先の Office が不確実なら、Interop は構造的に不安定。Office 不要で .xlsx を作れる ClosedXML 等が現実的
「まず動かす」なら Interop の条件を揃えるのが必要ですが、長期運用では「条件を揃え続ける」コストが効いてきます。WPF(.NET 6)のデスクトップアプリで Excel 出力が目的なら、最初から Office 非依存の設計に寄せておくと、後からの改修・配布が格段に楽になります。

コメント