C# 操作 Excel

发布于:2025-09-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

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/contentcse/112314.html

处理异常和性能

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

网站公告

今日签到

点亮在社区的每一天
去签到