首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 操作 Excel 完整指南:COM 互操作、陣列批量讀寫、資料導出實戰

     編輯:關於C語言

透過 Microsoft.Office.Interop.Excel (PIA),C# 可完整控制 Excel 應用程式:建立活頁簿、寫入/讀取儲存格、格式化、圖表、列印等。本文涵蓋COM 參考設定、核心物件模型、陣列批量讀寫 (效能關鍵)、WinForms 完整範例、資源釋放最佳實踐與常見坑規避。

環境準備與參考設定

  1. 專案 → 加入參考 → COM → Microsoft Excel XX.0 Object Library (XX = 版本號:14=2010, 15=2013, 16=2016/365)
  2. 命名空間別名 (建議):using Excel = Microsoft.Office.Interop.Excel;
  3. 專案屬性 → 建置 → 勾選內嵌 Interop 類型 (Embed Interop Types = True) → 部署時無需部署 PIA DLL

核心物件層級

Application (Excel.exe 進程)
  └─ Workbooks 集合
       └─ _Workbook (單一 .xls/.xlsx 檔案)
            └─ Worksheets/Sheets 集合
                 └─ _Worksheet (單一工作表)
                      └─ Range (儲存格區域) → Value2 / Formula / NumberFormat / Interior / Font / Borders

基礎操作範本


var app = new Excel.Application();
try
{
    app.Visible = false;           // 背景執行
    app.DisplayAlerts = false;     // 關閉儲存/覆蓋提示
    app.ScreenUpdating = false;    // 關閉畫面更新 (大量寫入加速)

    var wb = app.Workbooks.Add();  // 新活頁簿
    var ws = (Excel._Worksheet)wb.Worksheets[1]; // 索引從 1 開始

    // 單儲存格寫入
    ws.Cells[1, 1] = "標題";
    ws.get_Range("A1").Font.Bold = true;

    // 儲存
    wb.SaveAs(@"C:\Temp\Report.xlsx", Excel.XlFileFormat.xlOpenXMLWorkbook);
}
finally
{
    // 關鍵:正確釋放 COM 物件 (逆序)
    Marshal.ReleaseComObject(ws);
    wb.Close(false); Marshal.ReleaseComObject(wb);
    app.Quit(); Marshal.ReleaseComObject(app);
    GC.Collect(); GC.WaitForPendingFinalizers(); // 強制清理 RCW
}

高效能核心:陣列批量讀寫 (Value2)

逐儲存格存取極慢 (每次跨進程 COM 呼叫)。陣列一次讀寫 只需 1-2 次 COM 呼叫,效能提升 100-1000 倍。

寫入:二維陣列 → Range.Value2


var data = new object[1000, 5]; // 1000 列 x 5 欄
for (int r = 0; r < 1000; r++)
    for (int c = 0; c < 5; c++)
        data[r, c] = $"R{r}C{c}";

var range = ws.get_Range("A1", "E1000"); // 目標區域
range.Value2 = data; // 一次寫入!

讀取:Range.Value2 → 二維陣列


var range = ws.get_Range("A1", "E1000");
object[,] data = (object[,])range.Value2; // 1-based 索引!

// 遍歷 (注意下限為 1)
for (int r = 1; r <= data.GetUpperBound(0); r++)
    for (int c = 1; c <= data.GetUpperBound(1); c++)
        Console.Write($"{data[r, c]}\t");

關鍵細節

  • Value2 比 Value 快 (不轉換 Date/Currency 格式)
  • 陣列維度:object[row, col],下限為 1 (非 0)
  • 大資料集建議分批 (每批 5000-10000 列) 避免記憶體壓力
  • 寫入公式:陣列元素為以 = 開頭的字串

WinForms 完整互動範例


using System;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;

public partial class ExcelDemoForm : Form
{
    private Excel.Application _app;
    private Excel._Workbook _wb;

    public ExcelDemoForm() => InitializeComponent();

    // 按鈕1:建立活頁簿並寫入陣列資料
    private void btnCreate_Click(object sender, EventArgs e)
    {
        try
        {
            _app = new Excel.Application();
            _wb = _app.Workbooks.Add();
            var ws = (Excel._Worksheet)_wb.Worksheets[1];

            var range = ws.get_Range("A1", "E5");
            range = range.get_Resize(5, 5);

            if (chkString.Checked)
            {
                var data = new string[5, 5];
                for (int r = 0; r < 5; r++)
                    for (int c = 0; c < 5; c++)
                        data[r, c] = $"{r}|{c}";
                range.Value2 = data;
            }
            else
            {
                var data = new double[5, 5];
                for (int r = 0; r < 5; r++)
                    for (int c = 0; c < 5; c++)
                        data[r, c] = r * c;
                range.Value2 = data;
            }

            _app.Visible = true;
            _app.UserControl = true; // 使用者關閉視窗時自動釋放
        }
        catch (Exception ex) { MessageBox.Show($"Error: {ex.Message}"); }
    }

    // 按鈕2:從 Excel 讀取陣列並顯示
    private void btnRead_Click(object sender, EventArgs e)
    {
        try
        {
            if (_wb == null) { MessageBox.Show("請先按建立"); return; }

            var ws = (Excel._Worksheet)_wb.Worksheets[1];
            var range = ws.get_Range("A1", "E5");
            object[,] data = (object[,])range.Value2;

            var sb = new System.Text.StringBuilder("Array Data\n");
            for (int r = 1; r <= data.GetUpperBound(0); r++)
            {
                for (int c = 1; c <= data.GetUpperBound(1); c++)
                    sb.Append($"{data[r, c]}, ");
                sb.AppendLine();
            }
            MessageBox.Show(sb.ToString(), "Array Values");
        }
        catch (Exception ex) { MessageBox.Show($"Error: {ex.Message}"); }
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        if (_wb != null) { _wb.Close(false); Marshal.ReleaseComObject(_wb); }
        if (_app != null) { _app.Quit(); Marshal.ReleaseComObject(_app); }
        GC.Collect(); GC.WaitForPendingFinalizers();
        base.OnFormClosing(e);
    }
}

資料導出進階場景

場景關鍵技術
DataTable/DataSet 導出逐行寫入或 CopyFromRecordset (ADO.NET)
大量資料 (10萬+ 列)分批陣列寫入 + ScreenUpdating=false + 關閉自動計算
樣式/格式/合併儲存格Range.NumberFormat, Merge(), Borders, Interior.Color
圖表生成Charts.Add(), ChartWizard, SeriesCollection
樞紐分析表PivotTableWizard, PivotFields
無 Office 環境 (Server)EPPlus/ClosedXML/NPOI (純 .NET, 無 COM)

COM 物件釋放鐵律 (必讀)

  1. 兩個點法則:app.Workbooks.Add() 產生中間 RCW → 必須分層存變數釋放
  2. 逆序釋放:Range → Worksheet → Workbook → Application
  3. Marshal.ReleaseComObject 每個物件呼叫一次
  4. GC.Collect + WaitForPendingFinalizers 雙重呼叫確保 RCW 釋放
  5. 避免在迴圈中存取 ws.Cells[r, c] (每次產生新 RCW)

常見錯誤與解決

錯誤原因解決
0x800A03EC / 0x800401A8Excel 忙碌/對話框開啟/安全性阻擋DisplayAlerts=false, 信任中心設定, 重試機制
HRESULT: 0x80080005 (CO_E_SERVER_EXEC_FAILURE)Excel 未安裝/版本不符/64位元不匹配安裝對應版 Office, 專案目標平台設 x64/x86 一致
儲存失敗/檔案被鎖定前次進程未正確退出工作管理員殺掉 EXCEL.EXE, 加強釋放邏輯
中文亂碼編碼/字型問題確保 .NET 字串 Unicode, Excel 內部 Unicode, 字型支援中文

結語:C# 操作 Excel 核心在於陣列批量讀寫 (Value2) 與嚴格 COM 釋放。掌握這兩點,效能與穩定性即可達生產級。若部署環境無 Office (如 Linux Container、Azure Function),請直接改用 EPPlus/ClosedXML/NPOI 純 .NET 方案,API 相似且無 COM 依賴。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved