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# 瞄准的正是右边那一栏——而不是去抢左边 C++ 的活。
🔑 先记住这个定位

C# 不是来取代 C++ 的,它和 C++ 是两条赛道。 它想服务的是"业务复杂、人比机器贵、要求快速交付且别崩"的那类软件。 理解了这一点,你就不会奇怪"C# 为什么牺牲了一点点性能去换安全"。

1.2 站在 2000 年:C++ 的四道难题

微软当年做的一件事,是让无数工程师用 C++ 去写 Windows 上的业务软件。然后他们撞上了四堵墙。

难题一:内存安全 —— 编译器帮不了你

C++ 把内存的钥匙完全交给你。强大,但也意味着下面这些错误编译器几乎不会拦,要等程序跑起来才爆炸:

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::stringstd::vector 已经大幅缓解了这些问题——而这恰恰说明了问题有多严重:需要一整套约定和纪律去弥补。 而在 2000 年前后,这些工具还没普及。

💡 为什么 C# 的选择是"垃圾回收"?

与其让每个人都小心翼翼地配对 new/delete, C# 干脆把释放内存这件事从程序员手里拿走,交给运行时自动做。 代价是偶尔的"停顿"和额外的内存开销;收益是——上面四类事故中的 ①②④ 在 C# 里 从语言层面就写不出来(没有 delete,没有未初始化的引用)。 这笔账在企业开发里非常划算。详见第 03 章。

难题二:编译模型与平台绑定 —— 源码跨平台,但是二进制不跨

C++ 编译出的是针对特定 CPU + 操作系统的机器码。同一份源码,在 Windows 上编译出的 .exe 拿到 Linux 上跑不了,换 CPU 架构更不行。而且你总得重新编译。

对企业来说这意味着:一个产品要维护多套构建、多个二进制包、多套部署脚本。

难题三:工程化与生态 —— 缺少"官方大管家"

  • 没有统一的官方标准库能覆盖网络、数据库、界面、序列化——全靠第三方(Boost、Qt、各种库)。
  • 没有统一的包管理(直到很晚才有 vcpkg/conan),找个库、装个依赖是件痛苦的事。
  • 头文件 + 编译单元这套机制导致编译极慢,巨大项目一次全量编译要几小时。

难题四:部署 —— "DLL 地狱"

这是微软自己最头疼的问题。Windows 上大量程序共享系统 DLL(动态链接库)。 不同软件需要的版本不一样,装个新软件把系统 DLL 覆盖了,别的软件就崩了——俗称 DLL Hell。还有注册表、COM 组件注册、卸载残留……软件"装上 / 卸干净"本身就是个技术活。

DLL 地狱:共享的系统库被互相覆盖 系统共享目录 · C:\Windows\System32 软件 A 需要 comctl32 v5 软件 B 需要 comctl32 v6 软件 C 需要 comctl32 v5 B 装 v6,覆盖了全局 A、C 突然崩了
安装 B 覆盖了共享库,导致依赖旧版本的 A、C 崩溃——这就是"DLL 地狱"。
💡 C# 的对策:把"依赖"打包进程序自己

.NET 的设计之一就是程序不再直接依赖系统共享库:它依赖的是 .NET 运行时, 业务依赖以程序集(Assembly)形式跟着程序走,版本互不打架。 今天你用 dotnet publish 甚至能把整个运行时一起打包成 单文件可执行程序——双击即用,不需要用户先装环境。这在 2000 年是革命性的。

1.3 2000 年:Anders Hejlsberg 与设计目标

微软把这件事交给了 Anders Hejlsberg——他此前是 Turbo Pascal 和 Delphi 的主设计师 (没错,就是那套以"编译飞快、开发极爽"著称的工具)。让他来做"下一代语言",意图很明显: 要开发体验好、编译快、工程化强

C# 出生时被赋予的四个设计目标 熟悉的语法 让 C/C++ 开发者 零成本上手 内存安全 自动内存管理 消除指针类事故 组件化 属性/事件/委托 一等公民 跨语言 · 跨平台 编译成中间语言 供多种语言共用 注意:没有"取代 C++"这一条。目标是"另一种场景的语言"。
四个目标里,只有第一个是"像 C++",其余三个都在解决 C++ 的软肋。

1.4 C# 的血缘:一次"两系合流"

很多人以为 C# 是"C++ 的儿子"。更准确的说法是:C# 有两位"血亲"—— 从 C/C++ 那边继承了语法、大括号、类型体系的骨架; 从 Java / 虚拟机那条线借来了"编译成中间码 + 运行时托管"的模型

C# 的两位"血亲" C C++ 类/模板/RAII Simula/Smalltalk Java / JVM 中间码 + 虚拟机 C# 语法像 C++,运行像 Java 继承自 C++ 家族 大括号 · 分号 · 强类型 · 面向对象 借鉴自 Java / 虚拟机系 中间语言 · 垃圾回收 · 单继承接口
"语法像 C++、运行像 Java"——这就是你上手 C# 时"既熟悉又陌生"的根源。
🔑 这条血缘线解释了太多事

为什么 C# 的 forifclassoperators 看起来几乎和 C++ 一样? → 因为设计目标就是"让 C++ 开发者零成本上手"。
为什么 C# 没有 delete、有 GC、有"单继承"、有 interface? → 因为这些是从 Java/虚拟机那一系带来的思路。

1.5 存在即合理:C# 到底强在哪

把上面的动机翻译成"C# 实实在在的优点"。这张表建议你先扫一眼,后面每章都会回来印证它。

优点 C++ 里对应的痛点 C# 的做法
内存安全 野指针、悬垂指针、泄漏、缓冲区溢出 没有裸指针 / 手动释放,运行时自动回收与边界检查
开发效率 手动写资源管理、重复样板代码多 语言内建属性、事件、LINQ、GC,减少手工负担
一致的工具链 编译器 / 构建 / 包管理各选各的 官方 dotnet 一套命令搞定编译、依赖、发布
统一的大型标准库 标准库偏底层,业务能力靠第三方 BCL 覆盖网络 / IO / 数据 / 并发 / 加密 / 界面
部署简单 DLL 地狱、注册表残留、环境依赖 依赖随程序走;可自包含 / 单文件发布
跨平台二进制 源码跨平台,但需各自重新编译 IL + CLR,同一程序集可在多平台运行
现代语言特性 靠库和第三方实现(如 std::function) 委托、异步、模式匹配、可空引用类型等内建
同一个任务:"读一个文件,把每一行发给处理函数" C++ :要操心的事 · 打开流、检查是否成功 · 处理编码 / 平台换行差异 · 手动管理缓冲区 · 保证异常时流会关闭(RAII) · 错误码还是异常?要自己定策略 C# :一行就能干 · File.ReadAllLines("a.txt") 自动处理打开 / 读取 / 关闭 / 异常 返回 string[],直接 foreach 不需要你 remember 任何释放动作
这不是"C# 更聪明",而是"C# 把该操心的活儿搬进了标准库和运行时"。

1.6 贯穿全教程的三句话

把这一章压缩成三句话,请务必记住——后面每一章都在用它们解释差异:

① 语法的外表是 C++ 的,运行的内核是虚拟机的

别被相似的外表骗了。你在读写 C# 代码时的"直觉",很多来自 C++;但它跑起来的行为由 CLR 决定。冲突时,以内核为准。

② C# 用"运行时托管"换"程序员的心智负担"

内存、类型安全、边界、部署、跨平台……这些 C++ 交给程序员的事,C# 大量交给了运行时。代价是性能与可控性,收益是更难写出致命错误、更快交付

③ 每一条"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 章要讲的重点。

返回 C# 专栏 返回目录