Excel 作為最通用的試算表格式,是企業報表、資料匯出、列印客製化的首選。本文完整說明 C# 透過 COM 互操作操作 Excel 的架構、核心物件模型、實作範例、記憶體管理與部署注意事項。
| 層級 | 物件 | 說明 |
|---|---|---|
| 1 | Application | Excel 應用程式本身,進入點 |
| 2 | Workbook | 活頁簿(.xlsx 檔案),含多個工作表 |
| 3 | Worksheet | 工作表(分頁),含儲存格 |
| 4 | Range | 儲存格區塊,核心操作單位 |
Microsoft.Office.Interop.Excel.dll)using Excel = Microsoft.Office.Interop.Excel;using Excel = Microsoft.Office.Interop.Excel;
var app = new Excel.Application();
app.Visible = true; // 可見模式方便除錯,生產環境建議 false
var workbook = app.Workbooks.Add(Type.Missing);
var worksheet = (Excel.Worksheet)workbook.ActiveSheet;
// 寫入單一儲存格
worksheet.Cells[1, 1] = "標題";
worksheet.Cells[1, 2] = "數值";
// 批次寫入 Range(效能較好)
var range = worksheet.Range["A2", "B10"];
range.Value2 = new object[,]
{
{ "項目1", 100 },
{ "項目2", 200 },
{ "項目3", 300 }
};
// 儲存
workbook.SaveAs(@"C:\Temp\Report.xlsx");
workbook.Close();
app.Quit();
public void ExportToExcel(string connectionString, string query, string outputPath)
{
Excel.Application app = null;
Excel.Workbook wb = null;
Excel.Worksheet ws = null;
try
{
app = new Excel.Application();
app.DisplayAlerts = false; // 關閉覆蓋確認對話框
wb = app.Workbooks.Add();
ws = (Excel.Worksheet)wb.ActiveSheet;
using (var conn = new System.Data.OleDb.OleDbConnection(connectionString))
using (var cmd = new System.Data.OleDb.OleDbCommand(query, conn))
{
conn.Open();
using (var reader = cmd.ExecuteReader())
{
// 寫入欄位名
for (int i = 0; i < reader.FieldCount; i++)
ws.Cells[1, i + 1] = reader.GetName(i);
// 寫入資料
int row = 2;
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
ws.Cells[row, i + 1] = reader.GetValue(i)?.ToString();
row++;
}
}
}
// 自動調整欄寬
ws.Columns.AutoFit();
wb.SaveAs(outputPath);
}
finally
{
// 關鍵:正確釋放 COM 物件
ReleaseComObject(ws);
ReleaseComObject(wb);
if (app != null)
{
app.Quit();
ReleaseComObject(app);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void ReleaseComObject(object obj)
{
if (obj != null && System.Runtime.InteropServices.Marshal.IsComObject(obj))
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
}
}
Excel Interop 屬於 非受控 COM 物件,GC 不會自動釋放。必須:
Marshal.ReleaseComObject()app.Workbooks.Add() 會產生中間 RCW 物件洩漏// 錯誤:產生無法釋放的中間物件
var ws = app.Workbooks.Add().ActiveSheet;
// 正確:每層存變數再釋放
var wb = app.Workbooks.Add();
var ws = (Excel.Worksheet)wb.ActiveSheet;
// ... 使用 ...
ReleaseComObject(ws);
ReleaseComObject(wb);
| 技巧 | 說明 |
|---|---|
| 關閉螢幕更新 | app.ScreenUpdating = false; |
| 關閉自動計算 | app.Calculation = Excel.XlCalculation.xlCalculationManual; |
| 批次寫入 Range.Value2 | 一次寫入二維陣列 object[,],避免逐格寫入 |
| 避免在迴圈中存取 Cells | 每次存取都是跨程序呼叫,極慢 |
| 使用 UsedRange | 僅處理有資料區域 |
| 現象 | 原因 | 解決 |
|---|---|---|
| Excel 程序殘留不關閉 | COM 物件未釋放 | 完整 ReleaseComObject + GC.Collect |
| 存取被拒絕 (0x800A03EC) | 檔案被佔用/唯讀/路徑錯誤 | 檢查路徑、關閉 Excel、檢查權限 |
| 中文亂碼 | 編碼不匹配 | 確保 .NET 字串正確傳遞,Range.Value2 接受 Unicode |
| 大量資料極慢 | 逐格跨程序呼叫 | 改用 Range.Value2 = object[,] |
新專案優先考慮 EPPlus 或 ClosedXML,僅在需操作巨集、列印、圖表、樞紐分析等 Excel 獨有功能時,才使用 Interop。Interop 適合「自動化既有 Excel 檔案」,不適合「高效能產生報表」。