在 .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的环境中运行它,则可以正确确定无法处理它。