属性和位置模式

Tip

本文是已了解至少一种编程语言并正在学习 C# 的开发人员的 “基础知识 ”部分的一部分。 如果模式不熟悉,请从 模式匹配概述 开始。 有关完整的语言规则,请参阅语言参考中的 属性模式 和 位置模式 。

属性和位置模式都测试值的各个部分。 区别在于它们如何识别这些部分:

  • 属性模式命名要测试的属性或字段。
  • 位置模式按其顺序标识值。

析构公开一组有序的组件值。 元组已具有元素顺序;请参阅 析构元组。 对于另一种类型,方法Deconstruct定义公开哪些组件值及其顺序。

比较名称和职位

以下属性模式测试天气读数的两个命名属性,温度值以摄氏度为单位:

static bool IsHotAndHumid(WeatherReading reading) =>
    reading is { TemperatureC: > 30, HumidityPercent: > 70 };

sealed record WeatherReading(int TemperatureC, int HumidityPercent);

以下位置模式匹配一个信号值,后跟一个布尔值:

static string GetCrossingInstruction(
    PedestrianSignal signal, bool crossingIsClear) =>
    (signal, crossingIsClear) switch
    {
        (PedestrianSignal.Walk, true) => "Cross now",
        (PedestrianSignal.Walk, false) => "Wait for the crossing to clear",
        _ => "Wait for the walk signal"
    };

enum PedestrianSignal
{
    Stop,
    Walk
}

属性模式通过名称 TemperatureC 和 HumidityPercent 来标识其输入。 crossing 代码根据单独的 signal 和 crossingIsClear 值创建一个元组。 然后,元组模式按顺序识别这些值:首先是 signal,其次是 crossingIsClear。 位置模式非常适用,因为这个新建的元组只有两个值,而且它们的顺序在交叉判断中具有明确含义。

当成员名称帮助解释测试时,请选择属性模式。 对于类、结构和记录,属性模式通常更清晰。 当顺序已经使这些值的含义一目了然时,请选择位置模式。 位置模式最适用于元组,这些元组将多个相关值合并为一个具有固定顺序的值。

沿递归模式跟踪嵌套输入

属性和位置模式是 递归模式:它们将另一种模式应用于他们选择的每个属性、字段或位置。 所选值将成为该嵌套模式的输入。

在 IsHotAndHumid 中,reading 表达式是属性模式的输入。 C# 在匹配之前计算该表达式。 该模式从生成的对象获取两个值:

  • 关系模式 > 30 用于测试 TemperatureC 的值。
  • 关系模式 > 70 用于测试 HumidityPercent 的值。

外部类型测试是可选的,递归模式子句可以为空。 例如,空属性模式 { } 与任何非 null 计算值匹配。

属性模式和位置模式仅匹配求值结果为非空的值。 当 null 属于输入域时,请选择先检查非 null 值的递归模式:

static string DescribeNullableInput(object? value)
{
    if (value is not { } nonNullValue)
    {
        return "No value";
    }

    return nonNullValue switch
    {
        DateTime => "Date",
        string => "Text",
        _ => "Another type"
    };
}

输入表达式为 value. C# 会对其进行求值,而 { } 属性模式会在将其赋给 nonNullValue 之前先测试所得值是否为非 null。 然后,以下 switch 表达式可以测试多个可能的运行时类型。 它 DateTime 和 string 类型模式没有指定,因为该方法只需要标识每个类型,而不需要捕获其值。

当递归模式子句包含嵌套模式时,每个选定的属性、字段或位置将成为其嵌套模式的输入。

当输入表达式可以生成不同类型的时,可以在大括号前添加类型测试。 还可以使用成员路径测试嵌套属性:

static string DescribeDate(object? value) =>
    value switch
    {
        DateTime { Date.DayOfWeek:
            DayOfWeek.Saturday or DayOfWeek.Sunday } => "Weekend date",
        DateTime => "Weekday date",
        null => "No date",
        _ => "Not a date"
    };

value 是输入表达式。 C# 首先计算它,并测试结果值是否为 DateTime。 然后,Date 属性值将成为 DayOfWeek 成员访问的输入。 最后,该值 DayOfWeek 将成为测试两个常量逻辑模式的输入。 当外部值具有指定的类型且成员路径上所需的每个对象为非 null 时,匹配会成功。

将模式与分支语句进行比较

前面的 DescribeDate 方法将四个结果表示为模式:

static string DescribeDate(object? value) =>
    value switch
    {
        DateTime { Date.DayOfWeek:
            DayOfWeek.Saturday or DayOfWeek.Sunday } => "Weekend date",
        DateTime => "Weekday date",
        null => "No date",
        _ => "Not a date"
    };

以下方法使用一系列命令性分支语句生成相同的结果:

static string DescribeDateWithBranches(object? value)
{
    if (value is DateTime date)
    {
        if (date.DayOfWeek == DayOfWeek.Saturday ||
            date.DayOfWeek == DayOfWeek.Sunday)
        {
            return "Weekend date";
        }

        return "Weekday date";
    }

    if (value is null)
    {
        return "No date";
    }

    return "Not a date";
}

当多个分支测试值的类型和形状时,基于模式的版本将可能的结果一起保留。 命令性版本使每个测试和返回步骤都明确。 对于某一种情况,这两种形式中的任一种看起来都可能很相似。 随着相关分支的数量的增长,模式可以使替代方法更易于比较。

另见