首页 / C# 入门教程 / 属性模式/位置模式与记录解构

C# 入门教程

属性模式/位置模式与记录解构

本教程共 100 篇 · 第 89 篇 · 更新于 2026-07-31 · 约 7 分钟阅读

C#C# 入门教程编程语言模式匹配recordDeconstruct

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.Namep.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 安全共处。