22 五月 2025

C# 中的模式匹配

现代 C# 在处理条件逻辑方面经历了一场“静默革命”。那些类型检查和值比较需要冗长的 if-else 级联或笨拙的 switch 语句的时代已经一去不复返了。尤其是自 C# 8.0 以来引入的复杂模式匹配功能,从根本上改变了开发人员编写控制流的方式——使代码同时更具表现力、更简洁、更安全。

使用 C# 模式匹配增强控制流

C# 中的模式匹配提供了一种简洁的语法,用于测试表达式并在表达式匹配特定模式时执行操作。它使开发人员能够根据各种条件测试值,包括它们的类型、值、属性,甚至是复杂对象的结构。此功能主要通过 is 表达式和 switch 表达式(或 switch 语句)公开。虽然基本的类型检查在早期就已存在,但 C# 8.0 引入了更丰富的模式词汇,显著扩展了其效用和对日常编码实践的影响。此增强功能对于有效的 C# switch 模式匹配和 C# if 模式匹配至关重要,它将冗长的条件逻辑转换为紧凑、易于理解的结构。

让我们探索一些可用的关键模式类型以及它们如何简化常见的编程任务,提供清晰的 C# 模式匹配示例来阐明其优势。

C# 模式匹配的实际示例

模式匹配的真正强大之处在于将其与传统方法进行对比时变得显而易见。开发人员在代码可读性和简洁性方面获得了显著提升。

声明模式

声明模式检查表达式的运行时类型,如果匹配,则将结果赋值给一个新变量。这消除了在许多场景中对显式类型转换和 null 检查的需求。

之前:

public void ProcessAssetOld(object asset)
{
    if (asset is SoftwareLicense)
    {
        SoftwareLicense license = (SoftwareLicense)asset;
        Console.WriteLine($"Software License: {license.ProductName}, Expiration: {license.ExpirationDate.ToShortDateString()}");
    }
    else if (asset is HardwareComponent)
    {
        HardwareComponent hardware = (HardwareComponent)asset;
        Console.WriteLine($"Hardware Component: {hardware.ComponentName}, Serial: {hardware.SerialNumber}");
    }
    else
    {
        Console.WriteLine("Unknown asset type.");
    }
}

public class SoftwareLicense { public string ProductName { get; set; } public DateTime ExpirationDate { get; set; } }
public class HardwareComponent { public string ComponentName { get; set; } public string SerialNumber { get; set; } }

之后(使用声明模式):

public void ProcessAssetNew(object asset)
{
    if (asset is SoftwareLicense license)
    {
        Console.WriteLine($"Software License: {license.ProductName}, Expiration: {license.ExpirationDate.ToShortDateString()}");
    }
    else if (asset is HardwareComponent hardware)
    {
        Console.WriteLine($"Hardware Component: {hardware.ComponentName}, Serial: {hardware.SerialNumber}");
    }
    else
    {
        Console.WriteLine("Unknown asset type.");
    }
}

licensehardware 变量仅在模式匹配时才在作用域内并被赋值,这增强了类型安全性并减少了潜在的运行时错误。

类型模式

与声明模式类似,类型模式检查表达式的运行时类型。它通常用于 switch 表达式中,其中模式本身不严格需要新的变量声明。

之前:

public decimal CalculateShippingCostOld(Shipment shipment)
{
    if (shipment == null)
        throw new ArgumentNullException(nameof(shipment));
    if (shipment.GetType() == typeof(StandardShipment))
        return 5.00m;
    if (shipment.GetType() == typeof(ExpressShipment))
        return 15.00m;
    if (shipment.GetType() == typeof(InternationalShipment))
        return 25.00m;
    return 0.00m; // Default for unknown types
}

public abstract class Shipment { }
public class StandardShipment : Shipment { }
public class ExpressShipment : Shipment { }
public class InternationalShipment : Shipment { }

之后(在 switch 表达式中使用类型模式):

public decimal CalculateShippingCostNew(Shipment shipment) => shipment switch
{
    StandardShipment => 5.00m,
    ExpressShipment => 15.00m,
    InternationalShipment => 25.00m,
    null => throw new ArgumentNullException(nameof(shipment)),
    _ => 0.00m // Handle unknown shipment types
};

这展示了简洁的 C# switch case 模式匹配,用于不同类型。

常量模式

常量模式测试表达式的结果是否等于指定的常量值。这简化了离散值比较。

之前:

public string GetOrderStateOld(OrderStatus status)
{
    if (status == OrderStatus.Pending)
        return "Order is awaiting processing.";
    else if (status == OrderStatus.Shipped)
        return "Order has been dispatched.";
    else if (status == OrderStatus.Delivered)
        return "Order has been delivered.";
    else if (status == OrderStatus.Cancelled)
        return "Order has been cancelled.";
    else
        return "Unknown order state.";
}

public enum OrderStatus { Pending, Shipped, Delivered, Cancelled, Returned }

之后(使用常量模式):

public string GetOrderStateNew(OrderStatus status) => status switch
{
    OrderStatus.Pending => "Order is awaiting processing.",
    OrderStatus.Shipped => "Order has been dispatched.",
    OrderStatus.Delivered => "Order has been delivered.",
    OrderStatus.Cancelled => "Order has been cancelled.",
    _ => "Unknown order state."
};

这是处理特定 C# 字符串模式匹配或枚举值的一种简洁方式。

关系模式

关系模式使用比较运算符(<><=>=)将表达式的结果与常量进行比较。这使得范围检查更具可读性。

之前:

public string GetEmployeePerformanceLevelOld(int score)
{
    if (score < 50)
        return "Needs Improvement";
    else if (score >= 50 && score < 70)
        return "Meets Expectations";
    else if (score >= 70 && score < 90)
        return "Exceeds Expectations";
    else if (score >= 90)
        return "Outstanding";
    else
        return "Invalid Score"; // Should not happen with int, but for completeness
}

之后(使用关系模式):

public string GetEmployeePerformanceLevelNew(int score) => score switch
{
    < 50 => "Needs Improvement",
    >= 50 and < 70 => "Meets Expectations",
    >= 70 and < 90 => "Exceeds Expectations",
    >= 90 => "Outstanding",
    _ => "Invalid Score" // Catches any unexpected integer values
};

这展示了将关系模式与逻辑模式结合用于 C# 模式匹配的强大功能。

属性模式

属性模式允许将表达式的属性或字段与嵌套模式进行匹配。这对于检查对象的状态非常有用。

之前:

public decimal CalculateOrderDiscountOld(CustomerOrder order)
{
    if (order == null) return 0m;
    if (order.TotalAmount >= 500 && order.Customer.IsPremium)
    {
        return 0.15m; // 15% for premium customers with large orders
    }
    else if (order.TotalAmount >= 100)
    {
        return 0.05m; // 5% for general large orders
    }
    return 0m;
}

public class Customer { public bool IsPremium { get; set; } }
public class CustomerOrder { public decimal TotalAmount { get; set; } public Customer Customer { get; set; } }

之后(使用属性模式):

public decimal CalculateOrderDiscountNew(CustomerOrder order) => order switch
{
    { TotalAmount: >= 500, Customer.IsPremium: true } => 0.15m,
    { TotalAmount: >= 100 } => 0.05m,
    null => 0m,
    _ => 0m
};

这展示了强大的 C# 属性模式匹配,简化了对象状态检查。

位置模式

位置模式解构表达式的结果(如元组或具有 Deconstruct 方法的记录),并将结果值与嵌套模式进行匹配。

之前:

public string DescribeTransactionOld(Transaction transaction)
{
    if (transaction.Type == TransactionType.Deposit && transaction.Amount > 1000)
        return "Large Deposit";
    else if (transaction.Type == TransactionType.Withdrawal && transaction.Amount > 500)
        return "Significant Withdrawal";
    else if (transaction.Type == TransactionType.Fee && transaction.Amount > 0)
        return "Applied Fee";
    else
        return "Standard Transaction";
}

public record Transaction(TransactionType Type, decimal Amount);
public enum TransactionType { Deposit, Withdrawal, Fee, Transfer }

之后(使用位置模式):

public string DescribeTransactionNew(Transaction transaction) => transaction switch
{
    (TransactionType.Deposit, > 1000m) => "Large Deposit",
    (TransactionType.Withdrawal, > 500m) => "Significant Withdrawal",
    (TransactionType.Fee, > 0m) => "Applied Fee",
    _ => "Standard Transaction"
};

这种简洁的形式增强了可解构类型的 C# 模式匹配可读性。

逻辑模式(andornot

逻辑模式使用 andornot 关键字组合其他模式,从而在单个模式中启用复杂的条件检查。

之前:

public bool CanGrantAccessOld(UserRole role, int securityLevel, bool hasTwoFactorAuth)
{
    if ((role == UserRole.Administrator || role == UserRole.Manager) && securityLevel > 7 && hasTwoFactorAuth)
        return true;
    else if (role == UserRole.Developer && securityLevel > 5)
        return true;
    else if (role != UserRole.Guest && securityLevel >= 3 && securityLevel <= 10)
        return true;
    else
        return false;
}

public enum UserRole { Guest, User, Developer, Manager, Administrator }

之后(使用逻辑模式):

public bool CanGrantAccessNew(UserRole role, int securityLevel, bool hasTwoFactorAuth) => (role, securityLevel, hasTwoFactorAuth) switch
{
    (UserRole.Administrator or UserRole.Manager, > 7, true) => true,
    (UserRole.Developer, > 5, _) => true,
    (not UserRole.Guest, >= 3 and <= 10, _) => true,
    _ => false
};

C# 的 is 模式匹配与逻辑运算符结合后变得非常富有表现力。

Var 模式

var 模式总是匹配表达式并将其结果赋值给新的局部变量。当你需要捕获 switch 表达式的整个输入值(或复杂模式的一部分),以便在 when 子句中执行额外的、更复杂的检查时,它特别有用,尤其是那些涉及无法直接通过其他模式表达的方法调用或属性时。

之前:

using System.Collections.Generic;
using System.Linq;

public string AnalyzeNumberSequenceOld(IEnumerable<int> numbers)
{
    if (numbers == null)
        return "Invalid sequence (null)";
    if (numbers.Count() == 0)
        return "Empty sequence";
    if (numbers.Count() > 5 && numbers.Average() > 10.0)
        return "Long sequence with high average";
    return "Normal sequence";
}

之后(在 when 子句中使用 Var 模式):

using System.Collections.Generic;
using System.Linq;

public string AnalyzeNumberSequenceNew(IEnumerable<int> numbers) => numbers switch
{
    null => "Invalid sequence (null)", 
    var list when list.Count() == 0 => "Empty sequence",
    var list when list.Count() > 5 && list.Average() > 10.0 => "Long sequence with high average",
    _ => "Normal sequence"
};

在这里,var list 捕获 IEnumerable<int> 实例。这允许在 when 子句中直接对 list 变量执行后续复杂的 LINQ 查询(如 Count()Average()),而无需使用 is 进行显式类型检查。

列表模式(C# 11)

C# 11 中引入的列表模式允许匹配序列(如数组或 List<T>)中的元素。它们对于验证集合的结构和内容非常强大。

之前:

public bool ValidateLogEntryOld(string[] logParts)
{
    if (logParts == null || logParts.Length < 2) return false;

    if (logParts[0] == "ERROR" && logParts.Length >= 3 && logParts[1] == "AUTH" && logParts[2] == "FAILED")
        return true;
    if (logParts[0] == "INFO" && logParts.Length >= 2 && logParts[1] == "CONNECTED")
        return true;
    if (logParts[0] == "WARN" && logParts.Length >= 4 && logParts[1] == "DISK" && logParts[2] == "SPACE" && int.TryParse(logParts[3], out int space) && space < 10)
        return true;
    return false;
}

之后(使用列表模式):

public bool ValidateLogEntryNew(string[] logParts) => logParts switch
{
    ["ERROR", "AUTH", "FAILED", ..] => true, // Error authentication failed
    ["INFO", "CONNECTED", ..] => true,     // Client connected
    ["WARN", "DISK", "SPACE", var size, ..] when int.TryParse(size, out int space) && space < 10 => true, // Low disk space
    _ => false // Any other log entry
};

这是针对序列进行模式匹配的强大功能,在解析命令行参数或具有不同结构的日志条目等场景中特别有用。..(切片模式)匹配零个或多个元素,提供了灵活性。

超越基础:使用模式匹配编写更清晰的代码

模式匹配的收益超越了单纯的语法糖。通过采用这些功能,开发人员本质上编写出:

  • 更具表现力:模式直接传达意图,使代码一目了然,更容易阅读和理解。复杂的条件逻辑变得直观。
  • 更简洁:消除样板化的 if-else 块和显式类型转换减少了代码量,从而减少了维护的代码,也减少了出错的机会。
  • 更安全Switch 表达式尤其如此,通常需要穷尽模式匹配。C# 编译器通过警告开发人员 switch 表达式是否未覆盖所有可能的输入值来提供帮助,有助于防止运行时异常。这种编译器强制的穷尽性提高了软件的正确性。
  • 更易于重构:凭借更清晰、更模块化的条件逻辑,重构变得不那么令人生畏。开发人员可以以最小的影响修改或扩展行为。

模式匹配已将 C# 转变为一种为复杂数据处理和控制流提供优雅解决方案的语言。从基本的类型检查到解构对象和验证序列,C# 8.0+ 提供的功能是任何现代软件开发人员必不可少的工具。拥抱这些强大的功能不仅可以提高代码质量,还可以简化开发过程,有助于开发出更可靠、更易于维护的应用程序。仍然依赖旧式 if-elseswitch 语句的开发人员将从引入这些模式中大大受益,使他们的 C# 程序更健壮、更令人愉悦。

相关文章