Elytra
Blog2026

Unity 的 C# 运行时:从 Mono、IL2CPP 到 CoreCLR

Unity 为什么用了二十年的 Mono,又为何创造 IL2CPP,最终却选择拥抱 CoreCLR?从 JIT、AOT、性能与编译时间,到热重载、Mod 与 iOS 平台限制。

随笔
GPT 5.6+1
unitycsharpdotnetcoreclril2cpp

如果你长期使用 Unity,可能早已习惯在 Scripting Backend 里看到两个名字:MonoIL2CPP

Mono 编译快,适合开发;IL2CPP 构建慢,却通常被认为拥有更好的运行性能,也是 iOS 等平台的必选项。久而久之,这似乎成了一种理所当然的分工。

但 Unity 7 正准备打破这个持续多年的格局。

Unity 正在把整个编辑器和桌面 Player 从 Mono 迁移到微软现代 .NET 的 CoreCLR,同时带来 .NET 10、C# 14、RyuJIT、现代分代 GC、MSBuild,以及一套新的代码重载机制。1

这并不只是把一个旧运行时换成一个新运行时。它更像是 Unity 终于准备结束自己长期存在的「特殊 C# 世界」,重新接入现代 .NET 生态。

资料范围

本文资料整理截至 2026 年 9 月 21 日。CoreCLR 在 Unity 6.7 中仍是实验功能,仅面向 Windows、macOS、Linux Player;Unity 6.7 Editor 本身仍运行在 Mono 上。Unity 7 的 CoreCLR 全面迁移属于公开 Roadmap,不是已经稳定发布的功能。文中性能数据区分官方说明与社区 benchmark,不代表 Unity 对最终产品的性能承诺。

从一开始:Mono 与跨平台 JIT

Unity 很早便选择了 C# 作为主要脚本语言,而承担 C# 运行时工作的,就是 Mono

Mono 可以理解为一个开源的 .NET 实现。在微软自己的 .NET 尚且高度绑定 Windows 的年代,它让 C# 程序能够运行在 macOS、Linux 等平台,因此非常适合从一开始就强调跨平台的 Unity。Unity 使用的是自己维护的 Mono 分支。23

它的运行方式大致是:

C# 源码 Roslyn IL / DLL Mono JIT 机器码

这里最重要的是 JIT,Just-In-Time Compilation

C# 并不会直接编译成 x86 或 ARM 指令,而是先由 Roslyn 编译成平台无关的 CIL。程序运行时,Mono 再把即将执行的方法编译成当前 CPU 可以执行的机器码。

这带来一个很明显的优势:构建很快。 没有庞大的 C++ 中间代码,没有漫长的原生编译和链接过程。对于开发阶段来说,这种体验非常重要。

问题在于,Unity 使用的 Mono 逐渐老了。现代 .NET 在过去十几年里已经发生巨大变化:JIT、GC、SIMD、Span<T>、泛型优化、并发库乃至整个 Base Class Library 都在快速演进,而 Unity 长期维护的是一套带有大量历史包袱的 Mono 与自定义集成。3

Mono 并没有突然「坏掉」,只是现代 .NET 已经走得太远。

IL2CPP:为 AOT 平台而生

2010 年代,Unity 遇到了另一个更加现实的问题:不是所有平台都允许 JIT。

最典型的就是 iOS。因此 Unity 开发了一套很特殊的技术:IL2CPP,Intermediate Language To C++。4

它的流水线变成了:

C# 源码 Roslyn IL IL2CPP C++ Clang / MSVC Native Binary

不再在玩家设备上动态编译代码,而是在发布游戏之前,把 CIL 转换成 C++,再交给平台自己的 C++ 编译器生成最终机器码。这就是 AOT,Ahead-Of-Time Compilation

IL2CPP 最初正是为无法运行 Mono JIT 的平台开发的。2015 年初,Unity 首先通过 IL2CPP 支持了 iOS 64 位,Unity 5 随后也将它用于 WebGL。4

这也解释了为什么两者长期共存:

后端编译时机典型场景
Mono运行时 JIT编辑器、部分桌面 Player
IL2CPP构建时 AOTiOS、WebGL、主机、需要 AOT 的发布目标

一个容易混淆的点

IL2CPP 并不是 Mono 的「下一代替代品」,而是为 JIT 被禁止的平台 补上的另一条后端。CoreCLR 才是 Unity 真正意义上的现代托管运行时换代。

IL2CPP 之前,iOS 怎么跑 Unity?

这里有一个常被忽略的事实:在 IL2CPP 出现之前,Unity 并没有在 iPhone 上跑 JIT。

2008 年 Unity 宣布支持 iPhone 时,iOS 就已经不允许应用在设备上动态生成可执行代码。Aras Pranckevičius 当时在 Unity 论坛上解释得很直接:构建 iPhone 应用时,Unity 会生成引擎静态库、Mono 运行时静态库,以及已经 AOT 编译好的脚本代码,最后一起链接、签名,打包成可在 iPhone 上运行的原生 App。5

也就是说,IL2CPP 之前 iOS 上的路径其实是:

C# → IL → Mono AOT → Native Code

    Mono Runtime(GC、线程等,无 JIT)

玩家设备上运行的仍然是本机代码,但背后依赖的是 Mono 的 Full AOT,而不是桌面 Editor 里那种边跑边编的 JIT。

这与后来的 IL2CPP 有几个关键区别:

Mono AOT(早期 iOS)IL2CPP(2015 起)
转换路径IL → Mono AOT 直接生成机器码IL → C++ → 平台编译器
运行时Mono Runtime + Boehm GCIL2CPP Runtime + Boehm GC
构建工具链Unity 定制的 Mono 交叉编译Clang / Xcode
64 位 iOS后期才逐步跟上2015 年初即作为首批正式平台
长期维护逐渐停止演进Unity 持续投入的主线 AOT 后端

两者在「设备上不能 JIT」这一点上没有本质分歧,区别主要在 AOT 的实现方式、工具链和后续可维护性。早期 Mono AOT 还带来不少限制:例如不能 Assembly.Load 动态加载程序集、反射与泛型支持不完整、最终二进制体积和链接时间也更容易成为瓶颈。54

IL2CPP 并不是把 iOS 从 JIT 改成 AOT——iOS 从来就不是 JIT 平台。它做的是:用一条 Unity 自己掌控的 IL → C++ → Native 流水线,替换掉越来越难以维护的 Mono AOT 方案,并为 64 位 iOS、WebGL 和后来的主机平台铺平道路。

能不能像 JVM 一样,只跑 Mono 解释器?

这个问题很自然:既然 iOS 不让 JIT,那能不能像早期 JVM 那样,在设备上只跑一个虚拟机解释器,逐条执行 IL,既不 JIT,也不做 Full AOT?

先把三种执行方式分开:

方式设备上发生什么iOS 是否允许
JIT运行中把 IL 编译成新的本机机器码不允许
AOT发布前把 IL 编译成本机机器码,设备只执行允许(Unity iOS 一直走这条路)
解释器用已有的本机代码逐条解释 IL,不生成新机器码技术上与 JIT 不同,但仍有政策与性能问题

Unity 在 iPhone 上从未采用过「纯解释器、不要 AOT」的方案。 2008 年支持 iPhone 时,Unity 与 Mono 团队就已经在走 Full AOT:脚本在构建阶段交叉编译成原生代码,设备上只保留 Mono 运行时做 GC、线程等基础服务,不附带 JIT 编译器。56

原因并不只是「Apple 不让 JIT」这么简单:

  1. 性能。游戏脚本要跑在 Update() 循环里,纯解释执行 IL 通常比 JIT 或 AOT 慢一个数量级以上。桌面 Mono 早年也有解释器(mint),后来正是因为性能原因被 JIT 边缘化,最终在泛型时代干脆移除。7
  2. 当时已有可用的 AOT 路径。Miguel de Icaza 在 2008 年写道,Unity 已经改造了自己的 Mono 分支,能在构建时交叉编译到 iOS 目标;对游戏发布来说,AOT 是更现实的选择。6
  3. 解释器回归得太晚,且不在 Unity 主线里。Mono 在 2017 年才把解释器重新带回主线,并探索 mixed mode——核心库 AOT、其余代码走解释器,主要面向 Xamarin / 需要设备上动态加载 .NET 的场景。7 Unity 侧,Josh Peterson 在 2018 年曾表示团队在关注 Mono Interpreter,但当时没有任何接入 Unity 脚本后端的计划。8 到 IL2CPP 成为 iOS 默认路径之后,Unity 更没有把「纯解释器跑游戏脚本」作为产品方向。
  4. 即使不走 JIT,解释器也不等于能随便下 Mod。Apple 审核指南 2.5.2 限制的是「下载、安装或执行会改变 App 功能的代码」——这与是否 JIT 无关。就算技术上用解释器执行新 IL,从商店分发政策看仍然很麻烦。9

所以更准确的说法是:

iOS 上不存在「先 JIT、后 AOT、再 IL2CPP」的三段式演变;从一开始就是 AOT。IL2CPP 换的是 AOT 的实现,而不是在「JIT 虚拟机」和「AOT」之间做选择。

后来 Mono 解释器在 Xamarin 生态里确实用于部分 mixed mode 场景(例如 mscorlib AOT、其余走 interpreter),但那更接近「在已 AOT 的 App 里动态执行少量托管代码」,而不是「整个 Unity 游戏像 JVM 应用一样只靠解释器跑 C#」。10 Unity 若需要运行时逻辑,历来走的是 Lua、自研 VM、HybridCLR 等旁路,而不是把 Mono 解释器当作 iOS 主脚本后端。

为什么 IL2CPP 编译这么慢?

因为它实际上多走了非常长的一段路。

Mono 或 CoreCLR 的普通 Player 可以近似理解成:

C# → IL → 打包

而 IL2CPP 是:

C#
 ↓ Roslyn
IL
 ↓ IL2CPP
大量 C++
 ↓ Clang / MSVC
Native Object Files
 ↓ Link
Executable

项目越大,产生的 C++ 越多,原生编译器和 Linker 要处理的工作也越多。Unity 官方文档也明确指出,IL2CPP 的 C++ 代码生成、原生编译和链接通常会显著增加 Player Build 时间,同时也可能增加最终程序的代码体积。11

因此 IL2CPP 的优势从来不是「开发迭代快」。它解决的是另一个问题:

在不能 JIT 的平台上,把完整的托管程序提前变成本机代码。

IL2CPP 等于「C++ 性能」吗?

这是一个非常常见的误解。

C# → IL2CPP → C++

并不意味着:

C# → 手写 C++

IL2CPP 生成 C++ 的主要目的,是把 .NET 程序的语义可靠地映射到 Native,并获得优秀的跨平台 AOT 能力。最终确实会经过 Clang 或 MSVC,但编译器看到的是由 IL2CPP 自动产生的 C++,而不是程序员围绕内存布局、模板、SIMD 和缓存行为精心设计的原生 C++。12

Unity 工程师 Alexandre Mutel 在讨论 CoreCLR 时曾指出,IL2CPP 长期以来主要目标是把 .NET 程序可靠地 AOT 到禁止 JIT 的平台,而不是做「世界最强的 C# optimizer」。12

因此:

IL2CPP 是一种优秀的 AOT 后端,但「经过 C++ 编译器」并不能简单推导出「性能等同于最优 C++」。

这也是 CoreCLR 出现以后,一个非常有意思的事情:JIT 居然可能在某些 workload 下比 AOT 更快。

CoreCLR:重新回到现代 .NET

CoreCLR 是现代 .NET 的主要运行时。它和 Mono 一样属于 JIT Runtime,但两者之间已经隔着十几年的运行时技术演进。

MonoIL2CPPCoreCLR
编译模型JITAOTJIT
JIT 编译器老旧 Mono JITRyuJIT + 分层编译
GCUnity Mono / BoehmBoehm现代分代 GC
构建速度
JIT Warmup
动态代码能力较强很有限
现代 .NET 生态有限取决于 Unity原生生态
当前 Unity Player传统桌面iOS / 主机等Windows / macOS / Linux(实验)

Unity 7 计划进一步把 Editor 本身也迁移到 CoreCLR,并移除 Mono,同时升级到 .NET 10 与 C# 14。113

于是 Unity 的架构开始变成:

C# 游戏代码 托管程序集 / IL CoreCLR:桌面 Editor / Player RyuJIT + 现代 GC IL2CPP:iOS / 主机 / 受限平台 AOT Native Binary

这也是理解未来 Unity 最重要的一张图。CoreCLR 并不一定消灭 IL2CPP,它更可能把 Mono 消灭掉。

为什么做了那么多年,还没全面推出?

很多人的困惑在这里:既然 CoreCLR 这么香,为什么 Unity 从 2017 年 HackWeek 原型(一周跑起转方块)到 2026 年 6.7 实验性 Desktop Player,中间隔了近十年,Editor 还要等到 6.8?1415

这不是「换一下 Scripting Backend 下拉菜单」能搞定的事。Unity 官方在 2026 年的 The Path to CoreCLR 系列里写得很直白:把整台引擎从 Mono 迁到 CoreCLR,是一项 sprawling、几乎牵动所有引擎团队的工程,不是某个小组闷头写个新后端。16

几个最硬的坎:

1. Play Mode 建在 AppDomain 上,而 CoreCLR 没有这条路。

Unity Editor 的脚本热迭代长期依赖 Mono 的 多 AppDomain:改代码 → 卸载整个 Domain → 重新加载。微软收购 Xamarin、Mono 并入 dotnet/runtime 之后,.NET Core 路线早就放弃了 AppDomain;现代 CoreCLR 用 AssemblyLoadContext 走另一条路。Unity 不能只换运行时,还得 重写 Code Reload、序列化、Enter Play Mode 整条链路16

2. 原生引擎与 GC 的契约要重签。

Mono 时代,Unity 原生 C++ 侧长期假设托管对象在内存里相对「稳定」。CoreCLR 的现代 GC 会 移动对象,原生代码里大量 GCHandle、P/Invoke 边界都要重新审计。Unite 2024 上 Unity 工程师提到,这类迁移「大体完成,但仍有优化空间」——说明不是换个 DLL 就完事。17

3. 要同时照顾 IL2CPP、Package、Profiler、旧项目。

Unity 不是只有桌面 JIT Player。IL2CPP、代码裁剪、泛型 AOT 提示、Package 生态、Profiler、Burst 并存——CoreCLR 上线不能把这些全砸烂。官方 2026 年 3 月更新也强调:6.8 第一阶段目标是 performance parity(至少不比 Mono 慢),更大性能红利还要往后再排。15

4. 组织优先级并不总在这条线上。

2017 年原型之后,公开 Roadmap 又拖了数年才在 Unite 2024 系统宣讲;中间穿插 IPO、收购、渲染管线分裂(URP/HDRP)、Runtime Fee 风波、管理层更替。我不能凭公开信息断言「某次收购直接砍了 CoreCLR 人力」,但合理判断是:这是一项需要连续多年顶层排期的基建,而 Unity 那几年同时在打很多别的仗。 慢,不等于没人做;也不等于团队都在「养老」。

时间线可以压缩成:

阶段发生了什么
2017HackWeek 原型:Windows/macOS 跑起简单 Player14
2018–2021内部持续推进,公开信息较少;Mono 仍是唯一生产运行时
2022《Unity and .NET, what's next?》公开讨论现代化方向3
2024Unite 宣布全面迁 CoreCLR,但强调工程挑战仍大17
20266.7 实验性 CoreCLR Desktop Player;6.8 计划 CoreCLR Editor15

所以更贴切的问题不是「他们是不是在磨洋工」,而是:Unity 要换的是整台编辑器的托管地基,而 Godot / 第三方 Unreal 插件换的往往是可选模块的地基。

和 Godot、Unreal 比,是不是落后了?

这个对比需要先纠偏。

Godot 4 确实在 2023 年前后就把桌面 C# 切到了 CoreCLR——通过 hostfxr 嵌入标准 .NET,不再走 Mono embedding API。18 看起来比 Unity 快很多,但前提不同:

Godot 4 C#Unity CoreCLR
引擎主语言GDScript / C++;C# 是可选模块C# 是默认主脚本语言
迁移范围换 C# 模块的运行时宿主整台 Editor + Player 从定制 Mono 迁出
4.0 初版代价一度失去移动平台 C# 导出,后来才实验性补回18必须同时不砸 iOS IL2CPP、Package、旧项目
用户预期不用 C# 可以完全无视 .NET 迁移绝大多数项目直接受影响

Godot 能「先上 CoreCLR、再慢慢补平台」,是因为 C# 本来就不是引擎本体;Unity 若也这么干,等于让全球大多数项目在实验性运行时上赌稳定性——产品上不现实。

Unreal 则更没有「官方早就出了 CoreCLR」这回事。 Epic 的主力是 C++ 与 Blueprint;你能看到的 UnrealSharp、UnrealCLR 等,是 社区或第三方插件 把 .NET 宿主嵌进 UE,覆盖面和引擎绑定深度都远小于 Unity 要把 Mono 从编辑器里连根拔掉。19 插件可以「不好用就卸了」;Unity 官方运行时不能这么玩。

公平地说

慢,有真工程原因:AppDomain 遗产、原生 GC 契约、IL2CPP 双轨、百万级项目兼容。也有组织层面拖累:多年战略摇摆、资源被别的战线分流。但若把 Unity 和「Godot 换了个 C# 模块」或「Unreal 装了个 .NET 插件」放在同一刻度上比,刻度尺本身就歪了。

运行性能:JIT 为何可能追上 AOT

RyuJIT 与分层编译

CoreCLR 使用现代 .NET 的 JIT 编译器 RyuJIT

与传统理解中「JIT 只是运行时临时随便编一下」不同,现代 JIT 已经可以做非常激进的优化。其中一个关键技术是 Tiered Compilation20

一个函数第一次被执行时,RyuJIT 可以优先快速生成机器码,让程序尽快运行:

IL → Tier 0 → 快速生成 Native Code

如果运行时随后发现这个函数是热点代码,则可以重新优化:

Hot Method → Optimizing JIT → Inlining / Devirtualization / SIMD / PGO

现代 .NET 的 Tiered Compilation 本来就是用「快速启动」和「热点代码质量」之间的动态平衡来工作。这带来了 AOT 编译器很难拥有的一项信息:程序实际上是怎样运行的。

静态编译时,编译器只能推断。JIT 却可以知道一个虚函数实际上 99.9% 都落到哪个实现、什么代码是真正的热点,以及运行机器到底支持 AVX2 还是 ARM NEON。

因此「JIT 必然比 AOT 慢」已经不是一个成立的结论。

Alexandre Mutel 曾提到,他将 xxHash128 移植到 .NET 8 后,得到的实现可以达到 C++ 的速度,在部分情况下甚至更快。12 这当然不能被理解为「C# 比 C++ 快」,它真正说明的是:现代 .NET 已经强大到足以让普通托管 C# 与优秀原生代码处于同一个性能量级,而具体结果高度取决于 workload、内存布局、SIMD、JIT warmup 与编译策略。

Benchmark 该怎么读

Unity 6.7 Alpha 已经出现了一些社区 benchmark,在特定纯托管计算任务中 CoreCLR 明显超过 Mono,甚至超过 IL2CPP。例如一个社区 path tracing 测试中,CoreCLR 单线程约为 IL2CPP 的 1.8×,多线程约 1.78×;另一个 Apple M4 测试甚至达到约 2.5–2.8×。21 与此同时 Unity 自己仍然强调,目前实验阶段的目标首先是正确性和性能基线,而不是承诺 CoreCLR 在所有项目里都会更快。22

别把 Benchmark 当成 FPS 承诺

不要把某个 CoreCLR Benchmark 中的「2× IL2CPP」理解为 Unity 游戏整体 FPS 会直接翻倍。真实游戏通常大量时间消耗在渲染、物理、Native Engine、GPU、同步与资源系统上,脚本运行时只是其中一部分。

工具链:可能比 FPS 更重要的变化

我认为 CoreCLR 对 Unity 最大的影响,反而不是「C# 又快了百分之多少」。而是:

Unity 终于重新成为一个现代 .NET 应用。

传统 Unity 有大量自己的基础设施:

Unity Compilation Pipeline
.asmdef
Unity Generated .csproj
Unity Package Manager
Mono
AppDomain Reload

而标准 .NET 世界早已经形成另一套成熟生态:

Roslyn
MSBuild
.csproj
NuGet
CoreCLR
dotnet build

Unity 6.7 已经开始实验性支持 MSBuild。过去 Unity 生成的 .csproj 更多只是提供给 IDE 使用,并不是项目构建的真正权威来源;新的模式下,.csproj 可以真正描述项目、ProjectReferencePackageReference,并原生参与 NuGet 依赖解析。23

这看似只是开发工具变化,但长期影响非常大。它意味着未来 IDE、Coding Agent、CI、dotnet CLI 与 Unity Editor 可能越来越多地共享同一套标准构建模型,而不是所有 C# 编译工作都必须依附在 Unity Editor 内部。

迭代效率:Reload、Code Reload 与 Hot Reload

大型项目里,改一行 C# 后等几十秒甚至一分钟,往往不是因为 Roslyn 编译慢,而是因为 Reload 的粒度太粗

真正的瓶颈:Domain Reload

过去 Unity Editor 长期依赖 Mono 的 AppDomain。修改脚本以后,往往需要:

Serialize Managed State

Unload AppDomain

Unload Assemblies

Load Assemblies

Restore Managed State

Reinitialize

项目越大,真正漫长的往往不是「编译 C#」,而是整个 Domain Reload:序列化场景状态、卸载整个托管世界、再全部恢复。

CoreCLR 的细粒度 Code Reload

Unity 7 正在改变这一套机制。CoreCLR 不再使用传统的多 AppDomain 模型,而拥有 AssemblyLoadContext。Unity 因此正在构建一种更加细粒度的 Code Reload:修改代码以后,只卸载和加载真正受到影响的程序集,而不是简单重建整个托管世界。1

可以把区别粗略理解成:

过去:Gameplay.dll changed → 整个 Managed Domain Reload

未来:Gameplay.dll changed → 只 Reload Gameplay 及受影响依赖

这对大型项目的迭代速度可能比单纯提升 JIT 性能重要得多。

与 .NET Hot Reload 还差多远

这里需要先区分两个概念:Code Reload 和真正意义上的 Hot Reload 并不是一回事。

  • Code Reload:卸载并重新加载发生变化的程序集(及其依赖),托管世界其余部分尽量保留。
  • Hot Reload / Edit-and-Continue:在程序继续运行的前提下,直接 patch 正在执行的方法 IL 或 metadata,甚至不必卸载程序集。

Unity 目前公开承诺的重点是前者——新的细粒度 Code Reload 模型。至于最终能做到多接近 Visual Studio / dotnet watch 那种方法级 Hot Reload 体验,还应该等待 Unity 7 的正式实现,而不是提前把两者画等号。

最值得期待的迭代改善

最值得期待的并不是「以后再也没有 Reload」,而是 Reload 的粒度终于可能从「整个托管世界」下降到「真正发生变化的代码」。

运行时 Mod:CoreCLR 的潜力与边界

这里可能是游戏开发者最容易兴奋的部分。

现代 .NET 本身就拥有成熟的动态插件模型。通过 AssemblyLoadContext,一个程序可以在运行时加载单独的程序集及其依赖:

Game.exe

├─ Core Game Assemblies

└─ Mods/
   ├─ BetterWeapons.dll
   ├─ WorldEdit.dll
   └─ Magic.dll

甚至可以让不同插件拥有彼此隔离的依赖版本。微软官方文档本身就提供了使用 AssemblyLoadContext 构建 .NET 插件系统的教程。24

从架构上说,这和 Minecraft / JVM 的 Mod 世界已经非常接近:

Minecraft + JVM + .jar Mods

Unity Game + CoreCLR + .dll Mods

而 IL2CPP 很难自然做到这一点。因为 IL2CPP 发布时已经把已知 CIL 全部 AOT 成了机器码。玩家后来扔进来的一个新 DLL,并没有一个 JIT 在设备上把其中的新方法继续编译成机器码。

事实上,这一点在 IL2CPP 之前的 iOS Mono AOT 上就已经存在:Assembly.Load 无法在设备上动态加载新程序集。5 今天很多 Unity 游戏如果需要运行时脚本或大型 Mod,只能额外引入 Lua、JavaScript、WASM、C# Interpreter、HybridCLR 或自定义 VM 等方案。

CoreCLR Desktop Player 则理论上可以直接执行标准托管程序集。不过这里的关键词依然是 潜力。Unity 并没有因为使用 CoreCLR,就自动获得 Minecraft Forge 或 Fabric 那样成熟的 Mod 系统。游戏仍然需要自己设计稳定的 IMod API、版本机制、资产加载、生命周期、依赖关系和兼容策略。

更重要的是,普通 .NET DLL 并不是安全沙箱。微软也明确提醒:不能把不受信任的插件安全地加载进一个受信任的 .NET 进程,并把 AssemblyLoadContext 当作安全边界。24

一个 Mod 如果拥有完整 .NET 权限,理论上也可能访问文件、网络,甚至调用 Native Library。

CoreCLR 让 C# Mod 变得容易,但不会自动让 C# Mod 变得安全。

iOS 与平台限制

为什么 CoreCLR 上不了 iOS

如果 CoreCLR 这么好,一个自然的问题就是:为什么不让所有平台都用 CoreCLR?

因为 CoreCLR 当前在 Unity 中依赖 JIT,而有些平台根本不允许你这样做。iOS 就是其中最重要的例子。

截至 Unity 6.7,CoreCLR Player 只提供 Windows、macOS 和 Linux 技术预览;Unity 当前生成的 iOS 工程依然包含通过 IL2CPP 转换得到的 GameAssembly2225

原因不只来自 Unity。Apple 对 App Store 应用执行动态代码有非常严格的限制。其审核规则 2.5.2 要求 App 基本保持 self-contained,并禁止应用下载、安装或执行能够引入或改变 App 功能的代码,只有少数特定场景例外。9

所以:

PC:下载 BetterWeapons.dll → CoreCLR JIT → 直接运行

是非常自然的。但:

iOS:下载 BetterWeapons.dll → JIT 新机器码 → 运行

无论从平台执行模型还是 App Store 分发政策来看,都麻烦得多。

Unity 官方在 2026 年 6 月的更新中也明确指出,iOS 一侧仍在开发 Experimental IL2CPP Player,而不是 CoreCLR Player。13

IL2CPP 不会消失

如果只看 CoreCLR 的性能,很容易得出「那以后 IL2CPP 还有什么意义?」

其实意义仍然非常明确。未来 Unity 的运行时格局,很可能不是:

Mono → IL2CPP → CoreCLR

这种三代技术前后替换,而是:

Unity 7 / .NET 10 / C# 14 CoreCLR:Editor + 桌面 Player IL2CPP:iOS / 主机 / 受限平台 JIT / 动态程序集 / 快速构建 AOT / 平台合规 / Native Binary

真正被 CoreCLR 淘汰的是 Mono

IL2CPP 则逐渐回归它最原始也最擅长的角色:AOT Backend。 桌面平台可以用 CoreCLR 获得快速构建、现代 JIT、完整 .NET 和动态程序集能力;iOS、主机以及其他受到代码执行限制的平台,则继续依赖 IL2CPP 提前生成 Native Binary。这反而是一种更加合理的分工。

收束:三个时代

回过头看,Unity 的 C# 运行时历史其实可以划分成三个非常有意思的阶段。

时代核心问题Unity 的答案
Mono如何让 C# 跨平台运行?带一个跨平台 JIT Runtime
IL2CPPJIT 被禁止怎么办?把 IL 提前转成 C++
CoreCLR如何重新接入现代 .NET?使用标准现代 Runtime 与工具链

Mono 让早期 Unity 可以用一种极其舒服的高级语言跨平台开发游戏。IL2CPP 让这套 C# 世界突破了 iOS、WebGL 和主机等 AOT 平台的限制——但它接手的并不是「从 JIT 到 AOT」的突变,而是 Mono AOT 之后的新一代 AOT 实现。而 CoreCLR 做的事情似乎更加基础:它把 Unity 的 C# 从一个长期冻结在历史中的特殊分支,重新接回正在快速发展的现代 .NET 世界。

收束

最值得期待的地方,并不是「C# 终于和 C++ 一样快」。而是未来 Unity 开发者可能同时得到现代 JIT、现代 GC、标准 .NET 工具链、更快的代码迭代,以及过去 IL2CPP 很难实现的动态插件与 Mod 能力;当平台不允许这些能力时,再退回 IL2CPP 做 AOT。这可能才是 Unity 7 的 C# 运行时改革真正重要的地方。

若你对 Unity 更宏观的历史脉络感兴趣,也可以继续阅读 Unity 的二十年;若你想理解 .NET 为何长期与 Windows 绑定、Mono 又为何先行开路,.NET 的平台弯路 会是很好的补充。

注释

  1. Unity,2026,Unity 7 Roadmap Revealed At Unite Seoul。宣布 Unity 7 将引入 CoreCLR、.NET 10、C# 14、MSBuild,以及只 reload 必要代码的新 code reload 模型。 2 3

  2. Unity Manual,Mono scripting back end。说明 Mono 后端在运行时使用 JIT 把 IL 编译为机器码;Unity 维护的是自己的 Mono 分支,而非上游完整 Mono。

  3. Alexandre Mutel,Unity,2022,Unity and .NET, what's next?。阐述 Unity 长期维护定制 Mono、以及逐步接轨现代 .NET 的战略背景。 2 3

  4. Josh Peterson,Unity,2015,An introduction to IL2CPP internals。IL2CPP 首批正式应用之一是 2015 年初的 iOS 64-bit,随后也被用于 Unity 5 WebGL;文章说明其设计动机是覆盖不支持 JIT 的平台。 2 3

  5. Aras Pranckevičius,Unity Discussions,2008-09-29,Unity is Coming to the iPhone!(#441 帖)。说明 iPhone 构建会链接引擎、Mono 运行时与已编译的脚本静态库,设备上不自带可安装的 Mono 包。另见 Unity Discussions,Greetings / Unusual Use of Unity:iPhone 上外部程序集会在 Build Player 时 AOT 编译,且 Assembly.Load 在设备上不可用。 2 3 4

  6. Miguel de Icaza,2008-11-05,Static Compilation in Mono。介绍 Mono Full AOT 模式;并提到 Unity 当时已改造本地 Mono 副本以支持向 iOS 目标的交叉编译。 2

  7. Miguel de Icaza,Mono Project,2017-11-13,Mono's New .NET Interpreter。说明 Mono 早年有 mint 解释器后因性能移除;2017 年回归,并规划 mixed mode 以在 iOS 等静态编译平台上支持部分动态代码。 2

  8. Josh Peterson,Unity Discussions,2018-03-24,Mono Interpreter?。Unity 表示关注 Mono Interpreter,但当时没有接入 Unity 脚本后端的公开计划。

  9. Apple,App Review Guidelines §2.5.2。要求 App 保持 self-contained,禁止下载、安装或执行会引入或改变 App 功能的代码(除少数例外)。限制的是「动态改变应用行为的代码」,与 JIT/AOT/解释器的技术分类并不完全等同,但会直接影响运行时 Mod 等方案。 2

  10. mono/mono,GitHub PR #9493,add interp-mixed to iOS SDK。Xamarin/Mono 侧的 mixed mode 实验:mscorlib AOT,其余代码走解释器;属于 Xamarin 生态而非 Unity 主线脚本后端。

  11. Unity Manual,Introduction to IL2CPP。官方指出 IL2CPP 需要 IL → C++ → Native 的 AOT 流程,因此通常带来更长的 Player Build 时间以及更大的机器码体积。

  12. Alexandre Mutel,Unity Discussions,Expected performance of new CoreCLR vs Mono vs IL2CPP。Mutel 指出 IL2CPP 历史上并非以性能优化为主要目标;CoreCLR 原型相对 Mono 约有 2× 提升,外部 workload 下相对 IL2CPP 也可能更快,但应视为具体场景下的工程判断,而非普适结论。其 .NET 8 xxHash128 移植达到、部分情况下超过对应 C++ 实现的说法亦出自此讨论。 2 3

  13. Unity,2026-06-16,CoreCLR, Scripting, and Serialization Update – June 2026。官方工程师说明 Unity 7.0 计划移除 Mono、Editor 与 Desktop Player 转向 CoreCLR;iOS 侧仍在开发 Experimental IL2CPP Player,而非 CoreCLR Player。 2

  14. Alexandre Mutel (xoofx),2018-04-06,Porting the Unity Engine to .NET CoreCLR。记录 2017 年 5 月 HackWeek 在约一周内让 Unity Player 在 CoreCLR 上跑起简单场景;作者同时强调这仍是原型,全面落地需要大量后续工作。 2

  15. Unity,2026-03,CoreCLR, Scripting, and ECS Status Update - March 2026。产品更新:6.7 实验性 CoreCLR Desktop Player;6.8 计划 CoreCLR Editor、移除 Mono;6.8 阶段先追求 performance parity,更大性能收益拟在后续版本。 2 3

  16. Unity,2026,The Path to CoreCLR #1: The Problem。官方工程师系列文,说明 Mono 必须退场的原因(含 AppDomain 在现代 .NET 中无未来、Mono JIT 难以追上 RyuJIT),以及为何「换运行时」会演变成持续数年的全引擎工程。 2

  17. Unity Discussions,2024,CoreCLR and .NET Modernization - Unite 2024。工程师解释 CoreCLR 的 moving GC 迫使原生侧调整 GCHandle 等用法;并坦言仍有显著挑战、无法给出精确日期。 2

  18. Godot Engine,2023,What's new in C# for Godot 4.0Platform state in C# for Godot 4.2。说明 Godot 4 桌面 C# 改用 .NET SDK / CoreCLR;4.0 初版一度仅支持桌面导出,Android/iOS 为后续实验性补回。 2

  19. UnrealSharp,UnrealSharp README;UnrealCLR,nxrighthere/UnrealCLR。均为第三方/社区插件,将 .NET 宿主嵌入 Unreal Engine,非 Epic 官方内置脚本后端

  20. Microsoft Learn,.NET compilation configuration — Tiered compilation。说明现代 .NET 默认使用分层编译:Tier 0 快速 JIT,热点方法可升级为优化版本,并可配合 Dynamic PGO。

  21. Unity Discussions,Unity 6.7 Alpha is now available。社区 path tracing benchmark:在特定纯托管计算任务中,CoreCLR 单线程约为 IL2CPP 的 1.8×,多线程约 1.78×;Apple M4 上 Parallel.For 约 2.81× IL2CPP。社区测试,非 Unity 官方性能承诺。

  22. Unity 6.7 Manual,CoreCLR scripting back end (Experimental)。截至 Unity 6.7,CoreCLR Player 技术预览限于 Windows、macOS、Linux;使用 RyuJIT、Tiered Compilation 与现代分代 GC。官方提醒 benchmark 应区分 JIT warmup 与稳态性能,且当前阶段目标首先是功能验证与性能基线,而非普遍更快。 2

  23. Unity 6.7 Manual,Script compilation with MSBuild (Experimental)。Unity 6.7 开始实验性支持由权威 .csprojProjectReferencePackageReference 和 NuGet restore 组成的标准 MSBuild 工作流。

  24. Microsoft Learn,Create a .NET application with pluginsHow to use and debug assembly unloadability in .NET。说明 AssemblyLoadContext 可隔离加载与协作式卸载插件程序集,但不是安全沙箱,不受信任代码仍拥有进程权限。 2

  25. Unity Manual,Structure of a Unity Xcode project。说明 iOS 构建产物包含通过 IL2CPP 生成的 GameAssembly 等原生库,而非 CoreCLR Player。

On this page