用模式匹配描述集合形状 · 用编译期检查替代运行时报错
if (arr.Length == 3
&& arr[0] == 1
&& arr[1] >= 10
&& arr[2] <= 100) { }
if (arr is [1, >= 10, <= 100]) { }
方括号里的不是值——是模式。这就是 List Patterns:用模式匹配来解构和检查集合。
// ① 精确匹配——长度和元素都相等
int[] arr = { 1, 2, 3 };
Console.WriteLine(arr is [1, 2, 3]); // True
Console.WriteLine(arr is [1, 2]); // False——长度不匹配
// ② 元素使用任意模式(常量、关系、类型、属性……)
if (arr is [not 0, ..]) { } // 第一项不是 0
// ③ 切片模式(..)——匹配任意数量元素(含零个)
if (arr is [1, ..]) { } // 第一个是 1,后面随便
if (arr is [.., 9]) { } // 最后一个是 9
if (arr is [1, .., 9]) { } // 第一个 1,最后 9
// ④ 嵌套模式——元素是对象时匹配属性
if (orders is [{ Status: "New" }, .., { Total: >= 1000 }]) { }
// ⑤ var 捕获——提取匹配到的元素
if (arr is [var first, .. var middle, var last])
{ // first=arr[0], middle=arr[1..^1], last=arr[^1] }
列表模式不要求实现特定接口。编译器只检查:是否有 Length 或 Count 属性 + 可访问索引器。支持的类型:
| 类型 | 支持? | 说明 |
|---|---|---|
int[] / T[] | ✅ | 数组原生支持 |
List<T> | ✅ | 有 Count + 索引器 |
Span<T> / ReadOnlySpan<T> | ✅ | C# 11 新增——Span 模式匹配 |
string | ✅ | 有 Length + char 索引器 |
| 自定义集合 | ✅ | 满足鸭子类型条件即可 |
static string Describe(int[] arr) => arr switch
{
[] => "空数组",
[0] => "只有一个 0",
[var x, var y] => $"两个元素:{x}, {y}",
[1, .., 1] => "首尾都是 1",
[.., 0] => "最后一个元素是 0",
[not 0, ..] => "第一个不是 0",
_ => "其他",
};
// 实战:REST API 路由解析
static string ParseRoute(string[] segments) => segments switch
{
["api", "users"] => "用户列表",
["api", "users", var id] => $"用户详情 {id}",
["api", "users", var id, "orders"] => $"用户 {id} 的订单",
_ => "未知",
};
public class Person
{
public string Name { get; set; }
}
// 忘了设 Name?运行时才发现为 null
var p = new Person(); // 😱 编译器没意见
public class Person
{
public required string Name { get; set; }
}
// ❌ CS9035:Name is required
var p = new Person { Name = "Alice" }; // ✅
required 关键字将"必填属性"从运行时报错变成编译时错误。IDE 在你没写完代码时就显示红色波浪线。
public class Person
{
public required string Name { get; set; }
public required string Email { get; set; }
public int Age { get; set; } // 没有 required——可选
}
var p1 = new Person { Name = "Alice", Email = "a@b.com" }; // ✅
var p2 = new Person { Name = "Alice" }; // ❌ CS9035: Email 未赋值
var p3 = new Person(); // ❌ CS9035: Name 和 Email 都未赋值
public class Person
{
public required string Name { get; set; }
public required string Email { get; set; }
// 有 SetsRequiredMembers → 编译器信任此构造函数
[SetsRequiredMembers]
public Person(string name, string email)
{
Name = name;
Email = email;
}
// 无 SetsRequiredMembers → 调用者必须用初始化器补齐 Email
public Person(string name)
{
Name = name;
// Email 未赋值 → required 悬空
}
}
var p1 = new Person("Alice", "a@b.com"); // ✅ 无需初始化器
var p2 = new Person("Alice") { Email = "a@b.com" }; // ✅ 必须补齐
var p3 = new Person("Alice"); // ❌ CS9035: Email 悬空
[SetsRequiredMembers] 是承诺,不是验证。编译器不检查构造函数是否真的设置了所有 required 成员——你承诺了,编译器就信你。这是一个"信任程序员"的契约。
// 构造函数设默认值,初始化器补 required 差额——最常见模式
public class ApiClient
{
public required string BaseUrl { get; set; }
public required string ApiKey { get; set; }
public int Timeout { get; set; }
public ApiClient() { Timeout = 30; } // 不加 SetsRequiredMembers
}
var client = new ApiClient { BaseUrl = "https://...", ApiKey = "sk-xxx" };
// ✅ Timeout=30(构造函数),BaseUrl 和 ApiKey(初始化器)
public class ApiConfig
{
public required string BaseUrl { get; init; } // 必须设值,设了就不能改
public required string ApiKey { get; init; }
public int Timeout { get; init; } = 30; // 有默认值——不需要 required
}
int[] { 1, 2, 3, 4, 5 } 能被哪个模式匹配?[SetsRequiredMembers] 做了什么?required——看编译器在哪些 new 处报错。