.NET 新特性(一):.NET 5(C# 9)统一时代开端

写在前面

本文是 .NET 新特性系列第一篇。.NET 5(2020-11)是"统一 .NET"的第一代——从此去掉 “Core” 后缀,把原来的 .NET Framework + .NET Core + Xamarin 合并为一个平台。

虽然 .NET 5 是非 LTS 的过渡版本,但它带来的 C# 9 特性是划时代的:record、init、顶级语句、模式匹配增强——这些至今是日常代码的基石。从这版起,现代 C# 的面貌基本成型。


一、版本概览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.NET 5
  发布:2020-11
  支持:非 LTS(标准支持 18 个月)
  C#:9
  定位:统一时代开端

  历史意义:
    ✓ 去掉 "Core" 后缀,统一品牌
    ✓ Framework + Core + Xamarin 合并为一个 .NET
    ✓ 一个 BCL、一个运行时、一套工具链服务所有平台
    ✓ 跳过版本 4(避免和 .NET Framework 4.x 混淆)

二、C# 9 语言特性(重点)

2.1 record(记录类型)⭐

C# 9 最重要的特性。一行定义不可变、值相等的"数据类":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 一行定义(编译器自动生成属性、构造、相等、ToString、解构)
public record User(int Id, string Name, string Email);

var u1 = new User(1, "张三", "z@test.com");
var u2 = new User(1, "张三", "z@test.com");

u1 == u2;              // true(值相等,普通 class 是 false)
u1 with { Name = "李四" };   // with 表达式:复制并改一个字段
var (id, name, _) = u1;      // 解构

// 传统 class 要手写一堆:构造、Equals、GetHashCode、ToString、Deconstruct
// record 一行搞定
1
2
3
4
5
6
7
record 的本质:
  基于值相等的引用类型(不可变)
  自动生成:属性、主构造、Equals/GetHashCodeToStringDeconstruct
  支持 with(非破坏性修改)
  
  适用:DTO、值对象、消息、不可变数据
  C# 10 起还有 record struct(值类型版)

2.2 init 只读设置器 ⭐

对象初始化后不可修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class User
{
    public string Name { get; init; }
    public int Age { get; init; }
}

var u = new User { Name = "张三", Age = 25 };  // 初始化时设置
u.Age = 26;                                     // ❌ 编译错误!init 后不可改

// 对比:
//   get; set;       — 任意读写(不安全)
//   get;            — 只读(只能在构造里赋值)
//   get; init;      — 初始化时写,之后只读(两全)
1
2
3
4
init 解决的问题:
  对象初始化器(new X { ... })的灵活性
  + 创建后不可变的线程安全
  配合 record,是不可变数据建模的核心

2.3 顶级语句(top-level statements)⭐

Program.cs 不用写 classMain,直接写代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// C# 9 之前:Program.cs
using System;
namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello");
        }
    }
}

// C# 9:整个 Program.cs 就一行
System.Console.WriteLine("Hello");

// 还能用 async、args、返回值
var name = args.Length > 0 ? args[0] : "World";
Console.WriteLine($"Hello {name}");
1
2
3
4
顶级语句大幅简化小程序 / API 入口
  编译器自动包成 Main 方法
  ASP.NET Core 的 Minimal API 基于此(.NET 6)
  大型项目仍可用传统 Main

2.4 模式匹配增强

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 关系模式(< > <= >=)
static string Grade(int score) => score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 60 => "C",
    _ => "F"
};

// 逻辑组合(and / or / not)
static bool IsDigit(char c) => c is >= '0' and <= '9';
static bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

// not 模式
if (obj is not null) { }       // 比 obj != null 更"模式化"

// 类型 + 条件组合
static decimal GetFee(object o) => o switch
{
    int n when n > 100 => 0.5m,
    string s => 1.0m,
    _ => 2.0m
};

2.5 target-typed new(目标类型 new)

省略类型名,由上下文推断:

1
2
3
4
5
6
7
// 上下文已知类型,new() 即可
Dictionary<string, List<int>> dict = new();
List<User> users = new();
Process(new User(1, "x"));   // 参数类型已知,省略

// 对比旧写法:类型名写两遍
Dictionary<string, List<int>> dict = new Dictionary<string, List<int>>();

2.6 协变返回类型

重写方法可以返回更具体的派生类型:

1
2
3
4
5
6
7
8
class Animal
{
    public virtual Animal Clone() => new Animal();
}
class Dog : Animal
{
    public override Dog Clone() => new Dog();   // 返回 Dog(更具体),合法
}

2.7 本地大小整数(nint / nuint)

1
2
3
4
5
// nint = 平台相关的整数(32位系统是 int,64位是 long)
nint ptr = 123;        // 64位系统上是 8 字节
nuint uptr = 456;

// 用于互操作、指针运算(和 IntPtr 对应)

2.8 其他

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 静态匿名 lambda(不捕获,可缓存)
Func<int, int> f = static x => x * 2;

// target-typed 条件表达式(两分支类型不同,但有公共目标类型)
var result = condition ? 1 : 1.5;   // 推断为 double

// 函数指针(互操作,比 delegate 快)
delegate* unmanaged<int, int> fp;

// 模块初始化器(程序集加载时执行)
[ModuleInitializer]
internal static void Init() { /* 全局初始化 */ }

三、统一平台与运行时

3.1 统一 .NET

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.NET 5 之前:
  .NET Framework(仅 Windows,老)
  .NET Core(跨平台,新)
  Xamarin/Mono(移动端)
  互不兼容,库要分别维护

.NET 5 之后:
  一个 .NET 运行所有平台
  一个 BCL(基础类库)
  一套 SDK / 工具链
  库只需写一次,到处跑

3.2 Single-file 发布

1
2
3
4
# 打成单个可执行文件
dotnet publish -c Release -r linux-x64 --self-contained false /p:PublishSingleFile=true

# 一个文件即可运行(无需装运行时,配合 self-contained)

3.3 Windows Runtime 支持

1
2
C#/WinRT:.NET 5 重新支持 Windows Runtime API(UWP、WinUI 互操作)
  在 .NET Core 3.x 被移除,5 重新引入(通过 CsWinRT)

3.4 性能改进

1
2
3
4
5
✓ GC 改进(Gen 0/1 更快)
✓ JIT 改进
✓ ARM64 优化
✓ 异步开销降低
✓ System.Text.Json 性能(追赶 Newtonsoft)

四、BCL 改进

1
2
3
4
5
6
7
8
9
// System.Text.Json 大幅增强(接近生产可用)
//   可空引用类型、记录、异步流

// Half(16位浮点)
Half h = 1.5f;

// System.Text.Json 可靠性提升(默认严格)

// StringComparer 改进、Regex 性能

五、升级建议

1
2
3
4
5
6
.NET 5 已停止支持(2022-05),不应再用于生产
  → 升级到 LTS(6/8/10)

但 C# 9 的特性(record、init、顶级语句):
  → 仍是现代 C# 的核心,日常必用
  → 只要项目 ≥ C# 9(.NET 5+)就能用

六、小结

.NET 5 是统一时代的开端:

  • 意义:去掉 Core 后缀,Framework+Core+Xamarin 统一
  • C# 9(划时代):record、init、顶级语句、模式匹配(and/or/not、关系)、target-typed new、协变返回
  • 平台:单一运行时/BCL/SDK,Single-file 发布,Windows Runtime
  • 定位:非 LTS 过渡版,但 C# 9 特性奠定现代 C# 基础

下一篇讲 .NET 6(C# 10):首个统一 LTS——全局 using、文件命名空间、record struct、Minimal API。