属性模式/位置模式与记录解构
本教程共 100 篇 · 第 89 篇 · 更新于 2026-07-31 · 约 7 分钟阅读
89. 属性模式/位置模式与记录解构
本节目标:学完用属性模式与位置模式匹配对象内部结构,并理解记录如何被解构(Deconstruct)。
属性模式:按属性对照
属性模式(property pattern)检查对象的属性是否匹配某个模式。写法是在 {} 里列出”属性名: 子模式”。
Person p = new("码上学", 30);
string stage = p switch
{
{ Age: >= 18 } => "成年",
{ Age: < 18 } => "未成年",
_ => "未知"
};
Console.WriteLine(stage);
record Person(string Name, int Age);
只要对象的 Age 满足条件就匹配,不需要先拆箱或强制转换。
Note属性模式里每个子模式都能再用关系、逻辑、甚至嵌套属性模式,因此可以层层深入。
为什么要靠 Deconstruct
位置模式能写 (x, y) 是因为对象可被”解构”成若干值。解构由类型的 Deconstruct 方法定义:它告诉编译器”把这个对象拆成哪几个分量、按什么顺序”。
所以位置模式的本质是”先解构,再按位置对照”。C# 不会为任意对象凭空猜分量顺序,必须由你(或记录自动)提供 Deconstruct。这就是为什么没写它的类直接用位置模式会编译失败——不是语法忘了,是”拆法”没定义。
Note解构是独立于模式匹配的能力。平时
var (a, b) = point;就用到了它。模式匹配只是把”解构后的每个位置”拿去比对,是解构的一个用武之地。
嵌套属性模式
属性可以嵌套:对象的属性本身又是对象,用点号继续往下 match。这就是”扩展属性模式”。
record Address(string City);
record Person(string Name, int Age, Address Home);
Person p = new("小明", 20, new Address("北京"));
string where = p switch
{
{ Home.City: "北京" } => "住北京",
{ Home.City: "上海" } => "住上海",
_ => "其他地方"
};
Console.WriteLine(where);
不必写 p.Home.City == ...,模式直接表达”路径 + 条件”。
位置模式:解构后对照
位置模式(positional pattern)先把对象”解构”成一组值,再按位置逐个匹配。它依赖类型的 Deconstruct 方法。
Point pt = new(3, 4);
string quad = pt switch
{
(0, 0) => "原点",
(var x, 0) => $"在 x 轴,x={x}",
(0, var y) => $"在 y 轴,y={y}",
(var x, var y) => $"({x}, {y})"
};
Console.WriteLine(quad);
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
(var x, 0) 表示:第二个分量必须是 0,第一个分量捕获到 x。
Warning位置模式里分量顺序必须和
Deconstruct的参数顺序一致,写反了逻辑就错,编译还不报。
记录自带解构
记录(record,第 49 章详讲)会自动生成 Deconstruct,所以位置模式用记录最顺手,不必手写。
record Point(int X, int Y);
Point p = new(3, 4);
string info = p switch
{
(>= 0, >= 0) => "第一象限",
( < 0, >= 0) => "第二象限",
_ => "其他象限"
};
Console.WriteLine(info);
Tip位置记录(positional record)的主构造器参数顺序,就是 Deconstruct 的顺序,二者天然对齐。
属性模式与位置模式混用
两者可以在一个模式里叠加:先用位置解构,再对其中某个分量用属性模式,表达力很强。
record Person(string Name, int Age);
Person p = new("小红", 25);
string msg = p switch
{
("小红", >= 18) => "成年小红",
(_, >= 18) => "其他成年人",
_ => "未成年人"
};
Console.WriteLine(msg);
_ 在位置模式里表示”这个分量任意”,类似弃元。
元组也能用位置模式
不只是类,元组(tuple)天然可解构,同样适合位置模式,方便一次判断多个值。
var point = (x: 3, y: 7);
string label = point switch
{
(0, 0) => "原点",
( > 0, > 0) => "第一象限",
_ => "其他"
};
Console.WriteLine(label);
新手易踩的坑
坑一:类没写 Deconstruct 就用位置模式,编译报错。要么加 Deconstruct,要么改用属性模式。
坑二:位置顺序和 Deconstruct 不一致,匹配结果诡异。务必对照参数顺序。
坑三:嵌套属性模式写太深,可读性下降。超过三层考虑先取变量再判断。
Tip解构(deconstruction)是 C# 把对象”拆成变量”的通用能力,模式匹配只是它的一个用武之地。平时也能
var (a, b) = point;直接拆。
自定义 Deconstruct 的重载
一个类型可以写多个 Deconstruct,只要参数数量不同,编译器按”解构位置个数”选对应版本。
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
public void Deconstruct(out int x, out int y, out string label)
=> (x, y, label) = (X, Y, "点");
}
var p = new Point(1, 2);
var (x, y) = p; // 用双参版本
var (a, b, c) = p; // 用三参版本
Note解构不止服务于模式匹配。平时也能
var (x, y) = point;一次性把字段拆成局部变量,很方便。
记录解构的位置顺序
位置记录(positional record)的主构造器参数顺序,决定了 Deconstruct 的参数顺序,也决定位置模式的匹配顺序。
record Person(string Name, int Age); // Deconstruct 顺序:Name, Age
Person p = new("小李", 22);
string info = p switch
{
("小李", >= 18) => "成年小李", // 第一个位置对 Name,第二个对 Age
_ => "其他"
};
改了主构造器参数顺序,位置模式也得跟着改,否则逻辑错位。这是用位置模式要小心的耦合点。
Tip嫌位置模式顺序易错,可用属性模式
{ Name: "小李", Age: >= 18 },用名字而非位置,更稳更可读。
综合:用模式简化业务分支
把属性、位置、关系、逻辑组合,处理一段”订单”状态,看模式如何替代又臭又长的 if。
record Order(string Status, decimal Amount, bool IsVip);
Order o = new("Paid", 299m, true);
string Tag = o switch
{
{ Status: "Paid", Amount: >= 200 } => "大额已付",
{ Status: "Paid", IsVip: true } => "VIP 已付",
{ Status: "Cancelled" } => "已取消",
_ => "其他"
};
Console.WriteLine(Tag);
嵌套属性、常量、甚至后续可加关系模式,都在一个 switch 表达式里收口,逻辑集中且清晰。
Warning模式再强也别过度嵌套。一个分支里套四五层属性模式,可读性反而下降,必要时先取中间变量。
记录与 with 表达式配合
位置记录支持 with 复制并改部分值(第 49 章详讲)。配合解构,可方便地把”旧对象微调”变成新对象。
record Point(int X, int Y);
Point p = new(1, 2);
Point moved = p with { X = 10 }; // 复制并改 X
var (x, y) = moved; // 解构取回
Console.WriteLine($"({x}, {y})"); // (10, 2)
with 不改原对象(记录是不可变语义),解构则把字段摊平成变量,二者都服务于”数据为王”的风格。
Note记录 + 位置模式 + with,是函数式风格三件套:匹配形状、产出新值、不改旧的。很适合配置、状态这类数据。
解构在 foreach 里
若集合元素可解构(如记录、元组),foreach 能直接把元素拆成变量,配合模式更顺。
List<(string Name, int Age)> people = [("甲", 18), ("乙", 16)];
foreach (var (name, age) in people)
{
Console.WriteLine($"{name} 年龄 {age}");
}
这其实就是位置解构的日常用法:遍历时一步把元组/记录拆开,省去 p.Name、p.Age 的点取。
Tip返回多个值时,优先返回元组或记录,调用处用解构一次接住,比 out 参数清爽得多。
模式匹配的性能注记
模式匹配在编译期就翻译成高效判断,多数情况下和手写 if/else 性能相当,不会凭空变慢。
真正的收益在可读性与正确性:编译器会提醒未覆盖的分支,减少”漏情况”的 bug;嵌套属性模式也比一串 obj.A.B.C == ... 更短。
// 手写一长串 vs 模式,性能接近,后者更不易错
bool ok = p is { Home.City: "北京", Age: >= 18 };
Warning别为了微乎其微的性能去手写”优化版”模式。先求清晰正确,真有热点再测再改。
Deconstruct 与元组解构的一致性
元组也能解构:var (a, b) = (1, 2);。它和类/记录的 Deconstruct 用的是同一套机制,所以位置模式的写法对元组与记录完全通用。
var point = (X: 3, Y: 4);
var (x, y) = point; // 元组解构
Console.WriteLine($"({x}, {y})");
record Line(int X1, int Y1, int X2, int Y2);
var line = new Line(0, 0, 5, 5);
var (x1, y1, x2, y2) = line; // 记录解构,参数顺序即主构造器顺序
Tip解构是统一的”把对象拆成变量”能力:元组、记录、自定义类都支持,位置模式只是它的模式匹配用法。
属性模式还是位置模式
两者都能匹配对象内部,怎么挑?
- 关心”某几个具名属性” → 属性模式
{ Age: >= 18 },用名字,改了字段顺序也不崩。 - 关心”整体结构且顺序固定” → 位置模式
(x, y),更短,但耦合 Deconstruct 顺序。
经验:对外公开的 API、字段可能变动的类型,优先属性模式更稳;内部记录、坐标这类”顺序即意义”的数据,位置模式更利落。
Tip拿不准就用属性模式。它不依赖位置,可读性也更直白,少一处隐藏的耦合,日后改结构也更安全。
小结
属性模式 { Age: >= 18 } 按属性对照,可嵌套深入;位置模式 (x, y) 靠 Deconstruct 解构后按序匹配,记录自带解构最省事。两者可混用、也能套关系与逻辑模式。下章转向可空类型,学会与 null 安全共处。