C# 版本:数字背后隐藏的秘密及其工作原理
\n\n如果你曾经浏览过 Stack Overflow 上的讨论,你肯定见过一个获得了数千次浏览的问题:"What are the correct version numbers for C#?"。看起来,这有什么难的呢?打开文档就搞定了。但事实上,事情比想象的要复杂得多。尤其是当你发现 C# 7.0 随 .NET Framework 4.7 发布,而 C# 8.0 却随 .NET Core 3.0 发布时。为什么 C# 5.0 完全不绑定任何特定的 .NET 版本?欢迎来到一个数字具有欺骗性的世界。
\n\n在这篇文章中,我们不仅仅会列出版本。我们将深入探究 Roslyn 编译器的内部机制,弄清楚版本号如何与平台相关联,并通过实际的代码示例展示每个版本中出现了哪些特性。我们开始吧!
\n\n为什么关于 C# 版本的问题如此受欢迎?
\n\n答案很简单:C# 语言版本与 .NET 版本之间的混淆。许多开发者认为 C# 7.0 等同于 .NET Framework 4.7。但事实并非如此。C# 语言有自己的生命周期,而 .NET 平台也有自己的。Roslyn 编译器(也就是 csc.exe)是它们之间的桥梁。它接收 C# 代码并将其转换为 IL 代码(中间语言),然后由 CLR(公共语言运行时)执行。
关键点在于:同一个编译器版本可以支持不同的语言版本。例如,来自 Visual Studio 2019 的编译器可以根据项目设置编译 C# 7.0、7.1、7.2、7.3、8.0 的代码。这就是为什么 Stack Overflow 上会有这么多混淆。
\n\nC# 版本简史
\n\n让我们快速浏览一下主要的里程碑。我不会逐一列举所有 12 个版本——最好挑出那些真正改变游戏规则的版本。
\n\nC# 1.0 (2002) — 开端
\n第一个语言版本随 .NET Framework 1.0 一起发布。这是一种托管语言,语法类似于 Java,但具有独特的特性,如属性和索引器。没有 LINQ、async/await 或泛型。简单、可靠、乏味。
\n\nC# 2.0 (2005) — 泛型改变一切
\n这个版本带来了泛型、匿名方法、迭代器(yield return)和分部类。这是一个突破:你不再需要编写带有装箱/拆箱操作的 ArrayList,而是可以使用 List<T>。性能得到了飞跃。
// C# 2.0 — 泛型\nList<int> numbers = new List<int> { 1, 2, 3 };\nnumbers.ForEach(n => Console.WriteLine(n * 2)); // 匿名方法\n\n\nC# 3.0 (2007) — LINQ 和 Lambda
最具革命性的版本。LINQ(语言集成查询)改变了处理数据的方式。Lambda 表达式、扩展方法、匿名类型、对象初始化器——所有这些都出现在这里。那时 Roslyn 编译器尚未诞生,但正是在 C# 3.0 中,为未来奠定了基础。
// C# 3.0 — LINQ 和 Lambda\nvar evenNumbers = Enumerable.Range(1, 10)\n .Where(x => x % 2 == 0)\n .Select(x => x * x);\nConsole.WriteLine(string.Join(", ", evenNumbers)); // 4, 16, 36, 64, 100\n
C# 5.0 (2012) — Async/await
异步编程变得前所未有的简单。async 和 await 不仅仅是语法糖。编译器会生成一个复杂的状态机来管理线程。如果你使用 C# 5.0 及更高版本编写代码,你可以忘记手动管理 BeginInvoke 和 EndInvoke。
// C# 5.0 — async/await\nasync Task<string> FetchDataAsync(string url)\n{\n using (var client = new HttpClient())\n {\n return await client.GetStringAsync(url);\n }\n}\n
C# 6.0 (2015) — 日常使用的语法糖
这个版本没有带来新的范式,但让代码更简洁:字符串插值