根据模式匹配数据

本教程介绍如何使用模式匹配来检查 C# 中的数据。 你将编写少量的代码,然后编译并运行这些代码。 本教程包含一系列探索 C# 中不同类型的课程。 这些课程介绍了 C# 语言的基础知识。

小窍门

当代码片段块包含“运行”按钮时,该按钮将打开交互式窗口,或替换交互式窗口中的现有代码。 当代码片段不包含“运行”按钮时,可以复制代码并将其添加到当前交互式窗口。

前面的教程展示了内置类型和您定义为元组或记录的类型。 可以根据 模式检查这些类型的实例。 实例是否与模式匹配,决定了你的程序采取的操作。 在下面的示例中,你会注意到 ? 在类型名称之后。 此符号允许此类型的值为 null(例如, bool? 可以是 truefalsenull)。 有关详细信息,请参阅 可以为 Null 的值类型。 让我们开始探讨如何使用模式。

匹配一个值

本教程中的所有示例都使用文本输入,该输入将一系列银行交易表示为逗号分隔值(CSV)输入。 在每个示例中,可以使用isswitch表达式将记录与模式匹配。 第一个示例将每行拆分在,字符上,然后使用表达式将第一个字符串字段与值“DEPOSIT”或“WITHDRAWAL”进行is。 匹配时,将从当前帐户余额中添加或扣除交易金额。 若要查看它是否正常工作,请按“运行”按钮:

string bankRecords = """
    DEPOSIT,   10000, Initial balance
    DEPOSIT,     500, regular deposit
    WITHDRAWAL, 1000, rent
    DEPOSIT,    2000, freelance payment
    WITHDRAWAL,  300, groceries
    DEPOSIT,     700, gift from friend
    WITHDRAWAL,  150, utility bill
    DEPOSIT,    1200, tax refund
    WITHDRAWAL,  500, car maintenance
    DEPOSIT,     400, cashback reward
    WITHDRAWAL,  250, dining out
    DEPOSIT,    3000, bonus payment
    WITHDRAWAL,  800, loan repayment
    DEPOSIT,     600, stock dividends
    WITHDRAWAL,  100, subscription fee
    DEPOSIT,    1500, side hustle income
    WITHDRAWAL,  200, fuel expenses
    DEPOSIT,     900, refund from store
    WITHDRAWAL,  350, shopping
    DEPOSIT,    2500, project milestone payment
    WITHDRAWAL,  400, entertainment
    """;

double currentBalance = 0.0;
var reader = new StringReader(bankRecords);

string? line;
while ((line = reader.ReadLine()) is not null)
{
    if (string.IsNullOrWhiteSpace(line)) continue;
    // Split the line based on comma delimiter and trim each part
    string[] parts = line.Split(',');

    string? transactionType = parts[0]?.Trim();
    if (double.TryParse(parts[1].Trim(), out double amount))
    {
        // Update the balance based on transaction type
        if (transactionType?.ToUpper() is "DEPOSIT")
            currentBalance += amount;
        else if (transactionType?.ToUpper() is "WITHDRAWAL")
            currentBalance -= amount;

        Console.WriteLine($"{line.Trim()} => Parsed Amount: {amount}, New Balance: {currentBalance}");
    }
}

检查输出。 通过比较第一个字段中的文本值,可以看到每一行都得到处理。 可以使用== 运算符以类似方式构造前面的示例,来测试两个string的值是否相等。 将变量与常量进行比较是用于模式匹配的基本构建基块。 让我们进一步探索模式匹配中的构建模块。

枚举匹配项

模式匹配的另一个常见用途是匹配某个类型 enum 的值。 下一个示例处理输入记录以创建 元组 ,其中第一个 enum 值是记录存款或取款的值。 第二个值是交易的值。 若要查看它是否正常工作,请按“运行”按钮:

警告

不要复制和粘贴。 必须重置交互式窗口才能运行以下示例。 如果出错,窗口就会挂起,需要刷新页面才能继续。

public static class ExampleProgram
{
    const string bankRecords = """
    DEPOSIT,   10000, Initial balance
    DEPOSIT,     500, regular deposit
    WITHDRAWAL, 1000, rent
    DEPOSIT,    2000, freelance payment
    WITHDRAWAL,  300, groceries
    DEPOSIT,     700, gift from friend
    WITHDRAWAL,  150, utility bill
    DEPOSIT,    1200, tax refund
    WITHDRAWAL,  500, car maintenance
    DEPOSIT,     400, cashback reward
    WITHDRAWAL,  250, dining out
    DEPOSIT,    3000, bonus payment
    WITHDRAWAL,  800, loan repayment
    DEPOSIT,     600, stock dividends
    WITHDRAWAL,  100, subscription fee
    DEPOSIT,    1500, side hustle income
    WITHDRAWAL,  200, fuel expenses
    DEPOSIT,     900, refund from store
    WITHDRAWAL,  350, shopping
    DEPOSIT,    2500, project milestone payment
    WITHDRAWAL,  400, entertainment
    """;

    public static void Main()
    {
        double currentBalance = 0.0;

        foreach (var transaction in TransactionRecords(bankRecords))
        {
            if (transaction.type == TransactionType.Deposit)
                currentBalance += transaction.amount;
            else if (transaction.type == TransactionType.Withdrawal)
                currentBalance -= transaction.amount;
            Console.WriteLine($"{transaction.type} => Parsed Amount: {transaction.amount}, New Balance: {currentBalance}");
        }
    }

    static IEnumerable<(TransactionType type, double amount)> TransactionRecords(string inputText)
    {
        var reader = new StringReader(inputText);
        string? line;
        while ((line = reader.ReadLine()) is not null)
        {
            string[] parts = line.Split(',');

            string? transactionType = parts[0]?.Trim();
            if (double.TryParse(parts[1].Trim(), out double amount))
            {
                // Update the balance based on transaction type
                if (transactionType?.ToUpper() is "DEPOSIT")
                    yield return (TransactionType.Deposit, amount);
                else if (transactionType?.ToUpper() is "WITHDRAWAL")
                    yield return (TransactionType.Withdrawal, amount);
            }
            else {
            yield return (TransactionType.Invalid, 0.0);
            }
        }
    }
}

public enum TransactionType
{
    Deposit,
    Withdrawal,
    Invalid
}

前面的示例还使用语句 if 检查表达式的值 enum 。 另一种模式匹配形式使用 switch 表达式。 让我们探讨一下该语法以及如何使用它。

包含的详尽匹配项 switch

一系列 if 语句可以测试一系列条件。 但是,编译器无法判断一系列 if 语句是否 详尽 ,或者后续 if 条件是否被早期条件 子化 。 该 switch 表达式可确保满足这两个特征,这会导致应用中的 bug 更少。 让我们来尝试和实验。 复制以下代码。 将交互式窗口中的两 if 个语句替换为复制的 switch 表达式。 修改代码后,按交互窗口顶部的“运行”按钮运行新示例。

currentBalance += transaction switch
{
    (TransactionType.Deposit, var amount) => amount,
    (TransactionType.Withdrawal, var amount) => -amount,
    _ => 0.0,
};

运行代码时,会看到它的工作方式相同。 若要演示 子建议,请重新排序开关臂,如以下代码片段所示:

currentBalance += transaction switch
{
    (TransactionType.Deposit, var amount) => amount,
    _ => 0.0,
    (TransactionType.Withdrawal, var amount) => -amount,
};

重新排序开关臂后,按“运行”按钮。 编译器发出错误,因为 arm 与 _ 每个值匹配。 因此,最终的分支永远不会 TransactionType.Withdrawal 运行。 编译器会告诉你代码中存在错误。

如果在switch表达式中被测试的表达式包含不匹配任何switch分支的值,编译器将发出警告。 如果某些值可能无法匹配任何条件,则 switch 表达式并不 详尽。 如果输入的某些值与任何开关臂不匹配,编译器也会发出警告。 例如,如果删除了包含 _ => 0.0, 的行,则任何无效值都不匹配。 在运行时将会失败。 在环境中安装 .NET SDK 并生成程序后,可以测试此行为。 联机体验不会在输出窗口中显示警告。

类型模式

为了完成本教程,让我们再探索一个模式匹配的基础构件:类型模式类型模式在运行时测试表达式,以查看它是否为指定类型。 可以将类型测试与is表达式或switch表达式之一一起使用。 让我们通过两种方式修改当前示例。 首先,让我们构建代表事务的 DepositWithdrawal 记录类型,而不是使用元组。 在交互窗口底部添加以下声明:

public record Deposit(double Amount, string description);
public record Withdrawal(double Amount, string description);

接下来,在方法后面 Main 添加此方法以分析文本并返回一系列记录:

public static IEnumerable<object?> TransactionRecordType(string inputText)
{
    var reader = new StringReader(inputText);
    string? line;
    while ((line = reader.ReadLine()) is not null)
    {
        string[] parts = line.Split(',');

        string? transactionType = parts[0]?.Trim();
        if (double.TryParse(parts[1].Trim(), out double amount))
        {
            // Update the balance based on transaction type
            if (transactionType?.ToUpper() is "DEPOSIT")
                yield return new Deposit(amount, parts[2]);
            else if (transactionType?.ToUpper() is "WITHDRAWAL")
                yield return new Withdrawal(amount, parts[2]);
        }
        yield return default;
    }
}

最后,将foreachMain方法中的循环替换为以下代码:

foreach (var transaction in TransactionRecordType(bankRecords))
{
    currentBalance += transaction switch
    {
        Deposit d => d.Amount,
        Withdrawal w => -w.Amount,
        _ => 0.0,
    };
    Console.WriteLine($" {transaction} => New Balance: {currentBalance}");
}

然后,按“运行”按钮查看结果。 此最终版本针对 类型测试输入。

模式匹配提供了用于比较表达式与特征的词汇。 模式可以包括表达式的类型、类型的值、属性值以及它们的组合。 把表达式与某种模式进行比较,比做多个 if 比较更清晰。 你浏览了一些可用于匹配表达式的模式。 在应用程序中使用模式匹配的方法更多。 首先,访问 .NET 站点以下载 .NET SDK,在计算机上创建项目,并保持编码。 浏览时,可以在以下文章中详细了解 C# 中的模式匹配: