1. 项目概述Excel纵横相同列自动合并功能实现在数据处理和报表生成领域Excel的合并单元格功能是提升表格可读性的重要手段。传统手动合并方式在面对成百上千行数据时效率极低而C#作为企业级开发语言配合Office Interop或第三方库能够实现高效的自动化处理。这个功能的核心价值在于根据列内容的相同性自动判断合并范围支持横向同行多列和纵向同列多行两种合并模式。想象一下人力资源部门的考勤表当多个员工属于同一部门时部门列可以自动合并或者财务系统中相同科目的多个月份数据可以自动合并显示。2. 技术选型与准备2.1 主流Excel操作库对比在C#生态中操作Excel主要有三种方案方案优点缺点适用场景Office Interop官方支持功能最全依赖Office安装性能较差简单需求已安装OfficeEPPlus纯托管代码性能优秀仅支持xlsx格式新项目首选NPOI支持xls/xlsx跨平台性好API设计较老旧需要兼容旧格式提示如果项目需要处理xls旧格式文件必须选择NPOI新项目建议优先考虑EPPlus2.2 开发环境配置以EPPlus为例通过NuGet安装最新版本Install-Package EPPlus -Version 5.8.8基础使用模板using OfficeOpenXml; using System.IO; public class ExcelMerger { public void ProcessExcel(string filePath) { // 设置EPPlus商业授权非商业用途可跳过 ExcelPackage.LicenseContext LicenseContext.NonCommercial; FileInfo file new FileInfo(filePath); using (var package new ExcelPackage(file)) { var worksheet package.Workbook.Worksheets[0]; // 处理逻辑将在这里实现 package.Save(); } } }3. 核心算法设计与实现3.1 纵向合并算法实现纵向合并的核心是识别连续相同内容的单元格范围。以下是经过优化的实现public void MergeVerticalColumns(ExcelWorksheet worksheet, int columnIndex) { int startRow 2; // 通常从第二行开始跳过标题 int totalRows worksheet.Dimension.End.Row; string prevValue worksheet.Cells[startRow, columnIndex].Text; int mergeStart startRow; for (int row startRow 1; row totalRows 1; row) { string currentValue row totalRows ? worksheet.Cells[row, columnIndex].Text : ; // 哨兵值 if (currentValue ! prevValue) { if (mergeStart row - 1) // 需要合并的范围 { worksheet.Cells[mergeStart, columnIndex, row - 1, columnIndex].Merge true; // 垂直居中显示 worksheet.Cells[mergeStart, columnIndex].Style.VerticalAlignment OfficeOpenXml.Style.ExcelVerticalAlignment.Center; } mergeStart row; prevValue currentValue; } } }算法特点采用哨兵值技巧简化边界条件处理时间复杂度O(n)只需单次遍历自动处理空值情况合并后自动设置垂直居中显示3.2 横向合并实现方案横向合并需要考虑多列之间的关联性这里展示部门-小组两级合并的场景public void MergeHorizontalRows(ExcelWorksheet worksheet, int[] columnIndexes) { int totalRows worksheet.Dimension.End.Row; for (int row 2; row totalRows; row) { string[] prevValues columnIndexes .Select(col worksheet.Cells[row, col].Text) .ToArray(); int mergeStart columnIndexes[0]; int mergeCount 1; for (int col columnIndexes[0] 1; col columnIndexes.Last(); col) { string[] currentValues columnIndexes .Select(idx worksheet.Cells[row, idx].Text) .ToArray(); if (currentValues.SequenceEqual(prevValues)) { mergeCount; } else { if (mergeCount 1) { worksheet.Cells[row, mergeStart, row, mergeStart mergeCount - 1].Merge true; } break; } } } }4. 高级功能扩展4.1 条件合并控制实际业务中可能需要根据条件判断是否合并public bool ShouldMerge(string currentValue, string previousValue) { // 示例规则 // 1. 空值不参与合并 // 2. 特定前缀不合并 // 3. 数值差异大于阈值不合并 if (string.IsNullOrEmpty(currentValue) || string.IsNullOrEmpty(previousValue)) return false; if (currentValue.StartsWith([NO_MERGE]) || previousValue.StartsWith([NO_MERGE])) return false; if (double.TryParse(currentValue, out double currNum) double.TryParse(previousValue, out double prevNum)) { return Math.Abs(currNum - prevNum) 0.01; } return currentValue previousValue; }4.2 性能优化技巧处理大数据量时需要注意禁用公式计算worksheet.View.ShowFormulas false; worksheet.Calculate();批量操作模式using (var range worksheet.Cells[fromRow, fromCol, toRow, toCol]) { range.Style.HorizontalAlignment ExcelHorizontalAlignment.Center; range.Style.VerticalAlignment ExcelVerticalAlignment.Center; }内存管理// 处理前清理未使用的对象 GC.Collect(); GC.WaitForPendingFinalizers();5. 实战问题排查指南5.1 常见异常处理异常类型原因分析解决方案InvalidOperationException尝试合并已合并的单元格先检查Merge属性再操作ArgumentOutOfRangeException行列索引越界检查worksheet.Dimension范围IOException文件被占用或权限不足确保文件关闭使用try-catchFormatException数字格式转换失败增加格式验证逻辑5.2 调试技巧可视化调试// 临时标记待合并区域 worksheet.Cells[startRow, startCol, endRow, endCol].Style.Fill.PatternType ExcelFillStyle.Solid; worksheet.Cells[startRow, startCol, endRow, endCol].Style.Fill.BackgroundColor.SetColor(Color.Yellow);日志记录Debug.WriteLine($准备合并区域{startRow}:{startCol} - {endRow}:{endCol});单元测试建议[TestMethod] public void TestVerticalMerge() { // 准备测试数据 var testFile CreateTestExcel(); var merger new ExcelMerger(); // 执行测试 merger.MergeVerticalColumns(testFile, 1); // 验证结果 using (var package new ExcelPackage(testFile)) { var worksheet package.Workbook.Worksheets[0]; Assert.IsTrue(worksheet.Cells[A2:A4].Merge); Assert.IsFalse(worksheet.Cells[A5].Merge); } }6. 完整实现示例以下是整合所有功能的完整类实现public class SmartExcelMerger { public void ProcessWorkbook(string inputPath, string outputPath, int[] verticalColumns, int[] horizontalGroups) { FileInfo inputFile new FileInfo(inputPath); FileInfo outputFile new FileInfo(outputPath); using (var package new ExcelPackage(inputFile)) { var worksheet package.Workbook.Worksheets[0]; // 纵向合并处理 foreach (var col in verticalColumns) { MergeVerticalColumns(worksheet, col); } // 横向合并处理 foreach (var group in horizontalGroups) { MergeHorizontalRows(worksheet, group); } // 样式优化 OptimizeStyles(worksheet); package.SaveAs(outputFile); } } private void OptimizeStyles(ExcelWorksheet worksheet) { // 设置所有单元格自动换行 worksheet.Cells.Style.WrapText true; // 设置默认字体 worksheet.Cells.Style.Font.Name Microsoft YaHei; worksheet.Cells.Style.Font.Size 10; // 添加边框 var fullRange worksheet.Cells[worksheet.Dimension.Address]; fullRange.Style.Border.Top.Style ExcelBorderStyle.Thin; fullRange.Style.Border.Bottom.Style ExcelBorderStyle.Thin; fullRange.Style.Border.Left.Style ExcelBorderStyle.Thin; fullRange.Style.Border.Right.Style ExcelBorderStyle.Thin; } // 其他方法同前文实现... }使用示例var merger new SmartExcelMerger(); merger.ProcessWorkbook( inputPath: D:\input.xlsx, outputPath: D:\output.xlsx, verticalColumns: new[] {1, 3, 5}, // 合并A、C、E列 horizontalGroups: new[] {7, 8} // 合并G-H列组 );7. 实际应用中的经验分享预处理技巧对数据进行标准化处理去除前后空格、统一日期格式等使用Excel的TRIM()和CLEAN()函数预处理数据对可能包含换行符的内容进行统一替换性能实测数据 在i7-11800H处理器上测试不同数据量的处理时间数据行数处理列数耗时(ms)内存占用(MB)1,00051204510,0005850120100,00059,200450注意超过5万行数据建议分批次处理或考虑使用专业ETL工具样式优化建议合并后单元格建议设置居中显示添加适当的背景色区分不同合并区域冻结首行方便查看标题设置合适的列宽自适应内容异常数据应对try { // 合并操作代码 } catch (Exception ex) { // 记录错误单元格位置 var errorCell worksheet.Cells[currentRow, currentCol]; errorCell.Style.Fill.BackgroundColor.SetColor(Color.Red); // 写入错误信息 errorCell.Value $ERROR: {ex.Message.Substring(0, 30)}; errorCell.Style.Font.Color.SetColor(Color.White); }