📎 配套资产 / Companion assets:本篇所有示例的可下载文件在仓库 配套资产/B28_ExcelDNA/——完整 VS 工程(.csproj)+ 自定义函数 + RTD 服务器 + .dna 配置,每个子目录都带中文 README 与 English README。 English: downloadable files for every example in this article live in 配套资产/B28_ExcelDNA/ — a complete VS project (.csproj) + custom functions + RTD server + .dna config, each with a bilingual README.

【办公开发】用 C# + Excel-DNA 打造专业 Excel 工具

摘要:用 AI 帮你用 C# + Excel-DNA 写一个能在 Excel 里直接当公式用的专业工具。

一、痛点引入

你用 Excel 久了,迟早会撞上这三堵墙:

  • 第一堵墙:VBA 不够"专业"。 VBA 上手快,但跑大数据慢、调试难、代码还分散在各个工作簿里,想发给同事用,对方得先启用宏、还可能被安全策略拦下。它更像"个人小脚本",不像一个正经软件。
  • 第二堵墙:内置函数(公式)不够用。 你需要一个"按自己规则算"的函数,比如"把一串中文地址拆成省/市/区",或者"调我们公司内部的接口算个价"。内置公式写不出来,嵌套 IF 会写到怀疑人生。
  • 第三堵墙:实时数据进不来。 比如股价、设备传感器读数、线上订单数,Excel 公式只能你手动刷新,做不到"数据一变,单元格自动变"。这种活儿专业说法叫 RTD(Real-Time Data,实时数据)

那有没有一种办法,能让你用一门正经编程语言,写出一个双击就能装进 Excel、像原生功能一样当公式用、还能推实时数据的工具?有。这就是本文要带你做的:用 C# + Excel-DNA 打造专业 Excel 工具。

先解释两个词,免得后面发懵:

  • C#(读作 C Sharp):微软出的一门现代编程语言,写起来比 VBA 严谨、比 C++ 省心,是 .NET 平台的主力语言。你可以把它理解成"更强大、更工程化的 VBA 替代品"。
  • Excel-DNA:一个开源框架(你可以把它当成一个"翻译官"),它干一件事——让你用 C# 写的代码,能被 Excel 直接识别成公式和加载项。没有它,C# 和 Excel 之间隔着一道墙;有它,你编译出来的东西就是一个 .xll 文件(Excel 加载项),双击或一次加载,函数就出现了。

说明:本文是 L4 硬核篇,需要你有 Visual Studio 和基本的 C# 概念(看得懂"类""方法""using"即可)。它不神秘,但也不是"一键生成"——下面每一步都要你真实动手,才能跑通。

二、目标产出

照着本文做完,你会得到:

  1. 一台装好 Visual Studio(建议 2019 或更新版本)和 .NET Framework(本文以 .NET Framework 4.7.2 为例,原因见第四节与避坑指南)的电脑;
  2. 一个真实的 Visual Studio 工程,结构清晰、可在自己机器上编译;
  3. 一个能编译出 .xllExcel-DNA 加载项,里面包含两个东西:
    • 一个 custom function(自定义函数):像 =MyAdd(1,2) 这样在单元格里直接当公式用;
    • 一个 RTD(实时数据) 函数:单元格能自动刷新、显示"实时"数值;
  4. 一套"让 AI 帮你搭代码框架"的提示词模板,以及对应的 C# 代码框架(真实可运行结构,不是伪代码)。

最终效果:打开 Excel → 加载这个 .xll → 在任意单元格输入 =MyAdd(10,20),回车得到 30;再输入 =GetLivePrice(),单元格每隔约 1–2 秒自动跳一个新数。这就是"专业 Excel 工具"的雏形。

三、案例实战

环境前提(务必先确认)

  • Visual Studio 2019+(社区版免费即可),安装时勾选".NET 桌面开发"工作负载。
  • .NET Framework 4.7.2 开发者包(或 4.8,本文以 4.7.2 为例)。为什么不用更新的 .NET(也就是 .NET 5/6/7/8 那一支)?因为 Excel-DNA 对 .NET Framework 的支持最成熟、踩坑最少。用 .NET (Core) 也能做,但工程配置不同,本文先走最稳的路。(注:具体以你安装的 Excel-DNA 版本为准,见避坑指南第 2 条。)
  • Excel 任意较新版本(2016+ 均可)。
  • 能正常访问 NuGet(Visual Studio 的包管理器,相当于"代码版的软件应用商店",用来一键安装别人写好的库)。

不确定自己装没装 .NET Framework 4.7.2?在 VS 里新建工程时,如果"目标框架"下拉里能看到 .NET Framework 4.7.2,就说明有了。

第一步:新建工程、安装 Excel-DNA

  1. 打开 Visual Studio → "创建新项目" → 搜索并选择 "类库(.NET Framework)"(注意:是带 .NET Framework 字样的那一个,不是普通的"类库")→ 下一步。
  2. 工程名填 MyExcelAddIn,位置随便选一个你找得到的文件夹,框架选 .NET Framework 4.7.2 → 创建。
  3. 工程建好后,在右侧"解决方案资源管理器"里,右键点击工程名 MyExcelAddIn"管理 NuGet 程序包"
  4. 在打开的窗口里切到"浏览"选项卡,搜索 ExcelDna.AddIn → 选中 → 点"安装"。(这就是前面说的"软件应用商店"里那个叫 Excel-DNA 的库。)
  5. 安装完,VS 会自动帮你做几件关键的事:在工程里生成一个 .dna 配置文件,并把"生成时输出 .xll"这条规则接好。你不用手动配,这点很重要。

小提醒:NuGet 装的是 ExcelDna.AddIn(带 .AddIn 后缀)。它是"全家桶版",装完即包含运行所需的一切。别装错成只剩核心的 ExcelDna 包,新手用 .AddIn 最省心。

第二步:看清楚 Visual Studio 工程结构

装完包后,你的工程在"解决方案资源管理器"里大致长这样(这是真实结构,不是示意):

MyExcelAddIn/                      ← 你的工程根目录
├── MyExcelAddIn.csproj            ← 工程文件(VS 自动维护,一般不用手改)
├── MyExcelAddIn.dna               ← Excel-DNA 配置(装包时自动生成,见第三步)
├── Class1.cs                      ← 默认空类,可删,待会儿换成我们的代码文件
├── MyFunctions.cs                 ← 你自己建的:放自定义函数(custom function)
├── LivePriceServer.cs             ← 你自己建的:放 RTD 实时数据服务
└── bin/
    └── Debug/
        └── MyExcelAddIn.xll       ← 按 F6 生成后出现的文件,Excel 加载的就是它

操作:在"解决方案资源管理器"里,右键工程 → "添加" → "类",分别新建两个文件,名字填 MyFunctions.csLivePriceServer.cs。默认的 Class1.cs 可以删掉。

关键点:Excel 最终认的不是 .cs 源码,也不是 .dll,而是编译后那个 .xll 文件。.xll 是 Excel 加载项的专用格式,由 Excel-DNA 在生成时自动产出。

第三步:Excel-DNA 配置(.dna 文件)

装包时 VS 已经帮你生成了 MyExcelAddIn.dna。打开它,内容大致是这样的 XML(不同版本可能略有差异,以你机器上生成的为准):

xml
<DnaLibrary Name="MyExcelAddIn" RuntimeVersion="v4.0"> <ExternalLibrary Path="MyExcelAddIn.dll" Pack="true" /> </DnaLibrary>

逐行用人话解释:

  • <DnaLibrary ...>:整个配置文件的根标签,Excel-DNA 就认这个。
  • Name="MyExcelAddIn":加载项在 Excel 里显示的名字,随便起,别带空格和中文也行(中文可能在某些旧版 Excel 显示异常,稳妥起见用英文/拼音)。
  • RuntimeVersion="v4.0":告诉 Excel-DNA 用 .NET Framework 4.x 运行时。因为我们选的就是 .NET Framework,所以写 v4.0 没问题。
  • <ExternalLibrary Path="MyExcelAddIn.dll" Pack="true" />:这是核心一行。它说"我的 C# 代码编译成了 MyExcelAddIn.dll,请把它打进加载项里"。Pack="true" 表示打包——生成时会把 .dll 直接塞进 .xll,这样你给同事时只要发一个 .xll 文件就够了,不用附带一堆 .dll。(专业上这叫"单文件部署",最干净。)

一般情况下你不需要手改这个文件,装包时已经配好了。这里把它讲清楚,是为了让你知道"我的代码是怎么被 Excel 认识的"。如果你以后要引用别的库,才需要在这里加 <Reference> 等配置——那是进阶玩法,本文不展开。

第四步:写第一个自定义函数(C# 框架)

打开 MyFunctions.cs,把下面这段代码整段粘进去(这是真实可编译的框架,AI 辅助生成时也大致长这样):

csharp
using ExcelDna.Integration; // Excel-DNA 的核心命名空间,自定义函数全靠它 namespace MyExcelAddIn { // 自定义函数所在的类通常写成静态类(函数方法本身必须是 public static,见下方逐行解释) public static class MyFunctions { // [ExcelFunction] 这个"特性"告诉 Excel-DNA:下面这个方法,请变成 Excel 里能用的公式 [ExcelFunction( Name = "MyAdd", // 在 Excel 里输入的公式名,=MyAdd(...) Description = "把两个数相加,返回结果", // 鼠标悬停时看到的说明 Category = "我的工具")] // 在函数向导里归到哪一栏 public static double MyAdd( [ExcelArgument(Name = "数值1", Description = "第一个加数")] double x, [ExcelArgument(Name = "数值2", Description = "第二个加数")] double y) { return x + y; // 真正的计算逻辑:这里只是相加,你可以换成任何 C# 能写的逻辑 } } }

逐行用人话解释:

  • using ExcelDna.Integration;:把 Excel-DNA 的工具箱搬进来,不写这行,[ExcelFunction] 就不认识。
  • public static class MyFunctions:自定义函数方法本身必须是 public static——这是 Excel-DNA 的硬性要求(COM 反射调的就是静态方法)。所在类通常写成静态类public static class),让"这个类只用来挂函数、不能被实例化"这件事从代码层面一眼看出来;如果你不想写静态类,写成普通类也可以,只要里面的函数方法是 public static 就行。
  • [ExcelFunction(...)]:这是"魔法标签"。贴在方法上,Excel-DNA 就会在 Excel 里注册一个同名公式。Name 是用户在单元格里敲的公式名;Description 是悬停提示;Category 是把你的函数归到函数向导的某个分类下,方便别人找。
  • [ExcelArgument(...)]:贴在参数上,给每个参数起个中文名、写句说明,体验更友好。
  • return x + y;:这是函数体,现在只做加法。你可以把它换成任何业务逻辑——拆地址、算内部价、查字典都行。这就是 custom function 的本质:把"你的 C# 逻辑"暴露成"Excel 公式"。

第五步:写一个 RTD 实时数据函数(C# 框架)

实时数据(RTD)要稍微复杂一点。Excel-DNA 提供了一个基类 ExcelRtdServer,你继承它,就能做一个"会自己定时推送新值"的数据源。

打开 LivePriceServer.cs,粘入下面框架(这是真实结构,定时器和推送逻辑都齐了,你只需把"模拟数据"换成真实数据源,比如调你们公司的接口):

csharp
using ExcelDna.Integration; using ExcelDna.Integration.Rtd; // RTD 实时数据相关类都在这里 using System.Timers; namespace MyExcelAddIn { // 继承 ExcelRtdServer,Excel-DNA 会把它当一个实时数据源来用 public class LivePriceServer : ExcelRtdServer { private Timer _timer; // .NET 自带的计时器,用来定时触发刷新 private double _lastValue = 0; // 当前最新值 // 加载项启动这个 RTD 服务时调用(ExcelRtdServer.ServerStart 是无参虚方法,没有带 ServerStartEventArgs 的重载) protected override bool ServerStart() { _timer = new Timer(1000); // 每 1000 毫秒(1 秒)触发一次 _timer.Elapsed += (s, e) => { // 下面这行是"模拟数据":真实场景请换成调用 API / 读传感器 / 查数据库 _lastValue = new Random().NextDouble() * 100; // 把所有已订阅的主题都更新成最新值(用 GetActiveTopics() 获取当前活跃的主题集合) foreach (var topic in GetActiveTopics()) { topic.UpdateValue(_lastValue); // 告诉 Excel:这个值变了,请刷新 } }; _timer.Start(); return true; // 返回 true 表示服务启动成功 } // 服务关闭时调用,做清理 protected override void ServerTerminate() { _timer?.Stop(); _timer?.Dispose(); } // Excel 第一次订阅某个主题时调用,返回初始值 protected override object ConnectData(Topic topic, System.Collections.Generic.IList<string> topicInfo, ref bool newValues) { newValues = true; return _lastValue; } // Excel 不再需要某个主题时调用,做清理 protected override void DisconnectData(Topic topic) { // 这里一般无需特殊处理,留空即可 } } }

然后,在 MyFunctions.cs 里补一个"包装函数",让用户在 Excel 里用 =GetLivePrice() 就能拿到实时值(而不是手敲 =RTD(...) 那种难记的写法):

csharp
// 放到 MyFunctions 静态类里,和 MyAdd 并列 [ExcelFunction(Description = "获取实时价格(RTD 实时数据示例)")] public static object GetLivePrice() { // XlCall.RTD 是 Excel-DNA 提供的调用实时数据的方法 // 第一个参数是 RTD 服务的"全名"(命名空间.类名),后面是可变数量的主题参数 return XlCall.RTD("MyExcelAddIn.LivePriceServer", null, "PRICE"); }

关于 XlCall.RTD 第一个参数(所谓 ProgId,即"程序标识符"):本文按"命名空间.类名"的约定填写(MyExcelAddIn.LivePriceServer)。不同 Excel-DNA 版本对 RTD 注册的具体机制可能略有差别,请确保你机器上该服务的全名保持一致,实在对不上时,以你安装的 Excel-DNA 官方文档为准(见文末"技术准确性风险点"与避坑指南第 4 条)。

第六步:生成并加载到 Excel

  1. F6(或菜单"生成"→"生成解决方案")。VS 下方"输出"窗口若显示"生成成功",就说明编译通过了。
  2. 打开工程目录下的 bin\Debug\,你应该能看到 **MyExcelAddIn.xll** 文件——这就是成品。**该目录下出现哪个 .xll 就加载哪个**(如果工程名不同、或多配置并存,文件名会跟着变,别死记 MyExcelAddIn.xll)。
  3. 打开 Excel主路径(推荐,所有版本通用):文件 → 选项 → 加载项 → 底部"管理"选 "Excel 加载项" → 转到 → 浏览 → 选中 bin\Debug 下那个 .xll → 确定。旧版快捷键参考:在很老的 Excel 里也能用 Alt+T+I 打开"加载项"管理器(仅作历史快捷键参考,不同版本/语言可能略有差异,不要死记)。注意 Alt+F11 打开的是 VBA 编辑器,跟 .xll 加载项没关系,别按错。
    • 更省事:直接把 .xll 文件拖进 Excel 窗口,也能加载(Excel 会问是否加载,选是)。
  4. 加载成功后,在任意单元格输入 =MyAdd(10,20),回车,得到 30;再输入 =GetLivePrice(),每隔约 1–2 秒,单元格里的数字会自动跳变(Excel 对 RTD 更新默认有约 2 秒的节流间隔,不是每秒必跳,属正常)——说明 RTD 实时推送生效了。

如果 Excel 提示"宏已被禁用"或加载失败:先确认文件是从本地磁盘打开(不是从邮件/网盘直接点开),Excel 对本地 .xll 通常放行;公司电脑若有限制,需要让 IT 把该加载项加入信任列表。这不是代码问题,是安全策略,见避坑指南。

附:让 AI 帮你写代码的提示词模板

你不必从零背 C#。把下面这段发给任意 AI 助手,它就能给你搭出上面的框架(然后你再按本文核对、跑通):

我在用 C# + Excel-DNA 写一个 Excel 加载项(.xll)。请给我可直接编译的代码框架,要求:
1. 一个自定义函数(custom function),用 [ExcelFunction] 标记,函数名 MyAdd,两个 double 参数,
   带中文 Name / Description / Category,功能为两数相加;
2. 一个 RTD 实时数据服务,继承 ExcelRtdServer,用 Timer 每 1 秒推送一个随机数,
   并提供一个包装函数 GetLivePrice() 通过 XlCall.RTD 调用它;
3. 附上对应的 .dna 配置示例(ExternalLibrary,Pack=true);
4. 用 .NET Framework 4.7.2,命名空间 MyExcelAddIn。
请注意:只给真实可运行的框架,注明哪些地方需要我替换成真实数据源。

提示词要点:把"框架/版本/命名空间/要做什么"写死,再让 AI 注明"哪里要换真实数据"——这样它给的代码才贴近真实,而不是凭空编。拿到代码后,务必对照本文第四步、第五步逐行核对,别直接盲跑。

四、原理小结

一句话说清底层发生了什么:Excel-DNA 在你的 C# 工程生成时,把代码编译成 .dll,再按 .dna 配置把它打包进一个 .xll 加载项;当你在 Excel 里加载这个 .xll,Excel-DNA 运行时会扫描你贴了 [ExcelFunction] 的方法,把它们逐个注册成 Excel 能识别的公式,于是 =MyAdd(...) 就能像 SUM 一样用了;而 RTD 部分,则靠 ExcelRtdServer 在后台起一个定时器,数据一更新就通过 topic.UpdateValue(...) 主动推给 Excel,Excel 收到通知便自动刷新对应单元格——这正是"实时数据"和普通公式"你不动它就不动"的本质区别。

五、避坑指南

  1. 装错 NuGet 包:一定要装 ExcelDna.AddIn(全家桶),不是裸的 ExcelDna。装错会导致生成不出 .xll 或运行时报错。新手认准带 .AddIn 后缀的那个。
  2. .NET 版本选错:本文用 .NET Framework 4.7.2,这是与 Excel-DNA 配合最稳的组合。若你选了 .NET (Core) / .NET 5+,工程模板、.csproj 写法、.dna 里的 RuntimeVersion 都会不同,且对 COM/RTD 支持有额外要求。不确定就先用 .NET Framework,别一上来挑战新运行时。
  3. 加载项加载了但函数不出现:多半是代码没编译成功(先看 VS "输出"窗口有没有报错),或 .dnaExternalLibraryPath 与实际生成的 .dll 名对不上(通常装包后自动一致,别手改错)。还有一种是 Excel 64 位 / 32 位与你编译的"目标平台"不匹配——工程属性里"生成→目标平台"建议设为 Any CPU 或与你 Excel 位数一致。
  4. RTD 注册/ProgId 对不上XlCall.RTD 第一个参数用的是 RTD 服务的全名(命名空间.类名)。不同 Excel-DNA 版本在 RTD 自动注册上的细节可能有差异;若 =GetLivePrice() 返回 #VALUE!#NAME?,优先核对服务类名拼写,并查阅你所装版本的官方文档确认注册方式。(本文代码为通用框架,具体行为以你安装的版本为准。)
  5. 公司电脑加载被拦.xll 本质是可执行加载项,企业安全软件可能拦截。这不是代码错,需要把文件放到本地受信任位置,或由 IT 加入白名单。先用自己电脑跑通,再谈分发。

六、进阶延展

  • 回指基础:如果你还没系统玩过 Excel 的"轻量进阶三件套",建议先补本系列的 B2/B26(Power Query 数据清洗)B21/B23(Power BI 报表与数据模型)B22(DAX 度量值)——它们是"不改代码也能大幅提效"的地基;本文的 C# + Excel-DNA 是往下一层走:当公式和数据透视表都够不着你的需求时,才自己写加载项
  • 往深一层:跑通本文后,下一步可以学:① 用 Excel-DNA 的 Ribbon(功能区) 自定义 Excel 菜单按钮,让工具不只是函数,还能点按钮触发;② 把"模拟随机数"换成真实数据源(HTTP 接口、数据库、消息队列),做出真正有用的实时看板;③ 用 ExcelDnaPack 把依赖全打包成单文件 .xll,方便发给全公司;④ 给你的函数加 异步支持(async/await),调慢接口时不卡 Excel。
  • 横向对照:本文是"硬核开发"路线;若你只想批量处理现成 Excel 文件、不想写加载项,回到 B25(Python + openpyxl) 那条更轻的路会更顺手。

小结:今天你用一个真实可编译的 C# + Excel-DNA 工程,做出了"自定义函数 + RTD 实时数据"的专业 Excel 工具雏形。它不靠宏、不靠嵌套公式,而是一个正经的 .xll 加载项——这就是从"Excel 用户"通往"Excel 工具开发者"的第一步。