.NET 是一套"语言运行平台 + 统一类型系统 + 通用中间语言 + 托管运行时 + 基础类库 + SDK/构建工具 + 应用框架"的完整软件开发平台。
Microsoft 官方将 .NET 定义为免费、开源、跨平台的开发平台,可用于构建桌面、Web、云服务、移动端等多种形态的应用。其底层运行模型并非私有黑盒,核心部分由 ECMA-335《Common Language Infrastructure(CLI)》 正式标准化,包括通用中间语言、元数据格式、类型系统、虚拟执行系统等核心规范。
一、概念:
理解 .NET 的第一步,是厘清常被混淆的一组术语。
| 概念 | 本质 | 定位 |
|---|---|---|
| C# | 高级编程语言 | 开发者编写业务逻辑使用的语言 |
| .NET | 完整软件开发平台 | 承载 C#/F#/VB 等语言的开发与运行环境 |
| CLI | ECMA 国际标准 | 定义"通用语言基础设施"应当具备的能力与规范 |
| CLR | Common Language Runtime | .NET 的托管运行时,是 CLI 标准的具体实现 |
| CIL / IL | Common Intermediate Language | 与 CPU 架构无关的通用中间指令集 |
| CTS | Common Type System | .NET 统一类型系统,定义类型的规则与结构 |
| CLS | Common Language Specification | 不同 .NET 语言之间互操作的公共规则子集 |
| BCL | Base Class Library | .NET 平台提供的基础类库 |
| SDK | Software Development Kit | 包含编译器、构建工具、运行时的完整开发工具包 |
| Runtime | 运行时环境 | 负责加载与执行托管程序的最小环境 |
其中最容易混淆的是 CLI 与 CLR:
- ECMA-335 CLI 是标准与规范,定义了通用语言虚拟机应当遵循的规则;
- CLR / CoreCLR / Mono 是具体实现,在不同场景下承载程序的实际执行。
可以类比为:
ECMA-335 CLI 标准
↓
CLR / CoreCLR / Mono 实现
↓
程序真正运行
在现代统一 .NET 平台中,CoreCLR 主要服务于云、服务器与桌面场景,Mono 运行时则在移动端、WebAssembly 等场景继续发挥作用,二者同属 .NET 生态体系。
二、托管执行模型:程序从源码到 CPU 的链路
C# 不是编译成 exe 就直接跑在 CPU 上。默认的托管执行模型是一条清晰的多级编译链路:
C# / F# / VB 源码
↓ 语言编译器
CIL 指令 + 元数据
↓ 打包
程序集 Assembly (.dll / .exe)
↓ CLR 加载
程序集加载器 → 类型加载器
↓ JIT 编译
目标架构本机代码 (x86-64 / ARM64)
↓
操作系统 + CPU
语言编译器首先将源代码翻译为通用中间语言(CIL)并生成配套元数据;程序执行时,再由运行时的 JIT 编译器将 CIL 翻译为对应 CPU 架构的本机代码并执行。
什么是 CIL?
CIL(Common Intermediate Language,早期也称 MSIL)是一种与具体 CPU 指令集无关的虚拟机指令集。例如一段简单的加法:
csharp
int c = a + b;
在 CIL 层面被表达为:
il
ldloc.0 // 加载局部变量 a
ldloc.1 // 加载局部变量 b
add // 执行加法
stloc.2 // 保存结果到局部变量 c
它既不是高级语言语法,也不是 x86/ARM 的机器指令,而是处于二者之间的中间表示。ECMA-335 标准完整定义了 CIL 的指令集、类型系统编码与元数据格式。
三、程序集与元数据:反射能力的底层来源
一个典型的静态程序集包含四部分:
- 程序集清单(Manifest):记录程序集的身份、版本、依赖关系、文件列表;
- 类型元数据(Type Metadata):描述程序集中所有类型的定义、成员签名、引用的外部类型;
- CIL 代码:方法的中间语言指令;
- 资源:位图、字符串表、配置文件等嵌入资源。
Microsoft 官方将 Assembly 定义为 .NET 中部署、版本控制、重用、作用域与安全权限的基本单元,运行时通过元数据感知类型的完整实现。
元数据的价值:
CLR 为了实现托管执行与类型安全,必须在运行时掌握完整的类型信息。CoreCLR 类型加载器设计文档明确指出:运行时必须能够随时确定任意对象的类型,且类型查询必须足够高效,不能依赖字典查找等慢速路径。
这套机制也直接支撑了:
- 运行时反射与动态代码生成
- 序列化与反序列化
- 依赖注入容器
- ORM 框架的对象关系映射
- Attribute 元数据编程
- 插件化与动态程序集加载
- 运行时泛型实例化
四、CLR 子系统:
可以把 CLR 看作托管程序与操作系统/CPU 之间的一层大型运行系统,其核心由多个子系统协同构成。
#mermaid-svg-vxdBMtzquCivIgIa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vxdBMtzquCivIgIa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vxdBMtzquCivIgIa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vxdBMtzquCivIgIa .error-icon{fill:#552222;}#mermaid-svg-vxdBMtzquCivIgIa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vxdBMtzquCivIgIa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vxdBMtzquCivIgIa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vxdBMtzquCivIgIa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vxdBMtzquCivIgIa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vxdBMtzquCivIgIa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vxdBMtzquCivIgIa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vxdBMtzquCivIgIa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vxdBMtzquCivIgIa .marker.cross{stroke:#333333;}#mermaid-svg-vxdBMtzquCivIgIa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vxdBMtzquCivIgIa p{margin:0;}#mermaid-svg-vxdBMtzquCivIgIa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vxdBMtzquCivIgIa .cluster-label text{fill:#333;}#mermaid-svg-vxdBMtzquCivIgIa .cluster-label span{color:#333;}#mermaid-svg-vxdBMtzquCivIgIa .cluster-label span p{background-color:transparent;}#mermaid-svg-vxdBMtzquCivIgIa .label text,#mermaid-svg-vxdBMtzquCivIgIa span{fill:#333;color:#333;}#mermaid-svg-vxdBMtzquCivIgIa .node rect,#mermaid-svg-vxdBMtzquCivIgIa .node circle,#mermaid-svg-vxdBMtzquCivIgIa .node ellipse,#mermaid-svg-vxdBMtzquCivIgIa .node polygon,#mermaid-svg-vxdBMtzquCivIgIa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vxdBMtzquCivIgIa .rough-node .label text,#mermaid-svg-vxdBMtzquCivIgIa .node .label text,#mermaid-svg-vxdBMtzquCivIgIa .image-shape .label,#mermaid-svg-vxdBMtzquCivIgIa .icon-shape .label{text-anchor:middle;}#mermaid-svg-vxdBMtzquCivIgIa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vxdBMtzquCivIgIa .rough-node .label,#mermaid-svg-vxdBMtzquCivIgIa .node .label,#mermaid-svg-vxdBMtzquCivIgIa .image-shape .label,#mermaid-svg-vxdBMtzquCivIgIa .icon-shape .label{text-align:center;}#mermaid-svg-vxdBMtzquCivIgIa .node.clickable{cursor:pointer;}#mermaid-svg-vxdBMtzquCivIgIa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vxdBMtzquCivIgIa .arrowheadPath{fill:#333333;}#mermaid-svg-vxdBMtzquCivIgIa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vxdBMtzquCivIgIa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vxdBMtzquCivIgIa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vxdBMtzquCivIgIa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vxdBMtzquCivIgIa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vxdBMtzquCivIgIa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vxdBMtzquCivIgIa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vxdBMtzquCivIgIa .cluster text{fill:#333;}#mermaid-svg-vxdBMtzquCivIgIa .cluster span{color:#333;}#mermaid-svg-vxdBMtzquCivIgIa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vxdBMtzquCivIgIa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vxdBMtzquCivIgIa rect.text{fill:none;stroke-width:0;}#mermaid-svg-vxdBMtzquCivIgIa .icon-shape,#mermaid-svg-vxdBMtzquCivIgIa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vxdBMtzquCivIgIa .icon-shape p,#mermaid-svg-vxdBMtzquCivIgIa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vxdBMtzquCivIgIa .icon-shape .label rect,#mermaid-svg-vxdBMtzquCivIgIa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vxdBMtzquCivIgIa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vxdBMtzquCivIgIa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vxdBMtzquCivIgIa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-vxdBMtzquCivIgIa .root>*{fill:#1a5276!important;color:#fff!important;stroke:#0e2f44!important;}#mermaid-svg-vxdBMtzquCivIgIa .root span{fill:#1a5276!important;color:#fff!important;stroke:#0e2f44!important;}#mermaid-svg-vxdBMtzquCivIgIa .root tspan{fill:#fff!important;}#mermaid-svg-vxdBMtzquCivIgIa .group>*{fill:#2e86c1!important;color:#fff!important;stroke:#1b4f72!important;}#mermaid-svg-vxdBMtzquCivIgIa .group span{fill:#2e86c1!important;color:#fff!important;stroke:#1b4f72!important;}#mermaid-svg-vxdBMtzquCivIgIa .group tspan{fill:#fff!important;}#mermaid-svg-vxdBMtzquCivIgIa .sub>*{fill:#d4e6f1!important;color:#1a5276!important;stroke:#2980b9!important;}#mermaid-svg-vxdBMtzquCivIgIa .sub span{fill:#d4e6f1!important;color:#1a5276!important;stroke:#2980b9!important;}#mermaid-svg-vxdBMtzquCivIgIa .sub tspan{fill:#1a5276!important;} 🧩 CLR 公共语言运行时
🔧 其他核心服务
异常系统
SEH+托管
线程管理
ThreadPool
程序集
加载
互操作
P/Invoke
元数据
访问
📐 类型系统
值类型
引用类型
继承
接口
字段
方法
属性
泛型
特性
⚡ JIT 编译器
IL验证
安全校验
编译
IL→本机
优化
内联/边界
异常子句
EH
♻️ 垃圾回收器
分代回收
Gen 0/1/2
标记压缩
复制
终结器
队列
大对象堆
LOH
后台并发
回收
4.1 JIT 编译器:从快速启动到深度优化
JIT(Just-In-Time Compiler)负责在运行时将 CIL 翻译为目标 CPU 的本机指令。现代 CoreCLR 的主力 JIT 编译器名为 RyuJIT,支持 x86-64、ARM64 等多种架构,具备完整的 SSA 优化、值编号、线性扫描寄存器分配等能力。
经典 JIT 模型面临一个根本矛盾:
- 编译越充分,代码质量越高,但启动越慢;
- 编译越简单,启动越快,但长期运行性能越差。
现代 .NET 通过 分层编译(Tiered Compilation) 解决这一矛盾:
- Tier 0:方法首次调用时使用 Quick JIT 快速生成代码,或直接加载 ReadyToRun 预编译映像,优先保证启动速度;
- Tier 1:运行时检测到高频调用的方法后,在后台线程重新进行完整优化编译,替换原有代码。
.NET Core 3.0 之后分层编译默认开启,通过"冷代码快启、热代码深优"的策略平衡启动性能与峰值性能。在此基础上,动态 PGO(Profile-Guided Optimization)还会基于 Tier 0 的运行时剖面数据进一步指导 Tier 1 的优化方向。
4.2 垃圾回收:自动内存管理的实现
.NET 采用自动垃圾回收机制,开发者通常不需要手动释放托管内存。GC 的核心逻辑并不是"变量出作用域就立即释放",而是基于可达性分析。
GC 根与可达性
GC 从一组被称为 GC Roots 的根对象出发,遍历所有引用关系,构建对象可达图:
- 线程栈上的局部变量与参数
- 静态字段
- CPU 寄存器中持有的对象引用
- GC 句柄表
- 终结队列
能够被 Roots 直接或间接到达的对象标记为"存活",不可达的对象则被判定为垃圾并回收内存。
分代回收
基于"绝大多数对象生命周期很短"的经验假设,.NET GC 采用分代回收策略:
- 第 0 代(Gen 0):年轻代,新分配的对象默认在此,回收频率最高、速度最快;
- 第 1 代(Gen 1):缓冲代,存活过一次 Gen 0 GC 的对象晋升至此;
- 第 2 代(Gen 2):老年代,存放长期存活对象,回收频率最低、开销最大。
大小 ≥ 85000 字节的大型对象直接进入大型对象堆(LOH),逻辑上属于 Gen 2,默认不会被压缩以避免移动大对象的性能开销。
GC 的边界
需要特别注意:GC 只负责托管内存的管理。对于文件句柄、套接字、数据库连接、非托管内存、原生 SDK 对象等非托管资源,GC 无法确定性地自动释放。
为此 .NET 提供了 IDisposable 接口与标准 Dispose 模式,用于确定性释放非托管资源。官方文档明确指出:GC 不分配也不释放非托管内存,Dispose 模式专门用于处理文件句柄、系统句柄、非托管指针等资源的清理。
4.3 类型加载器
类型加载器(Type Loader)负责根据元数据构建运行时类型结构,其核心数据结构包括:
- MethodTable:每个类型对应一个方法表,存放虚函数表、基类信息、接口列表、字段布局等"热"数据;
- EEClass:存放类型加载、JIT 编译、反射所需的"冷"数据,多个泛型实例化可以共享同一个 EEClass 以节省内存。
为了解决循环依赖等问题,类型加载采用分级加载(Load Levels)机制,类型结构逐步构建完成,避免了原子性加载带来的死锁与无限递归。
五、跨语言互操作的基石:CTS 与 CLS
.NET 与单语言运行时最本质的区别之一,是从设计之初就支持多语言统一运行。这一能力建立在 CTS 与 CLS 两层规范之上。
5.1 通用类型系统 CTS
CTS(Common Type System)定义了 .NET 世界中类型的完整规则:
- 所有类型分为值类型 与引用类型两大类;
- 统一规定了类、结构、枚举、接口、委托五种类型范畴;
- 定义了类型的成员、继承、可见性、泛型等规则。
无论你用 C# 的 int、VB 的 Integer 还是 F# 的 int,在运行时都对应同一个类型 System.Int32。这就是不同 .NET 语言能够无缝共享类型、互相调用库的根本原因。
5.2 公共语言规范 CLS
CTS 的规则非常完整,但不同编程语言未必支持 CTS 的全部特性。例如有些语言不支持无符号整数,有些语言不区分大小写。
为此 .NET 定义了 CLS(Common Language Specification):它是 CTS 的一个子集,规定了所有 .NET 语言都应当共同支持的一组规则。如果类库的公开接口遵循 CLS 规范,那么它可以被所有支持 CLS 的语言无障碍使用。
三者的关系可以总结为:
CLI(整个运行平台标准)
│
┌─────────┴─────────┐
│ │
CTS CIL
(类型系统) (指令集)
│
↓
CLS
(跨语言公开接口规则子集)
六、基础类库 BCL:平台能力的载体
如果只有 CLR 虚拟机,.NET 只能运行 IL 代码,无法完成任何实际业务。.NET 同时提供了庞大的基础类库(Base Class Library),覆盖:
- 基础类型与文本处理
- 集合与数据结构
- 文件与流 IO
- 网络通信与 HTTP
- 线程、任务与同步原语
- 反射与动态编程
- 加密与安全
- 进程与环境交互
这里需要特别区分语言特性与平台能力:
async/await属于 C# 语言特性,由编译器生成状态机;Task、CancellationToken、SemaphoreSlim属于 .NET 平台 API,由运行时与类库提供实现。
语言负责表达能力,平台负责提供运行机制与基础设施。
七、生态脉络:.NET Framework、.NET Core 与现代 .NET
7.1 三条技术线的定位
- .NET Framework:2002 年诞生,Windows 专属技术体系,包含 WinForms、WPF、ASP.NET Web Forms、WCF 等传统 Windows 技术;
- .NET Core:2016 年发布,完全开源、跨平台的全新实现,面向云与跨平台桌面场景;
- 现代 .NET:从 .NET 5 开始,.NET Core 去掉"Core"后缀,成为统一品牌,每年 11 月发布一个大版本。
注意:不是".NET Framework 4.8 升级到了 .NET 5",而是两条技术线并行发展后,新的统一平台以 .NET Core 代码库为主体向前演进。
7.2 当前支持状态
- .NET Framework:4.8.1 是该产品线的最新版本。从 4.5.2 开始,.NET Framework 被定义为 Windows 操作系统的组件,其支持生命周期跟随所在 Windows 系统的生命周期。
- 现代 .NET :采用每年一发的节奏,偶数版本为 LTS(长期支持),奇数版本为 STS(标准支持)。根据官方 2026 年最新支持政策:
- .NET 8(LTS):支持至 2026 年 11 月 10 日
- .NET 9(STS):支持周期已延长至 24 个月,同样至 2026 年 11 月 10 日
- .NET 10(LTS):2025 年 11 月发布,支持至 2028 年 11 月 14 日
八、标准化与兼容性:.NET Standard 的定位
.NET Standard 是一份 API 规范。
它的作用可以理解为一份契约:只要某个 .NET 实现声明支持某个版本的 .NET Standard,它就必须提供该版本规定的全部 API。这样,面向 .NET Standard 编译的类库可以在所有符合该版本的 .NET 实现上运行。
几个关键事实:
- .NET Standard 2.0 是最后一个同时兼容 .NET Framework 与现代 .NET 的版本,也是跨平台类库最常用的目标;
- .NET Standard 2.1 不再支持 .NET Framework,仅适用于 .NET Core 3.0+、Mono 等实现;
- 进入 .NET 5 统一时代后,不再发布新版本的 .NET Standard。对于不需要兼容 .NET Framework 的新项目,直接目标对应版本的 .NET 即可。
官方建议:如果需要同时支持 .NET Framework 与现代 .NET,类库应目标 netstandard2.0;否则建议直接使用现代 .NET TFM。
九、开发与部署:SDK、Runtime 与目标框架
9.1 目标框架(TFM)
项目文件中的 TargetFramework 字段使用目标框架名字对象(TFM)声明程序面向的 API 契约,例如:
net481:.NET Framework 4.8.1net10.0:.NET 10 跨平台 APInet10.0-windows:.NET 10 + Windows 专属 API(如 WinForms、WPF)
OS 特定 TFM 继承基础 TFM 的全部 API,并额外叠加对应操作系统的专有能力。通过多目标框架与预处理器指令,可以编写同时适配多个平台的代码。
9.2 SDK 与 Runtime
- .NET Runtime:只包含运行托管程序所需的最小环境,用于生产环境或用户终端;
- .NET SDK:包含 Runtime、C#/F# 编译器、MSBuild 构建引擎、dotnet 命令行工具等完整开发环境。
安装 SDK 时会自动附带对应版本的 Runtime,开发机安装 SDK 即可,纯运行环境可只安装 Runtime。
9.3 部署模型
- 框架依赖部署(FDD):依赖目标机器上已安装的 .NET Runtime,程序包体积小;
- 独立部署(SCD):将运行时与程序一起打包,目标机器无需预装 .NET;
- Native AOT:编译时直接生成本机可执行文件,无运行时依赖,启动快、内存占用低,但限制反射、动态代码生成等能力。
十、执行模型的演进:多元编译体系
经典 .NET 的"IL + JIT"模型仍是主流,但现代 .NET 已经演化出多元编译体系,适配不同场景需求:
| 编译方式 | 时机 | 特点 | 适用场景 |
|---|---|---|---|
| JIT 编译 | 运行时按需编译 | 可根据当前 CPU 做针对性优化,代码质量高 | 长期运行的服务端程序、桌面应用 |
| ReadyToRun | 编译时预生成 + 运行时补足 | 减少启动阶段 JIT 开销,平衡启动与性能 | 中等启动要求的桌面、服务端程序 |
| Native AOT | 编译时完全生成本机代码 | 无运行时依赖,启动极快,内存占用小 | 云原生函数、命令行工具、短生命周期程序 |
CIL + CLR + JIT 仍是 .NET 的经典与核心执行模型,但现代 .NET 同时提供多种 AOT 编译方式以满足不同场景需求。
十一、为什么说 CLR 是"通用语言虚拟机"
常有人将 CLR 与 JVM 类比,二者确实同为托管虚拟机,但设计出发点有显著差异:JVM 最初围绕 Java 语言设计,而 CLR 从诞生之初就以"多语言共享运行时"为核心目标。
通过统一的 CTS、统一的 CIL、统一的元数据格式,不同语言编译后都运行在同一个 CLR 上,可以互相调用、互相继承、共享异常与泛型。这正是 CLR 名称中 Common Language 的真正含义------它不是"C# 运行时",而是"通用语言运行时"。
总结:
最后用一张全景图收尾,以后遇到任何 .NET 名词,都可以对应到体系中的相应位置:
.NET 平台
│
┌───────────────┼───────────────┐
│ │ │
语言层 运行时层 类库层
│ │ │
C# CLR BCL
F# ┌────┼────┐ System.IO
VB │ │ │ System.Net
│ │ │ 线程与任务
JIT GC 加载器 ...
│
├── CTS / CLS
├── 异常系统
├── 线程调度
├── 反射机制
└── 互操作服务
│
↓
程序集 Assembly
CIL 指令 + 元数据
│
↓
本机机器码
│
↓
操作系统 + CPU
而从历史维度看:
.NET 生态
├── .NET Framework --- Windows 传统体系(4.8 / 4.8.1)
├── .NET Core --- 跨平台开源体系(1.x / 2.x / 3.x)
└── 现代 .NET --- 统一平台(5 / 6 / 7 / 8 / 9 / 10 ...)