简介:在C#开发中,处理Excel文件是数据导入导出、报表生成等场景的常见需求。本文基于Visual Studio环境,使用EPPlus开源库实现Excel文件的读取、行列数据转换及结果输出。通过详细代码示例,展示如何加载Excel工作表、遍历数据、进行行列转置,并将结果保存至新文件。项目涵盖基础操作与核心流程,适用于各类数据处理应用,帮助开发者高效完成表格转换任务。
1. C#读写Excel表格的核心机制与技术选型
在现代企业级应用中,数据导入导出是高频刚需,尤其以Excel为核心的数据交互场景极为普遍。C#依托.NET平台提供了多种操作Excel的技术路径,主要包括传统COM组件、NPOI和EPPlus等方案。其中, EPPlus基于Open XML SDK ,直接读写.xlsx文件的ZIP+XML结构,无需依赖Office安装环境,具备跨平台、高性能优势。
// 示例:EPPlus核心读取逻辑示意
using (var package = new ExcelPackage(new FileInfo("data.xlsx")))
{
var worksheet = package.Workbook.Worksheets[0];
var value = worksheet.Cells[1, 1].Value;
}
EPPlus通过 System.IO.Packaging 底层API解析Open XML格式,将Excel视为一个包含工作表、样式、共享字符串等部分的OPC(Open Packaging Conventions)容器。这种设计使其能高效处理大数据量,并支持流式读取与内存优化,为后续章节的大规模处理能力奠定基础。
2. EPPlus环境搭建与Excel文件加载实践
在现代企业级数据处理场景中,将Excel作为数据载体进行自动化操作已成为常态。C#语言依托.NET平台的强大生态,为开发者提供了多种方式读写Excel文件。其中, EPPlus 因其对Open XML标准的深度封装、简洁直观的API设计以及出色的性能表现,成为当前最受欢迎的技术选型之一。然而,在正式进入数据读取与写入逻辑之前,必须完成一系列前置准备工作——包括库的引入、项目配置、运行时上下文初始化及安全加载机制的构建。本章将系统性地讲解如何从零开始搭建一个稳定可靠的EPPlus开发环境,并通过实际编码示例展示如何正确加载Excel工作簿和获取目标工作表。
2.1 EPPlus库的引入与NuGet包配置
要在C#项目中使用EPPlus操作Excel文件,首要任务是将其依赖库成功集成到项目中。目前主流的方式是通过NuGet包管理器完成自动安装,也可手动编辑项目文件实现更精细化控制。随着.NET平台的演进,不同版本框架(如.NET Framework、.NET Core、.NET 5+)对EPPlus的支持存在差异,因此版本兼容性问题不容忽视。
2.1.1 使用Visual Studio NuGet程序包管理器安装EPPlus
对于大多数使用Visual Studio的开发者而言,最便捷的方式是通过图形化界面安装EPPlus。打开解决方案资源管理器,右键点击目标项目,选择“管理NuGet程序包”,然后切换至“浏览”选项卡,在搜索框输入 EPPlus 。
此时会列出多个相关包,应优先选择由 Erik Ivarsson 发布的官方版本:
Package ID :
EPPlus
Author : Jan Källman, Erik Ivarsson et al.
Description : A .NET library that reads and writes Excel 2007/2010/2013/2016/2019 and Office365 files using the Open Packaging Conventions (OPC).
点击“安装”按钮后,Visual Studio会自动解析依赖项并更新 .csproj 文件,添加如下 <PackageReference> 节点:
<PackageReference Include="EPPlus" Version="5.7.14" />
该过程不仅下载核心库,还会一并引入必要的底层支持组件,例如 System.Drawing.Common (用于处理单元格样式)、 DocumentFormat.OpenXml (Open XML SDK基础)等。值得注意的是,自EPPlus 5起,该库已全面转向MIT开源许可模式,无需再配置商业许可证即可免费用于生产环境。
安装验证代码示例:
using OfficeOpenXml;
Console.WriteLine($"EPPlus Version: {typeof(ExcelPackage).Assembly.GetName().Version}");
执行上述代码若能正常输出版本号(如 5.7.14.0 ),说明安装成功。
⚠️ 注意事项:首次运行可能抛出
LicenseContext not set异常,这将在后续小节详细解释并解决。
2.1.2 手动编辑.csproj文件添加PackageReference依赖
某些情况下,开发者可能需要脱离IDE或批量管理多个项目的依赖关系。此时可直接编辑 .csproj 文件,手动插入 <PackageReference> 元素以声明EPPlus依赖。
示例 .csproj 配置片段:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EPPlus" Version="5.7.14" />
</ItemGroup>
</Project>
保存文件后,在命令行执行:
dotnet restore
即可恢复所有NuGet包。这种方式特别适用于CI/CD流水线、Docker镜像构建或多模块微服务架构中的统一依赖治理。
此外,还可结合 Directory.Build.props 文件在解决方案级别统一定义EPPlus版本号,避免版本碎片化:
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<EPPlusVersion>5.7.14</EPPlusVersion>
</PropertyGroup>
</Project>
然后在各 .csproj 中引用变量:
<PackageReference Include="EPPlus" Version="$(EPPlusVersion)" />
这种集中式管理极大提升了维护效率与一致性保障能力。
2.1.3 版本兼容性注意事项与目标框架匹配(.NET Framework vs .NET Core/.NET 5+)
EPPlus的发展经历了多个重要阶段,其对运行时框架的支持随版本迭代而变化。理解这些差异对于确保应用稳定性至关重要。
| EPPlus 版本 | 支持框架 | 许可证类型 | 主要特性 |
|---|---|---|---|
| v3.x ~ v4.x | .NET Framework 4.0+ | 商业授权(需注册) | 基础读写功能,COM互操作支持弱 |
| v5.x | .NET Standard 2.0 (.NET Framework 4.6.1+, .NET Core 2.0+) | MIT 开源 | 支持流式读取、内存优化、无COM依赖 |
| v6.x | .NET Standard 2.1 / .NET 5+ | MIT 开源 | 更高性能、异步API、增强图表支持 |
兼容性决策建议:
- 若项目基于 .NET Framework 4.8 或以下版本,推荐使用 EPPlus 5.7.14 。
- 若项目采用 .NET 6 / .NET 8 ,可考虑升级至 EPPlus 6.x 系列以利用新特性。
- 不推荐在 .NET Core 1.x 或 .NET Standard 1.x 环境中使用EPPlus,因缺乏必要API支持。
特殊情况处理:System.Drawing.Common 缺失问题
在非Windows平台上(如Linux Docker容器), System.Drawing.Common 可能无法正常使用,因其依赖GDI+本地库。解决方案有两种:
- 启用跨平台图像处理 :
csharp using System.Drawing; // 在 Program.cs 或 Startup 中添加 Configuration.ImageFormats.UseGdi(); - 禁用样式读取以规避依赖 :
csharp var package = new ExcelPackage(existingFile); package.Workbook.Properties.Title = "Sample"; // 不访问 Cell.Style 相关属性
下面是一个判断当前运行环境是否支持绘图功能的辅助方法:
public static bool IsDrawingSupported()
{
try
{
using var bitmap = new Bitmap(10, 10);
return true;
}
catch (TypeInitializationException)
{
return false;
}
}
✅ 最佳实践提示:若仅做数据导入导出而不涉及字体、颜色、边框等视觉元素,建议关闭样式加载以提升性能并减少依赖冲突风险。
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 或 Commercial
2.2 初始化ExcelPackage与文件读取准备
一旦EPPlus库成功引入,下一步便是创建 ExcelPackage 实例以承载整个Excel文档的操作上下文。该类是EPPlus的核心入口,封装了ZIP压缩包解压、XML结构解析、内存模型映射等底层细节。但在实例化前,必须完成若干关键初始化步骤,包括设置许可证上下文、封装文件路径对象以及进行前置校验。
2.2.1 引用ExcelPackage类并启用许可证上下文(LicenseContext设置)
自EPPlus 5起,虽然取消了强制注册机制,但仍要求明确指定 LicenseContext 属性,否则会在构造 ExcelPackage 时抛出异常:
System.InvalidOperationException: LicenseContext must be set prior to using ExcelPackage.
此设计旨在区分使用场景(商业或非商业),便于合规审计。
正确初始化方式如下:
using OfficeOpenXml;
// 必须在任何 ExcelPackage 实例化之前调用
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 个人/教育用途
// 或者
ExcelPackage.LicenseContext = LicenseContext.Commercial; // 商业项目
🔐 设置时机强调:该语句应在应用程序启动早期执行(如
Program.Main()开头或Startup.ConfigureServices中),全局只需设置一次。
参数说明:
| 枚举值 | 适用场景 | 是否需要付费授权 |
|---|---|---|
LicenseContext.NonCommercial | 学习、测试、非盈利项目 | 否 |
LicenseContext.Commercial | 企业级产品、收费软件 | 否(MIT协议下免费) |
尽管MIT许可允许商用,但社区建议根据项目性质合理标注上下文,体现尊重原作者的努力。
2.2.2 利用FileInfo对象封装源Excel路径的安全方式
直接使用字符串路径操作文件易引发路径注入、编码错误或权限越界等问题。推荐做法是借助 System.IO.FileInfo 类进行抽象封装,提供更强的类型安全性与路径验证能力。
示例代码:
string inputPath = @"C:\Data\source.xlsx";
var fileInfo = new FileInfo(inputPath);
if (!fileInfo.Exists)
{
throw new FileNotFoundException("指定的Excel文件不存在", inputPath);
}
using var package = new ExcelPackage(fileInfo);
var workbook = package.Workbook;
FileInfo 的优势体现在:
- 自动规范化路径格式(如合并双斜杠)
- 提供
Exists,Length,LastWriteTime等元数据访问 - 与
FileStream集成良好,支持只读打开
此外,当需要频繁处理多个文件时,可扩展为泛型路径处理器:
public class ExcelFileHandler
{
private readonly FileInfo _file;
public ExcelFileHandler(string path)
{
_file = new FileInfo(path ?? throw new ArgumentNullException(nameof(path)));
if (!_file.Exists)
throw new FileNotFoundException($"文件未找到: {_file.FullName}", _file.FullName);
if ((_file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
throw new UnauthorizedAccessException("文件为只读,无法写入");
}
public ExcelPackage LoadPackage() => new(_file);
}
2.2.3 检查文件是否存在及读取权限的预判逻辑
除了基本的存在性检查外,还应评估当前进程是否有足够权限访问目标文件,尤其是在服务账户或容器环境中。
完整校验流程图(Mermaid):
graph TD
A[开始] --> B{路径是否为空?}
B -- 是 --> C[抛出 ArgumentNullException]
B -- 否 --> D[创建 FileInfo 对象]
D --> E{文件是否存在?}
E -- 否 --> F[抛出 FileNotFoundException]
E -- 是 --> G{是否被其他进程占用?}
G -- 是 --> H[等待或重试]
G -- 否 --> I{是否有读取权限?}
I -- 否 --> J[抛出 UnauthorizedAccessException]
I -- 是 --> K[返回有效 FileStream]
实现带超时重试的健壮打开逻辑:
public static ExcelPackage OpenExcelWithRetry(FileInfo file, int maxAttempts = 3, int delayMs = 500)
{
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
using var tempStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var streamCopy = new MemoryStream();
tempStream.CopyTo(streamCopy);
streamCopy.Position = 0;
return new ExcelPackage(streamCopy); // 从内存流加载,避免长期锁定文件
}
catch (IOException) when (attempt < maxAttempts)
{
Thread.Sleep(delayMs * attempt); // 指数退避
continue;
}
catch (UnauthorizedAccessException)
{
throw new UnauthorizedAccessException($"无权访问文件 '{file.FullName}'", file.FullName);
}
}
throw new IOException($"无法在 {maxAttempts} 次尝试内打开文件: {file.FullName}");
}
💡 关键点解析:
- 使用FileShare.ReadWrite允许多进程同时读取
- 将内容复制到MemoryStream可立即释放原文件句柄,防止长时间占用
- 引入指数退避策略提高并发环境下成功率
2.3 加载工作簿与获取指定工作表
Excel文件通常包含多个工作表(Sheet),每个Sheet代表一张独立的数据表格。成功加载 ExcelPackage 后,需进一步定位具体的工作表才能开展数据提取或写入操作。EPPlus提供了灵活的访问方式,既可通过名称精确查找,也可按索引顺序遍历,同时还支持动态筛选符合条件的Sheet。
2.3.1 通过Workbook属性访问工作簿实例
ExcelPackage.Workbook 是顶层容器,代表整个Excel文件的逻辑结构,其主要成员包括:
-
Worksheets:ExcelWorksheetCollection类型,持有所有Sheet -
Properties: 文档元数据(标题、作者、创建时间等) -
Names: 定义的命名区域集合
获取工作簿示例:
using var package = new ExcelPackage(new FileInfo("data.xlsx"));
var workbook = package.Workbook;
Console.WriteLine($"工作簿包含 {workbook.Worksheets.Count} 个工作表");
foreach (var sheet in workbook.Worksheets)
{
Console.WriteLine($"- [{sheet.Index}] {sheet.Name}");
}
输出示例:
工作簿包含 3 个工作表
- [1] 销售数据
- [2] 成本明细
- [3] 汇总报表
📌 内部机制简析:
Workbook在ExcelPackage构造时即完成XML反序列化,将/xl/workbook.xml中的<sheets>节点映射为内存对象树。
2.3.2 根据名称或索引获取Worksheet对象的最佳实践
方法一:通过名称获取(推荐)
var worksheet = workbook.Worksheets["销售数据"];
if (worksheet == null)
{
throw new KeyNotFoundException("找不到名为 '销售数据' 的工作表");
}
优点:语义清晰,不易受Sheet顺序变动影响。
方法二:通过索引获取
var worksheet = workbook.Worksheets[1]; // 第一个Sheet(索引从1开始!)
⚠️ 注意:EPPlus中Worksheet索引 从1开始 ,而非编程常见的0基索引。
增强版查找函数(支持模糊匹配):
public static ExcelWorksheet FindSheetByNameOrPattern(ExcelWorkbook workbook, string pattern)
{
return workbook.Worksheets.FirstOrDefault(ws =>
ws.Name.Equals(pattern, StringComparison.OrdinalIgnoreCase) ||
Regex.IsMatch(ws.Name, pattern, RegexOptions.IgnoreCase));
}
使用示例:
var targetSheet = FindSheetByNameOrPattern(workbook, @"^销售.*\d{4}$");
可匹配类似“销售汇总2024”、“Sales_2024”等命名模式。
2.3.3 多Sheet场景下的遍历与筛选策略
在复杂报表系统中,往往需要扫描所有Sheet并依据规则分类处理。
示例:提取所有含有“数据”关键词的Sheet
var dataSheets = workbook.Worksheets
.Where(sheet => sheet.Name.Contains("数据"))
.ToList();
foreach (var sheet in dataSheets)
{
ProcessDataSheet(sheet); // 自定义处理逻辑
}
表格:常见筛选条件对比
| 筛选维度 | 条件表达式 | 适用场景 |
|---|---|---|
| 名称匹配 | Name == "主表" | 固定命名规范 |
| 正则匹配 | Regex.IsMatch(Name, "Report_\\d+") | 动态编号报告 |
| 数据范围检测 | Dimension != null && Dimension.Rows > 1 | 排除空Sheet |
| 隐藏状态 | Visibility == eWorkSheetVisibility.Visible | 忽略隐藏Sheet |
结合维度信息过滤有效Sheet:
var validSheets = workbook.Worksheets
.Where(ws => ws.Dimension?.Rows > 1) // 至少有一行数据
.Where(ws => ws.Visibility == eWorkSheetVisibility.Visible)
.ToList();
✅ 生产建议:始终检查
ws.Dimension是否为null,以防访问尚未初始化的Sheet导致空引用异常。
综上所述,EPPlus的Sheet访问机制兼具灵活性与安全性,配合合理的异常处理与路径校验,可构建出高度稳健的Excel自动化流程。下一章将深入探讨如何精准读取单元格数据并实现高效遍历。
3. Excel数据读取与行列遍历技术详解
在企业级数据处理场景中,从Excel文件中准确、高效地提取结构化数据是系统集成、报表生成和数据分析的前提。C#结合EPPlus库提供了强大且灵活的API来实现对Excel内容的深度访问。本章将围绕 Excel数据模型的理解、行与列的遍历策略、以及数据清洗机制的设计 三个方面展开深入剖析。通过掌握底层坐标系统的工作方式、合理选择遍历算法,并构建鲁棒的数据预处理逻辑,开发者能够应对各种复杂表格结构,确保数据读取过程既稳定又高性能。
3.1 理解Excel单元格坐标系统与数据模型
要精准操作Excel中的每一个单元格,首先必须理解其内在的数据组织结构和坐标体系。不同于大多数编程语言数组从0开始索引的习惯,Excel采用了一套独特的基于1的行列编号机制。这种设计源于电子表格的历史沿革,但在现代开发中却容易引发边界错误或越界异常。因此,深入理解 Cell 、 Row 、 Column 三者之间的关系及其属性结构,是实现可靠数据读取的基础。
3.1.1 行列索引从1开始的设计特点及其影响
Excel的行号(Row Index)和列号(Column Index)均从1开始计数,例如A1单元格对应的是第1行第1列。这一设计虽然符合用户直观认知,但对于习惯于零基索引的程序员而言,极易导致“off-by-one”错误。例如,在使用for循环时若未正确调整起始值,可能会跳过首行或访问不存在的第0行。
更重要的是,EPPlus库完全遵循这一规范。所有涉及行、列的操作函数(如 Worksheet.Cells[row, col] )都要求传入大于等于1的整数参数。如果传递0或负数,将抛出 ArgumentOutOfRangeException 。此外,当处理动态范围时,需特别注意最大行数(MaxRows,默认为1048576)和最大列数(MaxColumns,默认为16384)的限制。
为了规避常见陷阱,建议封装一个通用的坐标校验方法:
public static bool IsValidCell(int row, int col)
{
return row >= 1 && row <= 1048576 && col >= 1 && col <= 16384;
}
该方法可用于前置判断,防止非法访问。同时,在调试阶段可通过日志输出当前处理的行列位置,辅助定位逻辑错误。
3.1.2 Cell、Row、Column对象的关系与属性结构
EPPlus中,每个工作表( ExcelWorksheet )由多个 ExcelRow 、 ExcelColumn 和 ExcelRange 组成,而具体的数据存储在 ExcelRange 所代表的单元格区域中。最核心的对象是 ExcelRange ,它既可以表示单个单元格(如 A1 ),也可以表示矩形区域(如 A1:C10 )。当我们调用 worksheet.Cells[row, col] 时,返回的就是一个 ExcelRange 实例。
以下是关键对象之间的关系图示:
classDiagram
class ExcelWorksheet {
+ExcelRowCollection Rows
+ExcelColumnCollection Columns
+ExcelRange Cells
}
class ExcelRow {
+int Row
+double Height
+bool Hidden
}
class ExcelColumn {
+int Column
+double Width
+bool Hidden
}
class ExcelRange {
+object Value
+string Text
+ExcelStyle Style
+void SetValue(object value)
}
ExcelWorksheet "1" -- "many" ExcelRow : contains
ExcelWorksheet "1" -- "many" ExcelColumn : contains
ExcelWorksheet "1" -- "many" ExcelRange : cells
ExcelRange ..> ExcelStyle : references
如上所示, ExcelWorksheet 持有行、列集合以及统一的 Cells 属性用于访问任意单元格。每个 ExcelRange 除了包含 Value 外,还支持格式化信息(字体、颜色、边框等)、公式计算、合并单元格等功能。
值得注意的是, ExcelRange 的 Value 属性类型为 object ,实际运行时根据单元格内容自动转换为 string 、 double 、 DateTime 等类型。这为后续的数据解析带来了灵活性,但也增加了类型判断的复杂性。
3.1.3 数据类型识别:字符串、数值、日期的自动解析机制
EPPlus在读取单元格时会尝试根据Excel内部存储格式推断数据类型。Excel本身并不直接保存“类型”,而是通过 格式化规则(Format String) 和 原始值(Raw Value) 共同决定显示内容。例如,一个数值 44866 配合格式 yyyy-mm-dd 会被渲染为日期 2023-01-01 。
EPPlus通过以下机制进行自动解析:
| 存储值 | 格式字符串 | EPPlus解析结果 |
|---|---|---|
| 44866 | yyyy-MM-dd | DateTime 对象 |
| 123.45 | General | double |
| ABC123 | @ | string |
代码示例如下:
var cell = worksheet.Cells[2, 1];
var rawValue = cell.Value; // 原始对象
var textValue = cell.Text; // 格式化后的文本(用户可见)
if (cell.Value is DateTime date)
{
Console.WriteLine($"Parsed as Date: {date:yyyy-MM-dd}");
}
else if (cell.Value is double num)
{
if (DateTime.FromOADate(num) != null && IsDateFormatted(cell))
{
var dt = DateTime.FromOADate(num);
Console.WriteLine($"Treated as OLE Automation Date: {dt}");
}
else
{
Console.WriteLine($"Numeric Value: {num}");
}
}
else if (cell.Value is string str)
{
Console.WriteLine($"String Content: {str.Trim()}");
}
逻辑分析与参数说明:
-
cell.Value:获取原始值,类型不确定,需做类型判断。 -
cell.Text:返回格式化后的字符串,适合直接展示给用户。 -
DateTime.FromOADate():将双精度浮点数转换为.NETDateTime,适用于Excel使用的OLE Automation Date系统(以1900年1月1日为基准)。 -
IsDateFormatted(cell):自定义辅助函数,检查单元格是否应用了日期类格式(如yyyy,mm/dd等),可基于cell.Style.Numberformat.Format判断。
该机制使得开发者既能获取精确的原始数据,又能尊重用户的格式设定,实现语义正确的数据提取。
3.2 遍历行与列的数据提取方法
在完成对单元格模型的理解后,下一步是如何系统性地遍历整个工作表,提取出完整的二维数据集。常见的需求包括导入数据库、导出JSON、生成统计报表等。为此,必须设计高效的遍历策略,平衡性能、内存占用与准确性。本节将介绍三种主流的遍历方式:基于 UsedRange 的ForEach循环、双重for循环控制、以及最终如何构建结构化的数据容器。
3.2.1 使用ForEach循环遍历所有行(UsedRange的应用)
EPPlus提供了一个便捷属性 Worksheet.Dimension ,它可以快速获取当前工作表中“已使用”的最小外接矩形区域(即非空单元格的最大边界)。结合 UsedRange 的概念,我们可以安全地遍历所有可能含有数据的行。
if (worksheet.Dimension == null)
{
Console.WriteLine("No data found in worksheet.");
return;
}
int startRow = worksheet.Dimension.Start.Row;
int endRow = worksheet.Dimension.End.Row;
foreach (var row in worksheet.Rows(startRow, endRow))
{
var rowData = new List<object>();
int startCol = worksheet.Dimension.Start.Column;
int endCol = worksheet.Dimension.End.Column;
for (int col = startCol; col <= endCol; col++)
{
var cell = worksheet.Cells[row.Row, col];
rowData.Add(cell?.Value ?? DBNull.Value);
}
Console.WriteLine(string.Join("\t", rowData));
}
逻辑分析与参数说明:
-
worksheet.Dimension:返回ExcelAddressBase类型的对象,包含起始和结束行列坐标。若无数据则为null。 -
worksheet.Rows(start, end):返回指定范围内的ExcelRow枚举器,适合逐行处理。 -
cell?.Value ?? DBNull.Value:安全访问值,避免NullReferenceException;使用DBNull.Value标记空值便于后续数据库映射。
此方法优点在于简洁高效,尤其适合稀疏但连续分布的数据表。但由于 Dimension 仅检测非空单元格,若存在隐藏内容或公式结果为空的情况,可能导致遗漏。
3.2.2 基于最大行数和列数的双重for循环控制
对于需要精确控制遍历范围的场景(如固定模板表格),可以显式指定行数和列数进行嵌套循环。这种方式更适合结构清晰、行列固定的报表读取。
int rowCount = 1000;
int colCount = 10;
var dataTable = new object[rowCount][];
for (int i = 0; i < rowCount; i++)
{
dataTable[i] = new object[colCount];
for (int j = 0; j < colCount; j++)
{
var cell = worksheet.Cells[i + 1, j + 1]; // 注意+1偏移
dataTable[i][j] = cell?.Value ?? null;
}
}
| 参数 | 含义 |
|---|---|
rowCount | 预设最大行数,避免无限扩展 |
colCount | 固定列宽,适配模板结构 |
i + 1 , j + 1 | 补偿Excel从1开始的索引 |
该方法优势在于可控性强,易于并行化或分块处理。缺点是若实际数据远小于预设范围,则会造成大量无效读取,浪费资源。
2664

被折叠的 条评论
为什么被折叠?



