[C#]Excelファイルのセル読取/書込

[D&DしたPDFファイルをSQL Serverに登録＋プレビューする方法](https://qiita.com/ANNEX_IBS/items/52319d3cbb8678e68eea)の記事の一部になります。

#4. C#:Excelファイル書き換え

Excelファイルのセル読取/書込処理の忘備録になります。

##1) C#でExcelファイルを書き換える方法

大きく分けて、下記の２つがあります。

* ```Microsoft.Office.Interop.Excel``` を使った COM 参照による方法
* オープンソースライブラリを使用する方法

##2) Microsoft.Office.Interop.Excelを使う方法

###メリット

* COM 参照なので、現行形式 (.xlsx) だけでなく、旧型式 (.xls)、CSV 形式 (.csv)、マクロ付きも利用可能です。

###デメリット

* 処理が遅い
* Excelのインストールが必須
* プロセス解放漏れのリスクがある

###サンプルコード
```C#
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

    class ExcelManager : IDisposable
    {
        /// <summary>
        /// Excel操作用オブジェクト
        /// </summary>
        private Application _application = null;
        private Workbook _workbook = null;
        private Worksheet _worksheet = null;

        // Dispose処理は、下記のページを参考にしています。
        // [アンマネージドリソースをDisposeパターンで管理する]
        // (https://days-of-programming.blogspot.com/2018/04/dispose.html)

        #region "IDisposable Support"
        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: Managed Objectの破棄
                }

                if (_workbook != null)
                {
                    _workbook.Close();
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(_workbook);
                    _workbook = null;
                }

                if (_application != null)
                {
                    _application.Quit();
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(_application);
                    _application = null;
                }

                disposedValue = true;
            }
        }

        ~ExcelManager()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion "IDisposable Support"

        /// <summary>
        /// Excelワークブックを開く
        /// </summary>
        public void Open()
        {
            try
            {
                // Excelアプリケーション生成
                _application = new Application()
                {
                    // 非表示
                    //Visible = true
                    Visible = false
                };

                // Bookを開く
                _workbook = _application.Workbooks.Open(@"e:\book1.xlsx");

                // 対象シートを設定する
                _worksheet = _workbook.Worksheets["Sheet1"];
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }

        /// <summary>
        /// Excelワークブックをファイル名を指定して保存する
        /// </summary>
        /// <returns>True:正常終了、False:保存失敗</returns>
        public bool SaveAs()
        {
            try
            {
                // ファイル名を指定して保存する
                _workbook.SaveCopyAs(@"e:\book2.xlsx");
            }
            catch
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// セル設定
        /// </summary>
        /// <param name="rowIndex">row</param>
        /// <param name="columnIndex">col</param>
        /// <param name="value">value</param>
        public void WriteCell(int rowIndex, int columnIndex, object value)
        {
            // セルを指定
            var cells = _worksheet.Cells;
            var range = cells[rowIndex, columnIndex] as Range;

            // 値を設定
            range.Value = value;

            // cell解放
            Marshal.ReleaseComObject(range);
            Marshal.ReleaseComObject(cells);
        }
    }
```
##参考

[Excelファイルを C# と VB.NET で読み込む "正しい" 方法](https://qiita.com/midori44/items/acab9106e6dad9653e73)
[アンマネージドリソースをDisposeパターンで管理する](https://days-of-programming.blogspot.com/2018/04/dispose.html)
[Excel操作クラス](http://excelcsharp.lance40.com/category/class)
[Excel操作クラス](http://excelcsharp.lance40.com/category/cells)

##3) オープンソースライブラリを使用する方法
オープンソースライブラリとして ```EPPlus``` を想定しています。
（当初は ```NPOI``` を試用しましたが、**透過図形がコピーできず**、諦めました）

###メリット

* コア部分が Microsoft の純正ライブラリであるという安心感？
* ```NPOI``` よりも扱いやすい

###デメリット

* Open XML 規格用に作られたものであるため、旧形式ファイル (.xls) を読み込むことができない

###準備

メニューの [ツール] - [NuGet パッケージ マネージャー] から、「EPPlus」を検索してインストールします。

###サンプルコード
```C#:ExcelManagerEx.cs
using OfficeOpenXml;

    class ExcelManagerEx
    {
        /// <summary>
        /// Excel操作用オブジェクト
        /// </summary>
        private ExcelPackage _excelPackage = null;
        private ExcelWorksheet _excelWorksheet = null;

        /// <summary>
        /// Excelワークブックを開く
        /// </summary>
        public void Open()
        {
            try
            {
                // Excelファイルを開く
                var fileInfo = new FileInfo(@"e:\book1.xlsx");
                _excelPackage = new ExcelPackage(fileInfo);

                // シート名で参照
                _excelWorksheet = _excelPackage.Workbook.Worksheets["Sheet1"];
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }

        /// <summary>
        /// Excelワークブックをファイル名を指定して保存する
        /// </summary>
        /// <param name="filename">ファイル名</param>
        /// <returns>True:正常終了、False:保存失敗</returns>
        public bool SaveAs(string filename)
        {
            try
            {
                var fileInfo = new FileInfo(filename);
                _excelPackage.SaveAs(fileInfo);
            }
            catch
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// セル書込(R1C1形式)
        /// </summary>
        /// <param name="row">row</param>
        /// <param name="col">col</param>
        /// <param name="value">value</param>
        public void WriteCell(int row, int col, object value)
        {
            // 値を書込
            _excelWorksheet.Cells[row, col].Value = value;
        }

        /// <summary>
        /// セル書込("A1"形式)
        /// </summary>
        /// <param name="address">address("A1"形式)</param>
        /// <param name="value">value</param>
        public void WriteCell(string address, object value)
        {
            // 値を書込
            _excelWorksheet.Cells[address].Value = value;
        }

        /// <summary>
        /// セル読取(R1C1形式)
        /// </summary>
        /// <param name="row">row</param>
        /// <param name="col">col</param>
        /// <returns>セルの値</returns>
        public object ReadCell(int row, int col)
        {
            // セル読取
            return _excelWorksheet.Cells[row, col].Value;
        }

        /// <summary>
        /// セル読取("A1"形式)
        /// </summary>
        /// <param name="address">address("A1"形式)</param>
        /// <returns>セルの値</returns>
        public object ReadCell(string address)
        {
            // セル読取
            return _excelWorksheet.Cells[address].Value;
        }

        /// <summary>
        /// レンジ読取(R1C1R2C2形式)
        /// </summary>
        /// <param name="rowFrom">row From</param>
        /// <param name="colFrom">col From</param>
        /// <param name="rowTo">row To</param>
        /// <param name="colTo">col To</param>
        /// <returns>レンジ</returns>
        public object[,] ReadRange(int rowFrom, int colFrom, int rowTo, int colTo)
        {
            // レンジ読取
            return (object[,])_excelWorksheet.Cells[rowFrom, colFrom, rowTo, colTo].Value;
        }

        /// <summary>
        /// レンジ読取("A1:B2"形式)
        /// </summary>
        /// <param name="address">address("A1"形式)</param>
        /// <returns>レンジ</returns>
        public object[,] ReadRange(string addresso)
        {
            // レンジ読取
            return (object[,])_excelWorksheet.Cells[addresso].Value;
        }
    }
```

```C#:test.cs
using System;
using System.Windows.Forms;

    public partial class test: Form
    {
        public test()
        {
            InitializeComponent();
        }

        private void Btn1_Click(object sender, EventArgs e)
        {
            var excelEx = new ExcelManagerEx();
            excelEx.Open();

            // セル書込(R1C1形式)
            excelEx.WriteCell(row: 1, col: 1, value : "test");
            // セル書込("A1"形式)
            excelEx.WriteCell(address: "A1", value: 3.1415926538);

            // セル読取(R1C1形式)
            var cellValue1 = excelEx.ReadCell(row: 1, col: 1);
            // セル読取("A1"形式)
            var cellValue2 = excelEx.ReadCell("A1");
            // レンジ読取(R1C1R2C2形式)
            var rangeValue1 = excelEx.ReadRange(rowFrom: 1, colFrom: 1, rowTo: 2, colTo: 2);
            // レンジ読取("A1:B2"形式)
            var rangeValue2 = excelEx.ReadRange("A1:B2");
            // ２次元配列(0始まり)から要素を参照
            var a = rangeValue2[0, 0];    // aには、3.1415926538が入る

            // 名前を付けて保存する
            if (excelEx.SaveAs(@"e:\book2.xlsx") == false)
            {
                MessageBox.Show("ファイルが既に開かれています。\n閉じてから、再試行してください。",
                    "エラー",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
        }
    }
```

##参考
[[C#] EPPlusでExcelの基本操作](https://webbibouroku.com/Blog/Article/epplus1)
[EPPlusの使い方（目次）](https://chawatoyo.blog.fc2.com/blog-entry-15.html)