C# 概述与环境配置

26 min入门

C# 语言概述、.NET 生态、.NET 8/9 新特性、环境搭建与 Hello World

C# 概述与环境配置

前置知识

学习目标

  • 掌握「1. 历史动机与演化」的核心机制、典型用法与常见陷阱
  • 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
  • 掌握「3. 理论推导与证明」的核心机制、典型用法与常见陷阱
  • 掌握「4. 代码示例」的核心机制、典型用法与常见陷阱
  • 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱

本文是 FANDEX C# 系列的开篇。我们将从语言的诞生背景、设计哲学、运行平台、生态构成,到本地环境的搭建与第一个可运行程序,全方位带你建立对 C# 与 .NET 的宏观认知。内容对标 MIT 6.0001、Stanford CS106X、CMU 15-110 等海外名校入门课程的教学严谨度,同时融入企业级工程实战经验,支持 0 基础自学。


本节为增量补充,帮助你选择 .NET 与 C# 版本。

  • .NET:10 为当前 LTS(2025-11 发布,支持至 2028-11),随附 C# 14;.NET 11 计划 2026-11 发布。企业新项目推荐 .NET 10 LTS。
  • 工具链:dotnet CLI 是统一入口;编辑器可选 Visual Studio 2026、VS Code + C# Dev Kit 或 Rider。
  • 平台:ASP.NET Core 用于 Web,EF Core 用于数据访问,MAUI 用于跨平台客户端。

1. 历史动机与演化

理解一门语言的现在,必须先理解它走过的路。C# 的演化是一部”在工程实用主义与语言创新之间不断平衡”的历史。

1.1 史前背景:COM 时代的痛点

在 .NET 诞生之前(1990 年代),微软生态主流是 COM(Component Object Model)与 MFC(Microsoft Foundation Classes)。开发者面临的核心痛点包括:

  1. 语言割裂:C++、VB6、Delphi 各自为政,组件互操作需通过 COM 的 IUnknown、IDispatch、BSTR、SAFEARRAY 等繁杂 ABI,易错且低效。
  2. 内存管理混乱:C++ 需手工管理 new/delete,VB6 采用引用计数(Reference Counting),两者协作时易出现循环引用导致的内存泄漏。
  3. 跨语言调用成本高:C++ 写的 COM 组件被 VB 调用时需要 IDL(Interface Definition Language)转译,开发体验差。
  4. Java 的崛起:1995 年 Sun 推出 Java,凭借”Write Once, Run Anywhere”与垃圾回收(GC)抢占企业市场,对微软构成战略威胁。

1.2 .NET Framework 1.0(2002)与 C# 1.0

2000 年 6 月,微软在 Orlando 召开的 PDC(Professional Developers Conference)上正式发布 .NET 战略(Project Lightning / NGWS, Next Generation Windows Services)。其核心目标:

  • 统一所有微软语言运行于同一虚拟机(CLR, Common Language Runtime)。
  • 提供统一的类型系统(CTS)与统一类库(BCL)。
  • 引入垃圾回收(GC)解决 COM 的内存泄漏问题。
  • 推出新一代语言 C#(发音 C-Sharp,源自音乐符号 “升号” 表示升半音),由 Anders Hejlsberg 主导设计。

Anders Hejlsberg 在加入微软前,是 Borland Turbo Pascal 与 Delphi 的首席架构师。他在 C# 设计中融合了:

  • C++ 的语法外观与性能。
  • Delphi 的组件化思想。
  • Java 的 GC 与平台中立理念。
  • VB 的快速开发体验。

C# 1.0(2002 年发布) 的核心特性:

  • 类(Class)、结构(Struct)、接口(Interface)、委托(Delegate)、事件(Event)。
  • 属性(Property)、索引器(Indexer)、运算符重载(Operator Overloading)。
  • 特性(Attribute)。
  • 基于 foreach 的迭代器模式。
  • 完整的 GC 与 AppDomain 隔离。

1.3 .NET Framework 2.0(2005)与 C# 2.0

C# 2.0 引入了影响深远的特性:

  • 泛型(Generics):在 CLR 层而非语法糖层实现,支持运行时类型擦除的 Java 泛型在性能与类型安全上无法与之相比。
  • 可空类型(Nullable Types):int? x = null; 解决了值类型无法表达”未知”的问题。
  • 迭代器(Iterators):yield return 让自定义可枚举集合极其简洁。
  • 匿名方法(Anonymous Methods):delegate(int x) { return x*x; },为后续 Lambda 铺路。
  • 协变与逆变(Covariance/Contravariance):在委托层面初步支持。

1.4 .NET Framework 3.0 / 3.5(2006 / 2007)与 C# 3.0

C# 3.0 是该语言的”二次飞跃”,伴随 .NET Framework 3.5 推出,LINQ(Language Integrated Query)登场。核心特性:

  • LINQ to Objects / SQL / XML:统一查询语法,将数据库查询纳入语言层面。
  • Lambda 表达式:x => x * x 取代冗长的匿名方法。
  • 扩展方法(Extension Methods):public static int Square(this int x) 让类型在不被继承的情况下”扩展”。
  • 匿名类型(Anonymous Types):new { Name = "Alice", Age = 30 }。
  • 隐式类型(var):编译期类型推断。
  • 对象/集合初始化器:new List<int> { 1, 2, 3 }。
  • 表达式树(Expression Trees):Expression<Func<int,int>>,将代码作为数据,是 EF Core、IQueryable 的基石。
  • 自动实现属性:public int X { get; set; }。

1.5 .NET Framework 4.0(2010)与 C# 4.0

为应对动态语言(Python、Ruby)的崛起:

  • 动态类型(dynamic):基于 DLR(Dynamic Language Runtime),与 IronPython、IronRuby 互操作。
  • 命名参数与可选参数:M(x: 1, y: 2)。
  • 泛型协变/逆变:IEnumerable<out T>,让 IEnumerable<string> 可赋值给 IEnumerable<object>。
  • 嵌入式 COM 互操作:Office 自动化体验大幅提升。
  • TPL(Task Parallel Library)与 PLINQ:基于 Task 的并行编程范式,奠定后续 async/await 基础。

1.6 .NET Framework 4.5(2012)与 C# 5.0

C# 5.0 是异步编程的里程碑:

  • async / await:将复杂的 CPS(Continuation-Passing Style)变换封装为语言特性,使异步代码”长得像同步代码”。
  • Caller Information 特性:[CallerMemberName]、[CallerFilePath]、[CallerLineNumber],简化 INotifyPropertyChanged 实现。
  • foreach 与闭包改进:修复了 C# 5 之前闭包捕获循环变量的著名 bug。

1.7 .NET Framework 4.6(2015)与 C# 6.0

C# 6.0 以”小而精”的特性提高开发体验:

  • 静态导入(using static)。
  • 异常过滤器(Exception Filters):catch (Exception e) when (e.Code == 404)。
  • 自动属性初始化器:public int X { get; set; } = 42;。
  • 只读自动属性:public int X { get; }。
  • 表达式主体成员:public int Square(int x) => x * x;。
  • null 条件运算符:person?.Address?.City。
  • 字符串插值:$"{name} is {age}"。
  • nameof 表达式:nameof(x)。
  • 索引初始化器:new Dictionary<int,string> { [1]="a", [2]="b" }。

1.8 .NET Framework 4.7(2017)与 C# 7.0 / 7.1 / 7.2 / 7.3

C# 7.x 围绕”性能”与”模式匹配”展开:

  • 元组(Tuples)与解构:(string Name, int Age) = GetPerson();。
  • 模式匹配(Pattern Matching):if (o is int i)、case int i when i > 0。
  • 本地函数(Local Functions):方法内定义方法。
  • ref 返回与本地变量:ref int Find(int[] arr)。
  • out var 声明:if (int.TryParse(s, out var n))。
  • 通用异步返回(Generalized Async Return):ValueTask<T>。
  • readonly struct / ref struct / in 参数:高性能场景下的零拷贝编程。
  • Span 与 Memory:C# 7.2 起 Span<T> 作为 ref struct 提供,是 .NET 性能革命的核心。
  • 数字分隔符与二进制字面量:0b1010_1010。

1.9 .NET Core 1.0 / 2.0 / 2.1 / 2.2(2016 - 2018)

为应对容器化与跨平台需求,微软启动 .NET Core 重写:

  • 跨平台:从 Windows-only 到 Linux / macOS 一等公民。
  • 模块化:通过 NuGet 按需引入,告别庞大的 .NET Framework 单体安装。
  • 高性能:Server GC、Span、Pipeline 优化。
  • ASP.NET Core:完全重写,抛弃 System.Web,性能比 ASP.NET 4.x 提升数倍。
  • EF Core:抛弃复杂的 EDMX,转向 Code-First 与 DbContext。
  • CLI 优先:dotnet new / build / run / test / publish 一套命令覆盖全流程。

C# 7.x 在此阶段发布,配合 .NET Standard 2.0(2017)解决类库跨 Framework 与 Core 共享问题。

1.10 .NET Core 3.0 / 3.1(2019)

  • Windows 桌面支持回归:WPF、WinForms 在 .NET Core 3.0 复活。
  • 默认可空引用类型(NRT):C# 8.0 引入,编译期 null 安全。
  • 异步流(IAsyncEnumerable):await foreach。
  • 索引与范围(Index / Range):arr[^1]、arr[1..3]。
  • switch 表达式:模式匹配的”函数式”形态。
  • 默认接口方法:接口可包含实现。
  • unmanaged 泛型约束:where T : unmanaged。

1.11 .NET 5(2020)与 C# 9.0

微软统一品牌:从此不再有 ”.NET Core”,只有 ”.NET”。

  • 记录类型(record):record Person(string Name, int Age); 不可变值对象一行搞定。
  • 顶级语句(Top-Level Statements):Program.cs 只需 Console.WriteLine("Hello");。
  • init 访问器:public int X { get; init; },对象初始化器阶段可写、之后只读。
  • 模式匹配增强:and、or、not 模式组合。
  • 目标类型 new:Person p = new();。
  • 协变返回类型:子类 override 可返回更窄类型。
  • *函数指针(delegate)**:非托管调用零开销。
  • 源生成器(Source Generators):编译期代码生成,替代反射。

1.12 .NET 6(2021 LTS)与 C# 10.0

  • 全局 using:global using System.Linq;。
  • 文件范围命名空间:namespace MyApp; 一行声明。
  • record struct:值类型 record。
  • 常量字符串插值:const string s = $"x";(受限于常量表达式)。
  • 结构无参构造:struct S { public S() {} }。
  • CallerArgumentExpression:用于参数名诊断。
  • Hot Reload:运行时修改代码即时生效。
  • 最小 API(Minimal API):app.MapGet("/", () => "Hello");。
  • MAUI:跨平台 UI 框架(Android、iOS、Windows、macOS)。

1.13 .NET 7(2022 STS)与 C# 11.0

  • 原始字符串字面量:"""...""" 支持多行与插值。
  • 列表模式:[1, 2, .., 5] 模式匹配。
  • required 成员:required public string Name { get; set; }。
  • 泛型数学支持(Generic Math):interface IAdditionOperators<TSelf, TOther, TResult>,运算符重载泛型化。
  • file 作用域类型:file class Helper 仅文件内可见。
  • UTF-8 字符串字面量:"hello"u8 返回 ReadOnlySpan<byte>。
  • ref struct 改进:可扩展更多场景。
  • Native AOT:实验性发布 AOT 编译。

1.14 .NET 8(2023 LTS)与 C# 12.0

  • 主构造函数(Primary Constructor):class Person(string name)。
  • 集合表达式:int[] a = [1, 2, 3];。
  • ref struct 泛型约束:where T : allows ref struct。
  • 别名任意类型:using Point = (int X, int Y);。
  • 内联数组:[InlineArray(4)] struct Vec4 { private int _value; }。
  • Experimental 特性:标记实验 API。
  • 时间抽象:TimeProvider,便于测试时控时。
  • ASP.NET Core 8 重大性能优化:Kestrel 与 AOT 支持最小 API。
  • Native AOT 正式可用:发布时无运行时依赖。

1.15 .NET 9(2024 STS)与 C# 13.0

  • params 集合增强:params ReadOnlySpan<int>。
  • lock 类型化:lock(obj) 使用 System.Threading.Lock。
  • partial 属性:源生成器场景使用。
  • field 上下文关键字:public string Name { get; set => field = value; } 自动 backing field。
  • ref struct 协变:ref struct 可作为 scoped 参数。
  • 泛型 overload resolution 改进。
  • Workloads of AOT:减小体积。
  • ASP.NET Core 9 静态资产交付优化。
  • HybridCache:分布式缓存抽象。
  • OpenTelemetry 内置支持。
  • Pgo(Profile-Guided Optimization)改进:JIT 性能再升 15%。

1.16 演化时间线总览

年份.NET 版本C# 版本标志性特性LTS/STS
2002.NET Framework 1.01.0CLR、C# 诞生-
2005.NET Framework 2.02.0泛型、可空类型-
2007.NET Framework 3.53.0LINQ、Lambda-
2010.NET Framework 4.04.0dynamic、TPL-
2012.NET Framework 4.55.0async/await-
2015.NET Framework 4.66.0字符串插值、null 条件-
2017.NET Framework 4.7 / Core 2.07.x元组、模式匹配、Span-
2019.NET Core 3.18.0NRT、异步流LTS
2020.NET 59.0record、顶级语句STS
2021.NET 610.0MAUI、最小 APILTS
2022.NET 711.0泛型数学、原始字符串STS
2023.NET 812.0主构造、Native AOTLTS
2024.NET 913.0HybridCache、PGOSTS

1.17 设计哲学小结

C# 的演化贯穿三大原则:

  1. 实用主义:每项新特性都源于真实工程痛点,而非纯学术理想。
  2. 类型安全优先:通过编译期检查(如 NRT、模式匹配)消灭运行时错误。
  3. 性能不退步:任何抽象都可在性能关键场景被”穿透”(如 Span<T>、ref struct、ValueTask、Unsafe)。

理解这三点,能帮你预测未来 C# 演化方向。


2. 形式化定义

2.1 C# 语言的形式化定义

C# 是一种静态类型、强类型、面向对象、多范式、托管执行的编程语言。下面从形式化角度拆解这些性质。

2.1.1 类型系统

设 T\mathcal{T} 为类型集合,Γ\Gamma 为类型环境(typing context),表达式 ee 的类型推断关系记为 Γ⊢e:τ\Gamma \vdash e : \tau。C# 的类型规则可形式化为:

Γ⊢e1:τ1Γ⊢e2:τ2τ1≡τ2Γ⊢e1+e2:τ1(T-Add)\frac{\Gamma \vdash e_1 : \tau_1 \quad \Gamma \vdash e_2 : \tau_2 \quad \tau_1 \equiv \tau_2}{\Gamma \vdash e_1 + e_2 : \tau_1} \quad (\text{T-Add})

其中 τ1≡τ2\tau_1 \equiv \tau_2 表示类型相容(含隐式转换)。

C# 类型系统在 Hindley-Milner 之上做了若干扩展:

  • 子类型(Subtyping):τ1<:τ2\tau_1 <: \tau_2 表示 τ1\tau_1 是 τ2\tau_2 的子类型。
  • 协变(Covariance):若 IEnumerable<out T>,则 T<:U⇒IEnumerable<T><:IEnumerable<U>T <: U \Rightarrow \text{IEnumerable}<T> <: \text{IEnumerable}<U>。
  • 逆变(Contravariance):若 Action<in T>,则 T<:U⇒Action<U><:Action<T>T <: U \Rightarrow \text{Action}<U> <: \text{Action}<T>。
  • 可空性(Nullability):引用类型 TT 拆为 TT(不可空)与 T?T?(可空)。

2.1.2 语法与文法

C# 文法以 LL(k) 与 LR 混合方式解析。其文法可表示为:

CompilationUnit→ExternAliasListo UsingListo GlobalAttributeso NamespaceMemberListoNamespaceMember→NamespaceDecl∣TypeDeclTypeDecl→ClassDecl∣StructDecl∣InterfaceDecl∣EnumDecl∣DelegateDecl∣RecordDecl\begin{aligned} \text{CompilationUnit} &\to \text{ExternAliasList}_o \, \text{UsingList}_o \, \text{GlobalAttributes}_o \, \text{NamespaceMemberList}_o \\ \text{NamespaceMember} &\to \text{NamespaceDecl} \mid \text{TypeDecl} \\ \text{TypeDecl} &\to \text{ClassDecl} \mid \text{StructDecl} \mid \text{InterfaceDecl} \mid \text{EnumDecl} \mid \text{DelegateDecl} \mid \text{RecordDecl} \end{aligned}

详细文法见 C# Language Specification(Dotnet GitHub 仓库 dotnet/csharpstandard)。

2.2 .NET 平台的形式化定义

.NET 平台可形式化为三元组:

.NET=⟨CLR,BCL,AppModel⟩\text{.NET} = \langle \text{CLR}, \text{BCL}, \text{AppModel} \rangle

其中:

  • CLR(Common Language Runtime):执行引擎,提供 GC、JIT、类型安全、异常处理等运行时服务。
  • BCL(Base Class Library):基础类库,包含集合、IO、网络、LINQ、反射等。
  • AppModel(Application Model):应用模型,如 ASP.NET Core、EF Core、MAUI、WPF、WinForms。

2.3 程序执行流程的形式化

C# 源代码的执行流程可形式化为映射链:

Source→C# Compiler (Roslyn)CIL→JIT / AOTMachine Code→CPUResult\text{Source} \xrightarrow{\text{C\# Compiler (Roslyn)}} \text{CIL} \xrightarrow{\text{JIT / AOT}} \text{Machine Code} \xrightarrow{\text{CPU}} \text{Result}

详细步骤:

  1. 源代码:.cs 文件,人类可读。
  2. 词法分析 → 语法分析:生成抽象语法树(AST)。
  3. 语义分析:类型检查、可空性分析、特性应用。
  4. CIL 生成:输出 .dll / .exe,内容为 Common Intermediate Language(字节码)。
  5. 类加载:CLR 加载程序集,验证类型安全。
  6. JIT 编译:方法首次调用时,JIT 将 CIL 编译为本地机器码。
  7. 执行:CPU 执行机器码,GC 在后台管理内存。

2.4 内存模型形式化

.NET 的托管堆(Managed Heap)可形式化为:

H={(ai,ti,gi,si)∣ai∈Addr,ti∈Type,gi∈{0,1,2,LOH},si∈N}\mathcal{H} = \{(a_i, t_i, g_i, s_i) \mid a_i \in \text{Addr}, t_i \in \text{Type}, g_i \in \{0,1,2,\text{LOH}\}, s_i \in \mathbb{N}\}

其中 aia_i 为地址,tit_i 为类型,gig_i 为所属 GC 代(Generation 0/1/2/LOH),sis_i 为大小。

GC 的回收策略可表示为:

Collect(g):H→Hwhere g∈{0,1,2}\text{Collect}(g) : \mathcal{H} \to \mathcal{H} \quad \text{where } g \in \{0,1,2\}

回收后保留的对象晋升一代:

∀(a,t,gi,s)∈Hsurvived:gi′=min⁡(gi+1,2)\forall (a, t, g_i, s) \in \mathcal{H}_{\text{survived}} : g_i' = \min(g_i + 1, 2)

2.5 异步执行模型形式化

async/await 的状态机重写可形式化为变换 Tasync\mathcal{T}_{\text{async}}:

Tasync:AsyncMethod→StateMachine\mathcal{T}_{\text{async}} : \text{AsyncMethod} \to \text{StateMachine}

其中 StateMachine\text{StateMachine} 是一个有限状态机 (S,s0,Σ,δ,F)(S, s_0, \Sigma, \delta, F):

  • SS:状态集合,每个 await 点对应一个状态。
  • s0∈Ss_0 \in S:初始状态。
  • Σ\Sigma:输入符号(awaitable 完成事件)。
  • δ:S×Σ→S\delta : S \times \Sigma \to S:状态转移函数。
  • F⊆SF \subseteq S:终止状态集合。

详细推导见本系列《async-await 状态机》一文。


3. 理论推导与证明

3.1 类型安全性证明(Sketch)

C# 类型系统满足 Progress 与 Preservation 两大性质(受 Erasure 影响,对泛型稍作调整)。

定理 4.1(Progress):对于良型表达式 Γ⊢e:τ\Gamma \vdash e : \tau,ee 要么是一个值,要么存在 e′e' 使得 e→e′e \to e'。

证明(Sketch):对 ee 的结构归纳。

  • 若 ee 是值(变量、字面量),成立。
  • 若 e=e1+e2e = e_1 + e_2,由归纳 e1e_1 要么是值要么可归约;若 e1e_1 可归约,则 e→e1′+e2e \to e_1' + e_2;若 e1=v1e_1 = v_1 是值,同理处理 e2e_2;若两者都是值,依据算术运算法则可归约。

定理 4.2(Preservation):若 Γ⊢e:τ\Gamma \vdash e : \tau 且 e→e′e \to e',则 Γ⊢e′:τ\Gamma \vdash e' : \tau。

证明(Sketch):对归约步骤分类讨论。

  • 算术:Γ⊢v1+v2:int\Gamma \vdash v_1 + v_2 : \text{int},归约为 vv(v1+v2v_1 + v_2 的算术结果),Γ⊢v:int\Gamma \vdash v : \text{int} 成立。
  • 函数调用:(λx.e) v→e[x:=v](\lambda x.e) \, v \to e[x := v],由代入引理 Γ,x:τ1⊢e:τ2⇒Γ⊢e[x:=v]:τ2\Gamma, x : \tau_1 \vdash e : \tau_2 \Rightarrow \Gamma \vdash e[x := v] : \tau_2。

3.2 协变/逆变安全性证明

定理 4.3(协变安全):若 interface I<out T>,且 T<:UT <: U,则 I<T> <: I<U> 类型安全。

证明:协变要求 T 在 I 中只出现在输出位置(返回类型)。设 I<T> 暴露方法 T M(),对 I<T> 实例 o 调用 o.M() 返回 TT 实例。若将 o 视为 I<U>,调用 o.M() 期望返回 UU。由 T<:UT <: U,返回的 TT 自动是 UU,类型安全成立。

定理 4.4(逆变安全):若 interface I<in T>,且 T<:UT <: U,则 I<U> <: I<T> 类型安全。

证明:逆变要求 T 只出现在输入位置。设 I<T> 暴露 void M(T x),对 I<T> 实例 o 调用 o.M(x) 要求 x:Tx : T。若将 o 视为 I<U>(即接受 U 类型参数),调用 o.M(y) 其中 y:Uy : U。由于 o 实际是 I<T> 实例,要求 x:Tx : T,而 T<:UT <: U 意味着 UU 实例不一定是 TT,这看似不安全——但逆变的方向相反:I<U> <: I<T> 意味着 I<U> 可用作 I<T>。此时调用 o.M(x) 其中 x:Tx : T,由 T<:UT <: U,xx 也是 UU,传给 I<U> 的 M(U) 安全。证毕。

3.3 GC 暂停时间复杂度

命题 4.5:.NET 的分代 GC 第 ii 代回收时间 TiT_i 与该代对象数量 nin_i 近似成正比,即 Ti=O(ni)T_i = O(n_i)。

推导:分代 GC 采用标记-压缩(Mark-Compact)或标记-复制(Mark-Copy)算法。

  • 标记阶段:从 GC Root 出发,BFS 遍历所有可达对象,复杂度 O(∣E∣)O(|E|),∣E∣|E| 为对象引用边数。
  • 压缩/复制阶段:遍历存活对象,更新引用,复杂度 O(nisurvived)O(n_i^{\text{survived}})。

由于第 0 代存活率低(多数对象短命),n0survived≪n0n_0^{\text{survived}} \ll n_0,故 T0T_0 极小。第 2 代(Gen 2)回收涉及全堆扫描,T2T_2 较大。

3.4 JIT 编译优化

命题 4.6:JIT 编译产生的代码性能 PJITP_{\text{JIT}} 满足 PJIT≥PAOT−ϵP_{\text{JIT}} \geq P_{\text{AOT}} - \epsilon,其中 ϵ\epsilon 为预热开销。

直觉:JIT 拥有 AOT 不具备的运行时信息:

  • 类型 profile:哪些虚方法实际只有一个实现,可去虚化(Devirtualization)。
  • 分支 profile:哪些分支几乎不执行,可优化布局。
  • 内联:跨程序集边界的内联由 JIT 决定。

Profile-Guided Optimization(PGO)在 .NET 6+ 中将 JIT 优化推向新高度。.NET 9 的动态 PGO 进一步使 PJIT>PAOTP_{\text{JIT}} > P_{\text{AOT}} 的场景比例提升。

3.5 Roslyn 编译流水线

Roslyn 编译器流水线可形式化为:

Source→LexerTokens→ParserSyntaxTree→BinderBoundTree→EmitterCIL\text{Source} \xrightarrow{\text{Lexer}} \text{Tokens} \xrightarrow{\text{Parser}} \text{SyntaxTree} \xrightarrow{\text{Binder}} \text{BoundTree} \xrightarrow{\text{Emitter}} \text{CIL}

每一阶段都暴露为公开 API(Microsoft.CodeAnalysis.CSharp),使源生成器(Source Generators)、分析器(Analyzers)、代码修复(Code Fixes)成为可能。


4. 代码示例

4.1 安装 .NET SDK

4.1.1 Windows 安装

方式一:官方安装包

下载地址:https://dotnet.microsoft.com/zh-cn/download/dotnet/9.0

选择 SDK 9.0.x 的 Windows x64 安装包(dotnet-sdk-9.0.xxx-win-x64.exe),双击安装即可。安装包会自动配置 PATH 与注册表。

方式二:winget

winget install Microsoft.DotNet.SDK.9

方式三:Chocolatey

choco install dotnet-sdk -y

4.1.2 macOS 安装

# Homebrew
brew install --cask dotnet-sdk

4.1.3 Linux(Ubuntu 22.04)

# 添加微软源
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb

# 安装 SDK
sudo apt-get update && sudo apt-get install -y dotnet-sdk-9.0

4.1.4 验证安装

dotnet --version
dotnet --list-sdks
dotnet --list-runtimes

期望输出:

9.0.100
9.0.100 [/usr/local/share/dotnet/sdk]
Microsoft.NETCore.App 9.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.AspNetCore.App 9.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]

4.2 第一个控制台应用

4.2.1 创建项目

mkdir HelloFandex && cd HelloFandex
dotnet new console -n HelloFandex -o .
dotnet run

dotnet new console 生成如下结构:

flowchart TD
    T0["HelloFandex/"]
    T1["HelloFandex.csproj"]
    T2["Program.cs"]
    T3["obj/"]
    T4["HelloFandex.csproj.nuget.g.props"]
    T0 --> T1
    T0 --> T2
    T0 --> T3
    T3 --> T4

4.2.2 默认 Program.cs

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

这是 C# 9.0 之后的顶级语句形式,等价于:

using System;

namespace HelloFandex;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

4.2.3 csproj 文件解读

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <RootNamespace>HelloFandex</RootNamespace>
    <AssemblyName>HelloFandex</AssemblyName>
  </PropertyGroup>

</Project>
字段含义
Sdk="Microsoft.NET.Sdk"使用 .NET 默认 SDK,决定默认引用程序集与 build 任务
OutputTypeExe 表示可执行;Library 表示类库
TargetFramework目标框架:net9.0、net8.0、netstandard2.0
ImplicitUsings是否自动 using 常用命名空间
Nullable启用可空引用类型检查

4.3 编译与运行机制

4.3.1 编译

dotnet build

执行流程:

  1. Restore:解析 NuGet 引用,写入 obj/project.assets.json。
  2. GenerateAssemblyInfo:生成 AssemblyVersion 等元数据。
  3. Csc:调用 Roslyn 编译器,输出 CIL 到 bin/Debug/net9.0/HelloFandex.dll。
  4. CopyFilesMarkedCopyLocal:拷贝 NuGet 依赖到输出目录。

4.3.2 运行

dotnet run

dotnet run 等价于 dotnet build && dotnet <assembly.dll>,是开发期最常用命令。

4.3.3 发布

# 框架依赖发布(Framework-Dependent)
dotnet publish -c Release -o ./publish

# 自包含发布(Self-Contained)
dotnet publish -c Release -r win-x64 --self-contained

# Native AOT
dotnet publish -c Release -r linux-x64 /p:PublishAot=true

不同发布方式的差异:

发布方式体积启动速度运行时依赖适用场景
框架依赖小(~100KB)中需安装 .NET Runtime容器化、云原生
自包含大(~80MB)中无离线部署
Native AOT中(~10MB)极快无函数计算、CLI 工具

4.4 引入 NuGet 包

dotnet add package Newtonsoft.Json
dotnet add package Serilog.Extensions.Hosting --version 8.0.0

Program.cs:

using Newtonsoft.Json;

var person = new { Name = "Alice", Age = 30 };
string json = JsonConvert.SerializeObject(person);
Console.WriteLine(json);

var deserialized = JsonConvert.DeserializeObject<dynamic>(json);
Console.WriteLine($"Name: {deserialized.Name}, Age: {deserialized.Age}");

编译运行:

dotnet run

输出:

{"Name":"Alice","Age":30}
Name: Alice, Age: 30

4.5 多项目解决方案

mkdir Demo && cd Demo
dotnet new sln -n Demo
dotnet new console -o src/App -n App
dotnet new classlib -o src/Lib -n Lib
dotnet sln add src/App/App.csproj
dotnet sln add src/Lib/Lib.csproj
cd src/App && dotnet add reference ../Lib/Lib.csproj

Lib/Class1.cs(重命名为 Calculator.cs):

namespace Lib;

public static class Calculator
{
    public static int Add(int a, int b) => a + b;
    public static int Multiply(int a, int b) => a * b;
}

App/Program.cs:

using Lib;

Console.WriteLine(Calculator.Add(3, 5));
Console.WriteLine(Calculator.Multiply(3, 5));

4.6 dotnet 命令速查表

# 项目创建
dotnet new console|classlib|web|webapi|mstest|xunit|sln|gitignore|globaljson

# 编译运行
dotnet build
dotnet run
dotnet publish

# 包管理
dotnet add package <name>
dotnet add reference <project>
dotnet remove package <name>
dotnet list package --outdated

# 测试
dotnet test
dotnet test --filter "FullyQualifiedName~CalculatorTests"
dotnet test --collect:"XPlat Code Coverage"

# 工具
dotnet tool install -g dotnet-format
dotnet tool install -g dotnet-ef
dotnet tool list -g
dotnet tool update -g dotnet-format

# 解决方案
dotnet sln add <project>
dotnet sln remove <project>
dotnet sln list

# EF Core
dotnet ef migrations add InitialCreate
dotnet ef database update
dotnet ef dbcontext scaffold "<connection>" Microsoft.EntityFrameworkCore.SqlServer

4.7 调试配置

4.7.1 VS Code launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Core Launch (console)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceFolder}/bin/Debug/net9.0/${workspaceFolderBasename}.dll",
      "args": [],
      "cwd": "${workspaceFolder}",
      "stopAtEntry": false,
      "console": "internalConsole"
    },
    {
      "name": ".NET Core Attach",
      "type": "coreclr",
      "request": "attach",
      "processId": "${command:pickProcess}"
    }
  ]
}

4.7.2 VS Code tasks.json

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "command": "dotnet",
      "type": "process",
      "args": [
        "build",
        "${workspaceFolder}/${workspaceFolderBasename}.csproj",
        "/property:GenerateFullPaths=true",
        "/consoleloggerparameters:NoSummary"
      ],
      "problemMatcher": "$msCompile"
    }
  ]
}

4.8 完整示例:温度转换器

Program.cs:

using System.Globalization;

namespace HelloFandex;

public static class Program
{
    public static int Main(string[] args)
    {
        if (args.Length == 0)
        {
            PrintUsage();
            return 1;
        }

        if (!double.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var celsius))
        {
            Console.Error.WriteLine($"Invalid temperature: {args[0]}");
            return 2;
        }

        double fahrenheit = CelsiusToFahrenheit(celsius);
        Console.WriteLine($"{celsius:F2} °C = {fahrenheit:F2} °F");
        return 0;
    }

    /// <summary>
    /// 摄氏度转华氏度:F = C × 9/5 + 32
    /// </summary>
    public static double CelsiusToFahrenheit(double celsius) => celsius * 9.0 / 5.0 + 32.0;

    private static void PrintUsage()
    {
        Console.WriteLine("Usage: HelloFandex <celsius>");
        Console.WriteLine("Example: HelloFandex 36.5");
    }
}

编译与运行:

dotnet build
dotnet run -- 36.5
dotnet run --project HelloFandex.csproj -- 100

期望输出:

36.50 °C = 97.70 °F
100.00 °C = 212.00 °F

5. 对比分析

5.1 C# vs Java

维度C#Java
公司MicrosoftOracle(原 Sun)
首次发布20021995
编译目标CIL(CLR)Bytecode(JVM)
泛型实现真泛型(CLR 层)类型擦除(语法糖)
值类型struct(栈分配)仅 primitive
异步语法async/await(语言级)CompletableFuture + virtual thread(Java 21)
属性(Property)原生支持需 getter/setter 方法
事件(Event)原生支持需接口或监听器
LINQ语言集成Stream API(Java 8+)
运行时 GC分代 + 后台 + 服务端G1、ZGC、Shenandoah
AOTNative AOT(.NET 7+)GraalVM、Native Image
桌面WPF、WinForms、MAUIJavaFX
WebASP.NET CoreSpring Boot
ORMEF CoreHibernate、JPA
平台Win/Linux/macOS/iOS/Android同样跨平台

5.1.1 泛型差异深度对比

C#:

List<int> list = new() { 1, 2, 3 };
list.Add(4);
int sum = list.Sum();   // 无装箱

Java:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
int sum = list.stream().mapToInt(Integer::intValue).sum();  // 拆箱开销

C# 的 List<int> 在底层是 int[],无装箱;Java 的 ArrayList<Integer> 底层是 Object[],存在装箱开销。

5.1.2 异步对比

C#:

public async Task<string> GetDataAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

Java(CompletableFuture):

public CompletableFuture<String> getDataAsync(String url) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            return new HttpClient().send(
                HttpRequest.newBuilder(URI.create(url)).build(),
                BodyHandlers.ofString()
            ).body();
        } catch (Exception e) { throw new RuntimeException(e); }
    });
}

Java 21 Virtual Thread:

public String getData(String url) throws Exception {
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        return executor.submit(() -> new HttpClient().send(
            HttpRequest.newBuilder(URI.create(url)).build(),
            BodyHandlers.ofString()
        ).body()).get();
    }
}

C# 的 async/await 在编译期重写为状态机,无需操作系统线程;Java 的 Virtual Thread 类似,但需要 JVM 层支持。

5.2 C# vs TypeScript

维度C#TypeScript
类型系统静态、强静态、结构化、可选
运行时CLRV8 / Node.js
类型擦除否是(编译为 JS)
装箱值类型引用上下文无(JS 全是对象)
泛型真泛型类型擦除(语法糖)
异步Task / ValueTaskPromise
元组元组类型 (int, string)元组类型 [number, string]
Union 类型无(C# 13 部分支持)string | number
反射完整有限(设计时 only)
装饰器特性(Attribute)装饰器(实验性)

5.3 C# vs Kotlin

维度C#Kotlin
平台.NETJVM、Android、JS、Native
空安全NRT(编译期警告)类型系统层(T?)
数据类recorddata class
协程async/await + TaskCoroutine(suspend)
扩展方法全局静态全局静态
属性原生原生
默认参数支持支持
函数式LINQ、LambdaCollection API、Lambda
与 Java 互操作-完美

5.4 C# vs Go

维度C#Go
编译JIT + AOTAOT
启动速度慢(JIT 预热) / AOT 快极快
GC分代、并发并发标记清除
并发模型async/await + TaskGoroutine + Channel
类型系统复杂、面向对象简洁、结构化(结构体+接口)
错误处理异常多返回值 + error
泛型C# 2.0 起Go 1.18 起
包管理NuGetGo Modules

5.5 跨语言 Hello World 对比

C#:

Console.WriteLine("Hello, World!");

Java:

System.out.println("Hello, World!");

Kotlin:

println("Hello, World!")

Go:

package main
import "fmt"
func main() { fmt.Println("Hello, World!") }

TypeScript:

console.log("Hello, World!");

Python:

print("Hello, World!")

Rust:

fn main() { println!("Hello, World!"); }

C++:

#include <iostream>
int main() { std::cout << "Hello, World!" << std::endl; }

6. 常见陷阱与反模式

6.1 SDK 与 Runtime 版本不匹配

症状:

It was not possible to find any compatible framework version
The framework 'Microsoft.NETCore.App', version '9.0.0' was not found.

原因:开发机安装的 SDK 版本与生产环境 Runtime 不一致。

对策:

  • 生产环境始终使用 LTS(.NET 8)。
  • 在 global.json 锁定 SDK 版本:
{
  "sdk": {
    "version": "9.0.100",
    "rollForward": "latestFeature"
  }
}

6.2 顶级语句滥用

反模式:将整个企业级应用写在 Program.cs 顶级语句中,文件超过 2000 行。

对策:顶级语句仅用于启动器,业务逻辑分层到独立项目 / 类中。

6.3 var 滥用

反模式:

var result = DoSomething();   // result 是什么类型?
var x = GetX();

对策:

  • 类型明显时使用 var:var list = new List<int>();。
  • 类型不明显时显式声明:Person person = GetPerson();。

6.4 异常吞咽

反模式:

try { DoSomething(); }
catch (Exception) { /* 啥也不做 */ }

对策:

  • 至少记录日志:catch (Exception e) { _logger.LogError(e, "Failed"); }。
  • 区分可恢复与不可恢复异常。

6.5 同步阻塞异步方法

反模式:

var result = GetAsync().Result;  // 死锁风险
var result = GetAsync().Wait();

对策:异步方法应 await,不要 .Result / .Wait()。详见《异步编程详解》。

6.6 装箱陷阱

反模式:

ArrayList list = new();   // 非泛型,所有 int 装箱
list.Add(1);
list.Add(2);
int sum = 0;
foreach (int i in list) sum += i;

对策:始终使用泛型集合 List<int>。

6.7 字符串拼接陷阱

反模式:

string s = "";
for (int i = 0; i < 1000; i++) s += i;   // O(n²) 复杂度

对策:使用 StringBuilder:

var sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.Append(i);
string s = sb.ToString();

6.8 配置文件未拷贝到输出目录

症状:发布后找不到 appsettings.json。

对策:在 .csproj 中:

<ItemGroup>
  <None Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

6.9 NRT 与可空警告

症状:开启 NRT 后大量警告 CS8602: Dereference of a possibly null reference。

对策:

  • 不要粗暴 !(null-forgiving)压制。
  • 重新审视 null 来源,使用 ??、?.、if (obj is not null)。
  • 对外部输入显式校验:ArgumentException.ThrowIfNull(arg)。

6.10 .NET Standard 滥用

反模式:所有新类库都建为 .NET Standard 2.0。

对策:

  • 若库仅供 .NET 6/8 项目使用,目标框架设为 net8.0。
  • .NET Standard 2.0 仅用于需要兼容 .NET Framework 4.6.1+ 与旧 Mono 的场景。

7. 工程实践与最佳实践

7.1 项目结构约定

推荐的解决方案目录结构:

flowchart TD
    T0["MyApp/"]
    T1["src/"]
    T2["MyApp.Api/              # API 入口"]
    T3["MyApp.Application/      # 应用服务层"]
    T4["MyApp.Domain/           # 领域模型"]
    T5["MyApp.Infrastructure/   # 基础设施"]
    T6["MyApp.Tests/            # 测试"]
    T7["tests/"]
    T8["MyApp.UnitTests/"]
    T9["MyApp.IntegrationTests/"]
    T10["docs/"]
    T11["scripts/"]
    T12[".editorconfig"]
    T13[".gitignore"]
    T14["Directory.Build.props"]
    T15["Directory.Packages.props"]
    T16["global.json"]
    T17["nuget.config"]
    T18["MySolution.sln"]
    T0 --> T1
    T6 --> T7
    T9 --> T10
    T9 --> T11
    T9 --> T12
    T9 --> T13
    T9 --> T14
    T9 --> T15
    T9 --> T16
    T9 --> T17
    T9 --> T18

7.2 Directory.Build.props

集中配置所有项目的公共属性:

<Project>

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);CS1591</NoWarn>
    <Authors>fanquanpp</Authors>
    <Company>FANDEX</Company>
    <Product>FANDEX</Product>
    <Copyright>Copyright (c) 2026 FANDEX</Copyright>
    <Version>1.0.0</Version>
  </PropertyGroup>

</Project>

7.3 Directory.Packages.props

集中管理 NuGet 版本(Central Package Management):

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Serilog" Version="4.0.0" />
    <PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
    <PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
    <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
    <PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
    <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
  </ItemGroup>
</Project>

7.4 global.json

{
  "sdk": {
    "version": "9.0.100",
    "rollForward": "latestFeature",
    "allowPrerelease": false
  }
}

7.5 .editorconfig

# Top-level EditorConfig
root = true

# All files
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# C# files
[*.cs]
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = all
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_elsewhere = true:suggestion

# Naming conventions
dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
dotnet_naming_rule.public_members_must_be_capitalized.style = pascal_case_style
dotnet_naming_rule.public_members_must_be_capitalized.severity = warning

dotnet_naming_symbols.public_symbols.applicable_kinds = property,method,field,event,class,struct,interface,enum
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public

dotnet_naming_style.pascal_case_style.capitalization = pascal_case

7.6 .gitignore

使用 dotnet new gitignore 生成默认模板,重点忽略:

bin/
obj/
*.user
*.suo
.vs/
.idea/
*.DS_Store
publish/
TestResults/

7.7 代码格式化

# 安装 dotnet-format
dotnet tool install -g dotnet-format

# 格式化整个解决方案
dotnet format MySolution.sln

# 仅检查,不修改
dotnet format --verify-no-changes

7.8 项目模板定制

# 创建模板包
dotnet new install Microsoft.DotNet.Common.ProjectTemplates.9.0
dotnet new install Microsoft.AspNetCore.Templates
dotnet new install Microsoft.Maui.Templates
dotnet new list

7.9 单元测试

dotnet new xunit -o tests/MyApp.Tests
dotnet add tests/MyApp.Tests reference src/MyApp/MyApp.csproj

CalculatorTests.cs:

using HelloFandex;
using Xunit;

namespace MyApp.Tests;

public class CalculatorTests
{
    [Theory]
    [InlineData(0, 32)]
    [InlineData(100, 212)]
    [InlineData(-40, -40)]
    public void CelsiusToFahrenheit_Returns_Correct_Value(double c, double expected)
    {
        double actual = Program.CelsiusToFahrenheit(c);
        Assert.Equal(expected, actual, precision: 2);
    }
}

7.10 CI/CD(GitHub Actions)

.github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 9.0.x
      - run: dotnet restore
      - run: dotnet build --no-restore --configuration Release
      - run: dotnet test --no-build --configuration Release --verbosity normal
      - run: dotnet publish --no-build --configuration Release -o ./publish
      - uses: actions/upload-artifact@v4
        with:
          name: publish
          path: ./publish

7.11 Docker 化

Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["HelloFandex.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled AS final
WORKDIR /app
COPY --from=build /app/publish ./
ENTRYPOINT ["dotnet", "HelloFandex.dll"]

构建与运行:

docker build -t hello-fandex:1.0.0 .
docker run --rm hello-fandex:1.0.0 36.5

7.12 日志与可观测性

推荐使用 Serilog 或内置 Microsoft.Extensions.Logging:

using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole();
    builder.AddDebug();
});
var logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Application starting");
logger.LogWarning("This is a warning");
logger.LogError("This is an error");

OpenTelemetry(.NET 9 内置支持):

using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithTracing(tp => tp
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(mp => mp
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());

await builder.Build().RunAsync();

8. 案例研究

8.1 案例:ASP.NET Core 最小 API

Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.MapGet("/hello", () => new { Message = "Hello, FANDEX!" });
app.MapGet("/c2f/{celsius:double}", (double celsius) =>
{
    double f = celsius * 9.0 / 5.0 + 32.0;
    return new { Celsius = celsius, Fahrenheit = f };
});

app.Run();

创建命令:

dotnet new web -n WebDemo
cd WebDemo
dotnet add package Swashbuckle.AspNetCore
dotnet run

打开 http://localhost:5000/swagger 查看 API 文档。

8.2 案例:Unity 中的 C# 脚本

Unity 使用 C# 作为脚本语言,但运行时为 Mono / IL2CPP 而非完整 .NET Runtime。一个简单脚本:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed = 5f;

    private void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(horizontal, 0f, vertical);
        transform.Translate(movement * speed * Time.deltaTime);
    }
}

Unity 工程通常使用 .NET Standard 2.1 兼容子集,最新 Unity 2023+ 支持 .NET 8 的子集。

8.3 案例:MAUI 跨平台 App

MauiProgram.cs:

using Microsoft.Maui.Controls.Hosting;

namespace FandexMaui;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        return builder.Build();
    }
}

MainPage.xaml:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FandexMaui.MainPage">
    <StackLayout Padding="30">
        <Label Text="Hello, FANDEX MAUI!" FontSize="32" HorizontalOptions="Center" />
        <Button Text="Click Me" Clicked="OnButtonClicked" />
    </StackLayout>
</ContentPage>

8.4 案例:WPF 桌面应用

App.xaml.cs:

using System.Windows;

namespace FandexWpf;

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var window = new MainWindow();
        window.Show();
    }
}

MainWindow.xaml:

<Window x:Class="FandexWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="FANDEX" Height="450" Width="800">
    <Grid>
        <Button Content="Hello" Click="Button_Click" />
    </Grid>
</Window>

8.5 案例:Blazor WebAssembly

Program.cs:

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

Pages/Counter.razor:

@page "/counter"

<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount() => currentCount++;
}

8.6 案例:FANDEX 项目目录结构

FANDEX-Web 项目本身使用 Astro 框架,但本系列文档(C# 教程)涉及的所有示例项目结构如下:

flowchart TD
    T0["FANDEX-csharp-samples/"]
    T1["01-hello/"]
    T2["02-async/"]
    T3["03-linq/"]
    T4["04-aspnetcore/"]
    T5["05-efcore/"]
    T6["06-maui/"]
    T7["07-blazor/"]
    T8["08-unity/"]
    T9["09-source-generators/"]
    T10["README.md"]
    T0 --> T1
    T0 --> T2
    T0 --> T3
    T0 --> T4
    T0 --> T5
    T0 --> T6
    T0 --> T7
    T0 --> T8
    T0 --> T9
    T0 --> T10

读者可在每篇文档末尾的”延伸阅读”找到对应的示例仓库链接。

8.7 案例:从 .NET Framework 迁移到 .NET 9

初始状态:

  • .NET Framework 4.8 + ASP.NET MVC 5 + EF6。
  • 部署在 Windows Server + IIS。

迁移步骤:

  1. 目标框架兼容性分析:使用 .NET Portability Analyzer 工具。
  2. 替换不兼容 API:AppDomain → AssemblyLoadContext;System.Web.HttpContext → Microsoft.AspNetCore.Http.HttpContext。
  3. EF6 → EF Core:使用 EF Core Power Tools 逆向工程生成 DbContext。
  4. 迁移 ASP.NET MVC → ASP.NET Core MVC:Controller 与 View 大部分可平移。
  5. 配置文件:web.config → appsettings.json + IConfiguration。
  6. DI:Global.asax 中的初始化 → Program.cs + IServiceCollection。
  7. 测试:xUnit + 测试容器化。
  8. 部署:IIS → Kestrel + Nginx 反向代理,或 Docker。

迁移收益:

  • Linux 容器化部署,云成本降低 60%。
  • 启动速度提升 3 倍(Kestrel 比 IIS pipeline 简化)。
  • 内存占用降低 40%。

简答题知识点讲解

常见疑问 9:简述 C# 与 Java 在泛型实现上的差异。

解析讲解:

C# 在 CLR 层实现真泛型,每个值类型泛型实例化(如 List<int>)在运行时是独立的类型,底层为 int[],无装箱开销。引用类型泛型共享代码(如 List<string> 与 List<object> 共享同一份 JIT 代码),但类型安全由 CLR 保证。

Java 通过类型擦除(Type Erasure)实现泛型,编译后 List<Integer> 与 List<String> 都是 List,底层为 Object[]。这导致:

  1. 值类型存在装箱开销。
  2. 运行时无法获取泛型参数类型(List<Integer>.class 不存在)。
  3. 不能 new T()。
  4. 不能创建泛型数组。

C# 在这些场景下表现更好。

常见疑问 10:为什么 .NET Core 跨平台而 .NET Framework 不能?

解析讲解:

.NET Framework 设计于 2000 年代,深度耦合 Windows:

  1. BCL 依赖 Win32 API:System.IO、System.Drawing、System.Diagnostics.Process 等内部调用 Win32 函数。
  2. AppDomain:基于 Windows 进程模型。
  3. WCF / Web Forms / WPF:依赖 IIS、COM+、DirectX。
  4. GC:早期 Server GC 专为 Windows 设计。
  5. Registry:Microsoft.Win32.Registry 直接调用 Windows 注册表。

.NET Core 重写了这些子系统:

  1. PAL(Platform Adaptation Layer):抽象 OS 差异。
  2. BCL 重写:基于 System.IO.FileSystem、System.Net.Http 等跨平台实现。
  3. Kestrel:自研跨平台 Web 服务器。
  4. Native P/Invoke:每个平台 P/Invoke 不同的系统库。

编程题知识点讲解

常见疑问 11:编写一个 C# 控制台程序,接收命令行参数 <n>,输出斐波那契数列前 n 项。

解析讲解:

if (args.Length == 0 || !int.TryParse(args[0], out int n) || n <= 0)
{
    Console.Error.WriteLine("Usage: Fib <n>");
    return 1;
}

long a = 0, b = 1;
for (int i = 0; i < n; i++)
{
    Console.Write($"{a} ");
    (a, b) = (b, a + b);
}
Console.WriteLine();
return 0;

常见疑问 12:编写一个程序,将给定目录下所有 .cs 文件按行数从多到少排序输出。

解析讲解:

using System.IO;

if (args.Length == 0 || !Directory.Exists(args[0]))
{
    Console.Error.WriteLine("Usage: LineCount <directory>");
    return 1;
}

var files = Directory.EnumerateFiles(args[0], "*.cs", SearchOption.AllDirectories)
    .Select(path => new { Path = path, Lines = File.ReadAllLines(path).Length })
    .OrderByDescending(f => f.Lines);

foreach (var f in files)
{
    Console.WriteLine($"{f.Lines,6} {f.Path}");
}

return 0;

常见疑问 13:编写一个程序,使用 HttpClient 异步下载指定 URL 的内容,统计下载耗时与字节数。

解析讲解:

using System.Diagnostics;

if (args.Length == 0)
{
    Console.Error.WriteLine("Usage: Download <url>");
    return 1;
}

using var client = new HttpClient();
var sw = Stopwatch.StartNew();
byte[] data = await client.GetByteArrayAsync(args[0]);
sw.Stop();
Console.WriteLine($"Downloaded {data.Length:N0} bytes in {sw.ElapsedMilliseconds} ms");

9.6 综合应用题

常见疑问 16:你要为公司搭建一个内部微服务,技术选型在 .NET 8 与 Spring Boot 3 之间犹豫。请列出选型要点,并给出推荐。

参考答案要点:

维度.NET 8Spring Boot 3
性能Kestrel 吞吐量略高Tomcat 性能优秀
内存容器内存占用较低JVM 内存管理灵活
团队熟悉度取决于团队取决于团队
生态NuGet 包相对集中Maven 包海量
监控OpenTelemetry 内置Spring Cloud 全家桶
部署Docker 镜像较小Docker 镜像较大
启动速度Native AOT 极快GraalVM Native Image

推荐:若团队以 C# 为主且追求启动速度(如 AWS Lambda 函数计算),选 .NET 8 + Native AOT。若团队以 Java 为主且需要复杂业务集成(如大型金融系统),选 Spring Boot 3。


11.1 官方文档

11.2 系列内交叉引用

继续学习 C# 的下一站:

  • 基础语法 —— 类型、变量、运算符、控制流
  • 面向对象编程 —— 类、接口、继承、多态
  • 泛型与集合 —— List<T>、Dictionary<K,V>、泛型约束
  • 异步编程 —— Task、async/await、并行编程
  • LINQ 与函数式编程 —— 查询表达式、Lambda
  • 值类型与引用类型 —— 栈与堆、装箱拆箱
  • GC 代机制 —— 分代回收、LOH、Server GC
  • async-await 状态机 —— 编译器重写原理
  • Span 与 Memory —— 零拷贝编程

11.3 进阶书籍

  • Andrew Troelsen, Phil Japkiewicz. Pro C# 10 with .NET 6 (Apress, 2022).
  • Mark Michaelis. Essential C# 8.0 (Addison-Wesley, 2020).
  • Joseph Albahari. C# 12 in a Nutshell (O’Reilly, 2024).
  • Oren Eini. Pro .NET Memory Management and Pointers (Apress, 2022).
  • Stephen Cleary. Concurrency in C# Cookbook (O’Reilly, 2019).

11.6 工具与扩展

  • dotnet-format:代码格式化工具
  • dotnet-ef:EF Core CLI 工具
  • dotnet-counters:实时性能计数器
  • dotnet-dump:堆转储分析
  • dotnet-trace:性能追踪
  • PerfView:免费性能分析工具
  • dotMemory:JetBrains 内存分析(付费)
  • dotTrace:JetBrains CPU 分析(付费)
  • ILSpy / dnSpy:反编译工具

结语

至此,我们对 C# 语言与 .NET 平台建立了宏观认知。从 Anders Hejlsberg 在 2000 年的设计哲学,到 2024 年 .NET 9 的 Native AOT、HybridCache、动态 PGO,C# 已经从一个 Windows-only 语言演化为覆盖云、桌面、移动、游戏、IoT 的全栈平台。

下一步,请进入 基础语法,开始动手编写真正的 C# 代码。记住:

  • 不要满足于”能跑”:理解每行代码背后发生了什么。
  • 不要怕出错:错误是最好的老师,善用异常堆栈与调试器。
  • 不要孤立学习:每一篇文档都关联着上下游,交叉阅读能加深理解。

愿你在 FANDEX 的 C# 之旅中,既能掌握工程实战,也能体味语言设计之美。