CH 01 · 世界观
为什么会有 C#?
这一章不写一行"正经"代码。它只回答一个问题:在已经有了 C 和 C++ 的世界里, 2000 年为什么还要再造一门语言?
别跳过它。你后面遇到的每一条"C# 的奇怪规矩",答案几乎都藏在今天这一章里。
- C++ 在工程与安全层面留下了哪几类难以根治的问题?
- C# 从 C++ 继承了什么、又抛弃了什么、从哪里"借"了另一样东西?
- 为什么说 C# 的存在是有道理的(而不是"C++ 的简化版")?
1.1 先承认:C 和 C++ 极其成功
在你已经熟悉的那条线上,C 是"可移植的汇编",C++ 在它之上加了类、模板、异常、RAII、STL…… 这两门语言撑起了操作系统、浏览器、游戏引擎、数据库、嵌入式——几乎所有"性能敏感"的地方。
所以 C# 的诞生不是因为"C 不好"。恰恰相反:正因为 C/C++ 太成功,它被用在了越来越多 它并不擅长的场景里——比如需要几十上百人协作、要跑七年、要有图形界面、要连数据库的企业应用。
C# 不是来取代 C++ 的,它和 C++ 是两条赛道。 它想服务的是"业务复杂、人比机器贵、要求快速交付且别崩"的那类软件。 理解了这一点,你就不会奇怪"C# 为什么牺牲了一点点性能去换安全"。
1.2 站在 2000 年:C++ 的四道难题
微软当年做的一件事,是让无数工程师用 C++ 去写 Windows 上的业务软件。然后他们撞上了四堵墙。
难题一:内存安全 —— 编译器帮不了你
C++ 把内存的钥匙完全交给你。强大,但也意味着下面这些错误编译器几乎不会拦,要等程序跑起来才爆炸:
// ① 内存泄漏:忘了 delete,程序越跑越胖
int* p = new int[1000];
// ... 用完之后忘了 delete[] p;
// ② 悬垂指针:对象已经销毁,指针还在指向那块旧地
int* q = new int(42);
delete q;
*q = 10; // 💥 未定义行为,可能没事,可能崩
// ③ 缓冲区溢出:写越界,经典的安全漏洞来源
char buf[8];
strcpy(buf, "this string is way too long"); // 💥 踩坏别人的内存
// ④ 野指针:指针从未初始化就读写
int* r; // 里面是垃圾值
*r = 5; // 💥 天知道写到哪里去了
现代 C++ 用智能指针、RAII、引用、std::string、std::vector
已经大幅缓解了这些问题——而这恰恰说明了问题有多严重:需要一整套约定和纪律去弥补。
而在 2000 年前后,这些工具还没普及。
与其让每个人都小心翼翼地配对 new/delete,
C# 干脆把释放内存这件事从程序员手里拿走,交给运行时自动做。
代价是偶尔的"停顿"和额外的内存开销;收益是——上面四类事故中的 ①②④ 在 C# 里
从语言层面就写不出来(没有 delete,没有未初始化的引用)。
这笔账在企业开发里非常划算。详见第 03 章。
难题二:编译模型与平台绑定 —— 源码跨平台,但是二进制不跨
C++ 编译出的是针对特定 CPU + 操作系统的机器码。同一份源码,在 Windows 上编译出的 .exe 拿到 Linux 上跑不了,换 CPU 架构更不行。而且你总得重新编译。
对企业来说这意味着:一个产品要维护多套构建、多个二进制包、多套部署脚本。
难题三:工程化与生态 —— 缺少"官方大管家"
- 没有统一的官方标准库能覆盖网络、数据库、界面、序列化——全靠第三方(Boost、Qt、各种库)。
- 没有统一的包管理(直到很晚才有 vcpkg/conan),找个库、装个依赖是件痛苦的事。
- 头文件 + 编译单元这套机制导致编译极慢,巨大项目一次全量编译要几小时。
难题四:部署 —— "DLL 地狱"
这是微软自己最头疼的问题。Windows 上大量程序共享系统 DLL(动态链接库)。 不同软件需要的版本不一样,装个新软件把系统 DLL 覆盖了,别的软件就崩了——俗称 DLL Hell。还有注册表、COM 组件注册、卸载残留……软件"装上 / 卸干净"本身就是个技术活。
.NET 的设计之一就是程序不再直接依赖系统共享库:它依赖的是 .NET 运行时,
业务依赖以程序集(Assembly)形式跟着程序走,版本互不打架。
今天你用 dotnet publish 甚至能把整个运行时一起打包成
单文件可执行程序——双击即用,不需要用户先装环境。这在 2000 年是革命性的。
1.3 2000 年:Anders Hejlsberg 与设计目标
微软把这件事交给了 Anders Hejlsberg——他此前是 Turbo Pascal 和 Delphi 的主设计师 (没错,就是那套以"编译飞快、开发极爽"著称的工具)。让他来做"下一代语言",意图很明显: 要开发体验好、编译快、工程化强。
1.4 C# 的血缘:一次"两系合流"
很多人以为 C# 是"C++ 的儿子"。更准确的说法是:C# 有两位"血亲"—— 从 C/C++ 那边继承了语法、大括号、类型体系的骨架; 从 Java / 虚拟机那条线借来了"编译成中间码 + 运行时托管"的模型。
为什么 C# 的 for、if、
class、operators 看起来几乎和 C++ 一样?
→ 因为设计目标就是"让 C++ 开发者零成本上手"。
为什么 C# 没有 delete、有 GC、有"单继承"、有 interface?
→ 因为这些是从 Java/虚拟机那一系带来的思路。
1.5 存在即合理:C# 到底强在哪
把上面的动机翻译成"C# 实实在在的优点"。这张表建议你先扫一眼,后面每章都会回来印证它。
| 优点 | C++ 里对应的痛点 | C# 的做法 |
|---|---|---|
| 内存安全 | 野指针、悬垂指针、泄漏、缓冲区溢出 | 没有裸指针 / 手动释放,运行时自动回收与边界检查 |
| 开发效率 | 手动写资源管理、重复样板代码多 | 语言内建属性、事件、LINQ、GC,减少手工负担 |
| 一致的工具链 | 编译器 / 构建 / 包管理各选各的 | 官方 dotnet 一套命令搞定编译、依赖、发布 |
| 统一的大型标准库 | 标准库偏底层,业务能力靠第三方 | BCL 覆盖网络 / IO / 数据 / 并发 / 加密 / 界面 |
| 部署简单 | DLL 地狱、注册表残留、环境依赖 | 依赖随程序走;可自包含 / 单文件发布 |
| 跨平台二进制 | 源码跨平台,但需各自重新编译 | IL + CLR,同一程序集可在多平台运行 |
| 现代语言特性 | 靠库和第三方实现(如 std::function) | 委托、异步、模式匹配、可空引用类型等内建 |
1.6 贯穿全教程的三句话
把这一章压缩成三句话,请务必记住——后面每一章都在用它们解释差异:
别被相似的外表骗了。你在读写 C# 代码时的"直觉",很多来自 C++;但它跑起来的行为由 CLR 决定。冲突时,以内核为准。
内存、类型安全、边界、部署、跨平台……这些 C++ 交给程序员的事,C# 大量交给了运行时。代价是性能与可控性,收益是更难写出致命错误、更快交付。
例如"虚方法必须写 virtual"、"重写必须写 override"、"异常必须显式捕获或声明"……
它们都是为了防止 C++ 里"静默出错"。学到任何新规矩时,问自己一句:它在防 C++ 的哪个坑?
小测验(先想,再展开)
Q1:C# 是不是"C++ 的简化版 / 阉割版"?点开看答案
不是。它是面向不同问题域的另一门语言。C++ 的价值是"极致性能 + 完全可控",C# 的价值是"安全 + 效率 + 工程化"。 两者更准确地说是互补赛道:游戏引擎底层用 C++,游戏逻辑用 C#(Unity);后端核心用 C++,业务服务用 C#。用"C++ 的简化版"去理解 C#,会让你处处困惑。
Q2:为什么 C# 要"先编译成中间语言 IL",而不是像 C++ 直接编译成机器码?点开看答案
三个理由:
① 跨平台:IL 是平台无关的,同一份程序集可以由不同平台的 CLR 变成各自的机器码。
② 跨语言:C#、F#、VB.NET 都能编译成 IL,互相调用。
③ 运行时能做更多事:JIT 可以针对当前 CPU 优化、可以做安全检查、可以支持反射 / GC。
详细的编译模型见第 02 章。
Q3:下面这些说法,哪些是"错的 C++ 直觉"?点开看答案
❌ "C# 的 struct 和 C++ 的 struct 一样,就是个默认 public 的类。"
→ 完全不同:C# 的 struct 是值类型,class 是引用类型。这是第 04 章的核心。
❌ "C# 的 List<T> 就是 C++ 的模板,本质一样。"
→ 只是长得像,实现机制完全不同。参见第 06 章。
❌ "对象不用了,C# 会像 C++ 析构函数那样立刻释放。"
→ 不会。C# 的"析构"时机由 GC 决定,不确定。这是第 03、09 章要讲的重点。