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。
csharp
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:
csharp
// 使用示例
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 中。
csharp
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:
csharp
// 使用示例
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/contentcse/112314.html
处理异常和性能
- 异常处理 :务必使用
try-catch
块包裹 Excel 操作代码,妥善处理IOException
、UnauthorizedAccessException
等可能出现的异常。 - 性能 :处理大量数据时,注意:
- 释放资源 :使用
using
语句或手动调用Dispose()
确保释放文件句柄和 COM 对象(如果使用 Interop)。 - 分批处理:避免一次性将海量数据加载到内存,可以考虑分页读取。
- 减少交互:使用 Interop 时,尽量减少与 Excel 进程的来回调用。
- 释放资源 :使用