>> >> >> Reference << << << <<<<<<Ref>>>>>>
Pattern Matching
Modified: 2025-06-01 | Author:ljf12825

模式

模式就是一种匹配规则,用来判断对象的结构、类型或值,并可绑定变量
核心思想:

  1. 匹配类型(is it s string? int? Person?)
  2. 匹配值或条件(大于0? 小于0? 某属性为true?)
  3. 绑定变量(匹配成功后把值赋给变量)
  4. 可组合(and/or/not)

可以把模式理解为C#对象解构 + 条件判断 + 绑定语法糖的集合

模式的几大类别

C#从7.0到12逐步扩展了模式类型,主要有以下几种

模式类型语法示例作用
类型模式(Type Pattern)x is int n判断类型,并绑定变量
常量模式(Constant Pattern)x is 0判断值等于某常量
关系模式(Relational Pattern)x is > 0大小比较
逻辑模式(Logical Pattern)x is > 0 and < 10组合多个模式(and/or/not)
否定模式(Negation Pattern)x is not null对模式取反
属性模式(Property Pattern)p is { X: 0, Y: 0 }匹配对象的属性结构
位置模式(Positional Pattern)Point(var x, var y)对对象或record解构
递归模式p is { Name: "Alice", Age: > 0 }可以嵌套匹配对象内部结构

模式匹配

模式匹配是对对象结构和类型的“解构+判断+绑定”操作
简单来说,它让ifswitchis不再只是“类型判断”,而是能直接在匹配时提取数据、判断条件

解决的痛点

is类型模式

C#7.0之后可以这样写

object obj = 123;

if (obj is int n) Console.WriteLine($"是整数,值为{n}");

这相当于

if (obj is int)
{
    int n = (int)obj;
    Console.WriteLine(n);
}

区别:

类型模式 + 守卫条件(when

object obj = 42;

if (obj is int n && n > 0)
    Console.WriteLine("正整数");

// 或者在swtich里写
switch (obj)
{
    case int n when n > 0:
        Console.WriteLine("正数");
        break;
    case int n:
        Console.WriteLine("非正数");
        break;
}

常见模式类型

C#模式匹配的核心在于这些“模式类型”

类型模式(Type Pattern)

x is int n 作用:判断类型并绑定变量

常量模式(Constant Pattern)

if (x is 0)
    Console.WriteLine("零");

判断对象是否等于某个常量

关系模式(Relational Pattern)

if (x is > 0)
    Console.WriteLine("正数");
else if (x is < 0)
    Console.WriteLine("负数");

在C#9.0引入,用<, >, <=, >=做比较

逻辑模式(Logical Pattern)

可以组合多个模式

if (x is >= 0 and <= 100)
    Console.WriteLine("在 0~100之间");
if (x is < 0 or > 100)
    Console.WriteLine("超出范围");

否定模式(Negation Pattern)

if (obj is not null)
    Console.WriteLine("不是 null");

位置模式(Positional Pattern)

假设有一个结构体

public record Point(int X, int Y);

可以直接匹配并结构它

Point p = new(3, 4);

if (p is Point(var x, var y))
    Console.WriteLine($"x={x}, y={y}");
if (p is Point(0, _))
    Console.WriteLine("在Y轴上");

属性模式(Property Pattern)

可以直接匹配对象的属性结构

if (p is { X: 0, Y: 0 })
    Console.WriteLine("原点");
if (p is { X: > 0, Y: > 0})
    Console.WriteLine("第一象限");

组合使用

所有模式都可以嵌套组合

if (p is { X: > 0 and < 10, Y: > 0})
    Console.WriteLine("右上角小区域");

switch表达式(C# 8+)

现代C#最强的模式匹配表达式

string Describe(Point p) => p switch
{
    { X: 0, Y: 0 } => "原点",
    { X: > 0, Y: > 0 } => "第一象限",
    { X: < 0, Y: > 0 } => "第二象限",
    _ => "其他位置"
};

特点:

底层原理

模式匹配的本质:语法糖 + 编译器静态分析 + IL分支重写
C#的模式匹配——无论是is, switch,还是各种property/tuple/relational pattern——底层都不是运行时魔法,而是:
编译器再编译期,把高层模式匹配语法“翻译”成if/else、类型检查、结构调用、常量比较、IL分支指令等基础结构
所有模式匹配在运行时没有额外runtime支撑,它只靠CLR原有能力:类型检查、常量比较、虚方法调用、IL分支;是纯语法层能力

基础原理

编译器生成的IL做了以下几个步骤

  1. 类型模式(type pattern)
if (obj is string s) {}

编译成IL的核心是

IL大概类似

ldloc obj
isinst [System.String]
stloc s
ldloc s
brtrue label_matched 

也就是说:编译器帮你做类型检查和赋值,没有任何runtime pattern 引擎

  1. 常量模式(constant pattern)
if (x is 3) {}

编译成

ldloc x
ldc.i4 3
beq label_matched 

本质就是一个整数比较,没有额外开销

  1. Relational Pattern (关系模式:< > <= >=
if (x is > 10)

编译器会把它拆成

ldloc x
ldc.i4 10
cat (or clt)
brtrue ...

本质就是比较指令

  1. 属性模式(Property Pattern)
if (p is { X: > 10, Y: 3})

编译器做的事情是

没有额外消耗,本质是

if (p != null && p.X > 10 && p.Y == 3)

这是完全等价的

  1. 元组模式(Tuple Pattern)
if ((x, y) is (>0, <0))

编译器展开成

if (x > 0 && y < 0)

IL只是多个比较与逻辑与的组合

switch模式匹配底层:决策树优化(Decision DAG)

这是模式匹配最核心的底层部分,C#编译器针对switch语句创建了一套优化决策树,让匹配尽可能少走分支

switch (shape)
{
    case Circle { Radius: > 10 }: ...
    case Circle { Radius: <= 10 }: ...
    case Rectangle { Width: > 0, Height: > 0 }: ...
}

编译器不会按上面写的顺序逐行if/else,而是

  1. 先分析shape的动态类型
  2. 同类型的case合并
  3. 进行属性访问排序
  4. 用DGA优化重复条件

最终生成一套最短路径的IL分支逻辑(落到isinst/getter/比较指令上)

这提升性能,没有运行时消耗,也不是解析式,而是编译期优化成“最佳分支路径”
微软Roslyn团队的论文称之为:Decision DAG(决策有向无环图)

可以理解为:编译期把你的模式匹配表达式转换成一个智能的、最优的、最短路径的if-else树

总结

模式匹配不会产生运行时反射、不会有动态dispatch,所有复杂的逻辑都在编译期完成

运行时就是执行普通代码

CLR层面没有 pattern matching opcode,没有类似match, patterncase_pattern的IL指令;所有模式匹配最终落在这几个IL指令上

模式匹配的复杂性都在C#编译器,而非.NET runtime或IL层

示例

  1. 基本类型模式匹配
// is 表达式模式
public static void CheckType(object obj)
{
    if (obj is int number)
    {
        Console.WriteLine($"这是一个整数: {number}");
    }
    else if (obj is string text)
    {
        Console.WriteLine($"这是一个字符串: {text}");
    }
    else if (obj is null)
    {
        Console.WriteLine("这是空值");
    }
}

// switch 表达式
public static string GetTypeDescription(object obj) => obj switch
{
    int i => $"整数: {i}",
    string s => $"字符串: {s}",
    double d => $"双精度浮点数: {d}",
    null => "空值",
    _ => "未知类型"
};
  1. 属性模式匹配
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string City { get; set; }
}

public static string GetPersonCategory(Person person) => person switch
{
    { Age: < 18 } => "未成年人",
    { Age: >= 18 and < 65 } => "成年人",
    { Age: >= 65 } => "老年人",
    _ => "未知"
};

// 多个属性组合匹配
public static string GetPersonInfo(Person person) => person switch
{
    { Age: >= 18, City: "北京" } => "北京成年人",
    { Age: < 18, City: "上海" } => "上海未成年人",
    { Name: var name, Age: > 60 } => $"{name}是老年人",
    _ => "其他"
};
  1. 元组模式
public static string RockPaperScissors(string first, string second) 
    => (first, second) switch
    {
        ("rock", "scissors") => "rock 赢了",
        ("rock", "paper") => "paper 赢了",
        ("paper", "rock") => "paper 赢了",
        ("paper", "scissors") => "scissors 赢了",
        ("scissors", "rock") => "rock 赢了",
        ("scissors", "paper") => "scissors 赢了",
        (_, _) when first == second => "平局",
        _ => "输入无效"
    };
  1. 位置模式匹配(解构模式)
public readonly struct 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 static string GetQuadrant(Point point) => point switch
{
    (0, 0) => "原点",
    var (x, y) when x > 0 && y > 0 => "第一象限",
    var (x, y) when x < 0 && y > 0 => "第二象限",
    var (x, y) when x < 0 && y < 0 => "第三象限",
    var (x, y) when x > 0 && y < 0 => "第四象限",
    _ => "在坐标轴上"
};
  1. 递归模式匹配
public abstract class Shape { }
public class Circle : Shape { public double Radius { get; set; } }
public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }
public class Triangle : Shape { public double Base { get; set; } public double Height { get; set; } }

public static double CalculateArea(Shape shape) => shape switch
{
    Circle { Radius: var r } => Math.PI * r * r,
    Rectangle { Width: var w, Height: var h } => w * h,
    Triangle { Base: var b, Height: var h } => 0.5 * b * h,
    _ => throw new ArgumentException("未知形状")
};
  1. 列表模式匹配(C# 11+)
public static string CheckList(int[] numbers) => numbers switch
{
    [] => "空数组",
    [1] => "只有一个元素1",
    [1, 2] => "包含1和2",
    [1, 2, .. var rest] => $"以1,2开头,剩余{rest.Length}个元素",
    [var first, .., var last] => $"第一个元素是{first},最后一个元素是{last}",
    _ => "其他数组"
};

// 使用示例
Console.WriteLine(CheckList(new int[] { }));           // 空数组
Console.WriteLine(CheckList(new int[] { 1 }));         // 只有一个元素1
Console.WriteLine(CheckList(new int[] { 1, 2 }));      // 包含1和2
Console.WriteLine(CheckList(new int[] { 1, 2, 3, 4 }));// 以1,2开头,剩余2个元素
  1. 关系模式匹配
public static string GetTemperatureDescription(double temp) => temp switch
{
    < -10 => "极寒",
    >= -10 and < 0 => "寒冷",
    >= 0 and < 15 => "凉爽",
    >= 15 and < 25 => "舒适",
    >= 25 and < 35 => "温暖",
    >= 35 => "炎热"
};

public static string GetGrade(int score) => score switch
{
    >= 90 and <= 100 => "优秀",
    >= 80 and < 90 => "良好",
    >= 70 and < 80 => "中等",
    >= 60 and < 70 => "及格",
    >= 0 and < 60 => "不及格",
    _ => "无效分数"
};