• c#杂谈之四(模式匹配)



    一、模式匹配

    “模式匹配”是一种测试表达式是否具有特定特征的方法。 C# 模式匹配提供更简洁的语法,用于测试表达式并在表达式匹配时采取措施。 “is 表达式”目前支持通过模式匹配测试表达式并有条件地声明该表达式结果。 “switch 表达式”允许你根据表达式的首次匹配模式执行操作。 这两个表达式支持丰富的模式词汇。

    1.1 Null检查

    模式匹配最常见的方案之一是确保值不是 null。 使用以下示例进行 null 测试时,可以测试可为 null 的值类型并将其转换为其基础类型:

    class Program
    {
        static void Main()
        {
    
            //int? maybe = 12;
            int? maybe = null;
            if(maybe is int number) //
            {
                Console.WriteLine($"可为null的int maybe 具有值{number}");
            }
            else
            {
                Console.WriteLine("可为null的int 'maybe'不包含值");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    上述代码是声明模式,用于测试变量类型并将其分配给新变量。 语言规则使此方法比其他方法更安全。 变量 number 仅在 if 子句的 true 部分可供访问和分配。 如果尝试在 else 子句或 if 程序块后等其他位置访问,编译器将出错。 其次,由于不使用 == 运算符,因此当类型重载 == 运算符时,此模式有效。 这使该方法成为检查空引用值的理想方法,可以添加 not 模式:

    string? message = "This is not the null string";
    
    if (message is not null)
    {
        Console.WriteLine(message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.2 类型测试

    模式匹配的另一种常见用途是测试变量是否与给定类型匹配。 例如,以下代码测试变量是否为非 null 并实现 System.Collections.Generic.IList 接口。 如果是,它将使用该列表中的 ICollection.Count 属性来查找中间索引。 不管变量的编译时类型如何,声明模式均与 null 值不匹配。 除了防范未实现 IList 的类型之外,以下代码还可防范 null。

    public static T MidPoint<T>(IEnumerable<T> sequence)
    {
        if (sequence is IList<T> list)
        {
            return list[list.Count / 2];
        }
        else if (sequence is null)
        {
            throw new ArgumentNullException(nameof(sequence), "Sequence can't be null.");
        }
        else
        {
            int halfLength = sequence.Count() / 2 - 1;
            if (halfLength < 0) halfLength = 0;
            return sequence.Skip(halfLength).First();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.3 比较离散值

    public State PerformOperation(Operation command) =>
       command switch
       {
           Operation.SystemTest => RunDiagnostics(),
           Operation.Start => StartSystem(),
           Operation.Stop => StopSystem(),
           Operation.Reset => ResetToReady(),
           _ => throw new ArgumentException("Invalid enum value for command", nameof(command)),
       };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    前一个示例演示了基于枚举值的方法调度。 最终 _ 案例为与所有数值匹配的弃元模式。 它处理值与定义的 enum 值之一不匹配的任何错误条件。 如果省略开关臂,编译器会警告你尚未处理所有可能输入值。 在运行时,如果检查的对象与任何开关臂均不匹配,则 switch 表达式会引发异常。 可以使用数值常量代替枚举值集。 你还可以将这种类似的方法用于表示命令的常量字符串值

    public State PerformOperation(string command) =>
       command switch
       {
           "SystemTest" => RunDiagnostics(),
           "Start" => StartSystem(),
           "Stop" => StopSystem(),
           "Reset" => ResetToReady(),
           _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
       };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    前面的示例显示相同的算法,但使用字符串值代替枚举。 如果应用程序响应文本命令而不是常规数据格式,则可以使用此方案。 从 C# 11 开始,还可以使用 Span 或 ReadOnlySpan 来测试常量字符串值,如以下示例所示:

    public State PerformOperation(ReadOnlySpan<char> command) =>
       command switch
       {
           "SystemTest" => RunDiagnostics(),
           "Start" => StartSystem(),
           "Stop" => StopSystem(),
           "Reset" => ResetToReady(),
           _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
       };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.4 关系模式

    你可以使用关系模式测试如何将数值与常量进行比较。 例如,以下代码基于华氏温度返回水源状态:

    string WaterState(int tempInFahrenheit) =>
        tempInFahrenheit switch
        {
            (> 32) and (< 212) => "liquid",
            < 32 => "solid",
            > 212 => "gas",
            32 => "solid/liquid transition",
            212 => "liquid / gas transition",
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    string WaterState2(int tempInFahrenheit) =>
        tempInFahrenheit switch
        {
            < 32 => "solid",
            32 => "solid/liquid transition",
            < 212 => "liquid",
            212 => "liquid / gas transition",
            _ => "gas",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    if(person is { Name: "Kang", Age: >= 18}){}
    //等价于
    if(person.Name=="Kang" && person.Age >= 18){}
    
    • 1
    • 2
    • 3

    1.5 多个输入

    到目前为止,你所看到的所有模式都在检查一个输入。 可以写入检查一个对象的多个属性的模式。 请考虑以下 Order 记录:

    public record Order(int Items, decimal Cost);
    
    • 1

    前面的位置记录类型在显式位置声明两个成员。 首先出现 Items,然后是订单的 Cost。 有关详细信息,请参阅记录。

    以下代码检查项数和订单值以计算折扣价:

    public decimal CalculateDiscount(Order order) =>
        order switch
        {
            { Items: > 10, Cost: > 1000.00m } => 0.10m,
            { Items: > 5, Cost: > 500.00m } => 0.05m,
            { Cost: > 250.00m } => 0.02m,
            null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
            var someObject => 0m,
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    前两个开关臂检查 Order 的两个属性。 第三个仅检查成本。 下一个检查 null,最后一个与其他任何值匹配。 如果 Order 类型定义了适当的 Deconstruct 方法,则可以省略模式的属性名称,并使用析构检查属性:

    public decimal CalculateDiscount(Order order) =>
        order switch
        {
            ( > 10,  > 1000.00m) => 0.10m,
            ( > 5, > 50.00m) => 0.05m,
            { Cost: > 250.00m } => 0.02m,
            null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
            var someObject => 0m,
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.6 列表模式

    可以使用列表模式检查列表或数组中的元素。 列表模式提供了一种方法,将模式应用于序列的任何元素。 此外,还可以应用弃元模式 (_) 来匹配任何元素,或者应用切片模式来匹配零个或多个元素。

    当数据不遵循常规结构时,列表模式是一个有价值的工具。 可以使用模式匹配来测试数据的形状和值,而不是将其转换为一组对象。

    int[] arr = { 1, 2, 3 };
    // 列表用[]表示,每个元素进行匹配,
    // 后面的..两个点表示这个列表后面可能有或者没有元素了
    if(arr is [>=10 and <=20, _, not 42, ..])
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.7 var

    person[0] is var firstChar
    // 等价于
    person[0] is char firstChar
    
    • 1
    • 2
    • 3

    1.8 对位模式

    
    using System.Text.RegularExpressions;
    
    class Program
    {
        static void Main()
        {
            var person = new Person { Name = "Kang", Age = 30, Gender = Gender.male };
            if(person is ("Kang", 30))
            {
                Console.WriteLine("matche");
            }
            
        }
    }
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Gender Gender { get; set; }
    
        // 要使用对位模式必须写这个方法
        public void Deconstruct(out string name, out int age)
        {
            // 将输出参数赋值
            name = Name;
            age = Age;
        }
    }
    enum Gender
    {
        male, female
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    1.9 扩展对位模式

    别人写的API你不能去写Deconstruct()方法,我们使用扩展方法来实现。

    
    using System.Text.RegularExpressions;
    
    class Program
    {
        static void Main()
        {
            var person = new Person { Name = "Kang", Age = 30, Gender = Gender.male };
            if(person is ("Kang", 30))
            {
                Console.WriteLine("matche");
            }
            
        }
    }
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Gender Gender { get; set; }
    
    }
    enum Gender
    {
        male, female
    }
    
    static class PersonExtensions
    {
        public static void Deconstruct(this Person @this, out string name, out int age)
        {
            name = @this.Name;
            age = @this.Age;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    1.20 解构模式

    
    using System.Text.RegularExpressions;
    
    class Program
    {
        static void Main()
        {
            var person = new Person { Name = "Kang", Age = 30, 
            Gender = Gender.male, Chinese=85, English=90, Match=60 };
            if(person is var (name, avg) && avg >=60 )
            {
                Console.WriteLine(name);
            }
            
        }
    }
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Gender Gender { get; set; }
        public int Chinese { get; set; }
        public int English { get; set; }
        public int Match { get; set; }
    
    
    }
    enum Gender
    {
        male, female
    }
    
    static class PersonExtensions
    {
        public static void Deconstruct(this Person @this, out string name, out double avg)
        {
            name = @this.Name;
            avg = (@this.Chinese + @this.Match + @this.English) / 3D;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1.21 弃元模式

    static void Main()
        {
            var person = new Person { Name = "Kang", Age = 30, Gender = Gender.male, Chinese=85, English=90, Match=60 };
            // 这里的下划线_就是弃元模式,我们可能用不到name。
            if(person is var (_, avg) && avg >=60 )
            {
                Console.WriteLine(avg);
            }
            
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    void func(string? str)
    {
      // ?? null传播运算符
      // 如果str不为null返回str否则返回后面的表达式(抛异常)
      // 这个函数就是用来如判断为空抛异常的,所以返回str没意义,就用了弃元
       _ = str ?? throw new ArgumentNullException(nameof(str));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.22 属性模式

    static void Main()
        {
            var person = new Person { Name = "Kang", Age = 30, Gender = Gender.male, Chinese=85, English=90, Match=60 };
            // 这就是属性模式,不用写Deconstruct()方法了
            if(person is { Name: "Kang" })
            {
                Console.WriteLine(person.Name);
            }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    if(person is {}) // 等价于 person != null
    {
        Console.WriteLine(person.Name);
    }
    
    • 1
    • 2
    • 3
    • 4

    属性模式是支持递归的,它可以和对位模式、类型模式和声明模式进行内联,放在一起。
    语法:表达式 is 类型模式 (对位模式) {属性模式} 声明模式

    if(person is Person("Kang", _) {Age: 18} converted)
    {}
    
    • 1
    • 2

    等价于

    if(person is Person p&&p is ("Sunnie", _)&&p is {Age:18}){}
    
    • 1

    递归的模式匹配

    if(person is 
        {
            Name: { Length: 6 },
            Friend: { Name: "Tom" } friend
        }
    ){}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.23 分片模式

    就是后面的…两个点可以再次进行模式匹配

    int[] arr = {1, 2, 3};
    if(
        arr is 
        [
            >=10 and <=20, 
            _,
            not 42,
            ..
            [
                1,
                <=10,
                ..,
                42
            ]
        ]
    ){}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果一个对象要支持列表模式,它必须要有Count或者Length属性。

  • 相关阅读:
    从“熊怪吃桃”学循环和条件
    kettle在linux上的运行方法
    HTML: css中的display属性
    【人工智能】Mindspore框架中保存加载模型
    (十七)VBA常用基础知识:读写text文件
    Java面试之爱立信
    1.Gin 介绍
    TKMybatis的使用大全和例子(example、Criteria、and、or)
    基于springboot汽车租赁系统
    DQL查询数据(最重点)
  • 原文地址:https://blog.csdn.net/weixin_42710036/article/details/128068390