C# 中操作 Excel 有许多强大的库可供选择,它们各有特点,适合不同的场景。下面我用一个表格汇总这些常见库及其特点,并辅以说明和代码示例
| 库名称 | 类型 | 优点 | 缺点 | 适用场景 | NuGet 安装命令 |
|---|---|---|---|---|---|
| ClosedXML | 开源 | API 直观易用,无需 Excel 环境,性能较好 | 对 旧版 .xls 格式支持有限 | 快速创建、读取、修改 .xlsx 文件,报表导出 | Install-Package ClosedXML |
| EPPlus | 开源 | 功能丰富,性能优秀,支持图表、数据验证、条件格式等 | v6+ 版本商用需授权 (非商业用途免费,LGPL 协议下 v5 及以下版本可免费商用) | 复杂的 Excel 操作和数据导出 | Install-Package EPPlus |
| NPOI | 开源 | 无需 Office COM 组件,同时支持 .xls 和 .xlsx | API 相对底层,使用稍复杂 | 需要处理 旧版 .xls 格式 | Install-Package NPOI |
| Microsoft.Office.Interop.Excel | 官方 COM | 功能最全面,能实现 Excel 几乎所有功能 | 严重依赖本地安装的 Excel,性能开销大,稳定性相对较低,不适合服务器端 | 客户端应用且需要与 Excel 深度交互 | 通过 Visual Studio 的 COM 引用添加 |
| ExcelDataReader | 开源 | 专注于数据读取,速度快,内存占用低 | 主要用于读取,写入功能很弱 | 快速读取大量 Excel 数据到 DataSet/DataTable | Install-Package ExcelDataReader |
| Spire.XLS | 商业 | 功能强大,支持转换(如转 PDF),无需 Excel 环境 | 免费版有功能和水印限制,商用需购买授权 | 需要高级功能(如转换)且预算允许的项目 | Install-Package FreeSpire.XLS (免费版) |
如何选择 Excel 操作库
- 优先考虑开源方案:大多数情况下,ClosedXML (易用性优先) 或 EPPlus v5 (功能丰富且可免费商用) 或 NPOI (需处理旧版
.xls) 是不错的选择。 - 专注高性能读取:选择 ExcelDataReader。
- 需要与 Excel 进程深度交互:仅在客户端环境且必需时使用 Microsoft.Office.Interop.Excel。
- 有预算且需要强大功能:考虑 Spire.XLS 等商业库。
使用示例:ClosedXML 和 ExcelDataReader
下面我们重点看一下 ClosedXML (因其易用性) 和 ExcelDataReader (因其读取专业性) 的封装示例。
ClosedXML 封装示例 (写入与读取)
ClosedXML 提供了非常直观的 API 来操作 Excel。
using ClosedXML.Excel;
using System.Data;public class ClosedXmlExcelHelper : IDisposable
{private XLWorkbook _workbook;private IXLWorksheet _worksheet;/// <summary>/// 打开或创建一个 Excel 文件/// </summary>/// <param name="filePath">文件路径</param>public void OpenOrCreate(string filePath){if (File.Exists(filePath)){_workbook = new XLWorkbook(filePath);}else{_workbook = new XLWorkbook();}}/// <summary>/// 选择或创建一个工作表/// </summary>/// <param name="sheetName">工作表名称</param>public void SelectOrCreateWorksheet(string sheetName = "Sheet1"){if (_workbook.Worksheets.TryGetWorksheet(sheetName, out var ws)){_worksheet = ws;}else{_worksheet = _workbook.Worksheets.Add(sheetName);}}/// <summary>/// 写入单个单元格数据/// </summary>public void SetCellValue(int row, int column, object value){_worksheet.Cell(row, column).Value = value;}/// <summary>/// 写入一行数据/// </summary>public void WriteRow(int startRow, int startColumn, params object[] values){for (int i = 0; i < values.Length; i++){SetCellValue(startRow, startColumn + i, values[i]);}}/// <summary>/// 写入 DataTable 到工作表/// </summary>public void WriteDataFromTable(DataTable dataTable, bool includeHeader = true, int startRow = 1){if (includeHeader){for (int i = 0; i < dataTable.Columns.Count; i++){SetCellValue(startRow, i + 1, dataTable.Columns[i].ColumnName);}startRow++;}for (int rowIdx = 0; rowIdx < dataTable.Rows.Count; rowIdx++){for (int colIdx = 0; colIdx < dataTable.Columns.Count; colIdx++){SetCellValue(startRow + rowIdx, colIdx + 1, dataTable.Rows[rowIdx][colIdx]);}}}/// <summary>/// 将指定范围读取到 DataTable/// </summary>public DataTable ReadRangeToDataTable(int startRow, int startColumn, int numRows, int numColumns, bool firstRowIsHeader = false){var dataTable = new DataTable();var range = _worksheet.Range(startRow, startColumn, startRow + numRows - 1, startColumn + numColumns - 1);if (firstRowIsHeader){var headerRow = range.FirstRow();foreach (var cell in headerRow.Cells()){dataTable.Columns.Add(cell.Value.ToString());}startRow++;numRows--;}else{for (int i = 1; i <= numColumns; i++){dataTable.Columns.Add($"Column{i}");}}var dataRange = firstRowIsHeader ? range.Range(2, 1, numRows, numColumns) : range;foreach (var row in dataRange.Rows()){DataRow dataRow = dataTable.NewRow();for (int i = 1; i <= numColumns; i++){dataRow[i - 1] = row.Cell(i).Value;}dataTable.Rows.Add(dataRow);}return dataTable;}/// <summary>/// 保存文件/// </summary>public void Save(string filePath = null){_workbook.SaveAs(filePath);}public void Dispose(){_workbook?.Dispose();}
}
使用 ClosedXMLHelper:
// 使用示例
using (var excelHelper = new ClosedXmlExcelHelper())
{// 创建新文件并写入excelHelper.OpenOrCreate("test.xlsx");excelHelper.SelectOrCreateWorksheet("Data");excelHelper.SetCellValue(1, 1, "Hello");excelHelper.SetCellValue(1, 2, "World");excelHelper.WriteRow(2, 1, new object[] { 1, "Alice", 25 });excelHelper.WriteRow(3, 1, new object[] { 2, "Bob", 30 });// 保存excelHelper.Save("test.xlsx");
}// 读取示例
using (var excelHelper = new ClosedXmlExcelHelper())
{excelHelper.OpenOrCreate("test.xlsx");excelHelper.SelectOrCreateWorksheet("Data");DataTable dt = excelHelper.ReadRangeToDataTable(1, 1, 3, 3, true);// 处理 dt...
}
ExcelDataReader 封装示例 (专注于读取)
ExcelDataReader 非常适合快速将 Excel 数据读取到 DataSet 或 DataTable 中。
using ExcelDataReader;
using System.Data;
using System.Text;public class ExcelReaderHelper
{/// <summary>/// 读取 Excel 文件到 DataSet/// </summary>public DataSet ReadExcelToDataSet(string filePath, bool useHeaderRow = false){Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 重要:支持旧编码using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read))using (var reader = ExcelReaderFactory.CreateReader(stream)){var configuration = new ExcelDataSetConfiguration(){ConfigureDataTable = (_) => new ExcelDataTableConfiguration(){UseHeaderRow = useHeaderRow // 指示第一行是否作为列名}};return reader.AsDataSet(configuration);}}/// <summary>/// 读取指定工作表到 DataTable/// </summary>public DataTable ReadSheetToDataTable(string filePath, string sheetName = null, bool useHeaderRow = false){DataSet ds = ReadExcelToDataSet(filePath, useHeaderRow);if (sheetName != null){return ds.Tables[sheetName];}else{return ds.Tables[0]; // 返回第一个表}}/// <summary>/// 获取所有工作表名称/// </summary>public List<string> GetSheetNames(string filePath){Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read))using (var reader = ExcelReaderFactory.CreateReader(stream)){var result = reader.AsDataSet();return result.Tables.Cast<DataTable>().Select(t => t.TableName).ToList();}}
}
使用 ExcelReaderHelper:
// 使用示例
var excelReader = new ExcelReaderHelper();
try
{// 读取第一个工作表,且第一行是标题DataTable dt = excelReader.ReadSheetToDataTable("data.xlsx", null, true);foreach (DataRow row in dt.Rows){// 处理每一行数据Console.WriteLine($"{row["Name"]}, {row["Age"]}");}// 获取所有工作表名List<string> sheetNames = excelReader.GetSheetNames("data.xlsx");
}
catch (Exception ex)
{Console.WriteLine($"读取失败: {ex.Message}");
}
推荐项目 C# EXCEL完整封装 www.youwenfan.com/contentcnl/112314.html
处理异常和性能
- 异常处理:务必使用
try-catch块包裹 Excel 操作代码,妥善处理IOException、UnauthorizedAccessException等可能出现的异常。 - 性能:处理大量数据时,注意:
- 释放资源:使用
using语句或手动调用Dispose()确保释放文件句柄和 COM 对象(如果使用 Interop)。 - 分批处理:避免一次性将海量数据加载到内存,可以考虑分页读取。
- 减少交互:使用 Interop 时,尽量减少与 Excel 进程的来回调用。
- 释放资源:使用