02-CSharp运行时与跨平台入门

C#、IL 与运行时入门

状态:补充(从一篇提纲拆成两篇后的详细版)

上级导航:<00-编程基础导航>

下一篇(Unity 落地):<03-Unity跨平台与IL2CPP>

相关:<01-依赖注入DI入门>

这篇先解决什么

初学时最容易的坑是:名词很多,但它们不是并列的十个框架,而是同一条流水线上的工位。 这篇只讲通用那截:

人写的 C#,为啥不能直接给 CPU 吃?中间那份 IL 是谁?.NET、CLR、运行时、虚拟机各自站哪?

Unity 为啥能出 Android / iOS 包、IL2CPP、MonoBehaviour、编辑器版本号,放到下一篇 <03-Unity跨平台与IL2CPP>。两篇对着读,比塞在一篇里好找。

先记住这一条就够挂后面所有词:

复制代码
C# 源码  →  IL(中间稿)  →  再变成「当前这颗 CPU」的机器码

1. 先建立一张总图(别急着背定义)

把出程序想成做菜:

东西 像什么 你能不能直接端给客人
.cs 源码 中文菜谱 不能。CPU 不认人话
IL 全国统一的「标准步骤卡」 还不能。卡上不是某颗芯片的方言
机器码 这间厨房(这颗 CPU)真正在炒的手法 能。这才叫「在跑」
运行时 / CLR 后厨设备 + 厨师(读步骤卡、管火候、洗碗) 没有它,步骤卡只是纸

为啥要多一道 IL,而不是 C# 直接编成机器码?

因为 不同电脑的 CPU 指令不一样 。手机常见是 ARM64,很多 Windows PC 是 x64。如果编译器第一次就把你的脚本编成「只给某一颗 CPU 用的 0 和 1」,换平台就要从头来。先编成不绑死某一颗 CPU 的中间稿,后面再针对「今天这台机器」翻译------这就是 IL 存在的理由。

一头雾水时:IL 不是一门给你写的新语言,是编译器的草稿。你几乎不会手写它。


2. 「能跑」到底指什么

磁盘上有个 Player.cs不等于程序在跑。电脑只认机器码。

「能跑」= 至少同时满足:

  1. 代码已经变成当前这颗 CPU 听得懂的指令(或正在被翻译成这种指令)
  2. 操作系统里已经有一个进程(一块独立内存、一个正在执行的程序盒子)
  3. 若是 C# 这种托管语言,进程里通常还有人在管内存、异常、找 Main / Update------这就是运行时在干活

类比:菜谱印出来了(源码存在)≠ 锅在响(进程 + CPU 在执行)。


3. 编译时、运行时,还有没有别的「时」

程序员爱把程序的一生切成几段。名字都带「时」,但不是同一类东西。

复制代码
你写代码(还没编)
    ↓
编译时 compile time     把 C# 变成 IL(或把 C++ 变成机器码)
    ↓
(C/C++ 里还有链接时:把多个 .obj 拼成一个 exe)
    ↓
加载时 load time        系统把程序装进内存
    ↓
运行时 runtime          真正一条条执行  ← 最常问的这个
    ↓
程序退出

3.1 运行时有两层意思(最容易混)

日常说「运行时」,其实叠了两层:

意思 人话 例子
一段时间 进程已经启动、代码正在被执行 「这个 bug 是运行时才出现的」
一套程序 进程里面负责执行 IL、做 GC 的那套引擎 「Mono 运行时」「CLR 运行时」

两句都对,对象不同。可以记:

  • 「运行时阶段」≈ 进程正在执行的时候(你的理解对了一半)
  • 「某某运行时」≈ 盒子里那套执行引擎(还要再补这一半)

游戏在跑 → 一定有进程。

进程在跑 → 不一定还在用 Mono 读 IL(提前编译成原生码的包,也是进程,只是换了执行方式)。见下一篇 IL2CPP。

3.2 编译时 vs 运行时(对着报错练)

编译时 运行时
何时 保存脚本、点 Play 之前、出包时、IDE 红线 已经 Play / 已经打开游戏
谁在干活 编译器(C# 这边常是 Roslyn) CLR / Mono,或已经编好的原生程序
典型错误 类型写错、少分号、调用了不存在的方法 空引用、数组越界、真机路径不存在
你怎么发现 Console 里编译错误,游戏可能根本进不去 游戏能进,玩到某一刻才炸

#if UNITY_ANDROID 这种是编译时决定「哪段代码进包」。包里没有的代码,运行时想都想不起来。

3.3 其他「时」(知道有即可)

  • 编辑时: 人在 Unity 里拖组件、改 Inspector,还没按 Play
  • 链接时: C/C++ 课更常提;C# 很多拼接工作被编译器和打包器做掉了
  • 开发包 vs 发行包: 一个给你调试,一个给玩家(裁剪、关 Development Build)

入门只要把 编译时 / 运行时 分清。Console 里一条红字:是编不过,还是 Play 之后才炸?这是最快的直觉训练。


4. IL:中间语言

IL = Intermediate Language(中间语言)。 有时也叫 CIL(Common Intermediate Language,公共中间语言),都是同一类东西。

你写:

复制代码
int hp = 100;
hp -= 10;

C# 编译器不会 第一步就变成「Android 的 ARM 指令」或「Windows 的 x64 指令」。它先变成一份平台无关的草稿,这份草稿就叫 IL。在 Unity 里,你的脚本常常先出现在 Assembly-CSharp.dll 这类程序集里,里面装的就是 IL。

直觉:

  • IL 汇编(一步步很碎),但不是给某一颗具体 CPU 用的
  • 你不用学着写 IL;面试/排错时知道「C# 先变成这个」就够
  • CLR、Mono、IL2CPP 吃的都是这份稿 ,分叉发生在 IL 之后

5. 语言、类库、运行时:为啥 C# 老绑着 .NET

5.1 C# 是语言,.NET 不是语法老师

C# 规定:if 怎么写、class 是什么、类型怎么检查。它有正式标准(ECMA-334),理论上别人可以另做编译器。

.NET 不是「C# 语法的另一套规矩」,而是这座语言长大后住的城

  • 规定源码通常编成 IL
  • 提供一大堆现成类型(List<T>string 的许多方法、文件、网络......)
  • 规定要有一个运行时来执行 IL、做垃圾回收

所以上次说的「规矩」,指的是 怎么跑、有哪些现成零件 ,不是 if 归框架管。

日常你写的「C# 程序」其实混了三层:

例子 属于谁
语言 ifclassint、泛型怎么写 C#
类库(BCL,基础类库) List<int>Console.WriteLine .NET
运行时 new 之后谁分配内存、不用了谁回收 CLR 等

List<int> 不是 C# 关键字,是类库里的类型。没有类库,语言还能写循环,但你会觉得「这语言啥也干不了」。

5.2 「外骨骼」比喻怎么用才准确

.NET ≈ C# 的外骨骼 + 生命维持系统------这个比喻能用。

再拆开,避免把所有事都塞进「翻译」:

角色 像什么 具体是谁
C# 身体(怎么思考、怎么写句子) 语言本身
.NET 类库 外骨骼的手脚 List、IO、线程
CLR 等运行时 氧气面罩 + 新陈代谢 GC、加载程序集、执行 IL
编译器 + JIT/AOT 翻译官 C# → IL → 机器码

.NET 提供了「可以翻译、可以执行」的整套环境 ;「把 C# 变成机器码」是其中编译器和运行时(或 Unity 的 IL2CPP)在做,不是 List<T> 那层装甲在做。

这副外骨骼也不是只给 C# 穿:F#、VB.NET 也能编进同一套 IL。C# 只是住得最久的房客。

5.3 为啥不是「语言完全独立、框架完全可拆」

历史上它们是双胞胎:2000 年前后微软一起做了 C# 和 .NET,第一份能用的 C# 就跟着 .NET Framework 发。

对比:

  • C:语言相对独立,Linux / Windows / 单片机都能编,标准库很薄
  • Java:和 C# 很像,语言默认住在 JVM 这座城
  • C#:默认住在 .NET 这座城

语言规范可以独立写;好用的 C# 被设计成了「C# + .NET」。 不是宇宙定律,是产品和生态选择。

5.4 设计一门语言必须先做个平台吗?

不必。最少要有:

  1. 语法:源码长什么样
  2. 语义:这段代码到底是什么意思
  3. 类型系统 (多数现代语言有):intstring 能不能乱加
  4. 实现:编译器或解释器------总得有个程序把源码变成能执行的东西

然后才是加料:

加什么 作用 是不是必须先造 .NET 这种大平台
标准库 读写文件、容器 通常要有一点,但不等于整个 .NET
运行时 GC、异常、线程 托管语言几乎都要
虚拟机平台(JVM、.NET) 一份中间码,多语言多系统 可选的产品形态
IDE、包管理 语言要有人用几乎必须有 生态,不是语法的一部分

三条常见路:

  • C / Rust:编成原生机器码,运行时很薄
  • Python:解释器 + 运行时,离开解释器跑不了
  • Java / C#:故意做成语言 + 中间码 + 运行时 + 大类库

.NET 不是「设计语言的必交作业」,是微软选的交付形态。


6. .NET 家族:.NET、.NET Framework、现在的 .NET

.NET 当成品牌 / 整套规范。下面这些是实现或年代产品,不是另一门语言。

名字 大概何时 人话
.NET Framework 2002 年起,最后停在 4.8 第一代产品,几乎只在 Windows。WinForms、WPF、老 ASP.NET 在这棵树上
.NET Core 约 2016 重写的跨平台版(Windows / Linux / macOS)
.NET 5 及以后(.NET 8 等) 2020 年起把 Core 改名 今天的主力,不再叫 Core,避免和 Framework 抢 4.x 这个号

关系可以记成:

复制代码
.NET(总称呼 / 规范:C#、IL、类库、运行时该长什么样)
 ├── .NET Framework     老产品,Windows 专用
 └── 现在的 .NET        从 Core 改名而来,跨平台
         跑它们的引擎:CLR / CoreCLR

.NET 是图纸;CLR 是微软按图纸盖的一栋楼。

不要记成「.NET 和 CLR 是两种并列的、处理 IL 的方法」。

学 Unity 时还要知道:引擎并不是 把 Windows 上那套完整 .NET Framework 塞进手机。能用 List<T>,一般用不了 WPF。Unity 用的是兼容实现里「游戏脚本需要的那部分」,细节见下一篇。

现在不必深挖:.NET Standard 版本史、Framework 和 Core 的 API 差异表。知道「Framework = 老 Windows 产品;现在口头说的 .NET 多半是跨平台那代」就够入门。


7. CLR:C# 这边的「虚拟机」

CLR = Common Language Runtime(公共语言运行时)。

它干的事,对标 Java 的 JVM:

复制代码
Java:  源码 → 字节码 → JVM 执行 → 当前 CPU 的机器码
C#:    源码 → IL     → CLR 执行 → 当前 CPU 的机器码

CLR 在进程里大致负责:

  • 加载装着 IL 的程序集(.dll)
  • 把 IL 译成当前 CPU 的指令(常见手段叫 JIT:Just-In-Time,边跑边译)
  • 垃圾回收(GC):new 出来的对象不用了帮你收
  • 异常、类型安全检查、让 C# 能调到更底层的原生代码

微软更爱叫它「运行时」,不爱天天把「虚拟机」挂嘴上。但问「C# 有虚拟机吗?」------有,就是 CLR。 不是另有一个品牌叫「C# VM」。

JIT 入门只需一句:不是出包时把所有 IL 一次译完,而是运行中用到某段代码再译(可缓存)。有的平台禁止运行时再生成可执行指令(典型是 iOS),那就得改成提前译完。下一篇的 IL2CPP 就是为这种约束准备的。


8. JVM 是虚拟机吗?为啥和 VirtualBox 撞名

「虚拟机」被两拨人用了,必须拆开,否则会以为学 Java 还要再装一个 Windows。

系统虚拟机(你原来的认知) 语言 / 进程虚拟机
例子 VirtualBox、VMware、Hyper-V JVMCLR、Python 解释器
虚拟的是什么 一整台电脑:再装一个客操作系统 一种中间码的执行环境
长什么样 真的多一个「小系统」窗口 任务管理器里一个普通进程(如 java.exe
目的 隔离出另一台机器 让语言不绑死某一颗 CPU、某一个 OS

你的原认知「在主系统上装一个别的小系统」= 左列,完全正确,但是另一类

「或者说是运行环境」= 更接近右列。JVM、CLR 都是右列。

JVM = Java Virtual Machine。对 Java 程序来说,它面对的不是 ARM 也不是 x64,而是 JVM 规定的那套指令。换电脑时换的是「哪一个 JVM 实现」,字节码可以同一份------和 IL + CLR 同一套路。

一头雾水时:VirtualBox 虚拟一台电脑;JVM / CLR 虚拟一台「只懂某种中间码的假机器」,其实就是进程里的运行时。


9. 机器码、ARM、x64:这是操作系统课吗?

更靠计算机组成 / CPU 体系结构。 操作系统是上面一层。

复制代码
你写的 C# / C
        ↓ 编译器(可能经过 IL)
机器码(某一种 CPU 指令编成的 0 和 1)
        ↓
CPU 真正执行

几个词:

  • 指令集(ISA):这类芯片约定「哪些二进制表示加法、跳转......」
  • 机器码:按某一种 ISA 编好的二进制
  • x86 / x64:很多 PC、服务器用的指令集家族
  • ARM / ARM64:很多手机、平板、Apple 芯片用的指令集家族

同一句「两个数相加」,ARM 和 Intel 的二进制可以完全不同,所以要分别编译(或在运行时由 JIT 针对当前 CPU 再译)。这就是 IL 有价值的硬件原因。

操作系统 管进程、虚拟内存、文件、驱动、权限。它使用 CPU,不发明 ARM 还是 x86。

会碰到 OS 的地方:可执行文件格式(Windows 的 PE、Linux 的 ELF)、系统调用(读文件最后要找 OS)。那是「机器码怎么向 OS 求助」,不是「ARM 是 OS 规定的」。

以后对照课程:

你想搞懂 更靠近哪门课
ARM vs x64、机器码长啥样 组成原理、汇编
进程、内存、文件、权限 操作系统
Unity 出包勾 ARM64 为目标 CPU 选指令集(再加一层引擎出包)

10. 初学者小词典(对着总图查)

先记这句
源码 给人看的 .cs
编译器 把源码变成 IL(或别的中间/目标码)
IL 不绑死某颗 CPU 的中间稿
机器码 某颗 CPU 的方言,真正能执行
进程 OS 眼里「正在运行的那个程序盒子」
运行时(时间) 盒子已经在跑的那段时间
运行时(软件) 盒子里执行 IL、做 GC 的引擎
.NET C# 的城:规范 + 类库 + 运行时生态
.NET Framework 这座城的 Windows 老产品
CLR 微软的 IL 虚拟机 / 运行时
JVM Java 的字节码虚拟机(不是 VirtualBox)
JIT 运行时再把中间码译成机器码
AOT 出门前译完(提前编译)

Unity 专用词(Mono、IL2CPP、MonoBehaviour、Player Settings)见 <03-Unity跨平台与IL2CPP>。


迷你自测

  1. Player.cs 文件,算不算「能跑」?还缺什么?
  2. 运行时 = 进程正在执行,这个理解要补哪半句?
  3. IL 是给你写的语言吗?它解决了硬件上的什么问题?
  4. List<int> 是 C# 语言关键字,还是 .NET 类库?
  5. .NET 和 CLR 是并列的两种处理 IL 的方法吗?
  6. C# 有虚拟机吗?叫啥?和 VirtualBox 是一类吗?
  7. ARM 是操作系统规定的吗?

参考倾向:

  1. 不算。还要变成当前 CPU 的机器码,并且有进程在执行(C# 通常还要运行时)。
  2. 补:说「CLR 运行时」时,还指执行 IL 的那套程序。
  3. 不是给人写的。让同一份程序后续能对接到不同 CPU。
  4. 类库。
  5. 否。.NET 是规范/平台,CLR 是实现。
  6. 有,CLR。和 VirtualBox 不是一类(语言 VM vs 系统 VM)。
  7. 否。是 CPU 指令集,OS 只是用它。

待后续补充

  • 用 ildasm / ILSpy 看一眼自己写的方法变成了啥(只需猎奇,不必精读)
  • GC 为啥会导致游戏卡一下的直觉
  • NativeAOT(.NET 自己那条「提前变成原生码」)和 Unity IL2CPP 的对比

修订记录

日期 变更
2026-08-23 初稿提纲(含 Unity 跨平台,偏短)
2026-08-23 拆篇:本篇改为通用运行时详解;Unity / IL2CPP / 版号挪到 03
相关推荐
北域码匠16 天前
三分查找算法完整详解(C# 原生实现,无第三方库)
数值优化·分治算法·查找算法·编程基础·三分查找·c#算法·算法教程
2601_951644321 个月前
C语言二级备考指南
c语言·编程基础·编译环境·二级考试·题库选择
捷瑞电子工坊1 个月前
C语言条件操作符(? :)——三分钟上手
c语言·条件操作符·编程基础·三目运算符
Thanks_ks4 个月前
【第 002 讲】Python 标准开发环境搭建:运行环境 | 环境变量 | IDE 部署 | 配置优化
ide·python·pycharm·开发工具·环境配置·环境变量·编程基础
Byte不洛4 个月前
理解C++异常机制:栈展开、异常传播与异常安全
c++·异常处理·后端开发·编程基础·try catch
『昊纸』℃4 个月前
C语言学习心得集合 篇1
c语言·算法·编程基础·学习心得·实践操作
Lauren_Blueblue5 个月前
第十六届蓝桥杯省赛Python研究生组-C变换数组
python·算法·蓝桥杯·编程基础
2301_822782825 个月前
C语言数组通关攻略!从一维到字符数组,零基础也能轻松掌握
c语言·算法·数组·编程基础·避坑技巧
极客小俊5 个月前
【H5 前端开发笔记】第 03 期:HTML的历史、书写规范与文档类型声明<!DOCTYPE> 详解
html·学习笔记·前端开发·编程基础·免费教程·html文档类型声明·零基础学习