Работа с Excel в .NET независимо от установленной версии Excel

Страница обновлена :
Дата создания страницы :

Условия эксплуатации

Визуальная студия
  • Visual Studio 2022
.СЕТЬ
  • .NET 6.0
Виндоус
  • Windows 7
  • Windows 11
Превосходить
  • Microsoft 365
  • Office 2007

Необходимые условия

Виндоус
  • Одна из версий
Превосходить
  • Одна из версий

Проблемы с библиотеками, работающими с Excel

Одним из способов программного взаимодействия с Excel является Microsoft.Office.Interop.Excel ссылка на . Это похоже на работу непосредственно с приложением Excel через COM, поэтому немного отличается от работы с данными в файле 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.Application Type.GetTypeFromProgID мы получаем информацию Activator.CreateInstance о типе и передаем ее для создания Excel.Application экземпляра . Кстати, в среде, где Excel не установлен, вы не можете получить его в Type , поэтому можно изолировать, Type.GetTypeFromProgID установлен он или нет.

dynamic Недостатком использования является то, что класс не может быть определен при сборке программы. Intellisense, которая является функцией завершения при вводе кода, не отображает имена методов и свойств. Возможно, будет менее громоздким написать код со ссылкой на библиотеку в начале и заменить ее dynamic на более позднюю.

При работе в среде Microsoft 365 вы можете убедиться, что это отражается следующим образом.

Ниже приведен результат его запуска в гораздо более старой версии Office 2007. Если вы не используете функцию, которая работает только в определенной версии, вы можете заставить ее работать довольно широко.

Кстати, если запустить его в среде, где не установлен Excel, можно правильно определить, что он не поддается обработке.