C# 概述与环境配置
C# 语言概述、.NET 生态、.NET 8/9 新特性、环境搭建与 Hello World
C# 概述与环境配置
本文是 FANDEX C# 系列的开篇。我们将从语言的诞生背景、设计哲学、运行平台、生态构成,到本地环境的搭建与第一个可运行程序,全方位带你建立对 C# 与 .NET 的宏观认知。内容对标 MIT 6.0001、Stanford CS106X、CMU 15-110 等海外名校入门课程的教学严谨度,同时融入企业级工程实战经验,支持 0 基础自学。
目录
1. 学习目标(Bloom 分类法)
Bloom 分类法将认知能力由低到高划分为六个层次:记忆(Remember)、理解(Understand)、应用(Apply)、分析(Analyze)、评价(Evaluate)、创造(Create)。本节据此列出本篇学习目标,便于读者自检与教学评估。
1.1 记忆(Remember)
- R1:能复述 C# 语言的诞生年份(2000 年)、首席设计师(Anders Hejlsberg)及其前序作品(Turbo Pascal、Delphi、TypeScript)。
- R2:能列出 .NET 平台的三大核心组成:运行时(Runtime)、基础类库(BCL, Base Class Library)、应用模型(App Model)。
- R3:能背诵 .NET 主要版本的时间线(.NET Framework 1.0/2.0/3.5/4.x、.NET Core 1.0/2.0/3.x、.NET 5/6/7/8/9)。
- R4:能识别 SDK(Software Development Kit)与 Runtime(运行时)的语义差异。
1.2 理解(Understand)
- U1:能解释 CTS(Common Type System)、CLS(Common Language Specification)、CIL(Common Intermediate Language)三者的层次关系。
- U2:能用自己的话阐述托管代码(Managed Code)与原生代码(Native Code)的执行差异。
- U3:能说明 JIT(Just-In-Time)编译与 AOT(Ahead-Of-Time)编译的优劣。
- U4:能描述 NuGet 包管理器在依赖解析中的作用机制。
- U5:能阐述 .NET 跨平台(Windows / Linux / macOS / iOS / Android)的实现原理。
1.3 应用(Apply)
- A1:能在 Windows / macOS / Linux 上独立安装 .NET SDK 9.0。
- A2:能使用
dotnet new、dotnet build、dotnet run完成控制台应用的创建、编译、运行。 - A3:能使用
dotnet add package引入 NuGet 包并正确调用。 - A4:能配置 VS Code / Rider / Visual Studio 2022 的 C# 开发环境。
1.4 分析(Analyze)
- An1:能对比 .NET Framework 与 .NET(Core)在 GC、AppDomain、程序集加载机制上的差异。
- An2:能拆解
dotnet build背后的 MSBuild 任务链(Restore → Compile → Pack → Publish)。 - An3:能分析
csproj、Program.cs、bin/、obj/之间的依赖关系。
1.5 评价(Evaluate)
- E1:能评估在何种场景下应选择 .NET 而非 JVM、Go、Node.js。
- E2:能评判 AOT 编译在启动速度与运行时性能之间的权衡是否合理。
- E3:能评估 LTS(Long Term Support)版本与 STS(Standard Term Support)版本在生产环境中的选型。
1.6 创造(Create)
- C1:能设计一个具备项目分层(Presentation / Business / Data)的 C# 控制台应用骨架。
- C2:能为团队编写一份《C# 开发环境搭建指南》文档,涵盖版本管理、IDE 配置、调试技巧。
2. 历史动机与演化
理解一门语言的现在,必须先理解它走过的路。C# 的演化是一部”在工程实用主义与语言创新之间不断平衡”的历史。
2.1 史前背景:COM 时代的痛点
在 .NET 诞生之前(1990 年代),微软生态主流是 COM(Component Object Model)与 MFC(Microsoft Foundation Classes)。开发者面临的核心痛点包括:
- 语言割裂:C++、VB6、Delphi 各自为政,组件互操作需通过 COM 的
IUnknown、IDispatch、BSTR、SAFEARRAY等繁杂 ABI,易错且低效。 - 内存管理混乱:C++ 需手工管理
new/delete,VB6 采用引用计数(Reference Counting),两者协作时易出现循环引用导致的内存泄漏。 - 跨语言调用成本高:C++ 写的 COM 组件被 VB 调用时需要 IDL(Interface Definition Language)转译,开发体验差。
- Java 的崛起:1995 年 Sun 推出 Java,凭借”Write Once, Run Anywhere”与垃圾回收(GC)抢占企业市场,对微软构成战略威胁。
2.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 隔离。
2.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):在委托层面初步支持。
2.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; }。
2.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 基础。
2.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。
2.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" }。
2.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。
2.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 共享问题。
2.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。
2.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):编译期代码生成,替代反射。
2.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)。
2.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 编译。
2.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 正式可用:发布时无运行时依赖。
2.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%。
2.16 演化时间线总览
| 年份 | .NET 版本 | C# 版本 | 标志性特性 | LTS/STS |
|---|---|---|---|---|
| 2002 | .NET Framework 1.0 | 1.0 | CLR、C# 诞生 | - |
| 2005 | .NET Framework 2.0 | 2.0 | 泛型、可空类型 | - |
| 2007 | .NET Framework 3.5 | 3.0 | LINQ、Lambda | - |
| 2010 | .NET Framework 4.0 | 4.0 | dynamic、TPL | - |
| 2012 | .NET Framework 4.5 | 5.0 | async/await | - |
| 2015 | .NET Framework 4.6 | 6.0 | 字符串插值、null 条件 | - |
| 2017 | .NET Framework 4.7 / Core 2.0 | 7.x | 元组、模式匹配、Span | - |
| 2019 | .NET Core 3.1 | 8.0 | NRT、异步流 | LTS |
| 2020 | .NET 5 | 9.0 | record、顶级语句 | STS |
| 2021 | .NET 6 | 10.0 | MAUI、最小 API | LTS |
| 2022 | .NET 7 | 11.0 | 泛型数学、原始字符串 | STS |
| 2023 | .NET 8 | 12.0 | 主构造、Native AOT | LTS |
| 2024 | .NET 9 | 13.0 | HybridCache、PGO | STS |
2.17 设计哲学小结
C# 的演化贯穿三大原则:
- 实用主义:每项新特性都源于真实工程痛点,而非纯学术理想。
- 类型安全优先:通过编译期检查(如 NRT、模式匹配)消灭运行时错误。
- 性能不退步:任何抽象都可在性能关键场景被”穿透”(如
Span<T>、ref struct、ValueTask、Unsafe)。
理解这三点,能帮你预测未来 C# 演化方向。
3. 形式化定义
3.1 C# 语言的形式化定义
C# 是一种静态类型、强类型、面向对象、多范式、托管执行的编程语言。下面从形式化角度拆解这些性质。
3.1.1 类型系统
设 为类型集合, 为类型环境(typing context),表达式 的类型推断关系记为 。C# 的类型规则可形式化为:
其中 表示类型相容(含隐式转换)。
C# 类型系统在 Hindley-Milner 之上做了若干扩展:
- 子类型(Subtyping): 表示 是 的子类型。
- 协变(Covariance):若
IEnumerable<out T>,则 。 - 逆变(Contravariance):若
Action<in T>,则 。 - 可空性(Nullability):引用类型 拆为 (不可空)与 (可空)。
3.1.2 语法与文法
C# 文法以 LL(k) 与 LR 混合方式解析。其文法可表示为:
详细文法见 C# Language Specification(Dotnet GitHub 仓库 dotnet/csharpstandard)。
3.2 .NET 平台的形式化定义
.NET 平台可形式化为三元组:
其中:
- CLR(Common Language Runtime):执行引擎,提供 GC、JIT、类型安全、异常处理等运行时服务。
- BCL(Base Class Library):基础类库,包含集合、IO、网络、LINQ、反射等。
- AppModel(Application Model):应用模型,如 ASP.NET Core、EF Core、MAUI、WPF、WinForms。
3.3 程序执行流程的形式化
C# 源代码的执行流程可形式化为映射链:
详细步骤:
- 源代码:
.cs文件,人类可读。 - 词法分析 → 语法分析:生成抽象语法树(AST)。
- 语义分析:类型检查、可空性分析、特性应用。
- CIL 生成:输出
.dll/.exe,内容为 Common Intermediate Language(字节码)。 - 类加载:CLR 加载程序集,验证类型安全。
- JIT 编译:方法首次调用时,JIT 将 CIL 编译为本地机器码。
- 执行:CPU 执行机器码,GC 在后台管理内存。
3.4 内存模型形式化
.NET 的托管堆(Managed Heap)可形式化为:
其中 为地址, 为类型, 为所属 GC 代(Generation 0/1/2/LOH), 为大小。
GC 的回收策略可表示为:
回收后保留的对象晋升一代:
3.5 异步执行模型形式化
async/await 的状态机重写可形式化为变换 :
其中 是一个有限状态机 :
- :状态集合,每个
await点对应一个状态。 - :初始状态。
- :输入符号(awaitable 完成事件)。
- :状态转移函数。
- :终止状态集合。
详细推导见本系列《async-await 状态机》一文。
4. 理论推导与证明
4.1 类型安全性证明(Sketch)
C# 类型系统满足 Progress 与 Preservation 两大性质(受 Erasure 影响,对泛型稍作调整)。
定理 4.1(Progress):对于良型表达式 , 要么是一个值,要么存在 使得 。
证明(Sketch):对 的结构归纳。
- 若 是值(变量、字面量),成立。
- 若 ,由归纳 要么是值要么可归约;若 可归约,则 ;若 是值,同理处理 ;若两者都是值,依据算术运算法则可归约。
定理 4.2(Preservation):若 且 ,则 。
证明(Sketch):对归约步骤分类讨论。
- 算术:,归约为 ( 的算术结果), 成立。
- 函数调用:,由代入引理 。
4.2 协变/逆变安全性证明
定理 4.3(协变安全):若 interface I<out T>,且 ,则 I<T> <: I<U> 类型安全。
证明:协变要求 T 在 I 中只出现在输出位置(返回类型)。设 I<T> 暴露方法 T M(),对 I<T> 实例 o 调用 o.M() 返回 实例。若将 o 视为 I<U>,调用 o.M() 期望返回 。由 ,返回的 自动是 ,类型安全成立。
定理 4.4(逆变安全):若 interface I<in T>,且 ,则 I<U> <: I<T> 类型安全。
证明:逆变要求 T 只出现在输入位置。设 I<T> 暴露 void M(T x),对 I<T> 实例 o 调用 o.M(x) 要求 。若将 o 视为 I<U>(即接受 U 类型参数),调用 o.M(y) 其中 。由于 o 实际是 I<T> 实例,要求 ,而 意味着 实例不一定是 ,这看似不安全——但逆变的方向相反:I<U> <: I<T> 意味着 I<U> 可用作 I<T>。此时调用 o.M(x) 其中 ,由 , 也是 ,传给 I<U> 的 M(U) 安全。证毕。
4.3 GC 暂停时间复杂度
命题 4.5:.NET 的分代 GC 第 代回收时间 与该代对象数量 近似成正比,即 。
推导:分代 GC 采用标记-压缩(Mark-Compact)或标记-复制(Mark-Copy)算法。
- 标记阶段:从 GC Root 出发,BFS 遍历所有可达对象,复杂度 , 为对象引用边数。
- 压缩/复制阶段:遍历存活对象,更新引用,复杂度 。
由于第 0 代存活率低(多数对象短命),,故 极小。第 2 代(Gen 2)回收涉及全堆扫描, 较大。
4.4 JIT 编译优化
命题 4.6:JIT 编译产生的代码性能 满足 ,其中 为预热开销。
直觉:JIT 拥有 AOT 不具备的运行时信息:
- 类型 profile:哪些虚方法实际只有一个实现,可去虚化(Devirtualization)。
- 分支 profile:哪些分支几乎不执行,可优化布局。
- 内联:跨程序集边界的内联由 JIT 决定。
Profile-Guided Optimization(PGO)在 .NET 6+ 中将 JIT 优化推向新高度。.NET 9 的动态 PGO 进一步使 的场景比例提升。
4.5 Roslyn 编译流水线
Roslyn 编译器流水线可形式化为:
每一阶段都暴露为公开 API(Microsoft.CodeAnalysis.CSharp),使源生成器(Source Generators)、分析器(Analyzers)、代码修复(Code Fixes)成为可能。
5. 代码示例
5.1 安装 .NET SDK
5.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
5.1.2 macOS 安装
# Homebrew
brew install --cask dotnet-sdk
5.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
5.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]
5.2 第一个控制台应用
5.2.1 创建项目
mkdir HelloFandex && cd HelloFandex
dotnet new console -n HelloFandex -o .
dotnet run
dotnet new console 生成如下结构:
HelloFandex/
├── HelloFandex.csproj
├── Program.cs
└── obj/
└── HelloFandex.csproj.nuget.g.props
5.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!");
}
}
5.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 任务 |
OutputType | Exe 表示可执行;Library 表示类库 |
TargetFramework | 目标框架:net9.0、net8.0、netstandard2.0 |
ImplicitUsings | 是否自动 using 常用命名空间 |
Nullable | 启用可空引用类型检查 |
5.3 编译与运行机制
5.3.1 编译
dotnet build
执行流程:
- Restore:解析 NuGet 引用,写入
obj/project.assets.json。 - GenerateAssemblyInfo:生成
AssemblyVersion等元数据。 - Csc:调用 Roslyn 编译器,输出 CIL 到
bin/Debug/net9.0/HelloFandex.dll。 - CopyFilesMarkedCopyLocal:拷贝 NuGet 依赖到输出目录。
5.3.2 运行
dotnet run
dotnet run 等价于 dotnet build && dotnet <assembly.dll>,是开发期最常用命令。
5.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 工具 |
5.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
5.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));
5.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
5.7 调试配置
5.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}"
}
]
}
5.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"
}
]
}
5.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
6. 对比分析
6.1 C# vs Java
| 维度 | C# | Java |
|---|---|---|
| 公司 | Microsoft | Oracle(原 Sun) |
| 首次发布 | 2002 | 1995 |
| 编译目标 | 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 |
| AOT | Native AOT(.NET 7+) | GraalVM、Native Image |
| 桌面 | WPF、WinForms、MAUI | JavaFX |
| Web | ASP.NET Core | Spring Boot |
| ORM | EF Core | Hibernate、JPA |
| 平台 | Win/Linux/macOS/iOS/Android | 同样跨平台 |
6.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[],存在装箱开销。
6.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 层支持。
6.2 C# vs TypeScript
| 维度 | C# | TypeScript |
|---|---|---|
| 类型系统 | 静态、强 | 静态、结构化、可选 |
| 运行时 | CLR | V8 / Node.js |
| 类型擦除 | 否 | 是(编译为 JS) |
| 装箱 | 值类型引用上下文 | 无(JS 全是对象) |
| 泛型 | 真泛型 | 类型擦除(语法糖) |
| 异步 | Task / ValueTask | Promise |
| 元组 | 元组类型 (int, string) | 元组类型 [number, string] |
| Union 类型 | 无(C# 13 部分支持) | string | number |
| 反射 | 完整 | 有限(设计时 only) |
| 装饰器 | 特性(Attribute) | 装饰器(实验性) |
6.3 C# vs Kotlin
| 维度 | C# | Kotlin |
|---|---|---|
| 平台 | .NET | JVM、Android、JS、Native |
| 空安全 | NRT(编译期警告) | 类型系统层(T?) |
| 数据类 | record | data class |
| 协程 | async/await + Task | Coroutine(suspend) |
| 扩展方法 | 全局静态 | 全局静态 |
| 属性 | 原生 | 原生 |
| 默认参数 | 支持 | 支持 |
| 函数式 | LINQ、Lambda | Collection API、Lambda |
| 与 Java 互操作 | - | 完美 |
6.4 C# vs Go
| 维度 | C# | Go |
|---|---|---|
| 编译 | JIT + AOT | AOT |
| 启动速度 | 慢(JIT 预热) / AOT 快 | 极快 |
| GC | 分代、并发 | 并发标记清除 |
| 并发模型 | async/await + Task | Goroutine + Channel |
| 类型系统 | 复杂、面向对象 | 简洁、结构化(结构体+接口) |
| 错误处理 | 异常 | 多返回值 + error |
| 泛型 | C# 2.0 起 | Go 1.18 起 |
| 包管理 | NuGet | Go Modules |
6.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; }
7. 常见陷阱与反模式
7.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"
}
}
7.2 顶级语句滥用
反模式:将整个企业级应用写在 Program.cs 顶级语句中,文件超过 2000 行。
对策:顶级语句仅用于启动器,业务逻辑分层到独立项目 / 类中。
7.3 var 滥用
反模式:
var result = DoSomething(); // result 是什么类型?
var x = GetX();
对策:
- 类型明显时使用
var:var list = new List<int>();。 - 类型不明显时显式声明:
Person person = GetPerson();。
7.4 异常吞咽
反模式:
try { DoSomething(); }
catch (Exception) { /* 啥也不做 */ }
对策:
- 至少记录日志:
catch (Exception e) { _logger.LogError(e, "Failed"); }。 - 区分可恢复与不可恢复异常。
7.5 同步阻塞异步方法
反模式:
var result = GetAsync().Result; // 死锁风险
var result = GetAsync().Wait();
对策:异步方法应 await,不要 .Result / .Wait()。详见《异步编程详解》。
7.6 装箱陷阱
反模式:
ArrayList list = new(); // 非泛型,所有 int 装箱
list.Add(1);
list.Add(2);
int sum = 0;
foreach (int i in list) sum += i;
对策:始终使用泛型集合 List<int>。
7.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();
7.8 配置文件未拷贝到输出目录
症状:发布后找不到 appsettings.json。
对策:在 .csproj 中:
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
7.9 NRT 与可空警告
症状:开启 NRT 后大量警告 CS8602: Dereference of a possibly null reference。
对策:
- 不要粗暴
!(null-forgiving)压制。 - 重新审视 null 来源,使用
??、?.、if (obj is not null)。 - 对外部输入显式校验:
ArgumentException.ThrowIfNull(arg)。
7.10 .NET Standard 滥用
反模式:所有新类库都建为 .NET Standard 2.0。
对策:
- 若库仅供 .NET 6/8 项目使用,目标框架设为
net8.0。 .NET Standard 2.0仅用于需要兼容 .NET Framework 4.6.1+ 与旧 Mono 的场景。
8. 工程实践与最佳实践
8.1 项目结构约定
推荐的解决方案目录结构:
MyApp/
├── src/
│ ├── MyApp.Api/ # API 入口
│ ├── MyApp.Application/ # 应用服务层
│ ├── MyApp.Domain/ # 领域模型
│ ├── MyApp.Infrastructure/ # 基础设施
│ └── MyApp.Tests/ # 测试
├── tests/
│ ├── MyApp.UnitTests/
│ └── MyApp.IntegrationTests/
├── docs/
├── scripts/
├── .editorconfig
├── .gitignore
├── Directory.Build.props
├── Directory.Packages.props
├── global.json
├── nuget.config
└── MySolution.sln
8.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>
8.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>
8.4 global.json
{
"sdk": {
"version": "9.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}
8.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
8.6 .gitignore
使用 dotnet new gitignore 生成默认模板,重点忽略:
bin/
obj/
*.user
*.suo
.vs/
.idea/
*.DS_Store
publish/
TestResults/
8.7 代码格式化
# 安装 dotnet-format
dotnet tool install -g dotnet-format
# 格式化整个解决方案
dotnet format MySolution.sln
# 仅检查,不修改
dotnet format --verify-no-changes
8.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
8.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);
}
}
8.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
8.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
8.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();
9. 案例研究
9.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 文档。
9.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 的子集。
9.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>
9.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>
9.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++;
}
9.6 案例:FANDEX 项目目录结构
FANDEX-Web 项目本身使用 Astro 框架,但本系列文档(C# 教程)涉及的所有示例项目结构如下:
FANDEX-csharp-samples/
├── 01-hello/
├── 02-async/
├── 03-linq/
├── 04-aspnetcore/
├── 05-efcore/
├── 06-maui/
├── 07-blazor/
├── 08-unity/
├── 09-source-generators/
└── README.md
读者可在每篇文档末尾的”延伸阅读”找到对应的示例仓库链接。
9.7 案例:从 .NET Framework 迁移到 .NET 9
初始状态:
- .NET Framework 4.8 + ASP.NET MVC 5 + EF6。
- 部署在 Windows Server + IIS。
迁移步骤:
- 目标框架兼容性分析:使用
.NET Portability Analyzer工具。 - 替换不兼容 API:
AppDomain→AssemblyLoadContext;System.Web.HttpContext→Microsoft.AspNetCore.Http.HttpContext。 - EF6 → EF Core:使用
EF Core Power Tools逆向工程生成 DbContext。 - 迁移 ASP.NET MVC → ASP.NET Core MVC:Controller 与 View 大部分可平移。
- 配置文件:
web.config→appsettings.json+IConfiguration。 - DI:
Global.asax中的初始化 →Program.cs+IServiceCollection。 - 测试:xUnit + 测试容器化。
- 部署:IIS → Kestrel + Nginx 反向代理,或 Docker。
迁移收益:
- Linux 容器化部署,云成本降低 60%。
- 启动速度提升 3 倍(Kestrel 比 IIS pipeline 简化)。
- 内存占用降低 40%。
10. 习题与思考题
10.1 选择题(基础)
Q1:以下哪个不是 .NET 的核心组成?
- A. CLR
- B. BCL
- C. JVM
- D. AppModel
答案:C。JVM 是 Java 的虚拟机。
Q2:C# 由谁设计?
- A. James Gosling
- B. Anders Hejlsberg
- C. Bjarne Stroustrup
- D. Guido van Rossum
答案:B。Anders Hejlsberg 也是 Turbo Pascal 与 TypeScript 的设计者。
Q3:以下哪个版本引入了 async/await?
- A. C# 1.0
- B. C# 3.0
- C. C# 5.0
- D. C# 7.0
答案:C。C# 5.0(2012)引入 async/await。
Q4:以下哪个 C# 版本引入了顶级语句?
- A. C# 8.0
- B. C# 9.0
- C. C# 10.0
- D. C# 11.0
答案:B。C# 9.0(2020)。
Q5:Native AOT 在哪个 .NET 版本正式可用?
- A. .NET 5
- B. .NET 6
- C. .NET 7
- D. .NET 8
答案:D。.NET 8 正式支持 Native AOT 发布。
10.2 选择题(进阶)
Q6:以下关于 .NET GC 说法正确的是?
- A. 第 0 代回收时间最长
- B. LOH 用于存储大于 85,000 字节的对象
- C. Server GC 适合客户端应用
- D. GC 始终是停止世界的
答案:B。LOH 阈值约 85KB;第 0 代回收最快;Server GC 适合服务端;.NET GC 支持后台并发回收。
Q7:以下关于 NuGet 的说法错误的是?
- A. NuGet 是 .NET 的包管理器
- B.
dotnet add package会自动还原 - C. NuGet 包始终包含源代码
- D. NuGet 支持 private feed
答案:C。NuGet 包通常只包含编译后的 DLL,不含源代码。
Q8:以下关于 JIT 与 AOT 说法正确的是?
- A. JIT 启动速度比 AOT 快
- B. AOT 不能进行任何运行时优化
- C. JIT 可基于运行时 profile 进行优化
- D. AOT 体积一定大于 JIT
答案:C。JIT 拥有运行时信息,可进行 profile-guided optimization。
10.3 简答题
Q9:简述 C# 与 Java 在泛型实现上的差异。
参考答案:
C# 在 CLR 层实现真泛型,每个值类型泛型实例化(如 List<int>)在运行时是独立的类型,底层为 int[],无装箱开销。引用类型泛型共享代码(如 List<string> 与 List<object> 共享同一份 JIT 代码),但类型安全由 CLR 保证。
Java 通过类型擦除(Type Erasure)实现泛型,编译后 List<Integer> 与 List<String> 都是 List,底层为 Object[]。这导致:
- 值类型存在装箱开销。
- 运行时无法获取泛型参数类型(
List<Integer>.class不存在)。 - 不能 new T()。
- 不能创建泛型数组。
C# 在这些场景下表现更好。
Q10:为什么 .NET Core 跨平台而 .NET Framework 不能?
参考答案:
.NET Framework 设计于 2000 年代,深度耦合 Windows:
- BCL 依赖 Win32 API:
System.IO、System.Drawing、System.Diagnostics.Process等内部调用 Win32 函数。 - AppDomain:基于 Windows 进程模型。
- WCF / Web Forms / WPF:依赖 IIS、COM+、DirectX。
- GC:早期 Server GC 专为 Windows 设计。
- Registry:
Microsoft.Win32.Registry直接调用 Windows 注册表。
.NET Core 重写了这些子系统:
- PAL(Platform Adaptation Layer):抽象 OS 差异。
- BCL 重写:基于
System.IO.FileSystem、System.Net.Http等跨平台实现。 - Kestrel:自研跨平台 Web 服务器。
- Native P/Invoke:每个平台 P/Invoke 不同的系统库。
10.4 编程题
Q11:编写一个 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;
Q12:编写一个程序,将给定目录下所有 .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;
Q13:编写一个程序,使用 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");
10.5 思考题
Q14:为什么微软要放弃 .NET Framework,重写 .NET Core?请从技术、商业、生态三个维度分析。
参考答案要点:
- 技术:跨平台需求(Linux 容器化)、性能瓶颈(Framework 太重)、模块化(NuGet 化)、新场景(云原生、IoT)。
- 商业:与 Java 在企业市场(尤其 Linux 服务器)竞争;Azure 云服务需要轻量运行时。
- 生态:开源社区贡献需要更友好的开发模型;F#、VB.NET、IronPython 等共享运行时。
Q15:如果让你设计 C# 的下一项新特性,你会提议什么?请说明动机与潜在风险。
参考答案示例:
提议:协变返回类型扩展 —— 允许方法重写返回更窄的具体类型,包括值类型。当前 C# 9 支持引用类型协变返回,但不支持值类型。
动机:领域驱动设计中,基类 Repository<T> 的 Find(id) 返回 T,子类 UserRepository 想返回 User,目前无法做到零装箱。
风险:可能导致 ABI 不兼容;需评估 CLR 是否需要扩展。
10.6 综合应用题
Q16:你要为公司搭建一个内部微服务,技术选型在 .NET 8 与 Spring Boot 3 之间犹豫。请列出选型要点,并给出推荐。
参考答案要点:
| 维度 | .NET 8 | Spring 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. 参考文献
以下参考文献遵循 ACM Reference Format:
-
Hejlsberg, A., Torgersen, M., Wiltamuth, S., and Golde, P. 2010. The C# Programming Language (4th ed.). Addison-Wesley Professional, Boston, MA, USA.
-
Microsoft Corporation. 2024. C# Language Specification. Retrieved July 21, 2026 from https://learn.microsoft.com/dotnet/csharp/language-reference/
-
Microsoft Corporation. 2024. .NET Documentation. Retrieved July 21, 2026 from https://learn.microsoft.com/dotnet/
-
Wagner, B. 2017. Effective C# (Covers C# 7.0) (3rd ed.). Addison-Wesley Professional, Boston, MA, USA.
-
Wagner, B. 2018. More Effective C#: 50 Specific Ways to Improve Your C# (2nd ed.). Addison-Wesley Professional, Boston, MA, USA.
-
Skeet, J. 2019. C# in Depth (4th ed.). Manning Publications, Shelter Island, NY, USA.
-
Abramson, N., Torgersen, M., Huguet, P., and Wiltamuth, S. 2020. C# 9.0 on the record. Microsoft Build. Retrieved July 21, 2026 from https://devblogs.microsoft.com/dotnet/welcome-to-csharp-9/
-
Torgersen, M. 2023. Welcome to C# 12. Microsoft .NET Blog. Retrieved July 21, 2026 from https://devblogs.microsoft.com/dotnet/welcome-to-csharp-12/
-
Kennedy, A., and Syme, D. 2001. Design and Implementation of Generics for the .NET Common Language Runtime. Journal of Functional Programming 11, 6 (Nov. 2001), 605-628. DOI: https://doi.org/10.1017/S0956796801004256
-
Bierman, G. M., Parkinson, M. J., and Pitts, A. M. 2003. The effect of a null-able type on the Java programming language. In ECOOP 2003 – Object-Oriented Programming, Springer, Berlin, Germany, 30-45.
-
Duffy, J. 2010. Concurrent Programming on Windows. Addison-Wesley Professional, Boston, MA, USA.
-
Richter, J. 2012. CLR via C# (4th ed.). Microsoft Press, Redmond, WA, USA.
-
Stovell, D., and Park, S. 2023. .NET Microservices: Architecture for Containerized .NET Applications (2nd ed.). Microsoft Developer Division. Retrieved July 21, 2026 from https://learn.microsoft.com/dotnet/architecture/microservices/
-
Solomon, A., and Rousos, D. 2023. .NET Microservices: Architecture for Containerized .NET Applications. Microsoft Press, Redmond, WA, USA.
-
Miller, J., and Ragsdale, S. 2006. Common Language Infrastructure (CLI) Standard (ECMA-335). ECMA International, Geneva, Switzerland.
12. 延伸阅读
12.1 官方文档
- C# 语言参考:https://learn.microsoft.com/dotnet/csharp/language-reference/
- .NET 运行时仓库:https://github.com/dotnet/runtime
- Roslyn 编译器仓库:https://github.com/dotnet/roslyn
- C# 语言规范草案:https://github.com/dotnet/csharpstandard
- .NET Foundation:https://dotnetfoundation.org/
12.2 系列内交叉引用
继续学习 C# 的下一站:
- 基础语法 —— 类型、变量、运算符、控制流
- 面向对象编程 —— 类、接口、继承、多态
- 泛型与集合 ——
List<T>、Dictionary<K,V>、泛型约束 - 异步编程 —— Task、async/await、并行编程
- LINQ 与函数式编程 —— 查询表达式、Lambda
- 值类型与引用类型 —— 栈与堆、装箱拆箱
- GC 代机制 —— 分代回收、LOH、Server GC
- async-await 状态机 —— 编译器重写原理
- Span 与 Memory —— 零拷贝编程
12.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).
12.4 社区资源
- .NET 官方博客:https://devblogs.microsoft.com/dotnet/
- C# 设计会议记录:https://github.com/dotnet/csharplang
- Stack Overflow
c#标签:https://stackoverflow.com/questions/tagged/c%23 - Reddit
r/csharp:https://www.reddit.com/r/csharp/ - Microsoft Learn 学习路径:https://learn.microsoft.com/training/paths/
12.5 视频资源
- Microsoft Build 大会 .NET Sessions:https://build.microsoft.com/
- .NET Conf(每年 11 月):https://dotnetconf.com/
- YouTube
dotnet官方频道:https://www.youtube.com/@dotnet - Nick Chapsas 视频教程:https://www.youtube.com/@nickchapsas
- Milan Jovanovic 工程实战:https://www.youtube.com/@MilanJovanovicTech
12.6 工具与扩展
- dotnet-format:代码格式化工具
- dotnet-ef:EF Core CLI 工具
- dotnet-counters:实时性能计数器
- dotnet-dump:堆转储分析
- dotnet-trace:性能追踪
- PerfView:免费性能分析工具
- dotMemory:JetBrains 内存分析(付费)
- dotTrace:JetBrains CPU 分析(付费)
- ILSpy / dnSpy:反编译工具
12.7 习题答案汇总
| 题号 | 答案 |
|---|---|
| Q1 | C |
| Q2 | B |
| Q3 | C |
| Q4 | B |
| Q5 | D |
| Q6 | B |
| Q7 | C |
| Q8 | C |
| Q9-Q16 | 见各题参考答案 |
结语
至此,我们对 C# 语言与 .NET 平台建立了宏观认知。从 Anders Hejlsberg 在 2000 年的设计哲学,到 2024 年 .NET 9 的 Native AOT、HybridCache、动态 PGO,C# 已经从一个 Windows-only 语言演化为覆盖云、桌面、移动、游戏、IoT 的全栈平台。
下一步,请进入 基础语法,开始动手编写真正的 C# 代码。记住:
- 不要满足于”能跑”:理解每行代码背后发生了什么。
- 不要怕出错:错误是最好的老师,善用异常堆栈与调试器。
- 不要孤立学习:每一篇文档都关联着上下游,交叉阅读能加深理解。
愿你在 FANDEX 的 C# 之旅中,既能掌握工程实战,也能体味语言设计之美。
本文由 FANDEX 团队编写,最后更新于 2026-07-21。若有疑问或建议,欢迎在项目仓库提交 Issue。