3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。(jehupc.exblog.jpから移転中)

(.Net)C#から遅延バインディングでExcelファイルに読み書きする

以前に、.Net(C#)アプリからExcelのVBAを呼び出したい(遅延バインディング)で、C#からマクロを実行する方法を書きました。今回は、より一般的なセルへの読み書き方法のメモです。

開発マシンのExcelのバージョンに左右されないように遅延バインディングでアクセスします。そのためコードは煩雑ですが、考え方としてはシンプルです。

とりあえず、Excelファイル開いて、セルに読み書きして保存するというコードのサンプルです。(C#)

//COM参照変数定義
object oExcel = null;
object oBooks = null;
object oBook = null;
object oSheets = null;
object oSheet = null;
object oRange = null;

object oBook2 = null;

try
{
    // Excel操作用COMオブジェクトを生成する
    oExcel = Utility.CreateObject("Excel.Application");

    //ワークブックコレクションオブジェクトを生成する。
    oBooks = oExcel.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, oExcel, null);

    //Excelファイルのオープン
    oBook = oBooks.GetType().InvokeMember(
      "Open", BindingFlags.InvokeMethod, null,
      oBooks, new object[] {
          Environment.CurrentDirectory +   "\\test.xls"     //開くファイルパス
        , Type.Missing //UpdateLinks ファイル内のリンクの更新方法を指定。よくわからん。
        , Type.Missing //ReadOnly ブックを読み取り専用モードで開くには、True を指定。
        , Type.Missing //Format MicrosoftExcelがテキストファイルを開くときに、この引数に項目の区切り文字を指定。この引数を省略すると現在指定されている区切り文字が使われる。
        , Type.Missing //Password 書き込み保護されたブックに書き込みをするために必要なパスワードを指定。パスワードが必要なときにこの引数を省略すると、パスワードの入力を促すダイアログ ボックスが表示。
        , Type.Missing //IgnoreReadOnlyRecommended [読み取り専用を推奨する] チェック ボックスをオンにして保存されたブックを開くときでも、読み取り専用を推奨するメッセージを非表示にするには、True を指定。
        , Type.Missing //Origin 指定したファイルがテキスト ファイルのときに、それがどのような形式のテキスト ファイルかを指定。1:xlMacintosh 2:xlWindows 3:xlMSDOS
        , Type.Missing //Delimiter 指定したファイルがテキスト ファイルであり、引数 Format に 6 が設定されているときに、区切り記号として使う文字を指定。
        , Type.Missing //Editable 指定したファイルが Microsoft Excel 4.0 のアドインの場合、この引数に True を指定すると、アドインをウィンドウとして表示。
        , Type.Missing //Notify 指定したファイルが読み取り/書き込みモードで開けない場合に、ファイルを通知リストに追加するには、True を指定。
        , Type.Missing //Converter ファイルを開くときに、最初に使うファイル コンバータのインデックス番号を指定。
        , Type.Missing //AddToMru True を設定すると、最近使用したファイルの一覧にこのブックが追加。
        , Type.Missing //Local 
        });



    //ワークシート達を取得
    oSheets = oBook.GetType().InvokeMember("Worksheets", BindingFlags.GetProperty, null, oBook, null);

    //任意のワークシートを選ぶ 文字列にするとその名前のワークシート、数値にするとx番目のシートを選ぶ
    oSheet = oSheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null
        , oSheets, new object[] { "Sheet1" });

    //B1セルを選ぶ
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "B1", Missing.Value });
    //選択中のセルに値をセット
    oRange.GetType().InvokeMember("Value", BindingFlags.SetProperty, null
        , oRange, new object[] { 10 });

    //B2セルを選ぶ
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "B2", Missing.Value });
    //選択中のセルに値をセット
    oRange.GetType().InvokeMember("Value", BindingFlags.SetProperty, null
        , oRange, new object[] { 100 });

    //B3セルを選ぶ
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "B3", Missing.Value });
    //選択中のセルに計算式をセット
    oRange.GetType().InvokeMember("Formula", BindingFlags.SetProperty, null
        , oRange, new object[] { "=Sum(B1:B2)" });

    //A3選択
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "A3", Missing.Value });
    //選択中のセルにテキストをセット
    oRange.GetType().InvokeMember("Value", BindingFlags.SetProperty, null
        , oRange, new object[] { "合計" });

    //B3セルを選ぶ
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "B3", Missing.Value });
    //選択中のセルのテキスト取得
    Console.WriteLine("B3 Text:" + oRange.GetType().InvokeMember("Text", BindingFlags.GetProperty, null
        , oRange, null).ToString());
    //選択中のセルの値取得
    Console.WriteLine("B3 Value:" + oRange.GetType().InvokeMember("Value", BindingFlags.GetProperty, null
        , oRange, null).ToString());
    //選択中のセルの計算式取得
    Console.WriteLine("B3 Formula:" + oRange.GetType().InvokeMember("Formula", BindingFlags.GetProperty, null
        , oRange, null).ToString());


    //複数セル(A5:A10)を選択
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "A5:A10", Missing.Value });
    //選択中のセルに値をセット
    oRange.GetType().InvokeMember("Value", BindingFlags.SetProperty, null
        , oRange, new object[] { "piyo" });

    //上書き保存
    oBook.GetType().InvokeMember("Save", BindingFlags.InvokeMethod, null, oBook, null);

    //ブックを閉じる
    oBook.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, null, oBook, null);

    //-------------------------------------
    //新しいワークブックを作成
    oBook2 = oBooks.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, oBooks, null);
    //ワークシート達を取得
    oSheets = oBook2.GetType().InvokeMember("Worksheets", BindingFlags.GetProperty, null, oBook2, null);
    //新し追加したワークブックの1番目のシートを取得
    oSheet = oSheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null
        , oSheets, new object[] { 1 });
    //A3セルを選択
    oRange = oSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null
        , oSheet, new object[] { "A3", Missing.Value });
    //文字列セット
    oRange.GetType().InvokeMember("Value", BindingFlags.SetProperty, null
        , oRange, new object[] { "aaaaaaa" });


    //SaveAs保存時にメッセージを出さない(既にファイルがある場合は上書きするかダイアログで聞いてくる対策)
    oExcel.GetType().InvokeMember("DisplayAlerts", BindingFlags.SetProperty, null, oExcel, new object[] {false});

    float ver=0;
    //Excelバージョンを取得
    float.TryParse( oExcel.GetType().InvokeMember("Version", BindingFlags.GetProperty, null, oExcel, null).ToString() , out ver);
    int fileFormat = 43; //保存するファイルフォーマット。FileFormat定数:xlExcel9795 (Excel97-2000および5.0/95ブック)
    if (ver >= 12)
        fileFormat = 56; //FileFormat定数:xlExcel8 (Excel8ブック) Excel2007以上でxls保存する場合は、xlExcel9795は使えないのでこれにする。
    //名前をつけて保存
    oBook2.GetType().InvokeMember(
       "SaveAs", BindingFlags.InvokeMethod, null,
       oBook2, new object[] {
            Environment.CurrentDirectory +   "\\test2.xls"    //保存するファイルパス
          , fileFormat   //保存するファイルフォーマット(数値で指定)
          , Type.Missing //ファイルに指定する保護パスワードを表す最大 15 文字の文字列
          , Type.Missing //このファイルの書き込みパスワード
          , Type.Missing //ファイルを開いたときに、読み取り専用で開くことを推奨するメッセージを表示する場合は true 
          , Type.Missing //バックアップ ファイルを作成する場合は true 
          , Type.Missing //アクセスモードの指定。3:排他モード。2:共有モード。1:モード変更しない(既定値)
          , Type.Missing //共有ファイルの場合の変更コンフリクトの解決方法を指定。2:自動的にローカルユーザの変更を反映。3:他のユーザの変更を反映。1:[変更箇所のコンフリクト]ダイアログ・ボックス表示。
          , Type.Missing //このブックを最近使用されたファイルに追加する場合は true です。既定値は、false 。
          , Type.Missing //TextCodepage
          , Type.Missing //TextVisualLayout
          , Type.Missing //true の場合、Excel の言語 (コントロール パネルの設定を含む) でファイルを保存します。false (既定値) の場合、VBA (Visual Basic for Applications) の言語でファイルを保存。
          });
    
    //ワークブックを閉じる
    oBook2.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, null, oBook2, null);

    //Excel操作終了
    oExcel.GetType().InvokeMember("Quit", System.Reflection.BindingFlags.InvokeMethod, null, oExcel, null);
    //Excelの画面をユーザに表示させるには、以下のようにする。
    //oExcel.GetType().InvokeMember("Visible", BindingFlags.SetProperty, null, oExcel, new object[] { true });
    //Excelの制御をユーザに渡すには上の Quit を行わず以下のようにする。
    //oExcel.GetType().InvokeMember("UserControl", BindingFlags.SetProperty, null, oExcel, new object[] { true });

}catch (Exception ex) {
    MessageBox.Show("エラー "+ ex.Message);
} finally {
    //COM解放
    if (oExcel != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oExcel);
    if (oBooks != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oBooks);
    if (oBook != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oBook);
    if (oSheets != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oSheets);
    if (oSheet != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oSheet);
    if (oRange != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oRange);
    if (oBook2 != null)
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oBook2);

    oExcel = null;
    oBooks = null;
    oBook = null;
    oSheets = null;
    oSheet = null;
    oRange = null;
    oBook2 = null;

    GC.Collect(); //Excelプロセスがしばらく残るのが気になる場合は、強制的にガーベージコレクション実行。
}

ソースのコメントに意味を書いているので、これで大抵分かるかと思います。

基本的に、Excelファイルを開くかワークブックを新規作成するかして、Worksheets プロパティでワークシートコレクションを取得、更に Item プロパティで任意のシートを選択し、Range プロパティで操作対象のセルを選択します。
後はセルに対する操作ですが、数式のセットは Formula , 値のセットは Value を使います。
取得は、Formula(数式),Value(値) に加え Text(表示書式に従った結果文字列で取得) が使えます。

COMオブジェクトは解放忘れがないように要注意です。
そうしないと、Excelプロセスが残ってしまいます。Marshal.FinalReleaseComObjectを使えば参照カウンタが0になるまで解放処理をしてくれます。

SaveAs時に、既に対象ファイルが存在する場合、上書きするかどうか聞いてきます。
このメッセージを出さずに、強制保存する場合は、Excelオブジェクトの DisplayAlerts を false にすればいいようです。

遅延バインディングは開発PCと運用PCとのExcelバージョンを意識しなくて済むというメリットは有るんですが、いかんせんコードが複雑になるため開発効率はかなり落ちます。
今回は時間がないのでしませんでしたが、Excelへのアクセスをラッピングするようなラッパークラスを作るのがいいかもしれません。 と、思ったらExcelの参照を追加せずにExcelを使う[C#] zenmai softwareや、C#からExcelを操作するライブラリ - gounx2の日記で既に作成しておられました。

また、SaveAs で名前をつけて保存する時に、xlsファイルにするとき、Excel2007以降とそれより前では保存時に指定するファイルフォーマットに注意する必要があります。
Excel2007より前であれば、FileFormat定数で xlExcel9795 (整数値43) 、2007以降であれば xlExcel8 (整数値56) にしないと行けないようです。 フォーマットに種類については、FileFormatプロパティの定数でまとめられています。

参考:
MSサポート:Visual C# .NET で Office オートメーション サーバーをバインドする方法
MSDN:Workbooks メソッド (Microsoft.Office.Interop.Excel)
Excelファイル読み込み方法(遅延バインディング方式) - kurukuru-papaの日記
データテーブルをExcelに出力するサンプルコード:アーキテクト360
VBでExcelファイルを扱うときの注意 遅延バインディング編 - 社内SE奮戦記
C#でEXCELファイルの出力
Office 2007で問題になるマクロの後方互換性 - Kazzzの日記