설치된 Excel 버전과 독립적으로 .NET에서 Excel 작업

페이지 업데이트 :
페이지 생성 날짜 :

운영 환경

비주얼 스튜디오
  • 비주얼 스튜디오 2022
.그물
  • .NET 6.0
윈도우
  • 윈도우 7
  • 윈도우 11
뛰어나다
  • 마이크로소프트 365
  • 오피스 2007

필수 구성 요소

윈도우
  • 버전 중 하나
뛰어나다
  • 버전 중 하나

Excel에서 작동하는 라이브러리의 문제

프로그래밍 방식으로 Excel과 상호 작용하는 한 가지 방법은 를 참조하는 것입니다 Microsoft.Office.Interop.Excel . 이는 COM을 통해 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의 내부 버전 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 생성 방법은 약간 다르지만 나머지 코드는 거의 재사용됩니다.

위 코드는 엑셀 라이브러리를 참조하지 않기 때문에 대상 엑셀 관련 클래스 정보를 사용할 수 없습니다. 따라서 변수의 유형은 all dynamic 입니다. 또한 클래스를 직접Excel.Application 참조할 수 없기 때문에 대신 Excel.Application Type.GetTypeFromProgID Type 정보를 Activator.CreateInstance 가져와서 에 전달하여 의 인스턴스를 만듭니다Excel.Application. 덧붙여서, 엑셀이 설치되어 Type.GetTypeFromProgID 있지 않은 환경에서는 들어갈 Type 수 없기 때문에, 설치 여부를 격리 할 수 있습니다.

dynamic 사용의 단점은 프로그램을 빌드할 때 클래스를 결정할 수 없다는 것입니다. 코드를 입력할 때 완성 함수인 Intellisense는 메서드 이름과 속성 이름을 표시하지 않습니다. 처음에 라이브러리를 참조하여 코드를 작성하고 나중에 교체 dynamic 하는 것이 덜 번거로울 수 있습니다.

Microsoft 365 환경에서 실행하는 경우 다음과 같이 반영되는 것을 확인할 수 있습니다.

다음은 훨씬 이전 버전의 Office 2007에서 실행한 결과입니다. 특정 버전에서만 작동하는 함수를 사용하지 않으면 꽤 광범위하게 작동하도록 할 수 있습니다.

덧붙여서 Excel이 설치되지 않은 환경에서 실행하면 처리 할 수 없다는 것을 올바르게 판단 할 수 있습니다.