LoginSignup
42
42

More than 3 years have passed since last update.

.NET FrameworkからのExcel操作

Last updated at Posted at 2019-08-29

本記事は2012年末頃に、社内向けに作成した説明資料をQiita向けに編集したものです。
最新の仕様とは異なる場合があるので注意してください。

はじめに

 .NET Frameworkには「COM相互運用1」と呼ばれる機能があり、COMコンポーネント2を手軽に呼び出すことができます。一方、ExcelをはじめとするOffice製品は、その機能をマクロ(VBA)などからも活用できるようにCOMコンポーネントとして実装されています。このため、COM相互運用を使えば.NETアプリケーションから容易にExcelやWordのファイルを開き、それをさまざまに操作することが可能です。

 本記事ではC#で作成されたサンプルアプリケーションのソースコードを基に、COM相互運用機能を用いてExcelファイルを操作する方法を説明します。
 使用しているVisual Studioのバージョンは” Microsoft Visual Studio Express 2012 for Windows Desktop”です。ただし、Visual Studioの旧バージョンとの差は見た目ぐらいですので、特に問題はないと思います。
 .Net Frameworkのバージョン、およびC#のバージョンに依存している記述などについては、その都度言及して、補足を行なっていきます。

 以降の章では、サンプルアプリケーションのうち、Excelファイルを操作している部分に着目して説明を行なっていきます。ファイルの存在チェックや出力のフォーマットに関しては、本書の目的とそれるので割愛します。サンプルプロジェクトのソースファイルを見てください。

事前バインディング

 本章では、C#からExcelを操作する上で一般的に使用される「事前バインディング」方式を用いて、Excelファイルを操作する手順を説明します。
 「事前バインディング」方式とは、文字通り事前に必要なDLLの参照設定を行なっておき、そのDLL使用する方式です。参照を追加するため、コードの補完機能などが使用出来るので、Excelを操作するコーディングを行う上でも一番やりやすい方法となります。
 以下に、「事前バインディング」方式によるExcelファイルを操作する手順を示していきます。

プロジェクトへ参照を追加

 まずは、事前バインディングの名の通り、Excelファイルを操作するためのDLL参照を設定します。
 [プロジェクト]メニュー – [参照の追加]をクリックし、参照マネージャーを起動してください。ソリューションエクスプローラーから見える参照設定の右クリックメニューからでも同様の操作が行えます。図3.png
 参照マネージャーの[COM]タブをクリックし、表示されるライブラリーの一覧から「Microsoft Excel XX Object Library」を選択します。インストールされているExcelのバージョンによって、XXの部分に表示されるライブラリーのバージョン番号が異なります。
図4.png

Officeのバージョン ライブラリのバージョン3
Microsoft Office 2010 14.0
Microsoft Office 2007 12.0
Microsoft Office 2003 11.0
Microsoft Excel 2002 10.0
Microsoft Excel 2000 9.0
Microsoft Excel 97 8.0

 参照の追加が行われたら、ソリューションエクスプローラーの参照設定に「Micorosoft.Office.Core」と「Microsoft.Office.Interop.Excel」が追加されるのが見て取れます。
図5.png
 最後に、バインドした参照のうち、使用するクラスが含まれている名前空間を追加します。追加する名前空間は「Microsoft.Office.Interop.Excel」になります。
図6.png
 名前空間の追加が正しく行われると、コーディング時にコードサジェストが行われます。
図7.png

ソースコード解説

 ここからは、サンプルコードを元に、C#からどのようにExcelを操作するかを説明します。
 操作は主に5ステップ存在します。
   1. ファイルオープンなどの前処理
   2. シート、セルの領域指定
   3. 選択したセル領域から値の取得
   4. 取得した値の詰め替え
   5. ファイルクローズなどの後処理
 以下、ステップごとに行う処理について説明します。
 サンプルコードの全文はAppendix. 「事前バインディング」を参照してください。

前処理

// ファイルオープン
Application xlApp = new Application();
Workbooks xlBooks = xlApp.Workbooks;
Workbook xlBook = xlBooks.Open(
    ExcelFileName, Type.Missing, Type.Missing,
    Type.Missing, Type.Missing, Type.Missing,
    Type.Missing, Type.Missing, Type.Missing,
    Type.Missing, Type.Missing, Type.Missing,
    Type.Missing, Type.Missing, Type.Missing
);
解説
2 Applicationインスタンスの生成。
xlAppにExcelを操作するMicrosoft.Office.Interop.Excel.Application COMオブジェクトクラスのインスタンスが格納されます。
3 xlAppが持つWorkbooksプロパティ(MDIの親玉)を取得。
4〜10 xlBooksに、ExcelFileNameで指定したファイルをオープンし、そのインスタンスをxlBookに格納します。
第2引数以降で指定している”Type.Missing”で、パラメーターの既定値(所謂デフォルトパラメータ)を使用するようメソッドに通知しています。
Workbooks.Openメソッドの引数の詳細については、こちらを参照してください。

 なお、C# 4.0 (Visual Studio 2010)からはデフォルトパラメータを省略できるようになったので、4行目のExcelファイルのオープン処理は、以下のように記述することが可能です。

Workbook xlBook = xlBooks.Open( ExcelFileName );

 ExcelのCOMオブジェクト構成は、

Application
└ Workbooks[]
 ├ Workbook
 │ ├ Sheets[]
 │ │ ├ Sheet1
 │ │ │ ├ Cells[]
 │ │ ├ Sheet2
 │ │ ├ Sheet3
 │ │ ├ :
 ├ Workbook
 ├ Workbook
 ├  :

となっており、操作対象となるセルオブジェクトに到達するまで、上位のオブジェクトから順次取得して行く必要があります。上述の前処理で行ったのは、この内上位3階層分のオブジェクトの取得処理に当たります。

シート、セルの領域指定

// シートを選択
Worksheet sheet = xlBook.Sheets["メンバー一覧"];

// セルの領域を選択
Range TableRange = sheet.Range["A1", "B15"];
解説
OpenしたExcelファイルから、”メンバー一覧”シートを指定して取得します。
Sheets[]コレクションは、要素番号とシート名どちらでもアクセスできます。今回操作対象シートが明確なのでシート名でアクセスしています。
”メンバー一覧”シートの領域$A$1:$B$15を示すオブジェクトを取得します。
領域取得には他に、
  Range TableRange = sheet.Range["A1:B15"];
というふうに、1次元目だけを使用した記述方法も可能です。

 領域オブジェクトを取得する操作は、上記の他にget_Range()メソッドが存在します。
 しかし、get_Range()には後方互換性の不備があり、.NET Frameworkのある特定の更新に限り正常に動作しないというバグが存在します。
 領域オブジェクトを取得する際には、Range[]アクセサーからの取得を推奨します。

選択したセル領域から値の取得

// 選択した領域の値をメモリー上に格納
object[,] values = TableRange.Value;
解説
選択した領域の値を配列に格納しています。
領域オブジェクトのValueまたはValue2プロパティは、まとめて取得されメモリー上に配置されます。
今回、セルには文字が書き込まれているので、セルの書式設定が日付/通貨の時のみ意味があるValue2ではなく、Valueで取得しています。

 領域オブジェクトから、値を二次元配列に格納するという手法は、Excel VBAでもよく使われる高速化テクニックの一つです。領域内のセルをループで取得するのに比べ、格段に早くすることができます。
 なお、たとえ一列しか選択していなくても二次元目の深さが1である二次元配列になります。
 また、配列の添字は1が最初です。要素0へアクセスすると例外が発生するので、注意してください。

取得した値の詰め替え

// 配列アクセスができるので、それぞれをDictionaryに追加
for (int i = 1; i <= values.GetLength(0); i++)
{
    dic.Add((string)values[i, 1], (string)values[i, 2]);
}
解説
2〜5 値を格納した二次元配列から値を取り出し、それぞれkeyとvalueとしてDictionaryに格納しています。配列の添字は1が最初。

後処理

// 使用したCOMオブジェクトを解放
System.Runtime.InteropServices.Marshal.ReleaseComObject(TableRange);
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet);

// Excelのクローズ
xlBook.Close();
xlApp.Quit();

// 使用したCOMオブジェクトを解放その2
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBook);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBooks);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlApp);
解説
2〜3 使用したRangeやSheetのCOMオブジェクトを解放します。
6 Close()メソッドにより、ファイルをクローズします。
7 Quit()メソッドにより、アプリケーションを終了します。
10〜12 使用したApplicationやWorkbooksなどのCOMオブジェクトを解放します。後ほど詳しく説明します。

 ReleaseComObject()/FinalReleaseComObject()メソッドは、COMオブジェクトの参照カウントを1/すべて減らすメソッドです。
 参照カウント絡みの話は割りと深く面倒くさいものですので、今はあまり時間がないけどとにかく使えるようになりたい方は、「COMは、メモリーのalloc()/free()と同様に、使用者が管理するもの」という程度に理解していただければまず十分です。詳しく知りたい方はAppendix. 「COMの仕組み」をお読みください。
 なお、COMオブジェクトが正しく解放されなかった場合、Excelのプロセスが残り続けてしまいます。オブジェクトの解放忘れには十分に注意してください。

COMオブジェクトを解放し忘れる典型的パターン

 以下に、解放を忘れるパターンを示します。この記述を行うとCOMオブジェクトの解放し忘れにつながるため、使用してはいけません。この記述を見つけた場合、速やかに指摘してあげてください。

  1. 一気にOpen
    一般的にC#でファイルをオープンするときは、以下の様な記述をすることがよくあります。
      FileStream fs = System.IO.File.Open(filename, FileMode.Open);
    これと同じ感覚でExcelファイルのオープンを記述すると
      Workbook book = new Application().Workbooks.Open(ExcelFileName);
    となりますが、ApplicationオブジェクトとWorkbooksオブジェクトをどの変数にも格納していないため、後々ReleaseComObject()で解放することができず、COMオブジェクトが残存します。COMオブジェクトを返すメソッドやプロパティでは、面倒でもひとつひとつ変数に格納して行きましょう。
  2. Cellsプロパティ
    セルの領域を取得する方法として、上ではRange[]アクセサーを使用する方法を紹介しましたが、Cellsプロパティを使用して取得することもできます。
       Range TableRange = sheet.Cells["A1", "B15"];
    しかし、CellsプロパティはCOMオブジェクトを返すので、上記の記述ではCellsを受け取る変数がおらず、COMオブジェクトを開放することができません。
     (実際はこうなっている)
      Range TableRange = sheet.Cells.Range["A1", "B15"];
    Cellsプロパティを使用して各セルにアクセスする場合は、Cellsのオブジェクトを変数に格納し、そこから各セルのRangeオブジェクトをアクセサーで取得する必要があります。
      Range cells = sheet.Cells; // cellsはあとでReleaseComObject()で解放する
      Range TableRange = cells["A1", "B15"];

 その他、COMオブジェクトを返すプロパティを、変数へ格納せずに使用している箇所はすべて解放忘れにつながります。発見次第、修正してください。COMオブジェクトを返すプロパティについては、Appendix.「COMオブジェクトを返すプロパティ」を参照してください。

遅延バインディング

 「遅延バインディング」方式とは、DLLやCOM内のクラス・関数を、必要になったときに、必要な分だけ実行時に読み込む機能です。実行時に読み込まれるということは、プログラマーはそのDLLの存在やバージョンについて、実装時に意識する必要がなくなるということになります4
 バージョンを意識する必要がなくなるため、事前バインディングの時のように、Excelのバージョンに応じてライブラリーを読み込む必要がなく、将来新しいバージョンのExcelが出てきたとしても、プログラムに改変を加えることなくサポートできる可能性が高いということになります。
 以下に、「遅延バインディング」方式によるExcelファイルを操作する手順を示していきます。

ソースコード解説

 サンプルコードを元に、遅延バインディングを使用してC#からExcelを操作する方法を説明します。
 操作自体は事前バインディングと同じ5ステップで、ステップごとに行っている処理も同様のため省略し、ここでは、遅延バインディング独特の構文について説明します。
 サンプルコードの全文はAppendix.「遅延バインディング」を参照してください。
 以下に例として、前処理で行うExcelファイルオープン処理を、遅延バインディング形式で記述したものを示します。

// ファイルオープン
object xlApp = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
object xlBooks = xlApp.GetType().InvokeMember("Workbooks", 
    System.Reflection.BindingFlags.GetProperty, null, xlApp, null);
object xlBook = xlBooks.GetType().InvokeMember("Open",
    System.Reflection.BindingFlags.InvokeMethod, null, xlBooks,
    new object[] {
        ExcelFileName, Type.Missing, Type.Missing,
        Type.Missing, Type.Missing, Type.Missing,
        Type.Missing, Type.Missing, Type.Missing,
        Type.Missing, Type.Missing, Type.Missing,
        Type.Missing, Type.Missing, Type.Missing
    });

 突然摩訶不思議な記述になりましたが、処理の内容自体は事前バインディングの時と何らかわりがありません。Applicationオブジェクトを作成し、Workbooksプロパティを取得し、Workbookをオープンしているだけです。ここでは、その摩訶不思議な部分であるActivator.CreateInstance()、Type.GetTypeFromProgID()、Type.InvokeMember()メソッドの解説を行います。

  • Type.GetTypeFromProgID()
    指定したプログラムID(ProgID)に関連付けられている型を取得します。ここで指定している引数は”Excel.Application”なので、取得できる型はMicrosoft.Office.Interop.Excel.Applicationクラスとなります。
  • Activator.CreateInstance()
    CreateInstanceメソッドは、指定した引数に最も一致するコンストラクターを呼び出して、アセンブリで定義された型のインスタンスを作成します。つまり、Applicationクラスのインスタンスを作成してくれます。
  • Type.InvokeMember()
    InvokeMemberは、引数の文字列と一致するメソッドやメンバーの操作を行うメソッドです。コンストラクターのメンバーまたはメソッドのメンバーの呼び出し、プロパティのメンバーの取得または設定、データフィールドのメンバーの取得または設定、または配列のメンバーの要素の取得または設定を行います。
    身も蓋もなく日本語に直すなら「”第4引数”から、”第1引数”のプロパティまたはメソッドを、[”第5引数”のobject配列を”第3引数”に変換したものを引数として] ”第2引数”します」という感じ。
引数インデックス 説明
第1引数 メンバー名、メソッド名の文字列
第2引数 クラスへのアクセス用フラグ
第3引数 引数のコンバータ用クラス
第4引数 制御対象インスタンス
第5引数 メソッド引数

 上述の例のように、基本的にはCOMオブジェクトを操作する処理をInvokeMember()メソッドに書き換えることで、遅延バインディング方式の記述となります。

遅延バインディングのデメリット

 Excelのバージョンに寄らず記述できる遅延バインディングですが、以下の様なデメリットが存在します。

  1. Excel操作クラスの形を自分で知る必要がある
    DLL参照を追加しておけばIntellisenseによる補完機能が働きますが、遅延バインディングでは参照を追加しないためその機能は働いてくれません。自分が操作したいCOMオブジェクトのメソッドやプロパティ名が正しいかどうかは、自分の目で確かめる必要があります。
  2. 実行時にしか正当性が判別できない
    (1)と同様に、Intellisenseによる誤り訂正が働かないので、実際に実行してみるまでメソッド名を正しく記述できたかどうかはわかりません。そして、間違っていた場合は例外が発生することもあるので、例外処理をおこなっていないとCOMオブジェクトが解放されずに……
  3. 記述がとても長くなる
    例を見ればわかるように、あからさまに横に伸びます。ただでさえCOMの解放処理で縦に伸びているのに、これで横にまで伸びてしまうのは明らかに可読性が下がっていきます。ただでさえInvokeMethod()は可読性が悪いのに……

[C# 4.0] dynamicキーワードを用いた遅延バインディング

 C# 4.0で導入された、動的言語との連携の仕組みの1つが動的型付け変数(dynamicキーワード)です。動的型付け変数を使うことで、動的な(コンパイル時にメンバー情報がわからない型の)メンバーアクセスが可能になります。このdynamicキーワードを用いると、先ほど指摘した遅延バインディングのデメリットがほぼ解消されます。早速、前処理で行うExcelファイルオープン処理を、dynamicキーワードを用いた記述で見てみましょう。

// ファイルオープン
dynamic xlApp = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
dynamic xlBooks = xlApp.Workbooks;
dynamic xlBook = xlBooks.Open(ExcelFileName);

2行目だけ遅延バインディング形式と同じで、あとは事前バインディング方式の時とほぼ同じです。違うのは、変数の型がdynamicになっているだけです。このことから、
1. 事前バインディング形式でExcel操作クラスを実装し、動作確認を行う。
2. COMオブジェクトを格納する変数の型をdynamicへ変更する。
3. DLLの参照を削除する。
という手順を踏めば、Excelのバージョンに依存する事前バインディングの記述を、バージョンに依存しない遅延バインディングに変換できます。

Appendix.

COMの仕組み

 .NET から COM オブジェクトを扱うということは、マネージドからアンマネージにアクセスするということであり、すなわちマーシャリングが必要となります。これは、ランタイム呼び出し可能ラッパー(RCW: Runtime Callable Wrapper)なるしくみによって行われています。
スクリーンショット 2019-08-29 16.53.20.png
 通常、.NET クライアント(アプリケーション)が直接的に扱うのは、生身の COM オブジェクトではなく、そのラッパーたる RCW です。ランタイム(.NET Framework)によってサポートされるため意識しづらいですが、RCW というプロキシを挟んで COM オブジェクトにアクセスしているのです。これは重要な事実であり、認識しておかなければなりません。
 COMの規定によれば、COMオブジェクトたるもの「自分のメモリーは自分で解放する責任がある」とのこと。そして、それを実現するためには、参照カウントというテクニックが用いられます。参照カウントとは、その COMオブジェクトが他からどれだけ参照されているかを示す数値です。COMオブジェクトは、自分が使ったメモリーを自分で解放するために、自身への参照数を自分で管理していて、参照数が"0"になったら他からの参照がなくなった(=用済み)と判断して、自分自身のメモリーを解放してくれます。だけどもそれ故に、いま誰が必要とし、必要としなくなったのかは、使う側から通達しなくてはなりません。
 .NETでメモリー管理といえばガベージコレクション(GC)です。COMオブジェクトを内包するRCWは、マネージドであり、もちろんGCの管理対象です。しかし、COMオブジェクトは、自分のメモリーは自分で解放する責務を負っています。ここに、メモリー管理のアプローチの違いからくるミスマッチが生まれています。RCWはGCの管理下にありますが、内包するCOMオブジェクトがGCの対象ではないので、結局、アプリケーション作成者が RCW を通じてCOMオブジェクトが使用するメモリーを、参照カウントというしくみで管理しなければならない、ということになっています。
 管理するとは、つまり、RCWをすべて変数に保持しておくということであり、そしてその参照カウントをデクリメントするのがReleaseComObject()メソッドです。

VBScript
Sub ShowRangeA1()
    Dim excel
    Dim book
    Set excel = CreateObject("Excel.Application")
    Set book = excel.Workbooks.Open("hoge.xls")

    Dim a1val
    a1val = book.Sheets(1).Range("A1").Value

    book.Close False
    excel.Quit

    WScript.Echo a1val
End Sub

VBScriptだとこれだけですむのに…

public void ShowRangeA1()
{
    Application xlApp = new Application();
    Workbooks xlBooks = xlApp.Workbooks;
    Workbook xlBook = xlBooks.Open(@"hoge.xls");
    Sheets xlSheets = xlBook.Worksheets;
    Worksheet xlSheet = (Worksheet)xlSheets[1];
    Range xlCells = xlSheet.Cells;
    Range xlRangeA1 = (Range)xlCells[1, 1];

    string a1val = (string)xlRangeA1.Value2;

    Marshal.ReleaseComObject(xlRangeA1);
    Marshal.ReleaseComObject(xlCells);
    Marshal.ReleaseComObject(xlSheet);
    Marshal.ReleaseComObject(xlSheets);
    xlBook.Close(false, Type.Missing, Type.Missing);
    Marshal.ReleaseComObject(xlBook);
    Marshal.ReleaseComObject(xlBooks);
    xlApp.Quit();
    Marshal.ReleaseComObject(xlApp);

    MessageBox.Show(a1val);
}

C#だとごらんの有様だよ!

上記例を見ればわかるように、処理の本質ではないコード、タイプ量が激増しています。これだけでも十分面倒くさいのに、実は例外発生時にも確実にデクリメントを行うためには、RCWを生成するごとにtry~finallyをしてやらなければいけません。詳しくはこちらの記事を参考にして頂きますが、こうなると、超絶ネスト構造の一丁上がりです。意味がわかりません。しかし、COM相互参照を使用するということは、本来はこういうことを行う必要があると理解してください。

COMオブジェクトを返すプロパティ一覧

Application Areas Borders Cells
Characters Columns Comment CurrentArray
CurrentRegion Dependents DirectDependents DirectPrecedents
End EntireColumn EntireRow Font
FormatConditions Hyperlinks Interior Item
ListObject MDX MergeArea Next
Offset Parent Phonetic Phonetics
PivotCell PivotField PivotItem PivotTable
Precedents Previous QueryTable Range
Resize Rows ServerActions SmartTags
Validation Worksheet XPath

サンプルアプリケーションのソースコード

事前バインディング

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//インターフェースの名前空間
using ExtractExcelDataInterface;

//Excelを操作するために必要なクラスの名前空間を追加
using Microsoft.Office.Interop.Excel;

namespace ExtractExcelData
{
    public class ExtractExcelUseExcelDLL : IExtractExcelData
    {
        public Dictionary<string, string> ExtractExcelData(string ExcelFileName)
        {
            //returnするDicionaryインスタンス
            Dictionary<string, string> dic = new Dictionary<string, string>();

            // ファイルオープン
            Application xlApp = new Application();
            Workbooks xlBooks = xlApp.Workbooks;
            Workbook xlBook = xlBooks.Open(
                    ExcelFileName, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing, Type.Missing,
                    Type.Missing, Type.Missing, Type.Missing
                );

            // シートを選択
            Worksheet sheet = xlBook.Sheets["メンバー一覧"];

            // セルの領域を選択
            Range TableRange = sheet.Range["A1", "B15"];

            // 選択した領域の値をメモリー上に格納
            // (1セルずつ見ていくよりも早い)
            object[,] values = TableRange.Value;

            // 配列アクセスができるので、それぞれをDictionaryに追加
            // [WARNING] 配列の開始インデックスは1から
            for (int i = 1; i <= values.GetLength(0); i++)
            {
                dic.Add((string)values[i, 1], (string)values[i, 2]);
            }

            // 使用したCOMオブジェクトを解放
            System.Runtime.InteropServices.Marshal.ReleaseComObject(TableRange);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet);

            // Excelのクローズ
            xlBook.Close();
            xlApp.Quit();

            // 使用したCOMオブジェクトを解放その2
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBook);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBooks);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlApp);

            return dic;
        }
    }
}

遅延バインディング

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using ExtractExcelDataInterface;

namespace ExtractExcelData
{
    public class ExtractDelayBinding : IExtractExcelData
    {
        public Dictionary<string, string> ExtractExcelData(string ExcelFileName)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();

            object xlApp = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
            object xlBooks = xlApp.GetType().InvokeMember("Workbooks", System.Reflection.BindingFlags.GetProperty, null, xlApp, null);
            object xlBook = xlBooks.GetType().InvokeMember("Open", System.Reflection.BindingFlags.InvokeMethod, null, xlBooks, object[] { ExcelFileName, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,Type.Missing, Type.Missing, Type.Missing,Type.Missing, Type.Missing, Type.Missing });

            // シートを選択
            object sheet = xlBook.GetType().InvokeMember("Sheets", System.Reflection.BindingFlags.GetProperty, null, xlBook, new object[] { "メンバー一覧" });

            // セルの領域を選択
            object TableRange = sheet.GetType().InvokeMember("Range", System.Reflection.BindingFlags.GetProperty, null, sheet, new object[] { "A1", "B15" });

            // 選択した領域の値をメモリー上に格納
            // (1セルずつ見ていくよりも早い)
            object[,] values = TableRange.GetType().InvokeMember("Value", System.Reflection.BindingFlags.GetProperty, null, TableRange, null) as object[,];

            // 配列アクセスができるので、それぞれをDictionaryに追加
            // [WARNING] 配列の開始インデックスは1から
            for (int i = 1; i <= values.GetLength(0); i++)
            {
                dic.Add((string)values[i, 1], (string)values[i, 2]);
            }

            // 使用したCOMオブジェクトを解放
            System.Runtime.InteropServices.Marshal.ReleaseComObject(TableRange);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet);

            // Excelのクローズ
            xlBook.GetType().InvokeMember("Close", System.Reflection.BindingFlags.InvokeMethod, null, xlBook, null);
            xlApp.GetType().InvokeMember("Quit", System.Reflection.BindingFlags.InvokeMethod, null, xlApp, null);

            // 使用したCOMオブジェクトを解放その2
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBook);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBooks);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlApp);

            return dic;
        }
    }
}

dynamicキーワードを使用した遅延バインディング

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using ExtractExcelDataInterface;

namespace ExtractExcelData
{
    public class ExtractDelayBindUseDynamic : IExtractExcelData
    {
        public Dictionary<string, string> ExtractExcelData(string ExcelFileName)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();

            // ファイルオープン
            dynamic xlApp = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
            dynamic xlBooks = xlApp.Workbooks;
            dynamic xlBook = xlBooks.Open(ExcelFileName);

            // シートを選択
            dynamic sheet = xlBook.Sheets["メンバー一覧"];

            // セルの領域を選択
            dynamic TableRange = sheet.Range["A1", "B15"];

            // 選択した領域の値をメモリー上に格納
            // (1セルずつ見ていくよりも早い)
            object[,] values = TableRange.Value;

            // 配列アクセスができるので、それぞれをDictionaryに追加
            // [WARNING] 配列の開始インデックスは1から
            for (int i = 1; i <= values.GetLength(0); i++)
            {
                dic.Add((string)values[i, 1], (string)values[i, 2]);
            }

            // 使用したCOMオブジェクトを解放
            System.Runtime.InteropServices.Marshal.ReleaseComObject(TableRange);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet);

            // Excelのクローズ
            xlBook.Close();
            xlApp.Quit();

            // 使用したCOMオブジェクトを解放その2
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBook);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBooks);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlApp);

            return dic;
        }
    }
}

  1. 簡単に言うと、アプリを外から操作する技術 

  2. COMを使用して開発されたソフトウェア 

  3. 「13」は忌み数なので欠番 

  4. 実行時にDLLが存在しないと行けないし、バージョン依存の記述もあるでしょうから、一概にすべて意識する必要がなくなるという訳でもないですが…… 

42
42
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
42