C# 模式匹配(Pattern Matching)

官方文档:

标签:record、Deconstruct

PS:Deconstruct可查看我的另一篇👉C# Deconstruct | 简化元组与对象的数据提取-CSDN博客

目录

    • [1. 概述](#1. 概述)
        • [1.1 支持以下功能](#1.1 支持以下功能)
      • [1.2 应用场景](#1.2 应用场景)
      • [1.3 优点](#1.3 优点)
    • [2. 传统方式 vs 模式匹配](#2. 传统方式 vs 模式匹配)
        • [2.1 传统方式(C# 6.0 及之前)](# 6.0 及之前))
        • [2.2 模式匹配方式(C# 7.0+)](# 7.0+))
    • [3. C# 中的模式匹配类型](# 中的模式匹配类型)
        • [3.1 类型模式(Type Pattern)](#3.1 类型模式(Type Pattern))
        • [3.2 声明模式(Declaration Pattern)](#3.2 声明模式(Declaration Pattern))
        • [3.3 var 模式](#3.3 var 模式)
        • [3.4 常量模式 (Constant Pattern)](#3.4 常量模式 (Constant Pattern))
        • [3.5 关系模式(Relational Pattern)](#3.5 关系模式(Relational Pattern))
        • [3.5 逻辑模式(Logical Pattern)](#3.5 逻辑模式(Logical Pattern))
        • [3.6 属性模式(Property Pattern)](#3.6 属性模式(Property Pattern))
        • [3.7 位置模式(Positional Pattern)- 与 record 配合使用](#3.7 位置模式(Positional Pattern)- 与 record 配合使用)
        • [3.8 列表模式 **(List Pattern)**(C# 11+)](# 11+))
    • [3. 实际应用示例](#3. 实际应用示例)
    • [4. 优势](#4. 优势)
    • [5. 最佳实践](#5. 最佳实践)
    • [6. 总结](#6. 总结)

1. 概述

模式匹配是现代编程语言中的一种强大表达力丰富的条件分支方式,它允许检查值的特征(如类型、属性、值等)来简化条件逻辑,并根据检查结果执行相应的代码。

随着版本更新,C# 的模式匹配能力不断增强。

1.1 支持以下功能
  1. 解构提取 -- 从复杂结构(如对象、元组等)中提取出内部元素
  2. 类型检查 -- 判断值的类型是否符合预期
  3. 条件逻辑 -- 将条件判断与变量绑定结合,增强代码表达力
  4. 声明式条件处理 -- 以更清晰、结构化的方式编写多分支条件逻辑

1.2 应用场景

  • is表达式:直接条件判断。
  • switch表达式:返回值的模式匹配。
  • switch语句:多分支条件判断。

1.3 优点

模式匹配提高了代码的:

  • 可读性:更简洁地表达复杂条件
  • 安全性:编译器验证完整性,减少遗漏情况
  • 表达力:支持丰富的模式组合和嵌套

2. 传统方式 vs 模式匹配

2.1 传统方式(C# 6.0 及之前)
c# 复制代码
// 需要多次类型检查和转换
if (shape is Circle)
{
    Circle circle = (Circle)shape;
    if (circle.Radius > 5)
    {
        Console.WriteLine($"Large circle: {circle.Radius}");
    }
}
else if (shape is Rectangle)
{
    Rectangle rect = (Rectangle)shape;
    Console.WriteLine($"Rectangle: {rect.Width}x{rect.Height}");
}
2.2 模式匹配方式(C# 7.0+)
c# 复制代码
// 单一行内完成类型检查、条件判断和转换
if (shape is Circle { Radius: > 5 } circle)
{
    Console.WriteLine($"Large circle: {circle.Radius}");
}
else if (shape is Rectangle rect)
{
    Console.WriteLine($"Rectangle: {rect.Width}x{rect.Height}");
}

3. C# 中的模式匹配类型

3.1 类型模式(Type Pattern)

主要用于检查表达式的运行时类型,通常与 switch 表达式配合使用来实现基于类型的分支处理。

  1. 检查表达式是否匹配指定类型(不声明变量)。

    c# 复制代码
    if (obj is string)
    {  
        Console.WriteLine("这是一个字符串");
    }
  2. switch 表达式配合使用

    c# 复制代码
    var area = shape switch
    {
        Circle c => Math.PI * c.Radius * c.Radius,
        Rectangle r => r.Width * r.Height,
        _ => 0 // 处理 null 和其他值,也叫做弃元模式
    };
3.2 声明模式(Declaration Pattern)

同时进行类型检查变量声明

如果表达式能与指定类型匹配,则条件为真,同时会创建一个新的类型化变量(此例中的 circle)来访问该实例的成员,从而避免了显式类型转换。

c# 复制代码
if (shape is Circle circle)
{
    Console.WriteLine($"Circle with radius {circle.Radius}");
}
3.3 var 模式

匹配任何表达式(包括 null)并将结果分配给变量。

c# 复制代码
static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

// 在 when 子句中使用
static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};
3.4 常量模式 (Constant Pattern)

检查表达式是否等于指定常量(包括 null、枚举、字面量等)。

c# 复制代码
if (value is null) return;
if (value is 42) Console.WriteLine("答案是42");
if (value is "hello") Console.WriteLine("打招呼");

枚举值匹配

c# 复制代码
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)),
   };

字符串值匹配

c# 复制代码
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)),
   };
3.5 关系模式(Relational Pattern)

此模式允许在模式匹配中使用关系运算符(<, >, <=, >=)来将属性值与常量进行比较。它使得基于数值范围的匹配变得非常直观和清晰,完美替代了多个 if-else if 语句链。

c# 复制代码
// 使用关系运算符进行比较
string sizeCategory = shape switch
{
    Circle { Radius: >= 10 } => "Extra large",
    Circle { Radius: >= 5 } => "Large",
    Circle { Radius: >= 1 } => "Medium",
    Circle { Radius: > 0 } => "Small",
    _ => "Invalid"
};
3.5 逻辑模式(Logical Pattern)

逻辑模式允许您使用逻辑运算符 andornot 将多个模式组合起来,形成一个更复杂的复合条件。

模式组合器优先级 (从高到低):not > and > or

c# 复制代码
// 使用逻辑运算符组合条件
string category = shape switch
{
    Circle { Radius: > 0 and <= 5 } => "Small to medium circle",
    Rectangle { Width: > 0, Height: > 0 } and not { Width: var w, Height: var h } when w == h => "Non-square rectangle",
    Rectangle { Width: var w, Height: var h } when w == h => "Square",
    _ => "Other"
};

//Null 检查
string? message = ReadMessageOrDefault();

if (message is not null)
{
    Console.WriteLine(message);
}

int? maybe = 12;

if (maybe is int number)
{
    Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{
    Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}
3.6 属性模式(Property Pattern)

属性模式允许直接对对象的属性值进行匹配,而无需先检查类型再访问属性,支持递归匹配。

c# 复制代码
if (person is { Address.City: "Beijing" }) 
{
    Console.WriteLine("北京人");
}
c# 复制代码
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

// 结合类型检查和变量声明
static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s[..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."),
};

// 扩展属性模式(C# 10+)
static bool IsInDomain(WeightedPoint point) => point is { X: >= 0, Y: >= 0, Weight: >= 0.0 };
   
3.7 位置模式(Positional Pattern)- 与 record 配合使用

此模式用于对实现了 Deconstruct 方法的类型(如元组(Tuple)、记录(record)类型)进行解构,并根据其解构后的元素值进行匹配。

_ 代表丢弃符,表示忽略该位置的值。它是处理元组和记录等复合数据的强大工具。

c# 复制代码
public readonly struct Point
{
    public int X { get; }
    public int Y { get; }
    
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

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",
};

// 元组模式匹配
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate) => (groupSize, visitDate.DayOfWeek) switch
{
    (<= 0, _) => throw new ArgumentException("Group size must be positive."),
    (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
    (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
    (>= 10, DayOfWeek.Monday) => 30.0m,
    (>= 5 and < 10, _) => 12.0m,
    (>= 10, _) => 15.0m,
    _ => 0.0m,
};
3.8 列表模式 (List Pattern)(C# 11+)

处理不规则数据结构,匹配数组或列表中的元素序列,支持切片模式 ..

c# 复制代码
int[] numbers = { 1, 2, 3 };

// 基本匹配
Console.WriteLine(numbers is [1, 2, 3]); // True

// 结合关系模式和弃元模式
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True

// 使用切片模式
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True
Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True

// 捕获切片结果
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);
}
MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.
c# 复制代码
decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
{
    balance += transaction switch
    {
        [_, "DEPOSIT", _, var amount]     => decimal.Parse(amount),
        [_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
        [_, "INTEREST", var amount]       => decimal.Parse(amount),
        [_, "FEE", var fee]               => -decimal.Parse(fee),
        _ => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"),
    };
    Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}");
}

3. 实际应用示例

c# 复制代码
public record Order(string Id, decimal Amount, string Status, DateTime OrderDate);

public decimal CalculateDiscount(Order order) => order switch
{
    // 高金额且是新客户 - 大折扣
    { Amount: > 1000, Status: "New" } => 0.15m,
    
    // 节假日订单 - 中等折扣
    { OrderDate: { Month: 12, Day: >= 20 and <= 31 } } => 0.10m,
    
    // 普通订单 - 小折扣
    { Amount: > 500 } => 0.05m,
    
    // 默认无折扣
    _ => 0m
};

// 使用示例
var order = new Order("123", 1200m, "New", new DateTime(2023, 12, 25));
decimal discount = CalculateDiscount(order); // 返回 0.15 (15% 折扣)

4. 优势

  1. 更简洁的代码:减少样板代码
  2. 更安全:编译器可以帮助检查穷尽性
  3. 更表达力强:清晰表达复杂的条件逻辑
  4. 更好的可读性:意图更加明确
  5. 与 record 类型完美配合:充分利用 record 的解构功能

5. 最佳实践

  1. 使用括号明确优先级:当组合复杂模式时,使用括号提高可读性和确保正确优先级
  2. 穷尽性检查 :确保 switch 表达式处理所有可能情况,避免运行时异常
  3. 性能考量:模式匹配通常比传统 if-else 链更高效,编译器会优化模式匹配逻辑
  4. 可读性:对于复杂匹配逻辑,考虑将模式分解为多个简单模式或使用辅助方法

6. 总结

模式匹配通过将类型检查、属性检查和条件逻辑结合在一起,它提供了比传统 if-elseswitch 语句更强大、更简洁的表达能力。

建议根据需求选择合适的模式,并注意兼容的 C# 版本(如列表模式需 C# 11+)。

相关推荐
程序设计实验室6 小时前
重写 StarBlog 的搜索功能和页面,支持权重设置和结果高亮
c#·starblog番外
我是唐青枫7 小时前
从 Skip Take 到 Keyset:C# 分页原理与实践
开发语言·c#·.net
c#上位机10 小时前
wpf之Canvas
c#·wpf
c#上位机10 小时前
wpf之样式
c#·wpf
用户37215742613511 小时前
告别手动复制粘贴:C# 实现 Excel 与 TXT 文本文件高效互转
c#·.net
忧郁的蛋~12 小时前
在.NET标准库中进行数据验证的方法
后端·c#·asp.net·.net·.netcore
用户83562907805112 小时前
C# 转换 Word 文档为图片:解锁文档处理的新维度
后端·c#
lee57612 小时前
UniApp + SignalR + Asp.net Core 做一个聊天IM,含emoji 表情包
前端·vue.js·typescript·c#
ccut 第一混13 小时前
c# winform 拼图游戏
开发语言·c#