Lesson 20: C# 13 — 小特性合集

.NET 9(2024.11)· \e 转义 · 方法组 · partial 属性 · 重载优先级 · 更多

前置:已完成 Lesson 19(ref struct 三部曲)Lesson 18(params + Lock)。本课是 C# 13 最后一课——小特性合集,风格延续 L08L17
阅读:Microsoft Learn: C# 13 新增功能

一、\e 转义序列 ESC Character 日常

问题:写 ANSI 终端控制码太啰嗦

ESC 字符(Unicode U+001B)是 VT100/ANSI 终端控制序列的起始字节。C# 12 只有两种方式:

// 写法 1:Unicode——安全但冗长
string red1 = "红色";

// 写法 2:十六进制——有歧义风险!
string red2 = "\x1b[31m红色\x1b[0m";  // \x 会贪婪匹配后续十六进制数字
// "\x1b2F" → 被解析成单个字符,而非 ESC + '2' + 'F'

C# 13:\e

// C# 13:简洁、安全、无歧义
char esc = '\e';                    // 等价于 (char)0x1B
string red = "\e[31m红色\e[0m";      // ANSI 红色文字

// 终端效果展示:
Console.WriteLine("\e[1m粗体\e[0m \e[4m下划线\e[0m");
Console.WriteLine("\e[2J");  // 清屏

如果你不写终端应用,可能用不到。但写 CLI 工具、进度条、Spectre.Console 风格终端 UI 时——每天都会用上。

二、方法组自然类型改进 Method Group Natural Type 进阶

问题:var 推断被无关扩展方法"污染"

C# 10 起 var f = obj.Method; 可推断委托类型。但算法有 Bug——它会收集所有作用域的候选方法(包括无关扩展方法),只要有一个不匹配,全部推断失败:

// 你的类:只有一个无参 M 方法
public class C {
    public void M() { }
}

// 远处某个 using 引入的扩展方法(你根本不知道它存在)
public static class SomeExtensions {
    public static void M(this C c, object o) { }  // 参数个数不同
}

// C# 12:编译错误 CS8917——扩展方法污染了候选集!
// var z = new C().M;

// C# 13:成功推断为 Action ✅
var z = new C().M;  // 编译器先看实例方法,匹配了就停止

改进算法:逐作用域剪枝

  1. 先看实例方法——匹配了就停止,不看扩展方法
  2. 再看扩展方法——按作用域从近到远,每个作用域内立即剪枝(去掉泛型参数个数不对的、约束不满足的候选)
一句话:修复了"远处无关扩展方法干扰 var 推断"的 Bug。大多数开发者不会感知到这个改进——但过去被 CS8917 困扰过的人不会再遇到了。

三、partial 属性与索引器 Partial Properties & Indexers 源生成器

问题:源生成器需要生成属性,但没有 partial 属性

C# 2.0 只有 partial classpartial method。C# 9 增强了 partial method。但属性一直是盲区。

// C# 13:属性也能 partial 了
// 手写部分(声明):
public partial class ViewModel {
    public partial string Name { get; set; }
}

// 源生成器自动产出(实现——另一个 partial 文件):
// public partial class ViewModel {
//     private string _name;
//     public partial string Name {
//         get => _name;
//         set { _name = value; OnPropertyChanged(); }
//     }
// }

// 索引器同样支持
public partial int this[int index] { get; set; }
日常你不会手写 partial 属性——但每个 MVVM 框架、JSON 序列化源生成器、AOT 友好的 ORM都在后台生成 partial 属性的实现。这是为工具链服务的特性,间接让所有人受益。

四、重载解析优先级 OverloadResolutionPriority 库作者

场景:API 演进时控制重载选择

你是库作者。有旧 API,现在想加新版本。两个重载都匹配时——编译器挑哪个?

// 旧 API(保留是为了兼容)
public void Log(params object[] args) { }

// 新 API(希望编译器默认选这个)
[OverloadResolutionPriority(1)]  // ← 数值越大优先级越高
public void Log(params ReadOnlySpan<object> args) { }

// 调用处:两个重载都匹配 → 编译器选优先级高的
Log(userId, operation, elapsed);  // 自动选 Span 版本 ✅

规则

优先级值效果典型场景
正数(如 1优先选择新 API——性能更好、更现代
0 或不加默认普通 API
负数(如 -1降低优先级旧 API——废弃但不删除
只在编译器无法区分时生效。如果一个重载是"完全匹配"另一个需要隐式转换,正常的类型匹配规则仍然优先——这个属性只在两个重载同等适用时打破平局。

五、^ 运算符进对象初始器 Implicit Index Access 小改进

C# 8 引入的 ^ 运算符(从末尾索引)现在可以在对象初始器中使用了:

// C# 12:对象初始器中不能写 ^ → 必须手动算 Length - N
var arr = new BufferHolder {
    buffer = { [7] = 1, [8] = 2, [9] = 3 }  // 先得知道 Length = 10
};

// C# 13:直接用 ^ 表述"从末尾"——意图一目了然
var arr = new BufferHolder {
    buffer = { [^1] = 3, [^2] = 2, [^3] = 1 }
};

六、field 关键字 Preview 预览

C# 13 以预览特性引入 field 关键字——在自动属性中直接引用编译器生成的后备字段,无需手动声明:

😣 以前——手动声明后备字段

private string _name;
public string Name {
    get => _name;
    set => _name = value?.Trim()
        ?? throw new ArgumentNullException();
}

😎 field 关键字——告别样板

public string Name {
    get => field;
    set => field = value?.Trim()
        ?? throw new ArgumentNullException();
}
⚠️ 预览特性:需要在 .csproj 中设置 <LangVersion>preview</LangVersion> 并添加 <Features>field</Features>。API 和语法在最终发布前可能变动。预计在 C# 14(.NET 10)中正式发布。

七、实际用到几个?

这六个特性在日常编程中的出场率差别很大——诚实评估:

特性频率谁在用你什么时候受益
\e 转义低频CLI / 终端工具作者写控制台应用输出彩色文字、进度条时
方法组自然类型改进极低修了个 Bug你被 CS8917 困扰过才感知到——大多数人不遇到
partial 属性零(你不写)源生成器 / 框架作者MVVM 框架、JSON 序列化器替你生成属性实现
OverloadResolutionPriority极低库作者API 演进时控制重载选择——普通项目用不上
^ 进对象初始器偶尔所有人省一次心算 Length - N,代码意图更清晰
field 关键字(C# 14 正式后)所有人每个手写 get/set 的属性省一行 _field 声明
本课最有价值的两个:field 关键字(预览→C# 14 正式)——这会是日常频率最高的改进,每写一个带逻辑的属性都少一行样板;② partial 属性——你从不写,但当你用 CommunityToolkit.Mvvm 或 System.Text.Json 源生成器时,后台就在用这个特性替你干活。

八、小测验

1/4 · \e 转义——C# 13 引入它的主要原因是什么?
2/4 · 方法组自然类型——C# 13 的改进核心是什么?
3/4 · partial 属性——它主要为谁服务?
4/4 · OverloadResolutionPriority——以下哪项描述错误

九、C# 13 全部特性总结

特性一句话影响面课程
params 集合params 支持 ReadOnlySpan → 零分配⭐⭐⭐⭐⭐L18
新 Lock 对象object → Lock,更快更安全⭐⭐⭐⭐L18
ref struct 实现接口Span 可参与泛型抽象⭐⭐⭐⭐L19
allows ref struct泛型反约束——放行 ref struct⭐⭐⭐L19
ref/unsafe 进 asyncSpan 可在 await 前使用⭐⭐⭐L19
\e 转义ESC 字符的简洁安全写法⭐⭐本课
方法组自然类型改进var 推断不再被扩展方法污染⭐⭐本课
partial 属性源生成器能生成属性了⭐⭐⭐本课
重载解析优先级库作者控制重载选择本课
^ 入对象初始器[^1] 初始化末尾元素本课
field 关键字直接访问自动属性的后备字段预览本课

十、下一步

💬 有问题?\e 在非终端场景的替代方案?partial 属性和源生成器的实际开发流程?随时问。