ILSpy、dnSpy、dotPeek 怎么选:.NET 反编译器对比,Mac 上用什么
本页目录
结论先行(2026-11-02 核对): 在 Mac 或 Linux 上,用 ILSpy。它从 11.0 起提供原生的 macOS ARM64 版本,命令行版 ilspycmd 在任何能跑 .NET 的地方都能用。需要在 Windows 上对没有源码的 .NET 代码单步调试,用 dnSpyEx。已经在用 JetBrains 全家桶、平台是 Windows,想要一个还能充当符号服务器的免费反编译器,用 dotPeek。原版 dnSpy 仓库从 2020 年起就已归档,dnSpyEx 是仍在维护的延续版本。
哪些实测了,哪些没有。 测试环境是一台 Apple Silicon Mac,没有 Windows 电脑或虚拟机。ILSpy 11.2 的图形界面和 ilspycmd 我都亲自跑过。dnSpyEx 的图形界面和调试器只能在 Windows 上运行,我没有跑;不过我用一个不受官方支持的办法(见下文)让它的命令行反编译器 dnSpy.Console 在 macOS 上跑了起来。dotPeek 只提供 Windows 安装包,完全没有运行。关于 dnSpyEx 调试器和 dotPeek 的描述都来自厂商文档,文中注明了出处。
三款工具一览
| ILSpy | dnSpyEx | dotPeek | |
|---|---|---|---|
| 最新版本(2026-11-02 核对) | 11.2,2026-10-27 | 6.6.0,2026-06-20 | 2026.2.4,2026-10-30 |
| 许可证 | MIT(开源) | GPL-3.0(开源) | 免费,闭源 |
| 平台 | Windows、macOS(ARM64)、Linux | Windows(WPF) | Windows(x64、x86、ARM64 安装包) |
| 反编译为 C# | 支持(本文实测) | 支持(用变通办法实测了命令行引擎) | 支持(厂商文档,未运行) |
| 调试器 | 无 | 有(厂商文档,未运行) | 无,可为调试器提供符号(厂商文档) |
| 命令行 | ilspycmd(dotnet tool) |
dnSpy.Console |
产品页未提及 |
| 单文件包 | 支持 | 我参考的资料里未提及 | 我参考的资料里未提及 |
资料来源:ILSpy 11.2、11.1 和 11.0 的发布说明,dnSpyEx 6.6.0 的发布说明和 README,以及 JetBrains 的 dotPeek 页面。dotPeek 的版本和平台取自 JetBrains 公开的发布数据,2026-11-02 时只列出了 Windows 下载(x64、x86、ARM64 和在线安装器)。
2026 年各项目的状况
ILSpy:已经跨平台
ILSpy 11.0(2026 年 8 月)把界面从 WPF 移植到了 Avalonia。项目原话是:“With this port, ILSpy is now available on Linux and Mac in addition to Windows”,WPF 前端随之移除。11.2 的发布页(2026-10-27)提供 x64 和 ARM64 的 Windows 安装包,Linux 的 zip、.deb 和 .rpm 包,以及 macOS ARM64 的 zip。所以在 Mac 和 Linux 上,浏览和反编译的 dnSpy 替代品就是 ILSpy。但它没有调试器。
11.0 还专门照顾了 agent 对 ilspycmd 的使用:新增 --member 用于只反编译单个成员,新增 --dump-table 用于输出元数据表。发布说明提到,这个 dotnet tool 每周下载量超过 7 万,并把增长归功于 Claude 等 agent。
Visual Studio 调试器自带的反编译功能就是基于 ILSpy 的。微软的反编译文档写明源码生成“is based on the open source ILSpy project”,从 Visual Studio 2019 16.5 起就已提供。
dnSpy 和 dnSpyEx
GitHub 上原版 dnSpy/dnSpy 仓库已归档,最后一次推送是 2020-12-20。dnSpyEx 自称“an unofficial continuation of the dnSpy project which is a debugger and .NET assembly editor”。6.6.0 是它两年来第一个稳定版:迁移到 .NET 10,新增使用本机已装 .NET 10 运行时的 dnSpy-net 构建,新增 String References 窗口,并修复了 runtime-async 方法导致的反编译崩溃。
发布页上有两点值得在选型时注意:
- 它是 Windows 的 WPF 程序。6.6.0 “detects when it is being run under Wine on Linux”,会关闭硬件加速来提高兼容性。这只是兼容性调整,并不是 Linux 或 macOS 版本。
- 维护者明确表示“will not be an official dnSpy AI integration, or an MCP server”。
大多数人选它是为了调试器。README 列出:可以调试 .NET Framework、.NET 和 Unity 程序集,“no source code required”;可以在任意程序集里下断点;有局部变量、监视和调用堆栈窗口。dnSpyEx 还能编辑并保存程序集,请只用在自己的代码或有权检查的代码上。
dotPeek
JetBrains 对 dotPeek 的描述是“a free-of-charge standalone tool based on ReSharper’s bundled decompiler”。它能反编译成 C# 或 IL,能把反编译结果另存为 Visual Studio 项目,还“can also perform as a symbol server”,让 Visual Studio 调试器单步进入反编译出来的库代码。能在 macOS 上运行的 JetBrains Rider 有自己的集成反编译器,也能调试反编译出来的外部代码;但按 Rider 2026.2 的外部代码调试帮助,ARM64 macOS 上不支持单步进入外部方法。Rider 是单独收费的 IDE,我没有测试。
同一个 DLL 的反编译结果
为了对比输出,我写了一个小类库,包含 record、LINQ 链、async 方法和 switch 表达式,用 .NET 10.0.403 SDK 以 Release 模式编译(Demo.dll,10,240 字节):
public record Order(int Id, string Customer, decimal Total, bool Paid);
public static class OrderReport
{
public static IEnumerable<string> TopCustomers(IEnumerable<Order> orders, int count) =>
orders.Where(o => o.Paid)
.GroupBy(o => o.Customer)
.OrderByDescending(g => g.Sum(o => o.Total))
.Take(count)
.Select(g => $"{g.Key}: {g.Sum(o => o.Total):0.00}");
public static async Task<int> CountLinesAsync(string path, CancellationToken ct = default)
{
using var reader = new StreamReader(path);
var lines = 0;
while (await reader.ReadLineAsync(ct) is not null)
lines++;
return lines;
}
public static string Describe(Order o) => o switch
{
{ Paid: false } => "unpaid",
{ Total: > 1000m } => "large",
_ => "normal",
};
}
ILSpy 11.2
ilspycmd -t Demo.Order Demo.dll 把 record 还原成了一行,和源码一模一样:
public record Order(int Id, string Customer, decimal Total, bool Paid);
ilspycmd -t Demo.OrderReport Demo.dll 的输出如下(省略了 using 行):
public static IEnumerable<string> TopCustomers(IEnumerable<Order> orders, int count)
{
return from g in (from o in orders
where o.Paid
group o by o.Customer into g
orderby g.Sum((Order o) => o.Total) descending
select g).Take(count)
select $"{g.Key}: {g.Sum((Order o) => o.Total):0.00}";
}
public static async Task<int> CountLinesAsync(string path, CancellationToken ct = default(CancellationToken))
{
using StreamReader reader = new StreamReader(path);
int lines = 0;
while (await reader.ReadLineAsync(ct) != null)
{
lines++;
}
return lines;
}
public static string Describe(Order o)
{
if ((object)o != null)
{
if (!o.Paid)
{
return "unpaid";
}
if (o.Total > 1000m)
{
return "large";
}
}
return "normal";
}
record 和 async 方法都还原成了正常写法,编译器生成的状态机被隐藏了。LINQ 链逻辑正确,但被改写成了查询语法,lambda 参数类型也写了出来。switch 表达式变成了嵌套的 if,还多了一个 (object)o != null 判断:逻辑相同,形状不同。在 Apple M5 Pro 上反编译一个类型耗时 0.43 秒,峰值内存约 105 MB。
ILSpy 11.2 的图形界面(ILSpy_macos-arm64_11.2.0.10050.zip)在 macOS 上也能打开 Demo.dll,显示为 Demo (1.0.0.0, .NETCoreApp, v10.0)。Mac 上有个坑:ILSpy.app 只做了 ad-hoc 签名,没有公证,spctl 检查不通过。我是用 curl 下载的,不会带隔离标记,所以能直接打开;如果用浏览器下载,Gatekeeper 很可能会拦下,需要手动放行。
dnSpyEx 6.6.0(命令行引擎,在 macOS 上用变通办法运行)
dnSpyEx 发布了一个依赖框架的构建 dnSpy-net.zip,里面有 dnSpy.Console.dll。在 macOS 上原样运行会失败,因为它要求 Windows 桌面运行时:
$ dotnet bin/dnSpy.Console.dll --help
You must install or update .NET to run this application.
Framework: 'Microsoft.WindowsDesktop.App', version '10.0.0' (arm64)
No frameworks were found.
我复制了一份目录,把 dnSpy.Console.runtimeconfig.json 里的 Microsoft.WindowsDesktop.App 一项删掉,命令行工具就能启动并反编译了。这不是官方支持的用法:它说明不了图形界面和调试器的情况,任何版本都可能失效。但它能展示 dnSpyEx 的反编译引擎在默认选项下的输出。dnSpy.Console -t Demo.OrderReport Demo.dll(去掉了每个成员上的 token 注释):
public static IEnumerable<string> TopCustomers(IEnumerable<Order> orders, int count)
{
return Enumerable.Select<IGrouping<string, Order>, string>(Enumerable.Take<IGrouping<string, Order>>(Enumerable.OrderByDescending<IGrouping<string, Order>, decimal>(Enumerable.GroupBy<Order, string>(Enumerable.Where<Order>(orders, (Order o) => o.Paid), (Order o) => o.Customer), (IGrouping<string, Order> g) => Enumerable.Sum<Order>(g, (Order o) => o.Total)), count), delegate(IGrouping<string, Order> g)
{
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler;
defaultInterpolatedStringHandler..ctor(2, 2);
defaultInterpolatedStringHandler.AppendFormatted(g.Key);
defaultInterpolatedStringHandler.AppendLiteral(": ");
defaultInterpolatedStringHandler.AppendFormatted<decimal>(Enumerable.Sum<Order>(g, (Order o) => o.Total), "0.00");
return defaultInterpolatedStringHandler.ToStringAndClear();
});
}
public static Task<int> CountLinesAsync(string path, CancellationToken ct = default(CancellationToken))
{
OrderReport.<CountLinesAsync>d__1 <CountLinesAsync>d__;
<CountLinesAsync>d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
<CountLinesAsync>d__.path = path;
<CountLinesAsync>d__.ct = ct;
<CountLinesAsync>d__.<>1__state = -1;
<CountLinesAsync>d__.<>t__builder.Start<OrderReport.<CountLinesAsync>d__1>(ref <CountLinesAsync>d__);
return <CountLinesAsync>d__.<>t__builder.Task;
}
public static string Describe(Order o)
{
if (o != null)
{
if (!o.Paid)
{
return "unpaid";
}
if (o.Total > 1000m)
{
return "large";
}
}
return "normal";
}
每个成员上都带一行 // Token: 0x06000017 RID: 23 RVA: 0x00002440 File Offset: 0x00000640 这样的注释,需要把 C# 对应回文件字节时很方便。在这个 .NET 10 构建上,它和 ILSpy 11.2 的差别是:
- async: dnSpyEx 输出的是编译器生成的状态机入口,而不是
async/await,尽管它的--async选项默认是开启的。 - 字符串插值:
$"..."被展开成了编译器实际生成的DefaultInterpolatedStringHandler调用,还包括不合法的 C# 写法..ctor(2, 2)。 - record:
Order被还原成一个完整的类,带EqualityContract、PrintMembers、Equals、GetHashCode和Deconstruct,一百多行,而不是一行。 - LINQ: 变成层层嵌套的
Enumerable.*静态调用,既不是查询语法,也不是链式写法。 - switch 表达式: 和 ILSpy 几乎一样,只是少了
(object)转换。
两者的逻辑都正确,但 ILSpy 的输出明显更接近人写的现代 C#。单个类型的速度差不多(dnSpyEx 0.36 秒,峰值内存约 82 MB)。Windows 上 dnSpyEx 图形界面的默认设置是否不同,我没法验证。
dotPeek
没有运行:它没有 macOS 或 Linux 版本。如果你有 Windows,可以用同一个 Demo.dll 跑一下,先看这四类写法:record、async、字符串插值和模式匹配。不同反编译器、同一反编译器的不同版本,差异主要都出在这里。
没有源码时调试自己的代码
常见的正当场景:程序崩在你依赖的某个库里,或者崩在你自己应用的旧版本里,而对应的 PDB 已经找不到了。以下内容我都没有实际运行,是对厂商文档的归纳。
- 先试源码。 如果包启用了 Source Link,或者 PDB 还在,普通调试器就能显示真实源码。微软的反编译文档也建议对启用了 Source Link 的库和 NuGet 包优先走这条路。
- Visual Studio 2022 17.7 及以后,在关闭“仅我的代码”时单步进入外部 .NET 代码,或者在调用堆栈窗口双击某一帧,会自动反编译,还能在生成的代码里下断点。微软列出的限制:优化过的构建可能把断点绑到错误的行,局部变量名可能缺失,async 代码可能反编译不完整。
- dnSpyEx 按其 README,作为独立的 Windows 工具提供同样的“无源码调试”,覆盖 .NET Framework、.NET 和 Unity。
- dotPeek 按 JetBrains 的说法可以充当符号服务器,让 Visual Studio 调试器为没有 PDB 的程序集拿到源码。
ILSpy 本身不能调试。ilspycmd -genpdb 可以根据反编译结果生成便携式 PDB,再交给调试器加载。这条路我没有测试。
用 ilspycmd 做自动化
ilspycmd 以 .NET tool 的形式安装:
dotnet tool install ilspycmd --version 11.2.0.10050 --tool-path ./tools
./tools/ilspycmd --version
# ilspycmd: 11.2.0.10050
# ICSharpCode.Decompiler: 11.2.0.10050
我用过的选项:-t <type> 反编译单个类型,-m <token 或 doc id> 反编译单个成员,-l c 列出所有类,-p -o <dir> 导出可编译的项目,--bundle-entry 和 -d 处理单文件包(见这个 EXE 是不是 .NET 程序),脚本里加 --disable-updatecheck。遇到 NativeAOT 程序或原生 apphost,它会报 PE file does not contain any managed metadata.。11.2 的发布说明新增了 linux-x64 和 win-x64 的可选 Native AOT 版 ilspycmd,称在 Windows 上每次调用快 4 到 7 倍;没有 macOS 的 AOT 包,我用的是普通托管版。
什么时候用不着反编译器
很多问题只看元数据就能回答:目标框架、引用、版本号、P/Invoke 导入,或者这个文件到底是不是 .NET。本站的 .NET 程序集元数据与 IL 查看器 能在浏览器里显示这些信息和每个方法的 IL,文件不会上传,但它不生成 C#。框架和版本的问题,见查看 .NET DLL 的目标框架。
到底选哪个?
- 在 macOS 或 Linux 上,或者用在 CI 和 agent 流程里: ILSpy 和
ilspycmd。它没有调试器。 - 在 Windows 上,必须对没有源码的代码单步调试: 有 Visual Studio 就用它内置的反编译,没有就用 dnSpyEx。
- 已经在 Windows 上用 ReSharper 或 Rider: dotPeek 很顺手,它的符号服务器还能帮上 Visual Studio 调试器。
- 文件是 NativeAOT: 三个都不行,里面没有 IL,请用原生工具。
常见问题
能在 Mac 上用 Wine 跑 dnSpyEx 吗? 6.6.0 的发布说明只提到会检测 Linux 上的 Wine。macOS 上的 Wine 我没有试过。
混淆过的程序集能看懂吗? 反编译器只能展示 IL 里有的东西。被改名的符号还是改过的名字,被打乱的控制流也还是乱的。请只分析你自己的代码,或你有权检查的代码。