【C#】Excelファイルを編集する(COM相互運用、早期バインディング編)

C#

この記事では、Microsoft Office Interop Excelを早期バインディング(Early Binding)で使用し、Excelファイルに対して以下の操作をする方法を紹介します。

  • セルの数値の取得と設定
  • テキストの取得と設定
  • セルの背景色の設定
  • セルのフォーマットの設定

サンプルコード実行の準備

準備1:フォームの設定

フォームにボタンをひとつ配置します。名前はbutton1とします。デザイナ上でボタンをダブルクリックしておき、事前にイベントハンドラを生成します。

準備2:Excelファイルの作成

編集対象のExcelファイルを準備してください。対象のファイルには “Sheet1” が存在している必要があります。

準備3:Excelの参照設定を追加

プロジェクトの参照で、Excelを参照するよう設定します。

サンプルコード:Excel操作クラスを使用する側

ボタン押下時のイベントハンドラにExcelファイル操作処理を実装しています。Excelファイルの実際の操作は、MyExcel名前空間のMyWorkbookクラスで行います。

using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using MyExcel;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MyWorkbook workbook = null;

            try
            {
                workbook = new MyWorkbook(@"c:\path\to\your\file.xlsx");
                MyWorksheet worksheet = workbook.GetSheet("Sheet1");

                // セルから数値を取得
                int value = worksheet.GetValue<int>(1, 1);

                // セルに数値を設定
                worksheet.SetValue(1, 1, value + 1);

                // セルからテキストを取得
                string text = worksheet.GetValue<string>(2, 1);

                // セルにテキストを設定
                worksheet.SetValue(2, 1, text + "a");

                // セルの背景色を黄色に設定
                worksheet.SetColor(3, 1, Color.Yellow);

                // セルのフォーマットをテキスト形式に設定して、値を設定
                worksheet.SetFormat(4, 1, "@");
                worksheet.SetValue(4, 1, "1234");

                // 変更を保存
                workbook.Save();

                // ワークブックを閉じる
                workbook.Close();

                MessageBox.Show("Excelファイルの操作が完了しました。");
            }
            catch (Exception ex)
            {
                // ワークブックを閉じる
                if (workbook != null)
                {
                    workbook.Close();
                }

                MessageBox.Show("エラーが発生しました: " + ex.Message);
            }
        }
    }
}

サンプルコード:Excel操作クラス

こちらは、MyWorkbookクラスの実装です。COMオブジェクトを使用する際のオブジェクト解放などの複雑な処理を、こちらのクラスで隠蔽します。

// MyExcel.cs
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
using Excel = Microsoft.Office.Interop.Excel;

namespace MyExcel
{
    // ワークブック
    class MyWorkbook
    {
        Excel.Application m_xlApp;
        Excel.Workbooks m_xlWorkbooks;
        Excel.Workbook m_xlWorbook;
        List<MyWorksheet> m_WorkSheetList = new List<MyWorksheet>();

        // コンストラクタ
        public MyWorkbook(string filename)
        {
            // Excelアプリケーション生成
            m_xlApp = new Excel.Application();
            m_xlApp.Visible = false;   // 非表示で動作

            // エラーチェック
            if (!File.Exists(filename))
            {
                throw new FileNotFoundException("ファイルが見つかりません。" + Environment.NewLine +
                                                 filename);
            }

            // ワークブック取得
            m_xlWorkbooks = m_xlApp.Workbooks;
            m_xlWorbook = m_xlWorkbooks.Open(filename);

            // ブック内のシート取得
            Excel.Sheets sheets = m_xlWorbook.Worksheets;
            foreach (Excel.Worksheet xlWorkSheet in sheets)
            {
                MyWorksheet sheet = new MyWorksheet(xlWorkSheet);
                m_WorkSheetList.Add(sheet);
            }
            Marshal.ReleaseComObject(sheets);
        }
        // シート取得(見つからない場合null)
        public MyWorksheet GetSheet(string name)
        {
            return m_WorkSheetList.Find(sheet => sheet.Name == name);
        }
        // 上書き保存
        public void Save()
        {
            m_xlWorbook.Save();
        }
        // ワークブックを閉じる
        public void Close()
        {
            // シートの後始末
            foreach (MyWorksheet sheet in m_WorkSheetList)
            {
                sheet.Close();
            }
            // ワークブック
            if (m_xlWorbook != null)
            {
                m_xlWorbook.Close();
                Marshal.ReleaseComObject(m_xlWorbook);
                m_xlWorbook = null;
            }
            // ワークブックス
            if (m_xlWorkbooks != null)
            {
                m_xlWorkbooks.Close();
                Marshal.ReleaseComObject(m_xlWorkbooks);
                m_xlWorkbooks = null;
            }
            // アプリケーション
            if (m_xlApp != null)
            {
                m_xlApp.Quit();
                Marshal.ReleaseComObject(m_xlApp);
                m_xlApp = null;
            }
            // ガベージコレクタを強制的に実行
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
    // ワークシート
    class MyWorksheet
    {
        Excel.Worksheet m_xlWorkSheet;
        // シート名
        public string Name
        {
            get { return m_xlWorkSheet.Name; }
        }
        // コンストラクタ
        public MyWorksheet(Excel.Worksheet xlWorkSheet)
        {
            m_xlWorkSheet = xlWorkSheet;
        }
        // クローズ
        public void Close()
        {
            Marshal.ReleaseComObject(m_xlWorkSheet);
        }
        // 値設定
        public void SetValue<T>(int row, int col, T value)
        {
            Range cells = m_xlWorkSheet.Cells;
            Range range = cells[row, col];
            range.Value = value.ToString();
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);
        }
        // 値取得
        public T GetValue<T>(int row, int col)
        {
            Range cells = m_xlWorkSheet.Cells;
            Range range = cells[row, col];
            string value = range.Value != null ? range.Value.ToString() : "";
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);

            // 数値の場合は空文字列をゼロにする
            if (typeof(T) != typeof(string))
            {
                value = string.IsNullOrWhiteSpace(value) ? "0" : value;
            }
            return (T)Convert.ChangeType(value, typeof(T));
        }
        // 色設定
        public void SetColor(int row, int col, Color color)
        {
            Range cells = m_xlWorkSheet.Cells;
            Range range = cells[row, col];
            Interior interior = range.Interior;
            interior.Color = ColorTranslator.ToOle(color);
            Marshal.ReleaseComObject(interior);
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);
        }
        // 色取得
        public Color GetColor(int row, int col)
        {
            Range cells = m_xlWorkSheet.Cells;
            Range range = cells[row, col];
            Interior interior = range.Interior;
            Color color = ColorTranslator.FromOle(interior.Color);
            Marshal.ReleaseComObject(interior);
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);
            return color;
        }
        // フォーマット設定
        public void SetFormat(int row, int col, string format)
        {
            Range cells = m_xlWorkSheet.Cells;
            Range range = cells[row, col];
            range.NumberFormatLocal = format;
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);
        }
        // フォーマット取得
        public string GetFormat(int row, int col)
        {
            Range cells = m_xlWorkSheet.Cells;
            Range range = cells[row, col];
            string format = range.NumberFormatLocal.ToString();
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);
            return format;
        }
    }
}

COMオブジェクトを使用する処理について

COM(Component Object Model)オブジェクトを使用してMicrosoft Officeのアプリケーション(この場合はExcel)を操作する際には、取得したオブジェクトの管理が非常に重要です。COMオブジェクトは、ネイティブリソースを扱うため、.NETのガベージコレクション(GC)による自動的な管理の対象外です。したがって、これらのオブジェクトを正しく解放しないと、以下の現象を引き起こす可能性があります。

  • Excelプロセスの残留
  • メモリリーク
  • パフォーマンスの低下

たとえば、COMオブジェクトの解放忘れがあると、プログラム上でExcelワークブックを閉じたとしても、Excelプロセスの残留(Excelプロセスが生き続ける現象)が発生します。

COMオブジェクトの解放について、MyWorksheetクラスのSetValueメソッドでは、セルに値を設定するために、以下のようにオブジェクトの取得や解放処理を行う必要があります。

// 値設定
public void SetValue<T>(int row, int col, T value)
{
    Range cells = m_xlWorkSheet.Cells;   // cellsオブジェクト取得
    Range range = cells[row, col];       // rangeオブジェクト取得
    range.Value = value.ToString();      // 値を設定
    Marshal.ReleaseComObject(range);     // rangeオブジェクト解放
    Marshal.ReleaseComObject(cells);     // cellsオブジェクト解放
}

COMオブジェクトの取得

上記のSetValueメソッドでは、m_xlWorkSheet.Cellsを使ってcellsオブジェクト(Excelのセルのコレクション)を取得しています。次に、cells[row, col]を使って特定のセルを表すrangeオブジェクトを取得しています。ここで使用されるCellsプロパティとインデクサー[row, col]は、Excelのワークシート内のセルにアクセスするためのCOMオブジェクトを生成します。

COMオブジェクトの解放

生成・取得したCOMオブジェクトは、使用後に明示的に解放する必要があります。SetValueメソッドの最後には、Marshal.ReleaseComObjectメソッドを使用してrangeオブジェクトとcellsオブジェクトを解放しています。Marshal.ReleaseComObjectメソッドは、COMオブジェクトへの参照カウントをデクリメントします。COMオブジェクトは、参照カウントが0になった時点でオブジェクトがメモリから解放されます。

解放の順序

COMオブジェクトを解放する際には、オブジェクトを取得した逆の順序で解放することが推奨されます。この例では、最初にrangeオブジェクトを解放し、次にcellsオブジェクトを解放しています。この順序で解放する理由は、rangeオブジェクトがcellsオブジェクトに依存しているためです。依存しているオブジェクトを先に解放してしまうと、後で解放しようとしたオブジェクトが既に無効な状態になっている可能性があり、エラーや不具合の原因となります。

ガベージコレクタの起動

Marshal.ReleaseComObjectを呼び出した後、ガベージコレクターを強制的に実行して未使用になったメモリをクリーンアップすることが推奨されます。これにはGC.CollectとGC.WaitForPendingFinalizersを呼び出します。

注意点

  • 本記事のサンプルコードを使用する場合、お使いのPCにExcelがインストールされている必要があります。
  • 本記事のサンプルコードは、Microsoft Office Interop ExcelによるExcelファイル操作に焦点をあてるため、それ以外の部分の実装は簡略化しています。実際にアプリケーションに組み込む際は、各種の例外処理を実装する必要があります。
  • COMオブジェクトの解放漏れがあると、プログラム上でExcelファイルを閉じたとしても、Excelプロセスが生き残り続けます。このような現象を防ぐため、Disposeパターンなどを取り入れ、リソースの解放漏れを防いでください。

まとめ

Microsoft Office Interop Excelを使用する主な利点は、Excelアプリケーションとの高い互換性と、高度な機能へのアクセス可能性にあります。COMオブジェクトの使用に若干の難しさはありますが、Excelアプリケーションをきめ細かく制御したい場合は、第一の選択肢となるでしょう。

関連記事

タイトルとURLをコピーしました