首页 / C# 入门教程 / switch 的模式匹配

C# 入门教程

switch 的模式匹配

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

C#C# 入门教程模式匹配switch 表达式when 守卫

18. switch 的模式匹配

本节目标:用 switch 表达式和类型/常量/关系/属性模式写出更短的分支,并用 when 守卫做精细判断。

第 17 章的 switch 只能比对”等于某值”。现代 C# 给 switch 加了模式匹配(pattern matching),让它能按”类型、范围、属性”来分支,代码瞬间瘦下来。这一章把常用模式一次讲透。

switch 表达式:把选择变成值

传统 switch 是语句,要自己写 break。C# 8 引入 switch 表达式(switch expression),它直接”返回一个值”:

namespace CSharpDemo;

int score = 85;
string level = score switch
{
    >= 90 => "优秀",
    >= 75 => "良好",
    >= 60 => "及格",
    _ => "不及格"
};
Console.WriteLine(level);

注意三点变化:把 switch (score) 写成了 score switch;每个分支用 => 直接给结果;_ 是”其他情况”的通配符,相当于 default。整个 switch 算出一个 level 值,没有 break、没有大括号,干净很多。

Note

>= 90 这种叫关系模式(relational pattern),C# 9 起支持。它让 switch 也能表达”范围”,不必再用 if/else if 链。模式必须”穷尽”——也就是所有情况都要有分支覆盖,_ 兜底正是为了满足这点。

常量模式:比对固定值

最基础的”等于某值”叫常量模式,和老 switch 一样,但用在表达式里更紧凑:

string day = "周一";
string type = day switch
{
    "周六" or "周日" => "休息日",
    _ => "工作日"
};
Console.WriteLine(type);

这里 "周六" or "周日" 用了逻辑模式 or,把两个常量合并成一个分支。C# 9 的逻辑模式 and/or/not 让条件组合更自然,读起来也像人话。

类型模式:按类型分支

当你手里是 object 或基类引用,想按”实际类型”分别处理,用类型模式:

object value = 3.14;

string desc = value switch
{
    int i => $"整数 {i}",
    double d => $"小数 {d}",
    string s => $"文本 {s}",
    _ => "其他类型"
};
Console.WriteLine(desc);   // 小数 3.14

编译器判断 value 实际是 double,走对应分支,并自动把值装进 d。这比一堆 if (value is double d) 简洁太多,且每个分支拿到的是正确类型的变量。

属性模式:按对象属性分支

还能直接按对象的属性值分支,叫属性模式(property pattern):

namespace CSharpDemo;

class Student
{
    public int Score { get; set; }
}

var stu = new Student { Score = 85 };
string result = stu switch
{
    { Score: >= 90 } => "优秀",
    { Score: >= 60 } => "及格",
    _ => "不及格"
};
Console.WriteLine(result);

{ Score: >= 90 } 表示”Score 属性满足大于等于 90”。不用先取出 Score 再判断,模式里直接写。属性模式特别适合”根据一个对象的多个字段做分类”,比如按订单状态、金额组合判定优惠。

Tip

嵌套属性也能写,如 { Address: { City: "北京" } },表示从对象里逐层取出属性匹配。路径别嵌套太深,否则可读性反而下降,那时不如先取出对象再判断。

when 守卫:在模式上再加条件

模式本身不够细时,用 when 追加额外条件:

string grade = (score, true) switch
{
    (>= 90, _) => "优秀",
    (>= 60, _) when DateTime.Now.Day == 1 => "月初及格特惠",
    (>= 60, _) => "及格",
    _ => "不及格"
};

when 接在模式后,只有模式匹配且 when 条件也成立才走该分支。它让模式匹配能表达”模式 + 任意布尔条件”的组合,是处理复杂分支的利器。

与 if 的取舍

模式匹配不是要取代 if,而是让某些分支更短:

  • 多分支、基于”值/类型/属性”做分类 → 优先 switch 表达式。
  • 条件纵横交错、含复杂布尔逻辑 → 仍用 if/else,可读性更好。

一个信号:当你在 switch 里堆了五六个 when 还很绕,多半该换回 if。模式匹配是工具,清晰优先。它在编译期就被翻译成高效的判断逻辑,和普通 if/switch 性能相当,不会有额外负担,所以可放心用它提升可读性。

十、为什么要用 switch 表达式(设计动机)

你可能会问:老 switch 语句也能用,为啥要表达式?核心好处有两个,而且都很实在。

第一,编译期逼你想全。 switch 表达式要求「穷尽(exhaustive)」所有情况,少一个分支编译器直接报错,不让你编译。老 switch 语句漏写 default 只是「悄悄跳过」,常常埋下隐蔽 bug;而表达式用 _ 兜底,把「别漏了」从「靠自觉」变成「靠编译器」。

第二,它是值,能就地用。 表达式整体算出一个值,所以能直接赋给变量、当方法返回值、甚至嵌进插值字符串里。老语句要先声明变量、再在每个 casebreak 前赋值,啰嗦且容易忘写某一支。

一句话:switch 表达式 = 更短的代码 + 更稳的兜底,这正是 C# 现代风格偏爱它的原因。

十一、模式还能这样组合

模式不是孤立的,逻辑模式 and/or/not 能把它们拼出更细的判断。

not 模式:取反

not 表示「不满足该模式」,常用来处理「非空 / 非某值」:

object? input = null;
string kind = input switch
{
    not null => "有内容",
    _ => "空的"
};
Console.WriteLine(kind);

属性 + 关系组合

属性模式里的关系条件,也能用 and 连成一段范围,不必写两个分支:

var stu = new Student { Score = 65 };
string note = stu switch
{
    { Score: >= 90 } => "优秀",
    { Score: >= 60 and < 70 } => "刚踩线及格",
    { Score: >= 60 } => "及格",
    _ => "不及格"
};
Console.WriteLine(note);   // 刚踩线及格

{ Score: >= 60 and < 70 } 就是说「大于等于 60 且小于 70」。比拆成 >= 60 再在分支里判 < 70 更直观。

类型 + 关系组合

类型模式也能顺手加关系判断,比如「正整数」:

object value = 42;
string tag = value switch
{
    int n and > 0 => $"正整数 {n}",
    int => "非正整数",
    _ => "其他"
};
Console.WriteLine(tag);
Tip

组合模式的读法是「从左到右逐段念」:int n and > 0 就是「是整数且大于 0」。念得通,写法通常就对了。

十二、常见坑与注意

模式匹配虽好,也有几处要留心:

  1. 分支顺序仍是从上到下:更具体的放前面,宽泛的放后面,否则会被先匹配掉(和 if/else if 同一原则)。
  2. 表达式必须穷尽:忘了 _ 兜底,编译器直接报错,这是保护不是麻烦。
  3. 别硬凹:当 when 越堆越多、逻辑越来越绕,果断换回 if,可读性优先。
Warning

嵌套属性模式(如 { Address: { City: "北京" } })别超过两三层。一旦路径太深,光看模式就眼花,那时应该先取出对象、用普通判断分步处理,或把判定抽成独立方法。模式匹配是让代码变清晰的,别让它反过来变糊涂。

常见疑问解答

问:switch 表达式必须用 _ 收尾吗? 必须有一个”覆盖其余情况”的分支,否则编译器认为”可能没分支匹配、却要返回个值”,会报错。_ 就是那个兜底,相当于 default。即使你觉得前面都覆盖了,编译器也要你明确保底。

问:关系模式 >= 90 能组合 and/or 吗? 能。例如 >= 60 and < 70 表示”60 到 69”。逻辑模式 and/or/not 让范围表达更自然,还能配合类型模式:o is int and > 0

问:属性模式能嵌套多深? 可以嵌套,如 { Address: { City: "北京" } }。但嵌套过深可读性下降,此时不如先取出对象判断,或抽成方法。

要点速记

  • switch 表达式把”多选一”变成”算一个值”。
  • 关系模式管范围,类型模式管类别,属性模式管字段。
  • when 在模式上追加额外布尔条件。
  • 复杂逻辑仍用 if,别硬凹模式。

动手练一练

  1. 用 switch 表达式把 score 映射为 "优秀/良好/及格/不及格",用关系模式 >= 90 等写分支。
  2. 定义一个 object 变量,依次赋 intstringdouble,用类型模式 switch 表达式分别返回描述。
  3. 给一个 Student 类写属性模式,按 Score 范围分类,体会”直接在模式里读属性”的写法。

模式也能用在 is 里

模式匹配不只服务于 switch,也能用在 is 运算符上做”类型+条件”判断:

object value = 42;
if (value is int n && n > 0)
{
    Console.WriteLine($"正整数:{n}");
}

is int n 既判断类型,又把值装进 n&& n > 0 追加范围条件。这种”is 模式 + 守卫”和 switch 的 when 思路一致,只是用在 if 里更灵活。

解构模式预告

C# 还支持”解构模式”:把对象的多个属性一次性拆开匹配。配合记录类型(record)尤其顺手,比如 point is (var x, var y) 直接取出坐标分量。这部分在讲记录(第 49 章)和模式匹配进阶(第 89 章)时会系统展开,这里先知道有这能力。

综合实战:订单分级

把几种模式叠起来,看一个贴近真实的例子——按订单金额和是否会员分级:

namespace CSharpDemo;

class Order
{
    public decimal Amount { get; set; }
    public bool IsMember { get; set; }
}

var order = new Order { Amount = 1200m, IsMember = true };
string level = order switch
{
    { Amount: >= 1000, IsMember: true } => "钻石会员",
    { Amount: >= 1000 } => "大客户",
    { Amount: >= 100 } => "普通订单",
    _ => "小额订单"
};
Console.WriteLine(level);

这里同时用了属性模式(读 AmountIsMember)和关系模式(金额范围),两个属性一起参与匹配。对比用一堆 if (order.Amount >= 1000 && order.IsMember) 的写法,switch 表达式把”数据形状”摆得清清楚楚。

Note

分支顺序依旧重要:把”会员且大额”这种更具体的放前面,宽泛的放后面,否则会被先匹配掉。这和 if/else if 的”从严到宽”原则完全一致。

小结与下一步

switch 表达式把”多选一”变成”算一个值”;关系模式管范围、类型模式管类别、属性模式管字段、when 做兜底细化。它们共同把臃肿的 if 链压成几行优雅代码。

下一章讲循环与跳转:用 for / foreach / while 重复做事,并用 break / continue 控制节奏。