Trabajar con Excel en .NET independientemente de la versión instalada de Excel

Actualización de la página :
Fecha de creación de la página :

Entorno operativo

Visual Studio
  • Visual Studio 2022
.RED
  • .NET 6.0
Windows
  • Windows 7
  • Windows 11
Sobresalir
  • Microsoft 365
  • Office 2007

Prerrequisitos

Windows
  • Una de las versiones
Sobresalir
  • Una de las versiones

Problemas con bibliotecas que funcionan con Excel

Una manera de interactuar mediante programación con Excel es Microsoft.Office.Interop.Excel hacer referencia a . Esto es similar a trabajar directamente con la aplicación de Excel a través de COM, por lo que es ligeramente diferente de trabajar con datos en un archivo de Excel. Como premisa, Excel debe estar instalado en el entorno a ejecutar, pero muchas funciones de Excel están disponibles desde el lado del programa.

Microsoft.Office.Interop.Excel Si usas el programa, pensarás que el programa se escribirá de la siguiente manera.

// 実行プログラムの場所にある 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);
}

Es problemático tener que liberar recursos cada vez, pero dado que puede usar las clases preparadas, puede realizar el procesamiento de Excel de manera relativamente intuitiva.

El inconveniente, sin embargo, es que la versión de la biblioteca a la que hace referencia el programa debe coincidir o ser compatible con la versión de Excel instalada en el entorno en el que se está ejecutando. Por ejemplo, un programa creado haciendo referencia a la biblioteca interna de la versión 15.0 (2013) de Excel solo se puede ejecutar cuando Excel 2013 está instalado. Si desea que el programa se ejecute en muchos entornos, es posible que tenga que mantener la instalación de Excel ejecutándose en una versión coherente.

Si tal operación es posible, está bien, pero si la versión instalada de Excel es diferente, no se puede acomodar. Por lo tanto, es necesario hacer algo en el lado del programa.

Hacer referencia a bibliotecas dinámicamente con dynamic en lugar de hacer referencia directamente a ellas

El problema en este caso se produce porque se hace referencia a la versión de la biblioteca fija cuando se crea el programa. Esto significa que en lugar de determinar la versión en el momento de la creación del programa, es necesario determinar la versión en el momento de la ejecución del programa.

Hay varias formas de hacerlo, pero en esta ocasión lo resolveremos usando "" "" y "Type.GetTypeFromProgIDActivator.CreateInstance".dynamic

Normalmente, en C#, es necesario determinar la información de tipo en el momento de la creación del programa, pero si el tipo es dynamic , el tipo se puede determinar en el momento de la ejecución. object A diferencia dynamic de los tipos, si la instancia establecida en una variable es una clase, también puede llamar a los métodos y propiedades de esa clase. Sin embargo, dado que lo que entra en la variable se determina en el momento de la ejecución, se producirá un error en el momento de la ejecución si no hay ningún método especificado.

El código creado con estos es el siguiente.

// 実行プログラムの場所にある 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);
}

El tipo de declaración de la variable es , y Excel.Application la creación de es ligeramente diferente, pero el resto del código es dynamic casi reutilizado.

Dado que el código anterior no hace referencia a la biblioteca de Excel, no es posible utilizar la información de clase relacionada con Excel de destino. Por lo tanto, el tipo de la variable es all dynamic . Además, dado que no podemos hacer referencia a la clase directamente,Excel.Application en su lugar Excel.Application Type.GetTypeFromProgID obtenemos la información Activator.CreateInstance Type y la pasamos a para crear Excel.Application una instancia de . Por cierto, en un entorno en el que Excel no está instalado, no se puede entrar Type , Type.GetTypeFromProgID por lo que es posible aislar si está instalado o no.

dynamic La desventaja de usar es que la clase no se puede determinar al compilar el programa. IntelliSense, que es una función de finalización al escribir código, no muestra los nombres de método ni los nombres de propiedad. Puede ser menos engorroso escribir código con referencia a la biblioteca al principio y reemplazarlo por dynamic más adelante.

Cuando se ejecuta en un entorno de Microsoft 365, puede confirmar que se refleja de la siguiente manera.

A continuación se muestra el resultado de ejecutarlo en una versión mucho más antigua de Office 2007. Si no usas una función que solo funciona en una versión específica, puedes hacer que funcione bastante ampliamente.

Por cierto, si lo ejecuta en un entorno en el que Excel no está instalado, puede determinar correctamente que no se puede procesar.