1.1 .NET 平台与运行时全景


1.1 .NET 平台与运行时全景

本节摘要:.NET 是一个分层的运行平台,C# 只是最上层的语言皮肤。本节从平台二十余年的演进讲起,拆解 Roslyn 编译器、CIL 中间语言、CLR 运行时与元数据各自的职责,让你建立"代码从文本到机器指令"的完整链路认知。

从一次编译报错说起

新手常遇到这样的报错:

CS0103: 当前上下文中不存在名称"Console"

这条信息里的"CS"前缀告诉我们一个事实:错误发生在 C# 编译器阶段,代码还没到运行时。而另一类错误,比如把 null 转型成值类型抛出的 InvalidCastException,则是运行时才爆出来。分清错误来自编译器还是运行时,是理解 .NET 分层的第一步,也是排错的第一直觉。

C# 的编译器叫 Roslyn,从 2015 年起它本身也是用 C# 写的、以 NuGet 包形式发布的库。它读入你的源码,做词法与语法分析得到语法树,再做语义绑定——把每个名字绑定到具体的类型与成员,执行类型检查与重载决议,最后生成 CIL(公共中间语言)指令,连同元数据一起打包成程序集文件。运行时 CLR 接手后,方法在第一次被调用前由 JIT 即时编译成本机机器码。也就是说,你的代码被翻译了两次。

平台演进:三条分支合流

图 .NET 平台二十年演进时间线

图 .NET 平台二十年演进时间线

2002 年发布的 .NET Framework 绑定 Windows,企业内 Web 服务与桌面工具沉淀了大量存量代码。2016 年微软另起炉灶推出开源跨平台的 .NET Core,把运行时重写一遍、类库拆成包、提供命令行工具链。2020 年的 .NET 5 统一品牌,跳过"4"直接命名,宣告 Framework 与 Core 的合流;之后的 .NET 6、8 都是长期支持版本,.NET 8(2023)还带来了原生 AOT 编译——发布时直接产本机码,JIT 都省了,启动速度接近原生程序。选型结论很直接:新项目一律用 .NET 8,老 Framework 项目在迁移前评估依赖兼容性即可,不必恐慌性重写。

一段代码的完整旅程

int Add(int a, int b) => a + b; Console.WriteLine(Add(2, 3));

这两行代码的经历如下:Roslyn 把 Add 的表达式体编译成两条 CIL 指令(加载参数、相加、返回),把方法签名与名字写进元数据表;程序启动时,CLR 的类型加载器读到 Add 所属类型,方法本体此刻还是 CIL;第一次调用发生时,JIT 把 CIL 翻译成 x64 或 ARM64 机器码并缓存,后续调用直接走缓存。你可以用工具反编译程序集亲眼看到 CIL——比如命令行下用自带的反汇编工具,能看到 Add 对应 ldarg.1; ldarg.2; add; ret 四条指令。这种"第一次调用才编译"的模式解释了一个现象:程序里从不执行的分支永远不会付编译成本。

元数据是容易被忽略的一环。程序集里除了 CIL 还有完整的类型描述:类有哪些字段、方法签名、特性标注。反射、动态代理、序列化框架全都依赖它。第三章讲反射时我们会回来细看——那是程序"阅读自己"的通道。

CTS:让多种语言共用一个运行时

公共类型系统 CTS 规定了所有 .NET 语言都必须映射到的类型模型:一切类型要么是值类型(继承自 System.ValueType),要么是引用类型;两者的公共祖先都是 System.Object。C#、F#、VB 编译出的程序集遵守同一套模型,所以 F# 写的库可以被 C# 无缝调用——语言只是语法皮肤,程序集层面的契约是统一的。CTS 里还有一条对初学者最重要的规定:int 只是 System.Int32 的别名,boolSystem.Boolean,写出别名还是全名,编译产物完全一样。

环境与工具链

命令行一条命令即可体验全流程:

dotnet new console -o Hello cd Hello dotnet run

dotnet build 产物在 bin 目录下的 dll 就是程序集;dotnet publish 可做框架依赖发布或自带运行时的独立发布。IDE 层面 Visual Studio 功能最全,VS Code 加插件更轻,本教程示例用任何一种都能跑。值得强调的是 SDK 版本管理:global.json 文件可以钉住项目使用的 SDK 版本,避免团队里不同机器各用各的编译器。

本节要点回顾

  • 两次翻译:Roslyn 把 C# 编成 CIL,CLR 的 JIT 在首次调用时把 CIL 编成机器码,CS 前缀报错属于前一阶段;
  • 演进主线:Framework 到 Core 到统一 .NET,新项目选 .NET 8,存量按需迁移;
  • 程序集 = CIL + 元数据:元数据是反射与互操作的基础,第四章前的所有话题都建立在它之上;
  • CTS 统一类型模型:一切类型归为值类型与引用类型,多语言互操作因此成立;
  • JIT 惰性:未执行的代码不付编译成本,这也解释了启动预热现象。

下一节我们把镜头拉近,看一个完整的 C# 程序由哪些结构单元组成,顶级语句又替你省掉了什么。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U