在 .NET 中使用 Excel 獨立於已安裝的 Excel 版本

更新頁 :
頁面創建日期 :

操作環境

Visual Studio的
  • Visual Studio 2022 中
。網
  • .NET 6.0
窗戶
  • 視窗 7
  • 窗戶11
勝過
  • Microsoft 365
  • 辦公室 2007

先決條件

窗戶
  • 其中一個版本
勝過
  • 其中一個版本

使用 Excel 的庫的問題

以程式設計方式與 Excel 互動的一種方法是 Microsoft.Office.Interop.Excel 引用 . 這類似於通過 COM 直接使用 Excel 應用程式,因此與處理 Excel 檔案中的數據略有不同。 作為前提,必須在要執行的環境中安裝 Excel,但 Excel 的許多功能都可以從程式端獲得。

Microsoft.Office.Interop.Excel 如果你使用這個程式,你會認為這個程式會按以下方式編寫。

// 実行プログラムの場所にある Excel ファイル
var excelFilePath = $@"{Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName)}\Sample.xlsx";

// Excel のオブジェクトは参照したら必ず解放する必要があります。
// 解放しないと Excel のプロセスが残り続けます。
Microsoft.Office.Interop.Excel.Application? excel = null;
Microsoft.Office.Interop.Excel.Workbooks? books = null;
Microsoft.Office.Interop.Excel.Workbook? book = null;
Microsoft.Office.Interop.Excel.Sheets? sheets = null;
Microsoft.Office.Interop.Excel.Worksheet? sheet = null;
Microsoft.Office.Interop.Excel.Range? cells = null;
Microsoft.Office.Interop.Excel.Range? range1 = null;
Microsoft.Office.Interop.Excel.Range? range2 = null;
try
{
  // Excel アプリケーションを生成(起動)します
  excel = new Microsoft.Office.Interop.Excel.Application
  {
    DisplayAlerts = false
  };

  // ワークブック一覧を参照します。参照する場合は必ず変数に保持します
  books = excel.Workbooks;

  // Excel ファイルを開きます
  book = books.Open(excelFilePath);

  // シート一覧を参照するので変数に保持します
  sheets = book.Worksheets;

  // シートを参照します。最初のシートは 1 になります
  sheet = (Microsoft.Office.Interop.Excel.Worksheet)sheets[1];

  // セル一覧を参照します
  cells = sheet.Cells;

  // 左上のセルを参照します。一番左上は [1, 1] になります
  range1 = (Microsoft.Office.Interop.Excel.Range)cells[1, 1];

  // セルの値を取得します。値の取得なので後で解放する必要はありません
  var value = (int)range1.Value;

  // 下のセルを参照します
  range2 = (Microsoft.Office.Interop.Excel.Range)cells[2, 1];

  // 日付分足してセットします
  range2.Value = DateTime.Now.Day + value;

  // 保存して閉じます
  book.Close(SaveChanges: true);

  Console.WriteLine("処理が完了しました。");
}
catch (Exception ex)
{
  // 閉じていなければ保存せずに閉じます
  // 開きっぱなしだと Excel 起動時に保存されていないデータとして表示される場合があります
  if (book != null) book.Close(SaveChanges: false);

  Console.WriteLine("処理が失敗しました。");
  Console.WriteLine(ex);
}
finally
{
  // 終了していなければ終了します
  if (excel != null) excel.Quit();

  // 例外が発生した場合でも必ずリソースを解放するようにします
  if (range1 != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(range1);
  if (range2 != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(range2);
  if (cells != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(cells);
  if (sheet != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sheet);
  if (sheets != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sheets);
  if (book != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(book);
  if (books != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(books);
  if (excel != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(excel);
}

每次都要釋放資源很麻煩,但是由於可以使用準備好的類,因此可以相對直觀地執行Excel處理。

但是,缺點是程式引用的庫版本必須與運行該庫的環境中安裝的 Excel 版本匹配或相容。 例如,通過引用 Excel 的內部版本 15.0 (2013) 庫創建的程式只能在安裝 Excel 2013 時運行。 如果希望程式在許多環境中運行,則可能需要使 Excel 安裝保持在一致的版本上運行。

如果這樣的操作是可能的,那很好,但如果安裝的Excel版本不同,則無法容納。 因此,有必要在程式方面做一些事情。

使用 dynamic 動態引用庫,而不是直接引用它們

在這種情況下,出現此問題的原因是,在創建程式時,引用了庫版本。 這意味著,與其在程式創建時確定版本,不如在程式執行時確定版本。

有幾種方法可以做到這一點,但這次我們將使用“”“和”Type.GetTypeFromProgIDActivator.CreateInstance“來解決它。dynamic

通常,在 C# 中,需要在創建程式時確定類型資訊,但如果類型為 dynamic ,則可以在執行時確定類型。 object 與類型不同 dynamic ,如果設置為變數的實例是一個類,則還可以調用該類的方法和屬性。 但是,由於進入變數的內容是在執行時確定的,因此如果沒有指定的方法,則在執行時將發生錯誤。

使用這些創建的代碼如下所示。

// 実行プログラムの場所にある Excel ファイル
var excelFilePath = $@"{Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName)}\Sample.xlsx";

// Excel のオブジェクトは参照したら必ず解放する必要があります。
// 解放しないと Excel のプロセスが残り続けます。
dynamic? excel = null;
dynamic? books = null;
dynamic? book = null;
dynamic? sheets = null;
dynamic? sheet = null;
dynamic? cells = null;
dynamic? range1 = null;
dynamic? range2 = null;
try
{
  // Excel.Application の Type を取得
  var type = Type.GetTypeFromProgID("Excel.Application");
  if (type == null)
  {
    Console.WriteLine("Excel がインストールされていません。");
    return;
  }

  // Excel アプリケーションを生成(起動)します
  excel = Activator.CreateInstance(type);
  if (excel == null)
  {
    Console.WriteLine("Excel.Application のインスタンスの生成に失敗しました。");
    return;
  }
  excel.DisplayAlerts = false;

  // ワークブック一覧を参照します。参照する場合は必ず変数に保持します
  books = excel.Workbooks;

  // Excel ファイルを開きます
  book = books.Open(excelFilePath);

  // シート一覧を参照するので変数に保持します
  sheets = book.Worksheets;

  // シートを参照します。最初のシートは 1 になります
  sheet = (Microsoft.Office.Interop.Excel.Worksheet)sheets[1];

  // セル一覧を参照します
  cells = sheet.Cells;

  // 左上のセルを参照します。一番左上は [1, 1] になります
  range1 = (Microsoft.Office.Interop.Excel.Range)cells[1, 1];

  // セルの値を取得します。値の取得なので後で解放する必要はありません
  var value = (int)range1.Value;

  // 下のセルを参照します
  range2 = (Microsoft.Office.Interop.Excel.Range)cells[2, 1];

  // 日付分足してセットします
  range2.Value = DateTime.Now.Day + value;

  // 保存して閉じます
  book.Close(SaveChanges: true);

  Console.WriteLine("処理が完了しました。");
}
catch (Exception ex)
{
  // 閉じていなければ保存せずに閉じます
  // 開きっぱなしだと Excel 起動時に保存されていないデータとして表示される場合があります
  if (book != null) book.Close(SaveChanges: false);

  Console.WriteLine("処理が失敗しました。");
  Console.WriteLine(ex);
}
finally
{
  // 終了していなければ終了します
  if (excel != null) excel.Quit();

  // 例外が発生した場合でも必ずリソースを解放するようにします
  if (range1 != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(range1);
  if (range2 != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(range2);
  if (cells != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(cells);
  if (sheet != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sheet);
  if (sheets != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sheets);
  if (book != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(book);
  if (books != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(books);
  if (excel != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(excel);
}

變數聲明的類型是 dynamic ,和 Excel.Application 的創建略有不同,但代碼的其餘部分幾乎是重用的。

由於上述代碼未引用 Excel 庫,因此無法使用與 Excel 相關的目標類資訊。 因此,變數的類型為all dynamic 。 此外,由於我們不能直接引用該類,Excel.Application因此我們改為Excel.ApplicationType.GetTypeFromProgID獲取 Type 資訊Activator.CreateInstance並將其傳遞給 以創建 Excel.Application 的實例。 順便說一句,在未安裝Excel的環境中,您無法將其放入TypeType.GetTypeFromProgID因此可以隔離是否已安裝。

dynamic 使用的缺點是在構建程式時無法確定類。 Intellisense 是輸入代碼時的完成函數,不顯示方法名稱和屬性名稱。 在開始時引用庫編寫代碼,然後用 dynamic 以後替換它,可能不那麼麻煩。

在 Microsoft 365 環境中運行時,可以確認它反映如下。

以下是在舊版本的 Office 2007 上運行它的結果。 如果您不使用僅在特定版本中起作用的函數,則可以使其廣泛使用。

順便說一句,如果在未安裝Excel的環境中運行它,則可以正確確定無法處理它。