ClosedXML 不支持 .xls 格式,仅支持 .xlsx 等新版格式;需显式 using ClosedXML.Excel;写入须调用 SaveAs();读取应使用 RowsUsed() 而非 RowCount() 或 LastCellUsed();日期可能返回字符串;字体修改需用 SetBold() 等方法;公式用 FormulaA1 写入;合并单元格仅左上角有值;不支持多线程共享实例和密码保护文件。

ClosedXML 不能直接读取 .xls(Excel 97–2003)格式,只支持 .xlsx/.xlsm/.xltx/.xltm —— 如果你手头是老式 Excel 文件,先用 Excel 或 Microsoft.Office.Interop.Excel 转成 .xlsx,否则会抛出 InvalidDataException 或静默失败。
安装包和基础写入:别漏掉 using ClosedXML.Excel;
通过 NuGet 安装 ClosedXML 后,必须显式引入命名空间,否则 XLWorkbook、IWorksheet 等类型无法识别。常见错误是只装了包却忘了加 using,编译直接报红。
基础写入示例:
using (var wb = new XLWorkbook())
{
var ws = wb.Worksheets.Add("Sheet1");
ws.Cell("A1").Value = "Hello";
ws.Cell(2, 1).Value = 123; // 行号在前,列号在后
wb.SaveAs("output.xlsx");
}
-
Cell("A1")和Cell(2, 1)是等价的,但后者更利于循环写入 - 列号从 1 开始(不是 0),
Cell(1, 1)对应 A1 - 不调用
SaveAs()或Save(),文件不会生成
读取已有 Excel:用 IXLWorksheet.RowsUsed() 而非 Row(1).LastCellUsed().RowNumber()
很多人想“获取最后一行”,于是用 Row(1).LastCellUsed().RowNumber(),结果在空行或合并单元格后严重误判。正确做法是用 RowsUsed() 获取所有**有值或有样式的行**:
using (var wb = new XLWorkbook("input.xlsx"))
{
var ws = wb.Worksheet(1);
foreach (var row in ws.RowsUsed()) // 自动跳过全空行
{
var value = row.Cell(1).Value.ToString();
Console.WriteLine(value);
}
}
-
RowsUsed()比RowCount()更可靠,后者返回的是 Excel 内部记录的“最大行数”,哪怕整张表只填了 A1,也可能返回 1048576 - 如果必须按行号遍历,用
ws.Rows(1, ws.LastRowUsed().RowNumber()) - 读取日期时,
Cell.Value默认返回DateTime,但若单元格格式为文本且内容是 “2023-01-01”,则返回字符串 —— 需手动TryParse
样式与公式:设置字体后必须调用 Cell.Style 的 setter,不能只改 Font 属性
以下代码无效:
var cell = ws.Cell("A1");
cell.Value = "Bold";
cell.Style.Font.Bold = true; // ❌ 不生效
正确写法是:
var cell = ws.Cell("A1");
cell.Value = "Bold";
cell.Style.Font.SetBold(true); // ✅ 推荐
// 或
cell.Style = cell.Style.Clone().Font.SetBold(true); // ✅
-
XLFont是只读结构,所有修改方法(如SetBold、SetFontName)都返回新实例,原对象不变 - 公式写入用
cell.FormulaA1 = "=SUM(A2:A10)",不要用Value;读取公式结果用cell.Value,读取原始公式用cell.FormulaA1 - 合并单元格后,只有左上角单元格保留值,其余
Value为null—— 判断是否合并请用cell.MergedRange()
最常被忽略的是:ClosedXML 在多线程下操作同一个 XLWorkbook 实例会崩溃,即使只读也不安全;每次操作都应新建实例或加锁。另外,它不支持密码保护的 Excel 文件 —— 尝试打开会直接抛 InvalidOperationException,没有绕过方式。


















