Поделиться через


Сопоставление данных с шаблонами

В этом руководстве описано, как использовать сопоставление шаблонов для проверки данных в C#. Вы пишете небольшие объемы кода, а затем компилируете и запускаете этот код. В этом руководстве содержится ряд уроков, которые изучают различные типы типов в C#. Эти уроки учат вас основам языка C#.

Подсказка

Если блок фрагмента кода включает кнопку "Выполнить", эта кнопка открывает интерактивное окно или заменяет существующий код в интерактивном окне. Если фрагмент не содержит кнопку "Выполнить", можно скопировать код и добавить его в текущее интерактивное окно.

В предыдущих руководствах показаны встроенные типы и типы, которые определяются как кортежи или записи. Экземпляры этих типов можно проверить на соответствие шаблону. Совпадает ли экземпляр с шаблоном, определяет действия, выполняемые программой. В приведенных ниже примерах вы заметите ? после имен типов. Этот символ позволяет значению этого типа иметь значение NULL (например, bool? может быть trueили falsenull). Дополнительные сведения см. в разделе "Типы значений, допускающих значение NULL". Начнем с изучения способа использования шаблонов.

Сопоставление значения

Во всех примерах в этом руководстве используются текстовые входные данные, представляющие ряд банковских транзакций в виде входных данных, разделенных запятыми (CSV). В каждом из примеров можно сопоставить запись с шаблоном, используя либо выражение is, либо выражение switch. Этот первый пример разбивает каждую строку на , символ, а затем сопоставляет первое строковое поле со значением "ДЕПОЗИТ" или "ВЫВОД" с помощью 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 гарантирует выполнение обоих этих характеристик, что приводит к меньшим ошибкам в приложениях. Давайте попробуем и поэкспериментируем. Скопируйте следующий код. Замените два 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,
};

После переупорядочения переупорядочения оружия нажмите кнопку "Выполнить". Компилятор выдает ошибку, потому что ветвь с _ совпадает с каждым возможным значением. В результате, этот последний сегмент с TransactionType.Withdrawal никогда не выполняется. Компилятор сообщает, что в коде что-то не так.

Компилятор выдает предупреждение, если выражение, проверенное в switch выражении, может содержать значения, которые не соответствуют ни одной ветви switch. Если некоторые значения не могут соответствовать любому условию, switch выражение не является исчерпывающим. Компилятор также выдает предупреждение, если некоторые значения входных данных не соответствуют ни одной из ветвей оператора switch. Например, при удалении строки с _ => 0.0,недопустимыми значениями не совпадают. Во время выполнения произойдёт ошибка. После установки пакета SDK для .NET и сборки программ в среде можно проверить это поведение. Веб-интерфейс не отображает предупреждения в окне вывода.

Шаблоны типов

Чтобы завершить работу с этим руководством, давайте рассмотрим еще один ключевой элемент для сопоставления шаблонов: типовой шаблон. Шаблон типа проверяет выражение во время выполнения, чтобы узнать, является ли он указанным типом. С выражением is или switch выражением можно использовать тест типа. Давайте изменим текущий пример двумя способами. Во-первых, вместо кортежа давайте создадим Deposit и Withdrawal типы записей, которые представляют транзакции. Добавьте следующие объявления в нижней части интерактивного окна:

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

Наконец, замените foreach цикл в Main методе следующим кодом:

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, чтобы скачать пакет SDK для .NET , создать проект на компьютере и сохранить код. При изучении вы можете узнать больше о сопоставлении шаблонов в C# в следующих статьях: