C#操作Excel实例:读取、写入、保存与避坑指南
简介这份资源面向需要在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 导入导出都会先把释放逻辑和批量写入这两块搭好再填业务代码。因为血泪经验告诉我功能跑通只是第一步进程残留和性能问题才是上线后最容易被投诉的。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Bellhop声线追踪模拟海底地形起伏传播特性

Bellhop声线追踪模拟海底地形起伏传播特性

简介:Bellhop在海底地形起伏条件下声波传播特性的Matlab源码包,面向水声学、海洋工程与水下通信领域的研究人员和工程师。基于射线理论求解Eikonal方程,可模拟复杂海底地形下声波传播路径与衰减特性,资源包包含154个文件&#xff…

2026/10/19 3:55:26 阅读更多 →
RAG系统框架实战:从数据清洗到混合检索与重排序的完整指南

RAG系统框架实战:从数据清洗到混合检索与重排序的完整指南

1. 为什么偏要大费周章:从“模型背不下来”说起如果你搭过聊天机器人或者智能客服,大概率遇到过这种场景:你问一个关于最新政策、公司内部制度或者某份历史合同的问题,大模型要么支支吾吾说不知道,要么一本正经地编一个…

2026/10/19 3:55:26 阅读更多 →
MCP Server开发实战:从协议核心到生产环境避坑指南

MCP Server开发实战:从协议核心到生产环境避坑指南

最近好几个开发者朋友在聊同一个话题:MCP Server 到底怎么开发。看了协议文档但不知道代码从哪写起、本地能跑但换到别的客户端就连不上、部署上线后工具调用经常超时——这些问题我在自己实践过程中基本都踩过一遍。这篇文章直接把 MCP Server/Tool 的开发脉络捋清…

2026/10/19 3:55:26 阅读更多 →

最新新闻

STM32F103C8T6读取PS2手柄:SPI通信与按键解析全流程

STM32F103C8T6读取PS2手柄:SPI通信与按键解析全流程

简介:基于STM32F103C8T6微控制器与PS2手柄进行数据交互的完整工程代码,适合嵌入式入门开发者以及需要快速实现手柄控制的项目人员。这份代码解决了从零配置SPI接口、解析PS2协议、读取按键与摇杆数据的常见问题,同时展示了中断与DMA两种数据接…

2026/10/19 4:37:53 阅读更多 →
基于GCN与GRU的交通流量时空预测项目实战指南

基于GCN与GRU的交通流量时空预测项目实战指南

开局一段话,先说我为什么要写这个交通流量预测这个东西,做过的同学都知道,它不像图像分类或者情感分析那样,拿一套现成的深度学习模型就能跑。交通路网本质上是一张图——交叉口是节点,道路是边,车辆在节点…

2026/10/19 4:37:53 阅读更多 →
LlamaIndex实战:从数据接入到知识库问答的RAG全流程指南

LlamaIndex实战:从数据接入到知识库问答的RAG全流程指南

很多做RAG应用的朋友应该都有过这种体验:模型选好了、API也通了,结果卡在数据接入和检索编排这一层,越写越痛苦。文件格式五花八门,切块策略调来调去,向量库换来换去,最后还要处理引用溯源和Agent调用。这些…

2026/10/19 4:37:53 阅读更多 →
open-code-review:一种可编程、可审计的代码评审新范式

open-code-review:一种可编程、可审计的代码评审新范式

1. “open-code-review”不是个工具名,而是一类新型代码评审范式的代号你点开 GitHub Trending,或者刷到某篇技术推文里提到“我们上线了 open-code-review 流程”,第一反应可能是——这又是个新开源的 CLI 工具?是不是要 pip ins…

2026/10/19 4:37:53 阅读更多 →
Flink Table API窗口实战:Group Windows与Over Windows深度解析

Flink Table API窗口实战:Group Windows与Over Windows深度解析

做实时计算这几年,“窗口”这个词几乎每天都绕不开。数据流本身是无限的,但业务问题通常是有边界的:我要看每分钟的PV、每小时的订单额、最近10条数据的平均时长,这些都需要在无限流上切出有限的一段。Flink的Table API和SQL把窗口…

2026/10/19 4:37:53 阅读更多 →
ARM板子上跑通海康威视SDK:交叉编译与C++工程避坑全记录

ARM板子上跑通海康威视SDK:交叉编译与C++工程避坑全记录

简介:交叉编译是嵌入式Linux开发中的基础环节,其核心在于工具链、系统架构与目标运行环境的匹配。它解决了资源受限设备无法本地编译的问题,让开发者可以在高性能宿主机上为ARM板卡生成可执行程序。在物联网与边缘计算场景下,x86端…

2026/10/19 4:36:50 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →