C# DataGridViewからExcelへ大量データを高速で出力する

1万行のデータを1秒以内で送信するテクニックです。

# ポイント
:::note 
**大切なポイント**
１．EXCELとの通信回数を最小限にする。
２．データ全体を１つの２次元配列オブジェクトへ加工し、転送を１回で済ませる。
:::

# 実行イメージ
![EXCEL出力.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546646/230a0117-97ad-9b06-67e8-943b2cf92218.gif)

:::note 
**1万行 ✕ 8列 のデータをEXCEL出力するための時間**
　　　計測時間 2.737ミリ秒  
:::
:::note warn
※ 上記のGIFは等速ですが、EXCELオブジェクトの作成や書式設定など
転送以外の処理も含んでいるので全体で2秒程度掛かっています。
:::
【実行環境】
　　　・CPU i7-7000 3.6GHz
　　　・メモリ 16GB 

# DataGridViewのDataSourceを２次元配列へ加工し、一回でEXCELへ転送する
```C#:
private void SetExcelList(Excel.Worksheet CurrentSheet)
{
    // dataGridViewからデータソースを取得する。
    BindingSource bs = (BindingSource)dataGridView.DataSource;
    var fullDataSource = (List<Object>)bs.DataSource;

    // 行数
    int rows = fullDataSource.Count;

    // 0行ならなにもしない
    if (rows == 0) return;

    // 列数
    IList<PropertyInfo> props = new List<PropertyInfo>(
        fullDataSource[0].GetType().GetProperties());
    int cols = props.Count;

    // 一回で転送するための２次元オブジェクト
    object[,] ListToExcel = new object[rows, cols];

    int row = 0;
    foreach (object rowItem in fullDataSource)
    {
        int col = 0;
        foreach (PropertyInfo prop in props)
        {
            ListToExcel[row, col] = prop.GetValue(rowItem, null);
            col++;
        }
        row++;
    }

    // 出力先を指定して転送
    Excel.Range range = CurrentSheet.Cells[2, 1];    //1行目はヘッダーを表示するので2行目に先頭行を表示
    range = range.get_Resize(rows, cols);
    range.Value = ListToExcel;
}
```
# 参考：EXCELシートの準備
```C#:
public void SendToExcel()
{
    if (dataGridView.Rows.Count == 0) return;

    //Excelオブジェクトの初期化
    Excel.Application ExcelApp = null;
    Excel.Workbooks exCurrentBooks = null;
    Excel.Workbook exCurrentBook = null;
    Excel.Worksheet exCurrentSheet = null;

    try
    {
        //Excelシートのインスタンスを作る
        ExcelApp = new Excel.Application();

        exCurrentBooks = ExcelApp.Workbooks;
        exCurrentBook = ExcelApp.Workbooks.Add();
        exCurrentSheet = exCurrentBook.Worksheets[1];
        exCurrentSheet.Select(Type.Missing);

        // ヘッダー
        SetExcelHeader(exCurrentSheet);

        // リスト
        SetExcelList(exCurrentSheet);

        // 書式
        SetExcelFormat(exCurrentSheet);

    }

    finally
    {

        exCurrentSheet.Cells.Columns.AutoFit();

        //excel表示
        ExcelApp.Visible = true;

        // Excelオブジェクトの開放
        Marshal.ReleaseComObject(exCurrentSheet);
        exCurrentSheet = null;
        Marshal.ReleaseComObject(exCurrentBook);
        exCurrentBook = null;
        Marshal.ReleaseComObject(exCurrentBooks);
        exCurrentBooks = null;

        // EXCELオブジェクトのガベージ コレクトを強制。
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        // Application オブジェクトを破棄。
        Marshal.ReleaseComObject(ExcelApp);
        ExcelApp = null;

        // Application オブジェクトのガベージ コレクトを強制。
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
}
```


# 参考：EXCELのヘッダー
```C#:
private void SetExcelHeader(Excel.Worksheet CurrentSheet)
{
    int cols = dataGridView.Columns.Count;
    string[] Header = new string[cols];

    int col = 0;
    foreach (DataGridViewColumn column in dataGridView.Columns)
    {
        Header[col] = column.HeaderText;
        col++;
    }

    Excel.Range rangeHeader = (Excel.Range)CurrentSheet.Cells[1, 1];
    rangeHeader = rangeHeader.get_Resize(1, cols);

    rangeHeader.Value = Header;
    rangeHeader.Interior.Color = Color.DarkBlue;
    rangeHeader.Font.Color = Color.White;
    rangeHeader.Font.Bold = true;
}
```

# 参考：EXCELの書式
```C#:
private void SetExcelFormat(Excel.Worksheet CurrentSheet)
{
   int col = 0;
    foreach (DataGridViewColumn column in dataGridView.Columns)
    {
        string currentLetter = IntToChars(col);
        Excel.Range range = CurrentSheet.get_Range(currentLetter + ":" + currentLetter);

        if (column.DefaultCellStyle.Format == "MM/dd")
        {
            // グリッド上での日付を年月日表示
            range.NumberFormatLocal = "yyyy/MM/dd";
        }
        else if (column.DefaultCellStyle.Format == "C")
        {
            // グリッド上での通貨を円で表示
            range.NumberFormatLocal = "\\ #,##0";
        }
        else
        {
            // バーコードの様な長い数字を省略せずに表示する
            range.NumberFormatLocal = "0";
        }
        col++;
    }
}

// 列番号の数値をEXCELの列番号（A列とか）へ変換（A列～ZZ列までの範囲）
private string IntToChars(int value)
{
    int i上1桁 = (int)Math.Floor(value / 26.0) - 1;
    int i下1桁 = (int)(value % 26.0);

    string c上1桁 = "";
    if (i上1桁 >= 0 )
    {
        ((char)(i上1桁 + 65)).ToString();
    }
    string c下1桁 = ((char)(i下1桁 + 65)).ToString();

    return c上1桁 + c下1桁;
}
```

# サンプルソースの公開
https://github.com/masayahak/GridPager