11 模式和模式匹配

11.1 常规

模式可与运算符(is)、switch_statement(§13.8.3)和switch_expression(§12.12)一起使用,以描述要对其比较传入数据的数据的形状。 模式可以嵌套,其中部分数据与 子模式匹配。

针对多个上下文中的值测试模式:

  • 在switch_statement中,针对switch_statement的selector_expression测试switch_label模式。
  • 使用 is-pattern 运算符时,右侧的 模式 针对左侧的表达式进行测试。
  • 在switch_expression中,switch_expression_arm模式针对switch_expression左侧的表达式进行测试。
  • 在嵌套上下文中,根据模式窗体,针对从属性、字段或从其他输入值编制索引的值测试 子模式 。

对其测试模式的值称为 模式输入值。

如果匹配的任何输入值都与其中一个成员Q匹配P,则模式P由一组未保护模式Q进行子求和。

在 switch 语句(§13.8.3)中,如果事例的模式被前面的未保护(§13.8.3)事例所子化,则为错误。 在开关表达式(§12.12)中,如果switch_expression_arm的模式被上述一组不受保护的switch_expression_arm模式子化,则这是一个错误。

如果对于每个可能的输入值,一组模式是详尽的,则集中的某些模式适用。 当实现检测到一组模式并不详尽时,它应发出警告。

11.2 模式窗体

11.2.1 常规

模式可能具有以下形式之一:

pattern
    : logical_pattern
    ;

primary_pattern
    : parenthesized_pattern
    | declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    | type_pattern
    | relational_pattern
    | list_pattern
    | slice_pattern
    ;

parenthesized_pattern
    : '(' pattern ')'
    ;

生产 '(' pattern ')' 允许将模式括在括号中,以强制使用 logical_pattern之一组合的模式之间的计算顺序。

如果输入可以语法地识别为 constant_pattern 和 positional_pattern ,则应选择 constant_pattern 。

某些 模式可能会导致局部变量的声明。

每个模式窗体定义可应用于该模式的输入值的类型集。 如果P模式是模式可能匹配的类型之一,则模式TT类型。 如果某个模式出现在程序中以匹配类型的P模式输入值(§11.1T如果P不适用T)。

示例:以下示例生成编译时错误,因为编译时类型 v 为 TextReader. 类型的 TextReader 变量永远不能具有与以下项兼容的 string值:

TextReader v = Console.In; // compile-time type of 'v' is 'TextReader'
if (v is string) // compile-time error
{
    // code assuming v is a string
}

但是,由于编译时类型 v 为 object,因此以下代码不会生成编译时错误。 类型的 object 变量可以具有与引用兼容的 string值:

object v = Console.In;
if (v is string s)
{
    // code assuming v is a string
}

end 示例

每个模式窗体定义模式 在运行时匹配 值的一组值。

未指定模式匹配期间对操作和副作用的Deconstruct评估顺序(对System.Runtime.CompilerServices.ITuple成员的调用、属性访问和调用)。

11.2.2 声明模式

declaration_pattern用于测试某个值是否具有给定类型,如果测试成功,可以选择在该类型的变量中提供该值。

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : discard_designation
    | single_variable_designation
    ;
discard_designation
    : '_'
    ;
single_variable_designation
    : identifier
    ;

如果discard_designation和single_variable_designation替代项均适用,则选择前者时,应识别simple_designation。

注意:由于 simple_designation的替代项的排序,ANTLR 会自动做出指定的选择。 尾注

如果 类型 为可以为 null 的值类型(§8.3.12)或可为空引用类型(§8.9.3),则为编译时错误。

值的运行时类型使用 is-type 运算符中指定的相同规则(§12.15.12.1)针对模式中的类型进行测试。 如果测试成功,则模式 与该值匹配 。

注意:当两者都有效时,is-type 表达式 e is T 和声明模式 e is T _ 是等效的。 尾注

给定模式输入值(§11.1) e,如果 simple_designation 是 discard_designation,则表示放弃(§9.2.9.2), e 的值不会绑定到任何内容。 否则,如果 simple_designation 是 single_variable_designation,则会引入由给定标识符命名的给定类型的局部变量(§9.2.9)。 当模式与值匹配时,向该局部变量分配模式输入值的值。

注意:这种_的处理方式不同于作为_(§11.2.7)编写的独立模式(§11.2.7):在后一种情况下,_隐藏作用域内常量或名称的类型(如果有)。 尾注

E如果存在标识转换、隐式或显式引用转换、装箱转换、取消装箱转换或隐式或显式可为 null 的值类型转换,或者是否TE为开放类型(T),则类型表示与该类型ET。 声明模式命名类型T适用于与模式兼容的每种类型EE(T)。 如果声明模式命名类型 T 与模式输入值(§11.1)匹配,则为编译时错误,其静态类型 E 与模式不兼容 T。

注意:在检查可能为结构或类类型的类型时,对开放类型的支持最有用,并且要避免装箱。 尾注

示例:声明模式可用于执行引用类型的运行时类型测试,并替换成语

var v = expr as Type;
if (v != null) { /* code using v */ }

稍微简洁一点

if (expr is Type v) { /* code using v */ }

end 示例

示例:声明模式可用于测试可为 null 类型的值:如果值不为 null 且Nullable<T>T为或某些基类型或接口T2 id,则T2类型(或装箱T)的值与类型模式T匹配。 例如,在代码片段中

int? x = 3;
if (x is int v) { /* code using v */ }

语句的条件if在true运行时,变量v保存块内类型的3值int。 块之后,变量 v 位于范围内,但未明确分配。 end 示例

11.2.3 常量模式

constant_pattern用于针对给定常量值测试模式输入值(§11.1)的值。

constant_pattern
    : constant_expression
    ;

如果存在从常量表达式P到该类型的TT隐式转换,或者T常System.Span<char>量表达式的类型或System.ReadOnlySpan<char>常量表达式P的类型不是文本,则常量模式P适用于类型stringnull。

对于常量模式 P,其 转换的值 为

  • 如果模式输入值的类型是整型类型或枚举类型,则模式的常量值转换为该类型;否则
  • 如果模式输入值的类型是整型类型或枚举类型的可为 null 版本,则模式的常量值转换为其基础类型;否则
  • 模式常量值的值。

给定模式输入值 e 和具有转换值 v 的常量模式P,

  • 如果 e 具有整型类型或枚举类型,或其中一种可为 null 的形式,并且 v 具有整型类型,则模式P;否则
  • 如果 e 的类型System.Span<char>或 System.ReadOnlySpan<char>,并且 v 是常量字符串,并且 v 没有常量值null,则模式P与返回true的值 eSystem.MemoryExtensions.SequenceEqual<char>(e, System.MemoryExtensions.AsSpan(v))匹配;否则
  • 如果返回,则模式P。

示例: switch 以下方法中的语句在其事例标签中使用五个常量模式。

static decimal GetGroupTicketPrice(int visitorCount)
{
    switch (visitorCount) 
    {
        case 1: return 12.0m;
        case 2: return 20.0m;
        case 3: return 27.0m;
        case 4: return 32.0m;
        case 0: return 0.0m;
        default: throw new ArgumentException(...);
    }
}

end 示例

11.2.4 Var 模式

var_pattern 匹配每个值。 也就是说,具有 var_pattern 的模式匹配操作始终成功。

var_pattern适用于每种类型。

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation (',' designation)*
    ;

给定模式输入值(§11.1)e,如果指定为discard_designation,则表示放弃(§9.2.9.2),e 的值不绑定到任何内容。 (尽管具有该名称的声明变量可能位于该点的范围中,但在此上下文中看不到该命名变量。否则,如果指定为single_variable_designation,在运行时,e 的值将绑定到该名称的新引入局部变量(§9.2.9),其类型为 e 的静态类型,并将模式输入值分配给该局部变量。

如果名称var绑定到使用var_pattern的类型,则为错误。

如果指定为tuple_designation,则模式等效于表单指定(var(§11.2.5),... ) 其中 ,指定项是在 tuple_designation中找到的。 例如,模式 var (x, (y, z)) 等效于 (var x, (var y, var z))。

11.2.5 位置模式

positional_pattern检查输入值是否不是null,从中提取一系列值,并将每个提取的值与相应的子模式匹配。 通过以下三种方式之一提取这些值:通过将输入视为元组、调用 Deconstruct 方法或通过索引输入 System.Runtime.CompilerServices.ITuple来提取值。

注意:此处的使用 Deconstruct 不同于 §12.7 中定义的源级析构转换。 尾注

positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern (',' subpattern)*
    ;
subpattern
    : pattern
    | subpattern_name ':' pattern
    ;
subpattern_name
    : identifier
    | subpattern_name '.' identifier
    ;

让 n 成为括号之间出现的 子模式数。 通过按顺序应用以下情况,在编译时选择匹配策略;使用满足条件的第一个事例,不考虑其余情况。 选择事例后,将提交该策略:报告该案例内声明的任何编译时错误,并且匹配不会流向后续案例。

  1. 元组窗体。 如果省略 类型 并且输入值的静态类型为元组类型(§8.3.11),或者输入值为元组文本(§12.8.6),则本例适用。 如果 n 不等于该元组类型的 arity,则为编译时错误。 在运行时,每个元组元素都与相应的 子模式匹配;如果所有这些成功,则匹配成功。 如果任何 子模式 具有 标识符,该 标识符 应将元组元素命名为元组类型中相应位置。
  2. 析构形式。 否则,如果存在任一 类型 或省略 类型 ,并且输入值的静态类型包含可访问 Deconstruct 的方法(§12.7),则此情况适用。 如果存在类型,则让 D 键入;否则,请让 D 成为输入值的静态类型。 使用与解构声明相同的重载解析规则从 Deconstruct 中选择一种方法,并要求其参数数out等于 n;如果不存在此类方法,则为编译时错误。 如果存在类型,则如果输入值的静态类型与类型不兼容(§11.2.2),则为编译时错误;在运行时,输入值将针对类型进行测试,如果测试失败,则位置模式匹配失败。 否则,输入值将转换为 D,并使用接收其Deconstruct参数的新变量调用所选out方法。 每个接收的值都与相应的 子模式匹配,如果全部成功,匹配会成功。 如果任何 子模式 具有 标识符,该 标识符 应将参数命名为相应的位置 Deconstruct。
  3. ITuple 窗体。 否则,如果省略 类型 ,则没有 子模式 具有 标识符,并且输入值的静态类型为 object, System.Runtime.CompilerServices.ITuple或者具有隐式引用转换的类型 System.Runtime.CompilerServices.ITuple,则此情况适用。 在运行时,输入值将测试为非null 实例 System.Runtime.CompilerServices.ITuple;如果该测试失败,则位置模式匹配失败。 否则,将读取该值 Length 的属性,如果该值不等于 n,则位置模式匹配将失败。 否则,对于每个 i 从 1 到 n,使用 i = 1 为输入值编制索引获取的值将与第 i 个 子模式匹配,如果所有这些值都成功,则匹配成功。
  4. 否则,不适用任何情况, positional_pattern 是编译时错误。

未指定运行时匹配子模式的顺序,并且失败的匹配可能不会尝试匹配所有子模式。

示例:此处,我们将解构表达式结果,并将结果值与相应的嵌套模式匹配:

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

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);
}

end 示例

示例:元组元素和析构参数的名称可用于位置模式,如下所示:

var numbers = new List<int> { 10, 20, 30 };
if (SumAndAverage(numbers) is (Sum: var sum, Average: var average))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}; average is {average}");
}
else
{
    // Note: sum and average are in scope here, but not definitely assigned
    Console.WriteLine("No numbers provided to compute sum and average.");   
}

static (double Sum, double Average)? SumAndAverage(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return count == 0 ? null : (sum, sum / count);
}

生成的输出为

Sum of [10 20 30] is 60; average is 20

end 示例

11.2.6 属性模式

property_pattern检查输入值是否不null,并且以递归方式匹配通过使用可访问属性或字段提取的值。

property_pattern
    : type? property_subpattern simple_designation?
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;

如果property_pattern的任何子模式不包含subpattern_name,则为错误。

如果 类型 为可以为 null 的值类型(§8.3.12)或可为空引用类型(§8.9.3),则为编译时错误。

注意:null 检查模式超出了一个普通的属性模式。 若要检查字符串 s 是否为非 null,可以编写以下任一形式:

#nullable enable
string s = "abc";
if (s is object o) ...  // o is of type object
if (s is string x1) ... // x1 is of type string
if (s is {} x2) ...     // x2 is of type string
if (s is {}) ...

声明示例x2if (s is var x2)类似于推断变量类型,但属性模式保证为x2非 null。 尾注

给定表达式 e 与模式类型{子模式}的匹配,如果表达式 e 与类型 T 指定的类型不兼容(§11.2.2),则为编译时错误。 如果该类型不存在,则假定该类型为 e 的静态类型。 每个subpattern_name在其子模式的左侧出现,应指定一个可访问的可读属性或 T 字段。如果property_pattern simple_designation存在,则声明类型为 T 的模式变量。

在运行时,表达式针对 T 进行测试。如果此作失败,则属性模式匹配失败,结果为 false。 如果成功,则读取每个 property_subpattern 字段或属性,并且其值与相应的模式匹配。 整个匹配 false 的结果仅当其中任何一项的结果为 false时。 未指定子模式的匹配顺序,失败的匹配可能无法在运行时测试所有子模式。 如果匹配成功,并且property_pattern的simple_designation为single_variable_designation,则会为声明的变量分配匹配的值。

property_pattern可用于与匿名类型进行模式匹配。

subpattern_name可以引用嵌套成员。 在这种情况下,每个名称查找的接收方都是上一个成员 T₀ 的类型,从property_pattern的输入类型开始。 如果 T 是可以为 null 的类型, 则 T₀ 是其基础类型,否则 T₀ 等于 T。例如,窗体 { Prop1.Prop2: pattern } 的模式与 { Prop1: { Prop2: pattern } }..

注意:当 T 是可以为 null 的值类型或引用类型时,这将包括 null 检查。 此 null 检查意味着可用的嵌套属性将是 T₀ 的属性,而不是 T 的属性。允许重复成员路径时,模式匹配的编译可以利用模式的常见部分。 尾注

示例:

var o = ...;
if (o is string { Length: 5 } s) ...

end 示例

示例:运行时类型检查和变量声明可以添加到属性模式,如下所示:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));            // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,
    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),
    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

生成的输出为

Hello
Hi!
12345
abc

end 示例

11.2.7 放弃模式

每个表达式都与放弃模式匹配,这会导致放弃表达式的值。

discard_pattern
    : '_'
    ;

如果语法上下文允许模式,如果令牌_将解析为可访问的常量或类型simple_name(§12.8.4),则_不会被视为discard_pattern。 相反:

  • 如果 _ 解析为可访问的常量,则 _ 解释为常量表达式为该常量 constant_pattern (§11.2.3)。
  • 如果 _ 解析为类型,则在运算符的右侧 is ,构造 relational_expressionis _ 解释为针对该类型的 is-type 运算符(§12.15.12.1)测试。 在承认模式的任何其他语法上下文中,对类型的裸_解析本身不是有效的模式;但是,_可能显示为declaration_pattern的类型(例如_ x)或其他显式命名类型的模式形式。

此规则与在引入放弃模式之前定义为 _ 类型或标识符的代码保持向后兼容性。 如果 _ 解析为可访问常量或类型以外的任何内容(例如局部变量、参数、字段或方法),则规则不适用,并且 _ 仍为 discard_pattern。

注意:这类似于 var 中的规则,但范围_内常量或类型会导致_解释为对该声明的引用,而不是生成错误。 尾注

如果应用上述规则后,令牌_仍是discard_pattern,则discard_pattern显示为表单relational_expression模式的整个 relational_expressionis或作为switch_label的整个模式显示编译时错误。 但是,discard_pattern可能显示为封闭模式的子模式(例如,作为positional_pattern或property_pattern的子模式)。

注意:在这些情况下,若要匹配任何表达式,请使用带放弃var _。 尾注

示例:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));
Console.WriteLine(GetDiscountInPercent(null));
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

生成的输出为

5.0
0.0
0.0

此处,使用放弃模式来处理 null 任何没有相应枚举成员的 DayOfWeek 整数值。 这可以保证 switch 表达式处理所有可能的输入值。 end 示例

示例:下面说明了命名_的作用域内常量如何更改表达式中 _ arm 的解释switch。 在 WithoutUnderscore中, _ arm 是 一个discard_pattern 并匹配任何值。 在WithUnderscore内常量__中,将 arm 解释为仅与值匹配0。

static string WithoutUnderscore(int n) => n switch
{
    1 => "one",
    _ => "other",
};

static string WithUnderscore(int n)
{
    const int _ = 0;
    return n switch
    {
        1 => "one",
        _ => "zero",
        var x => "other: " + x,
    };
}

end 示例

11.2.8 类型模式

type_pattern用于测试模式输入值(§11.1)是否具有给定类型。

type_pattern
    : type
    ;

类型模式命名类型T适用于与E模式E 每种类型T(§11.2.2)。

值的运行时类型使用 is-type 运算符中指定的相同规则(§12.15.12.1)针对类型进行测试。 如果测试成功,则模式与该值匹配。 如果类型为可以为 null 的类型 ,则为编译时错误。 此模式形式永远不会与值 null 匹配。

11.2.9 关系模式

relational_pattern用于根据常量值对模式输入值(§11.1)进行关系测试。

relational_pattern
    : '<'  relational_expression
    | '<=' relational_expression
    | '>'  relational_expression
    | '>=' relational_expression
    ;

relational_pattern中的relational_expression需要计算为常量值。

关系模式支持关系运算符、以及<支持具有相同类型的两个操作数的二元关系运算符的所有内置类型:<=、、>、>=、sbyte、byteshortushortintuintlongulongchar、float、 double和枚举。 decimalnintnuint

如果使用类型两个操作数定义合适的内置二进制关系运算符,或者存在从常量表达式类型到常量表达式类型的显式可为 null 或取消装箱转换,则relational_patternTT类型T。

如果表达式的计算结果 double.NaN为 null float.NaN常量,则为编译时错误。

当输入值的类型定义了合适的内置二进制关系运算符时,该运算符的计算将被视为关系模式的含义。 否则,输入值将使用显式可为 null 或取消装箱转换转换为常量表达式的类型。 如果不存在此类转换,则为编译时错误。 如果转换失败,则模式被视为不匹配。 如果转换成功,则模式匹配操作的结果是计算转换后的输入的e «op» v表达式e的结果,«op» 是关系运算符,并且v是常量表达式。

示例:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(double.NaN));
Console.WriteLine(Classify(2.4));

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

生成的输出为

Too high
Unknown
Acceptable

end 示例

11.2.10 逻辑模式

logical_pattern用于否定模式匹配的结果,或者使用联合()或解禁and(or)合并多个模式匹配的结果。

logical_pattern
    : disjunctive_pattern
    ;

disjunctive_pattern
    : disjunctive_pattern 'or' conjunctive_pattern
    | conjunctive_pattern
    ;

conjunctive_pattern
    : conjunctive_pattern 'and' negated_pattern
    | negated_pattern
    ;

negated_pattern
    : 'not' negated_pattern
    | primary_pattern
    ;

not、 and和 or 统称为 模式运算符。

如果被否定的模式不匹配, 则negated_pattern 匹配,反之亦然。 conjunctive_pattern需要两种模式才能匹配。 disjunctive_pattern需要任一模式才能匹配。 与其&&语言运算符对应项||不同, andor也不是短路运算符。

这是模式变量在模式not运算符下or声明的编译时错误。

注意:由于模式变量既notor不能也不能生成明确的赋值,所以在这些位置声明一个是错误的。 尾注

在conjunctive_pattern中,第二个模式的输入类型因第一个模式的类型缩小要求而and。 模式的P定义如下:

  • 如果是 P 类型模式,则 窄类型 是类型模式的类型。
  • 否则,如果是 P 声明模式,则 窄类型 是声明模式的类型。
  • 否则,如果 P 递归模式提供显式类型,则 窄类型 为该类型。
  • 否则,如果P通过ITuple(§11.2.5)中的规则进行匹配,则缩小的类型为类型System.ITuple。
  • 否则,如果P常量模式中常量不是 null 常量,而表达式没有常量表达式转换为输入类型,则窄类型是常量的类型。
  • 否则,如果P常量表达式没有常量表达式转换为输入类型的关系模式,则窄类型是常量的类型。
  • 否则,如果 P 为 or 模式, 则窄类型 是子模式的 窄类型的常见类型 (如果存在此类常见类型)。 为此,通用类型算法只考虑标识、装箱和隐式引用转换,并考虑一系列 or 模式的所有子模式(忽略括号模式)。
  • 否则,如果 P 为 and 模式,则 窄类型 为右模式的 窄类型 。 此外,左模式的 窄类型 是右模式的 输入类型 。
  • 否则,缩小的类型P为P“输入类型”。

注意:如语法所示, not 优先于 and具有优先 or顺序。 这可以通过括号显式指示或重写。 尾注

当模式出现在右侧is时,模式的范围由语法决定;因此,模式运算符andor和not模式内绑定比逻辑运算符&&更紧密,||并且!与模式外部绑定。

示例:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(-100));
Console.WriteLine(Classify(5.7));

static string Classify(double measurement) => measurement switch
{
    < -40.0 => "Too low",
    >= -40.0 and < 0 => "Low",
    >= 0 and < 10.0 => "Acceptable",
    >= 10.0 and < 20.0 => "High",
    >= 20.0 => "Too high",
    double.NaN => "Unknown",
};

生成的输出为

High
Too low
Acceptable

end 示例

示例:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    3 or 4 or 5 => "spring",
    6 or 7 or 8 => "summer",
    9 or 10 or 11 => "autumn",
    12 or 1 or 2 => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date),
      $"Date with unexpected month: {date.Month}."),
};

生成的输出为

winter
autumn
spring

end 示例

示例:

object msg = "msg";
object obj = 5;
bool flag = true;

// This is parsed as: (msg is (not int) or string)
bool result = msg is not int or string;
Console.WriteLine($"msg (\"msg\"): msg is not int or string: {result}");

// This is parsed as: (obj is (int or string)) && flag
result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int || obj is string && flag: {result}");

flag = false;
// This is parsed as: (obj is (int or string)) && flag
result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int || obj is string && flag: {result}");

生成的输出为

msg ("msg"): msg is not int or string: True
obj (5), flag (true): obj is int or string && flag: True
obj (5), flag (true): obj is int || obj is string && flag: True
obj (5), flag (false): obj is int or string && flag: False
obj (5), flag (false): obj is int || obj is string && flag: True

end 示例

11.2.11 列表模式

list_pattern与列表或数组中的元素序列匹配。

list_pattern
    : list_pattern_clause simple_designation?
    ;

list_pattern_clause
    : '[' (pattern (',' pattern)* ','?)? ']'
    ;

list_pattern与可计数的任意类型(§18.1)以及可编制索引(§18.1)兼容,它具有一个可取索引器,该索引器采用Index一个参数或具有单个int参数的可访问索引器。 如果两个索引器都存在,则首选前者。 (有关隐式索引支持的详细信息,请参阅 §18.4.2 。

窗体 expr is [1, 2, 3] 的模式等效于以下代码:

expr.Length is 3
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Index(1, fromEnd: false)] is 2
&& expr[new Index(2, fromEnd: false)] is 3

示例:

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3 and not 7]);  // True

end 示例

放弃模式(§11.2.7)与任何单个元素匹配。

示例:

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [_, var second, _])
{
    Console.WriteLine($"The second element is {second}.");
}

end 示例

11.2.12 切片模式

slice_pattern放弃零个或多个元素。 它只能直接在 list_pattern_clause中使用,然后在该子句中最多只使用一次。

slice_pattern
    : '..' pattern?
    ;

没有子模式 的slice_pattern 与任何与 list_pattern兼容的类型兼容。 具有子模式的slice_pattern与可计数的任意类型(§18.1)以及可切片(§18.1)兼容,它具有一个可访问的索引器,该索引器采用Range参数或具有两Slice个参数的可访问int方法。 如果两者都存在,则首选前者。 (有关隐式索引支持的详细信息,请参阅 §18.4.2 。

slice_pattern的行为类似于适当的放弃;也就是说,不应对此类模式进行测试。 相反,它只影响其他节点,即长度和索引器。 例如,表单 expr is [1, .. var s, 3] 模式等效于以下代码(如果通过显式 Index 和支持 Range 兼容):

expr.Length is >= 2
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Range(new Index(1, fromEnd: false), new Index(1, fromEnd: true))] is var s
&& expr[new Index(1, fromEnd: true)] is 3

slice_pattern的输入类型是基础this[Range]或Slice方法的返回类型,有两个例外:对于 strings 和数组, string.SubstringRuntimeHelpers.GetSubArray应分别使用。

示例:切片模式可用于仅在输入序列的开头或/和末尾匹配元素。

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

end 示例

示例:子模式可以嵌套在切片模式中:

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron"); // output: Message apron doesn't match.

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });     // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0]
        ? "valid" : "not valid";
    Console.WriteLine(result);
}

end 示例