Visual Studio 2022/.NET 6 WPFでExcel作成:Worksheet(CS0246)とInterop参照不整合の解決・Office不要でxlsx出力する方法

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 32bitx86互換性優先。WPF アプリ側は 32bit で動かす
Excel 64bitx64メモリ面では有利だが、配布先が限定される
配布先が不特定(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 registeredExcel 未インストール/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 非依存の設計に寄せておくと、後からの改修・配布が格段に楽になります。

この記事を書いた人

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

コメント

コメントする

目次