模式匹配概述

Tip

本文是已了解至少一种编程语言并正在学习 C# 的开发人员的 “基础知识 ”部分的一部分。 如果你不熟悉编程,请先 学习入门 教程。 经验丰富的 C# 开发人员可以使用完整的语法和规则的 模式参考 。

来自另一种语言? C# 中的模式起到与 Java 的模式匹配和 Python 的 match case 类似的作用。 C# 在 switch 表达式、switch 语句和 is 表达式中使用模式。

模式匹配 将模式应用于表达式。 模式是测试输入表达式的条件。 模式输入是该表达式。 C# 计算输入表达式;结果是 计算的值。 该模式测试该值具有特定类型、等于特定值还是具有特定形状。 当模式匹配时,代码可以使用测试所学的信息,例如更具体的类型或从对象中提取的值。

可以在三个上下文中使用模式:

  • 在 is 运算符的右侧。
  • 在 switch 语句的 case 标签中。
  • 在 switch 表达式的一个分支中。

模式将一个已求值的输入与类型、常量或可包含嵌套模式的结构进行匹配测试。 布尔条件还可以比较两个独立计算的表达式,其值不是常量。 当任一窗体可以表达相同的测试时,请选择更易于阅读的窗体。

例如,以下方法使用 switch 表达式选择传递消息:

static string GetDeliveryMessage(Delivery? delivery) =>
    delivery switch
    {
        null => "No delivery was scheduled.",
        ExpressDelivery express => $"Express package {express.TrackingCode}",
        StandardDelivery { Days: <= 2 } => "Standard delivery arriving soon",
        _ => "Standard delivery"
    };

属性模式可以包括外部类型测试和嵌套模式,但每个属性模式都不需要这两种模式。 在 StandardDelivery { Days: <= 2 },外部模式接收 delivery 表达式并测试其类型。 然后,属性 Days 表达式将成为嵌套关系模式 <= 2的输入。

前面的 switch 表达式是输入表达式。 大括号内的每一行都是一个 开关臂。 模式显示在前面 =>,结果显示在它之后。 C# 会计算输入表达式,然后按文本顺序选择第一个模式匹配且其可选的 when 保护条件为 true 的分支。 可选 when 防护是在模式后写入的其他布尔条件。 前面的示例显示了以下模式:

  • null 是 常量模式。 它检查 delivery 表达式的计算结果是否为 null。
  • ExpressDelivery express 是包含两个部分的 声明模式 。 ExpressDelivery 是型式试验部分。 它测试评估的值是否为非 null 对象,其运行时类型与 ExpressDelivery该对象兼容。 express 是 变量指示:它声明一个名为 express 的变量,并将匹配到的 ExpressDelivery 对象赋给它。
  • StandardDelivery { Days: <= 2 } 以类型测试开始。 StandardDelivery 测试评估的值是否为该类型的非 null 对象。 大括号包含 属性模式。 Days 将属性命名为要检查的属性,因此 Days 属性表达式将成为递归模式的输入。 该 <= 2 部分是关系 模式,用于测试评估的值是否小于或等于 2。
  • _(下划线)是 弃元模式。 它匹配每个求值结果,包括 null。 由于前面的分支已经处理了 null、加急配送以及两天内送达的标准配送,因此最后这个分支处理所有剩余值。

没有警卫的 when 手臂 不受保护。 第一个示例中的所有手臂都不受保护。 如果前面一个无保护条件的分支匹配了后面某个分支可能匹配的每个求值结果,那么后面的分支就会被 覆盖。 被覆盖的模式是指这样一种模式:它所能匹配的每个输入值都已经被前面的 switch 分支匹配过了。 它永远无法匹配,因此编译器报告错误。 弃元分支(_)必须放在最后,因为它会匹配所有输入表达式。 带守卫条件的分支不会仅根据其模式就涵盖后面的分支,因为守卫条件可能是 false。

当 switch 表达式的各个分支处理了所有可能的输入表达式时,它就是 穷尽的。 第一个示例是穷尽的,因为它最后的丢弃分支会处理前面各分支未匹配到的所有情况。 编译器在检测到某个潜在输入值没有任何分支处理时会发出警告。 编译器无法证明每种模式组合的详尽性,但此诊断可帮助你编写正确的模式匹配代码。 有关详细的匹配、涵盖和穷尽性规则,请参阅模式参考。

使用 is 测试一个条件

当您需要一个布尔值测试时,请使用 is 运算符。 以下代码计算 delivery 表达式并应用声明模式 ExpressDelivery express。 当计算值为非 null 并且其运行时类型与 ExpressDelivery 兼容时,类型部分匹配。 当模式匹配时,其变量指定声明 express:

static void PrintTrackingCode(Delivery delivery)
{
    if (delivery is ExpressDelivery express)
    {
        Console.WriteLine($"Track express package {express.TrackingCode}");
    }
}

编译器跟踪代码读取本地变量之前是否分配了局部变量。 这种跟踪机制称为 确定赋值。 只有当该模式将匹配对象赋给 express 时,在 if 块内,条件才可以是 true。 因此,编译器知道 express 在该处肯定已被赋值。 代码可以安全地使用其 TrackingCode 属性。 你不需要单独再进行类型转换。 如果只需要类型测试且不需要声明变量,请使用 类型模式,例如 delivery is ExpressDelivery。

使用 is null 或 is not null 用于 null 检查。 这些模式不调用用户定义的 == 或 != 运算符。

static void PrintPackageStatus(Delivery? delivery)
{
    if (delivery is null)
    {
        Console.WriteLine("No package is available.");
    }
    else
    {
        Console.WriteLine("A package is ready to track.");
    }
}

有关更多 null 安全指南,请参阅 可为 null 的引用类型。

在语句和表达式之间进行选择

模式适用于 switch 的这两种形式:

  • 当每个匹配项应运行一个或多个语句时,请使用 switchstatement 语句。
  • 当每个匹配项都应产生一个值时,请使用 switch 表达式。

以下 switch 语句报告传送状态更新。 加急配送分支写入两条消息,因此使用语句很自然:

static void PrintDeliveryUpdate(Delivery? delivery)
{
    switch (delivery)
    {
        case null:
            Console.WriteLine("No delivery was scheduled.");
            break;
        case ExpressDelivery express:
            Console.WriteLine($"Express delivery {express.TrackingCode} is ready.");
            Console.WriteLine("Notify the priority desk.");
            break;
        case StandardDelivery standard:
            Console.WriteLine($"Standard delivery arrives in {standard.Days} days.");
            break;
        default:
            Console.WriteLine("Another delivery type is scheduled.");
            break;
    }
}

每个 case 模式都应用于 delivery 表达式。 匹配分支可以在 break 退出 switch 之前运行任意数量的语句。 该 default 部分处理之前的情况不匹配的任何内容。

下面的 switch 表达式替换了一个用于赋出单个结果的 if / else if 链:

static string GetStatusMessage(StandardDelivery delivery) =>
    delivery.Days switch
    {
        0 => "Delivered today",
        1 => "Arriving tomorrow",
        <= 3 => "Arriving soon",
        _ => "In transit"
    };

开关表达式简洁,因为每个臂具有相同的目的:生成返回的值。 当分支执行操作时使用 switch 语句,当分支计算一个结果时使用 switch 表达式。

模式类别

C# 包括常见类型的数据测试模式:

模式类别 它测试的内容
声明、常量和 var 模式 运行时类型、特定常量值或要捕获的任何值
类型模式 无须声明变量的运行时类型
属性和位置模式 属性、字段或析构值
关系模式、逻辑模式和括号模式 比较和组合,例如 and, or和 not
列表和切片模式 支持的序列的值和形状
弃元模式和弃元 任何剩余值,或代码有意忽略的值

链接的基础知识文章说明了何时选择每个类别。 有关完整的语法和示例,请参阅 模式参考。

另见