使用表达式、switch 语句和 is 将输入表达式与任意数量的特征匹配。C# 支持多种模式,包括声明、类型、常量、关系、属性、列表、var 和弃元。可以使用布尔逻辑关键字 and、or 和 not 组合模式。

以下 C# 表达式和语句支持模式匹配:

  • is表达式
  • switch 语句
  • switch 表达式

在这些构造中,可将输入表达式与以下任一模式进行匹配:

  • 声明模式:用于检查表达式的运行时类型,如果匹配成功,则将表达式结果分配给声明的变量。
  • 类型模式:用于检查表达式的运行时类型。
  • 常量模式:测试表达式结果是否等于指定的常量。
  • 关系模式:用于将表达式结果与指定常量进行比较。
  • 逻辑模式:测试表达式是否与模式的逻辑组合匹配。
  • 属性模式:测试表达式的属性或字段是否与嵌套模式匹配。
  • 位置模式:用于解构表达式结果并测试结果值是否与嵌套模式匹配。
  • var 模式:用于匹配任何表达式并将其结果分配给声明的变量。
  • 弃元模式:用于匹配任何表达式。
  • 列表模式:测试元素序列是否与相应的嵌套模式匹配。在 C# 11 中引入。

逻辑、属性、位置和列表模式都是递归模式。也就是说,它们可包含嵌套模式。

声明和类型模式

使用声明和类型模式检查表达式的运行时类型是否与给定类型兼容。借助声明模式,还可声明新的局部变量。当声明模式与表达式匹配时,将为该变量分配转换后的表达式结果,如以下示例所示:

object ZM = "I am Time - Loser!";
if ( ZM is string zfc )
    {
        Console . WriteLine( zfc . ToLower ( ) ); // 输出:i am time - loser!
    }

类型为 T 的声明模式在表达式的结果为非空且下列条件之一为真时匹配表达式:

  • 表达式结果的运行时类型是 T。
  • 类型 T 是一个 ref 结构类型,有一个从表达式到 T 的标识转换。
  • 表达式结果的运行时类型派生自类型 T,实现接口 T,或者存在从它到 T 的另一个隐式引用转换。下面的例子演示了这个条件为真时的两种情况:

    static void Main(string[] args)
      {
          int [ ] Zhss = [ 10 , 20 , 30 ];
          Console . WriteLine ( FF获取源标签 ( Zhss ) );
    
          List<Char> Zms = ['a' , 'b' , 'c' , 'd'];
          Console . WriteLine ( FF获取源标签 ( Zms ) );
      }
    
    static int FF获取源标签<T> ( IEnumerable<T> 源 )
      {
          return 源 switch
          { Array => 1 , ICollection<T> => 2 , _ => 3 };
      }

    在前面的例子中,第一次调用 FF获取源标签 方法时,第一个模式与参数值匹配,因为参数的运行时类型 int [ ] 源自数组类型。在第二次调用 FF获取源标签 方法时,参数的运行时类型 List < T > 不是从 Array 类型派生的,而是实现了 ICollection < T > 接口。

  • 表达式结果的运行时类型是一个底层类型 T 的可空值类型。
  • 存在从表达式结果的运行时类型到类型 T 的 boxed 或 unboxed 转换。

下面的例子演示了最后两个条件:

int? i可能null = 7;
int y = 23;
object i封箱y = y;
if ( i可能null is int a && i封箱y is int b )
    {
        Console . WriteLine ( a + b ); // 输出:30
    }

如果你只想检查表达式的类型,可以用 “_” 代替变量名,如下面的例子所示:

public abstract class Lei交通工具
    {
    }

public class Lei轿车 : Lei交通工具
    {
    }

public class Lei卡车 : Lei交通工具
    {
    }

public static class Lei交通费计算器
    {
        public static decimal FF计算交通费 ( this Lei交通工具 GJ )
            {
                return GJ switch
                    {
                        Lei轿车 _ => 3.15m,
                        Lei卡车 _ => 7.25m,
                        null => throw new ArgumentNullException ( nameof ( GJ ) ),
                        _ => throw new ArgumentException ( "未知交通工具类型" , nameof ( GJ ) )
                    };
            }
    }

为此,可以使用类型模式,如下面的例子所示:

public static decimal FF计算交通费 ( this Lei交通工具 GJ ) => GJ switch
    {
        Lei轿车 => 2.00m,
        Lei卡车 => 7.50m,
        null => throw new ArgumentNullException ( nameof ( GJ ) ),
        _ => throw new ArgumentException ( "未知交通工具" , nameof ( GJ ) ),
    };

与声明模式一样,当表达式的结果是非空且其运行时类型满足上述任何条件时,类型模式与表达式匹配。

要检查是否为非 null,可以使用 “ - null ” 常量模式,如下面的例子所示:

if ( zfc输入 is not null)
{
    // ……
}

常数模式

常量模式是 “ == ” 的另一种语法,如果右边的操作数是常量。你可以使用常量模式来测试表达式的结果是否等于指定的常量,如下面的例子所示:

public static decimal FF获取团体票价 ( int 访客数 ) => 访客数 switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException ( $"不支持的访客数量: {访客数}" , nameof ( 访客数 ) ),
};

在常量模式中,可以使用任何常量表达式,例如:

  • 整数或浮点数值字面量
  • 一个字符
  • 字符串字面量。
  • 布尔值 true 或 false
  • 枚举值
  • 声明的常量字段或局部变量的名称
  • null

表达式必须是可以转换为常量类型的类型,但有一个例外:在 C# 11 和更高版本中,类型为 Span < char > 或 ReadOnlySpan < char > 的表达式可以与常量字符串进行匹配。

使用常量模式检查是否为 null,如下面的例子所示:

if (input is null)
    {
        return;
    }

编译器保证在计算表达式 x 为 null 时不会调用用户重载的相等运算符 “ == ”。

你可以使用 “ - null ” 常量模式来检查非 null,如下面的例子所示:

if (input is not null)
{
    // ……
}

关系模式

可以使用关系模式将表达式的结果与常量进行比较,如下面的例子所示:

static void Main ( string [ ] args )
    {
        Console . WriteLine ( FF腰围 ( 65 ) );
        Console . WriteLine ( FF腰围 ( 106 ) );
        Console . WriteLine ( FF腰围 ( 94 ) );
        Console . WriteLine ( FF腰围 ( double . NaN ) );
    }

static string FF腰围 ( double 腰围 ) => 腰围 switch
    {
        <= 70 => "太细了",
        >= 100 => "太粗了",
        double . NaN => "未知",
        _ => "正好"
    };

在关系模式中,可以使用任何关系操作符 <、>、<= 或 >=。关系模式的右侧必须是常量表达式。常量表达式可以是整数、浮点数、字符或枚举类型。

要检查表达式的结果是否在某个范围内,可以将其与连接词和模式进行匹配,如下面的例子所示:

static string FF季节 ( DateTime 日期 ) => 日期 . Month switch
    {
        >= 3 and < 6 => "春",
        >= 6 and < 9 => "夏",
        >= 9 and < 12 => "秋",
        12 or ( >= 1 and < 3 ) => "冬",
        _ => throw new ArgumentOutOfRangeException ( nameof ( 日期 ) , $"日期的月份不正确:{日期 . Month}。" )
    };

如果表达式的结果为 null,或者通过可空转换或 unbox 转换未能转换为常量的类型,则关系模式与表达式不匹配。

逻辑模式

使用 not、and 和 or 组合符可以创建如下逻辑模式:

  • 否定非模式,当被否定的模式与表达式不匹配时,匹配该表达式。下面的例子展示了如何对常量 null 模式求反,以检查表达式是否为非 null:

    if ( 输入 is not null )
      {
          // ……
      }
  • 当两个模式都匹配一个表达式时,匹配该表达式的合取词和模式。下面的例子展示了如何结合关系模式来检查一个值是否在某个范围内(检查一个学生的分数,150 满分,或许做出了附加题超出满分):

    static string FF分数范围 ( double 分数 ) => 分数 switch
      {
          < 70 => "极低",
          >= 70 and < 90 => "低",
          >= 90 and < 110 => "一般",
          >= 110 and < 135 => "高",
          >= 135 and <= 150 => "极高",
          > 150 => "太高了",
          double . NaN => "未知"
      };
  • 析取 or 模式,当其中一个模式与某个表达式匹配时,就匹配该表达式,如上节中的“季节”例子所示。可以在模式中重复使用模式组合器。

检查的优先级和顺序

模式组合器根据表达式的绑定顺序进行排序,如下所示:

  1. not
  2. and
  3. or

not 模式首先绑定到它的操作数。and 模式绑定在任何 not 模式表达式绑定之后。然后 or 模式绑定的是 not 和 and 操作数之后。下面的例子试图匹配所有非小写字母 a ~ z 的字符。它出错了,因为 not 模式绑定在 and 模式之前:

// 不正确的模式 `not` 绑定在 `and` 之前
static bool IsNotLowerCaseLetter ( char c ) => c is not >= 'a' and <= 'z';

默认绑定意味着前面的例子会被解析为下面的例子:

// 正确的模式。限定 “ and ” 在 “ not ” 之前
static bool IsNotLowerCaseLetterParentheses ( char c ) => c is not ( >= 'a' and <= 'z' );

随着模式变得越来越复杂,添加括号变得越来越重要。一般来说,为了让其他开发者明白你的模式,可以使用括号,如下面的例子所示:

static bool IsLetter ( char c ) => c is ( >= 'a' and <= 'z' ) or ( >= 'A' and <= 'Z' );

请注意:具有相同绑定顺序的模式的检查顺序是未定义的。在运行时,可以首先检查右侧的多个 or 模式和多个 and 模式的嵌套模式。

属性模式

可以使用属性模式将表达式的属性或字段与嵌套模式进行匹配,如下面的例子所示:

static bool IsConferenceDay ( DateTime 日期 ) => 日期 is { Year : 2020 , Month : 5 , Day : 19 or 20 or 21 };

当表达式的结果是非空时,属性模式与表达式匹配,并且每个嵌套模式都与表达式结果中相应的属性或字段匹配。

你也可以向属性模式中添加运行时类型检查和变量声明,如下面的例子所示:

{
    internal static readonly char [ ] zfs1 = [ '1' , '2' , '3' , '4' , '5' , '6' , '7' ];
    internal static readonly char [ ] zfs2 = [ 'a' , 'b' , 'c' ];

    static void Main ( string [ ] args )
        {
            Console . WriteLine ( $"{FF获取五个 ( "Hello, world!" )}" );
            Console . WriteLine ( $"{FF获取五个 ( "Hi!" )}" );
            Console . WriteLine ( $"{FF获取五个 ( zfs1 )}" );
            Console . WriteLine ( $"{FF获取五个 ( zfs2 )}" );
        }

    static string FF获取五个 ( object 输入 ) => 输入 switch
        {
            string { Length : >= 5 } zfc => zfc [ .. 5 ],
            string zfc => zfc,

            ICollection<char> { Count : >= 5 } symbols => new string ( [ .. symbols . Take ( 5 ) ] ),
            ICollection<char> symbols => new string ( [ .. symbols ] ),

            null => throw new ArgumentNullException ( nameof ( 输入 ) ),
            _ => throw new ArgumentException ( "不支持输入类型。" ),
        };
}

属性模式是一种递归模式。可以使用任何模式作为嵌套模式。使用属性模式将部分数据与嵌套模式进行匹配,如下面的例子所示:

static void Main ( string [ ] args )
    {
        Dian d1 = new ( 1 , 1 );
        Dian d2 = new ( 2 , 20 );
        Console .WriteLine ( FF是y轴内的点 ( new r始终 ( d1 , d2 ) ) );
    }

public record Dian ( int x , int y );
public record r始终 ( Dian 起 , Dian 终 );

static bool FF是y轴内的点 ( r始终 sz ) =>
    sz is { 起: { y: 0 } } or { 终: { y: 0 } };

前面的例子使用了 or 模式组合符和记录类型。

可以在属性模式中引用嵌套的属性或字段。这种功能称为扩展属性模式(extended property pattern)。例如,你可以将前面例子中的方法重构为如下等价的代码:

static bool FF是y轴内的点 ( r始终 sz ) => sz is { 起 . y : 0 } or { 终 . y : 0 };

提示:您可以使用 Simplify property pattern(IDE0170)样式规则,通过建议使用扩展属性模式的地方来提高代码的可读性。

位置模式

你可以使用位置模式来解构表达式结果,并将结果值与相应的嵌套模式进行匹配,如下面的例子所示:

static void Main ( string [ ] args )
    {
        Console . WriteLine ( FF点类 ( new ( 0 , 0 ) ) );
        Console . WriteLine ( FF点类 ( new ( 1 , 0 ) ) );
        Console . WriteLine ( FF点类 ( new ( 0 , 1 ) ) );
        Console . WriteLine ( FF点类 ( new ( 1 , 1 ) ) );
    }

public readonly struct Dian
    {
        public int X
            {
                get;
            }

        public int Y
            {
                get;
            }
            
        public Dian ( int x , int y ) => ( X , Y ) = ( x , y );

        public void Deconstruct ( out int x , out int y ) => (x, y) = (X, Y); // 解构方法
    }

static string FF点类 ( Dian 点 ) => 点 switch
    {
        ( 0 , 0 ) => "原点",
        ( 1 , 0 ) => "X 轴正基结束",
        ( 0 , 1 ) => "Y 轴正基结束",
        _ => "只是一个点"
    };

在前面的示例中,表达式的类型包含解构方法,该方法用于解构表达式的结果。

重要的:位置模式中成员的顺序必须与解构方法中参数的顺序匹配。这是因为为位置模式生成的代码调用了解构方法。

还可以根据位置模式匹配元组类型的表达式。这样,你就可以根据不同的模式匹配多个输入,如下面的例子所示:

static decimal FF解析组成员票价 ( int 组成员 , DateTime 日期 ) => ( 组成员 , 日期 . DayOfWeek ) switch
    {
        ( <= 0 , _ ) => throw new ArgumentException ( "组成员数量必须大于零。" ), // 不管哪天,组成员必须大于 0
        ( _ , DayOfWeek . Saturday or DayOfWeek . Sunday ) => 0.0m, // 周末来的话不收费
        ( >=5 and < 10 , DayOfWeek . Monday ) => 20.0m, // 周一 5 ~ 10 人收费 20
        ( >= 10 , DayOfWeek . Monday ) => 30.0m, // 周一多于 10 人收费 30
        ( >= 5 and < 10 , _ ) => 12.0m, // 除周一和周末,5 ~ 10 人收费 12
        ( >= 10 , _ ) => 15.0m, // 除周一和周末,多于 10 人收费 15
        _ => 0.0m,
    };

前面的例子使用了关系模式和逻辑模式。

你可以在位置模式中使用元组元素的名称和解构参数,如下面的例子所示:

static ( double 和 , int 个数 ) FF和与个数 ( IEnumerable<int> 数 )
    {
        int he = 0 , shu = 0;
        foreach ( int z in 数 )
            {
                he += z;
                shu++;
            }
        return ( he , shu );
    }

List<int> Zhss = new ( )
    {
        1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13
    };
if ( FF和与个数 ( Zhss ) is (和: double sum, 个数: > 0 ) )
    {
        Console . WriteLine ( $"【{string . Join ( "  " , Zhss )}】的和:{sum}" ); // 输出 91
    }

你还可以通过以下任何一种方式扩展位置模式。

  • 添加运行时类型检查和变量声明,如下面的例子所示:

    public record Dian2 ( int X , int Y );
    public record Dian3 ( int X , int Y , int Z );
    
    static string FF如果元素是正的 ( object 点 ) => 点 switch
      {
          Dian2 ( > 0 , > 0 ) d => d . ToString ( ),
          Dian3 ( > 0 , > 0 , > 0 ) d => d . ToString ( ),
          _ => string . Empty,
      };

    前面的例子使用了隐式地提供了解构方法的位置记录。

  • 在位置模式中使用属性模式,如下面的例子所示:

    public record JL权点 ( int X , int Y )
      {
          public double SJD权
              {
                  get => X * .85 + Y * .15;
              }
      }
    
    static bool Ber在域内 ( JL权点 点 ) => 点 is ( >= 0 , >= 0 ) { SJD权: >= 0.0 };
  • 结合上述两种用法,如下面的例子所示:

    JL权点 qd = new ( 2 , 3 );
    if ( qd is JL权点 ( > 0 , > 0 ) { SJD权 : > 0.0 } 点 )
      {
          Console . WriteLine ( $"权点的权(域内):{qd . SJD权},{点 . SJD权}" ); // 此时的“点”就是 qd
      }
    else
      {
          Console . WriteLine ( $"权点的权(域外):{qd . SJD权}" );
      }

位置模式是一种递归模式。也就是说,可以将任何模式用作嵌套模式。

var 模式

你可以使用 var 模式匹配任何表达式(包括 null),并将其结果赋值给一个新的局部变量,如下面的例子所示:

static int [ ] FF模拟获取数据 ( int ID )
    {
        Random SJS = new ( );
        return [ .. Enumerable
            . Range ( 0 , 5 )
            . Select ( s => SJS . Next ( -10 , 11 ) ) ];
    }

static bool Ber可接受 ( int ID , int 绝对值上限 ) =>
    FF模拟获取数据 ( ID ) is var JG
    && JG . Min ( ) >= -绝对值上限
    && JG . Max ( ) <= 绝对值上限;

当需要在布尔表达式中使用临时变量来保存中间计算的结果时,可以使用 var 模式。当需要对 switch 表达式或语句进行更多检查时,也可以使用 var 模式,如下面的例子所示。

static Dian2 FF2D变换 ( Dian2 点 ) => 点 switch
    {
        var (x, y) when x < y => new Dian2 ( -x , y ),
        var (x, y) when x > y => new Dian2 ( x , -y ),
        var (x, y) => new Dian2 ( x , y ),
    };

在前面的例子中,模式 var ( x , y ) 等价于位置模式 ( var x , var y )。

在 var 模式中,声明的变量的类型是与模式匹配的表达式的编译时类型。

丢弃模式

你可以使用丢弃模式 “ _ ” 来匹配任何表达式,包括 null,如下面的例子所示:

static decimal FF获取每日折扣 ( DayOfWeek? 周几 ) => 周几 switch
    {
        DayOfWeek . Sunday => 2m,
        DayOfWeek . Monday => 0.5m,
        DayOfWeek . Tuesday => 12.5m,
        DayOfWeek . Wednesday => 7.5m,
        DayOfWeek . Thursday => 12.5m,
        DayOfWeek . Friday => 5m,
        DayOfWeek . Saturday => 2.5m,
        _ => 0m
    };

在前面的示例中,使用丢弃模式来处理 null 和任何没有 DayOfWeek 枚举对应成员的 Integer 值。这保证了示例中的 switch 表达式可以处理所有可能的输入值。如果在 switch 表达式中没有使用丢弃模式,并且该表达式的所有模式都与输入不匹配,则运行时将抛出异常。如果 switch 表达式不能处理所有可能的输入值,编译器会生成一个警告。

丢弃模式不能作为 is 表达式或 switch 语句中的模式。在这种情况下,要匹配任何表达式,请使用 var 模式和 discard: var _。丢弃模式可以是 switch 表达式中的一个模式。

括弧内模式

任何模式都可以用括号括起来。通常,这样做是为了强调或改变逻辑模式中的优先级,如下面的例子所示:

if ( 输入 is not ( float or double ) )
    {
        return;
    }

列表模式

从 C# 11 开始,你可以使用一系列的模式来匹配数组或列表,如下面的例子所示:

int [ ] Zhss = [ 1 , 2 , 3 ];
Console . WriteLine ( $"数是 1 , 2 , 3 吗?{Zhss is [ 1 , 2 , 3 ]}" );
Console . WriteLine ( $"数是 1 , 2 , 4 吗?{Zhss is [ 1 , 2 , 4 ]}" );
Console . WriteLine ( $"数是 1 , 2 , 3 , 4 吗?{Zhss is [ 1 , 2 , 3 , 4 ]}" );
Console . WriteLine ( $"数是 0 或者 1 , 小于等于 2 , 大于等于 3 吗?{Zhss is [ 0 or 1 , <= 2 , >= 3 ]}" );

如前面的例子所示,当每个嵌套模式都匹配输入序列中相应的元素时,就匹配列表模式。可以在列表模式中使用任何模式。要匹配任何元素,可以使用丢弃模式,如果还想捕获元素,可以使用 var 模式,如下面的例子所示。

List < int > Zhss = [ 10 , 20 , 30 ];
if ( Zhss is [ var y , _ , _ ] )
    {
        Console . WriteLine ( $"三个元素的列表中第一个元素:{y}" ); // 输出:10
    } 

前面的例子使用列表模式匹配整个输入序列。如果只匹配输入序列开始或/和结尾的元素,则使用切片模式“..”,如下面的例子所示:

internal static readonly int [ ] value = new [ ] { 1 , 2 , 3 , 4 , 5 };
internal static readonly int [ ] valueArray = new [ ] { 1 , 1 };
internal static readonly int [ ] valueArray0 = new [ ] { 0 , 1 , 2 , 3 , 4 };
internal static readonly int [ ] valueArray1 = new [ ] { 1 };
internal static readonly int [ ] valueArray2 = new [ ] { 1 , 2 , 3 , 4 };
internal static readonly int [ ] valueArray3 = new [ ] { 2 , 4 };
internal static readonly int [ ] valueArray4 = new [ ] { 2 , 4 };
internal static readonly int [ ] valueArray5 = new [ ] { 1 , 2 , 3 , 4 };
internal static readonly int [ ] valueArray6 = new [ ] { 1 , 0 , 0 , 1 };
internal static readonly int [ ] valueArray7 = new [ ] { 1 , 0 , 1 };

static void Main ( string [ ] args )
    {
        Console . WriteLine ( value 是 [ > 0 , > 0 , .. ] );  // True
        Console . WriteLine ( valueArray 是 [ _ , _ , .. ] );  // True
        Console . WriteLine ( valueArray0 是 [ > 0 , > 0 , .. ] );  // False
        Console . WriteLine ( valueArray1 是 [ 1 , 2 , .. ] );  // False
        Console . WriteLine ( valueArray2 是 [ .. , > 0 , > 0 ] );  // True
        Console . WriteLine ( valueArray3 是 [ .. , > 0 , 2 , 4 ] );  // False
        Console . WriteLine ( valueArray4 是 [ .. , 2 , 4 ] );  // True
        Console . WriteLine ( valueArray5 是 [ >= 0 , .. , 2 or 4 ] );  // True
        Console . WriteLine ( valueArray6 是 [ 1 , 0 , .. , 0 , 1 ] );  // True
        Console . WriteLine ( valueArray7 是 [ 1 , 0 , .. , 0 , 1 ] );  // False
    }

切片模式匹配零个或多个元素。在列表模式中最多只能使用一个切片模式。切片模式只能出现在列表模式中。

还可以在切片模式中嵌套子模式,如下面的例子所示:

static void FF匹配信息 ( string 信息 )
    {
        var JG = 信息 is [ 'a' or 'A' , .. var s , 'a' or 'A' ]
        ? $"信息:{信息} 匹配;在位置 {s}。"
        : $"信息:{信息} 不匹配。";
        Console . WriteLine ( JG );
    }

static void FF验证 ( int [ ] 数值 )
    {
        string JG = 数值 is [ <  0, .. { Length: 2 or 4 }, > 0 ] ? "有效" : "无效";
        Console . WriteLine ( JG );
    }

FF匹配信息 ( "aBBA" ); // 输出:aBBA 匹配;在位置 BB。
FF匹配信息 ( "apron" ); // 输出:apron 不匹配。
FF验证 ( [ -1 , 0 , 1 ] ); // 输出:无效
FF验证 ( [ -1 , 0 , 0 , 1 ] ); // 输出:有效

兔子码农
4 声望1 粉丝

一个酒晕子