简介这份资源面向需要在C#中处理Excel文件的开发者尤其是从事数据导入导出、报表生成与数据分析的初中级程序员。内容围绕Microsoft.Office.Interop.Excel库展开涵盖创建Excel应用对象、打开工作簿、遍历单元格读取数据、写入单元格、保存工作簿以及释放COM资源等完整操作链路并附带ThreeDataReadFormExcle示例项目演示如何读取、处理和保存三个数据源的工作表内容。压缩包共58个文件以dll动态库、cs源码、xml配置、exe程序、config配置文件及resx资源文件为主另含sln解决方案与xls示例表格整体约1.55MB结构清晰便于直接运行调试。目前已有589人学习下载。通过该实例读者可掌握Interop方式操作Excel的核心流程理解进程内交互的注意事项与资源释放要点并对照示例代码快速迁移到自身的数据处理场景中。1. 从一份“脏数据”Excel说起这个C#实例到底能帮你省下多少时间如果你做过企业后台、报表工具或者数据导入功能大概率遇到过这种场景业务方甩过来一个 Excel里面合并单元格、空行、日期格式五花八门你打开 Visual Studio新建一个 Console 项目然后开始翻文档查Excel.Application怎么用。这个名为“c# excel读取 excel保存 excel操作实例”的资源就是冲着这类需求去的——它把 C# 操作 Excel 最常见的几条路径整理成了可以直接跑通的代码实例覆盖读取、写入、保存、格式处理几个核心动作。适合谁刚接触 C# 但需要快速交付 Excel 导入导出功能的新手以及想找一个干净参考实现、不想在 NuGet 包里反复试错的老手。下面我按“能干什么 → 怎么跑 → 怎么改”的顺序把这个实例拆开讲一遍。2. 技术选型与项目结构两条路线怎么选、目录里都有什么2.1 两种主流方案COM 互操作与 OpenXMLC# 操作 Excel绕不开两条路线。第一条是 Microsoft Office 的 COM 互操作也就是引用Microsoft.Office.Interop.Excel直接调用本机安装的 Excel 进程。它的优点是功能最全几乎你能在 Excel 里手动做的操作都能用代码完成格式、图表、透视表都不在话下。缺点是必须装 Office服务器上跑起来容易出进程残留速度也偏慢。第二条是 OpenXML SDK 或者第三方库如 EPPlus、NPOI直接读写.xlsx文件不依赖 Office 安装。优点是轻量、适合服务端批量处理缺点是某些复杂格式和旧版.xls支持有限。这个实例通常会把两条路线都给出示例让读者根据部署环境自己选。提示如果你的程序要部署到没有 Office 的服务器上优先看 OpenXML 或 NPOI 那部分代码不要直接照搬 COM 互操作。2.2 目录结构一个典型的实例工程长什么样这类实例工程一般不会太复杂常见做法是建一个 Console 或 WinForms 项目按功能拆成几个文件夹。下面是我拆过的一个典型结构你可以对照自己拿到的源码看ExcelDemo/ ├── Program.cs // 入口调用各个示例方法 ├── Interop/ │ ├── ExcelReader.cs // COM 方式读取 │ ├── ExcelWriter.cs // COM 方式写入与保存 │ └── ExcelFormatter.cs // 单元格格式、列宽、合并 ├── OpenXml/ │ ├── OpenXmlReader.cs // OpenXML 读取 │ └── OpenXmlWriter.cs // OpenXML 写入 ├── Models/ │ └── Employee.cs // 示例数据模型 └── Files/ └── sample.xlsx // 测试用 ExcelProgram.cs里通常是一个Main方法按顺序调用读取、写入、保存几个示例方便你一次性看到效果。Models文件夹放的是映射 Excel 行的实体类比如Employee有Name、Department、HireDate几个属性。Files里放一个测试用的sample.xlsx避免你跑代码时找不到文件。2.3 依赖与运行环境COM 路线需要本机安装 Microsoft OfficeExcel 组件项目里引用Microsoft.Office.Interop.Excel。OpenXML 路线需要安装DocumentFormat.OpenXmlNuGet 包或者EPPlus、NPOI。目标框架一般是 .NET Framework 4.7.2 或 .NET 6/8取决于实例的年代。如果你拿到的是老版本可能还是packages.config管理依赖新版本则是PackageReference。注意用 COM 互操作时Excel.Application对象用完必须调用Quit()并释放Marshal.ReleaseComObject否则任务管理器里会残留EXCEL.EXE进程跑几次就把内存吃满了。3. 核心代码拆解读取、写入、保存三个动作的完整实现3.1 读取 Excel从单元格到实体列表先看 COM 方式的读取。下面这段代码打开一个工作簿遍历第一张工作表把每一行映射成Employee对象using Excel Microsoft.Office.Interop.Excel; public ListEmployee ReadEmployees(string filePath) { var result new ListEmployee(); Excel.Application app new Excel.Application(); Excel.Workbook workbook null; Excel.Worksheet sheet null; try { workbook app.Workbooks.Open(filePath); sheet (Excel.Worksheet)workbook.Sheets[1]; // 第一张表 Excel.Range usedRange sheet.UsedRange; int rowCount usedRange.Rows.Count; int colCount usedRange.Columns.Count; // 从第 2 行开始假设第 1 行是表头 for (int row 2; row rowCount; row) { var emp new Employee { Name (sheet.Cells[row, 1] as Excel.Range)?.Text ?? , Department (sheet.Cells[row, 2] as Excel.Range)?.Text ?? , HireDate DateTime.TryParse( (sheet.Cells[row, 3] as Excel.Range)?.Text, out var d) ? d : DateTime.MinValue }; result.Add(emp); } } finally { // 释放顺序sheet - workbook - app if (sheet ! null) Marshal.ReleaseComObject(sheet); if (workbook ! null) { workbook.Close(false); Marshal.ReleaseComObject(workbook); } if (app ! null) { app.Quit(); Marshal.ReleaseComObject(app); } } return result; }逻辑说明UsedRange拿到实际有数据的区域避免遍历整个工作表Excel 一张表有 100 多万行全遍历会卡死。Cells[row, col]返回的是Range对象用.Text拿显示文本比.Value更安全因为.Value可能返回null或double。日期列用DateTime.TryParse兜底防止格式不统一导致抛异常。参数说明filePath是 Excel 文件的绝对路径建议用Path.GetFullPath处理一下。Sheets[1]是索引从 1 开始的不是 0这点和 C# 数组习惯不同容易翻车。如果用 OpenXML读取逻辑会变成SpreadsheetDocument.Open加WorksheetPart遍历代码更长但不需要装 Office。实例里一般会给出一个简化版核心是Cell.DataType判断和CellValue取值。3.2 写入与保存新建工作簿并填充数据写入部分COM 方式通常是新建一个Workbook往单元格里塞值最后SaveAspublic void WriteEmployees(ListEmployee employees, string outputPath) { Excel.Application app new Excel.Application(); Excel.Workbook workbook app.Workbooks.Add(); Excel.Worksheet sheet (Excel.Worksheet)workbook.Sheets[1]; try { // 写表头 sheet.Cells[1, 1] 姓名; sheet.Cells[1, 2] 部门; sheet.Cells[1, 3] 入职日期; // 写数据 for (int i 0; i employees.Count; i) { sheet.Cells[i 2, 1] employees[i].Name; sheet.Cells[i 2, 2] employees[i].Department; sheet.Cells[i 2, 3] employees[i].HireDate.ToString(yyyy-MM-dd); } // 自动调整列宽 sheet.Columns.AutoFit(); // 保存为 xlsx注意格式常量 workbook.SaveAs(outputPath, Excel.XlFileFormat.xlOpenXMLWorkbook); } finally { workbook.Close(false); app.Quit(); Marshal.ReleaseComObject(sheet); Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(app); } }逻辑说明Workbooks.Add()创建一个新工作簿默认有一张 Sheet。写值时直接给Cells赋值Excel 会自动识别类型。AutoFit()让列宽自适应避免内容被截断。SaveAs的第二个参数指定文件格式xlOpenXMLWorkbook对应.xlsx如果写成xlExcel8就是.xls。参数说明outputPath要包含文件名和扩展名比如D:\output\result.xlsx。如果文件已存在SaveAs会弹窗询问是否覆盖在服务端跑的时候要先把app.DisplayAlerts设为false否则会卡住。提示DisplayAlerts false这行最好在Application创建后立刻加上能避免大部分“弹窗卡死”的问题。3.3 保存时的格式与兼容性处理保存这一步有几个细节容易忽略。第一文件格式常量要选对.xlsx用xlOpenXMLWorkbook.xls用xlExcel8.csv用xlCSV。第二如果目标路径的目录不存在SaveAs会直接抛异常所以保存前要Directory.CreateDirectory。第三用 OpenXML 写入时保存就是document.Save()但要注意SharedStringTablePart的创建否则文本内容会丢失。实例里通常还会演示如何设置单元格背景色、字体加粗、边框这些通过Range.Interior.Color、Range.Font.Bold等属性完成。代码不复杂但属性名多建议对着实例里的ExcelFormatter.cs抄一遍。4. 避坑与排查跑这个实例时最容易翻车的几个点4.1 现象程序跑完任务管理器里一堆 EXCEL.EXE原因COM 对象没有正确释放Quit()和ReleaseComObject漏了或者顺序不对。解决严格按sheet → workbook → app的顺序释放并且workbook.Close(false)要在ReleaseComObject之前调用。如果还是残留检查有没有在try块里提前return导致finally没执行。4.2 现象读取时日期变成数字比如 44562原因Excel 内部日期以序列号存储.Text拿到的可能是格式化后的字符串但某些区域设置下会直接返回数字。解决用DateTime.FromOADate(double.Parse(...))转换或者统一用.Value2取值后判断类型。实例里一般会写一个ParseDate辅助方法处理这种情况。4.3 现象OpenXML 读取时CellValue为 null原因单元格是共享字符串SharedString值存在SharedStringTablePart里直接读CellValue拿不到。解决先判断Cell.DataType CellValues.SharedString然后去SharedStringTablePart.SharedStringTable里按索引取。这个坑几乎每个用 OpenXML 的人都会踩一次。4.4 现象保存时提示“文件正在被另一个进程使用”原因上一次运行的 Excel 进程没退干净或者文件被手动打开着。解决保存前先检查目标文件是否存在存在就换个文件名或者先File.Delete。更稳妥的做法是用using包裹SpreadsheetDocument确保文件句柄释放。4.5 现象列宽 AutoFit 后中文显示不全原因AutoFit对中文的宽度计算有时不准尤其是字体不是默认宋体的时候。解决手动设置ColumnWidth比如sheet.Columns[1].ColumnWidth 15根据内容长度估算一个值。实例里如果没写可以自己补一个SetColumnWidth方法。5. 二次开发改造从示例到生产可用的几个关键调整拿到这个实例后直接扔到生产环境大概率是不够用的。我一般会做三件事第一把 COM 操作封装成一个ExcelService类实现IDisposable用using保证释放第二加一层异常处理和日志记录哪一行哪一列解析失败而不是整个导入挂掉第三如果数据量大把逐行Cells赋值改成批量写入二维数组速度能差出十倍。下面是一个批量写入的改造示例public void WriteBatch(ListEmployee employees, string outputPath) { var data new object[employees.Count 1, 3]; data[0, 0] 姓名; data[0, 1] 部门; data[0, 2] 入职日期; for (int i 0; i employees.Count; i) { data[i 1, 0] employees[i].Name; data[i 1, 1] employees[i].Department; data[i 1, 2] employees[i].HireDate.ToString(yyyy-MM-dd); } Excel.Application app new Excel.Application(); app.DisplayAlerts false; Excel.Workbook workbook app.Workbooks.Add(); Excel.Worksheet sheet (Excel.Worksheet)workbook.Sheets[1]; Excel.Range startCell sheet.Cells[1, 1]; Excel.Range endCell sheet.Cells[employees.Count 1, 3]; Excel.Range range sheet.Range[startCell, endCell]; range.Value2 data; // 一次性写入比逐格赋值快很多 workbook.SaveAs(outputPath, Excel.XlFileFormat.xlOpenXMLWorkbook); workbook.Close(false); app.Quit(); Marshal.ReleaseComObject(range); Marshal.ReleaseComObject(sheet); Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(app); }逻辑说明把数据先装进object[,]二维数组然后通过Range.Value2一次性写入。Value2比Value快因为它不处理日期和货币的 COM 包装。参数说明数组的行列要和Range的尺寸严格对应否则会抛“类型不匹配”。验证方法跑完后打开生成的 Excel检查行数是否和employees.Count一致日期列是否显示为yyyy-MM-dd而不是数字。如果要做单元测试可以把SaveAs换成保存到MemoryStream但 COM 方式不支持流式保存所以测试时用临时文件跑完删掉。还有一个改造点是支持.xls和.xlsx自动识别。读取时根据扩展名决定用 COM 还是 OpenXML写入时根据业务需求选格式。我一般会在ExcelService里加一个GetFormat(string path)方法返回对应的XlFileFormat枚举。从那以后我每次写 Excel 导入导出都会先把释放逻辑和批量写入这两块搭好再填业务代码。因为血泪经验告诉我功能跑通只是第一步进程残留和性能问题才是上线后最容易被投诉的。希望帮到你。本文还有配套的精品资源点击获取