Partilhar via


Fazer a correspondência de dados com padrões

Este tutorial ensina como usar a correspondência de padrões para inspecionar dados em C#. Você escreve pequenas quantidades de código e, em seguida, compila e executa esse código. O tutorial contém uma série de lições que exploram diferentes tipos de tipos em C#. Estas lições ensinam os fundamentos da linguagem C#.

Sugestão

Quando um bloco de trecho de código inclui o botão "Executar", esse botão abre a janela interativa ou substitui o código existente na janela interativa. Quando o trecho não inclui um botão "Executar", você pode copiar o código e adicioná-lo à janela interativa atual.

Os tutoriais anteriores demonstraram tipos internos e tipos que você define como tuplas ou registros. Instâncias desses tipos podem ser verificadas em relação a um padrão. Se uma instância corresponde a um padrão determina as ações que o programa executa. Nos exemplos abaixo, irá notar ? após os nomes dos tipos. Este símbolo permite que o valor deste tipo seja nulo (por exemplo, bool? pode ser true, false ou null). Para obter mais informações, consulte Tipos de valor anulável. Vamos começar a explorar como você pode usar padrões.

Corresponder a um valor

Todos os exemplos neste tutorial usam entrada de texto que representa uma série de transações bancárias como entrada CSV (valores separados por vírgula). Em cada uma das amostras, pode-se fazer a correspondência entre o registo e um padrão usando uma expressão is ou switch. Este primeiro exemplo divide cada linha no , caractere e, em seguida, faz a correspondência entre o primeiro campo de cadeia de caracteres e o valor "DEPOSIT" ou "WITHDRAWAL" usando uma is expressão. Quando corresponde, o valor da transação é adicionado ou deduzido do saldo da conta corrente. Para vê-lo funcionar, pressione o botão "Executar":

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

Examina a saída. Você pode ver que cada linha é processada comparando o valor do texto no primeiro campo. A amostra anterior pode ser construída de forma semelhante usando o == operador para testar se dois string valores são iguais. Comparar uma variável com uma constante é um bloco de construção básico para correspondência de padrões. Vamos explorar mais dos blocos de construção que fazem parte da correspondência de padrões.

Jogos de Enum

Outro uso comum para correspondência de padrões é a correspondência com os valores de um tipo enum. Este próximo exemplo processa os registros de entrada para criar uma tupla onde o primeiro valor é um enum valor que registra um depósito ou um saque. O segundo valor é o valor da transação. Para vê-lo funcionar, pressione o botão "Executar":

Advertência

Não copie e cole. A janela interativa deve ser redefinida para executar os exemplos a seguir. Se você cometer um erro, a janela trava e você precisa atualizar a página para continuar.

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
}

O exemplo anterior também usa uma if instrução para verificar o valor de uma enum expressão. Outra forma de correspondência de padrões usa uma switch expressão. Vamos explorar essa sintaxe e como você pode usá-la.

Correspondências exaustivas com switch

Uma série de if declarações pode testar uma série de condições. Mas, o compilador não pode dizer se uma série de if instruções sejam exaustivas ou se condições if posteriores sejam subsumidas por condições anteriores. A switch expressão garante que ambas as características sejam atendidas, o que resulta em menos bugs em seus aplicativos. Vamos tentar e experimentar. Copie o código a seguir. Substitua as duas instruções if na janela interativa pelas switch expressões copiadas. Depois de modificar o código, pressione o botão "Executar" na parte superior da janela interativa para executar o novo exemplo.

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

Quando você executa o código, você vê que ele funciona da mesma forma. Para demonstrar a subsunção, reordene os braços do interruptor conforme mostrado no seguinte trecho:

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

Depois de reordenar os braços do interruptor, pressione o botão "Executar". O compilador gera um erro porque o ramo com _ corresponde a todos os valores. Como resultado, esse braço final com TransactionType.Withdrawal nunca é executado. O compilador informa que algo está errado em seu código.

O compilador emite um aviso se a expressão testada em uma switch expressão pode conter valores que não correspondem a nenhum braço de switch. Se alguns valores podem não corresponder a qualquer condição, a switch expressão não é exaustiva. O compilador também emite um aviso se alguns valores da entrada não corresponderem a nenhum dos braços do switch. Por exemplo, se você remover a linha com _ => 0.0,, quaisquer valores inválidos não corresponderão. Em tempo de execução, isso falharia. Depois de instalar o SDK do .NET e criar programas em seu ambiente, você pode testar esse comportamento. A experiência online não exibe avisos na janela de saída.

Padrões de tipo

Para concluir este tutorial, vamos explorar mais um bloco de construção para a correspondência de padrões: o padrão de tipo. Um padrão de tipo testa uma expressão em tempo de execução para ver se é o tipo especificado. Você pode usar um teste de tipo com uma is expressão ou uma switch expressão. Vamos modificar o exemplo atual de duas maneiras. Primeiro, em vez de uma tupla, vamos construir Deposit e Withdrawal tipos de registo que representam as transações. Adicione as seguintes declarações na parte inferior da janela interativa:

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

Em seguida, adicione este método após o Main método para analisar o texto e retornar uma série de registros:

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

Finalmente, substitua o ciclo foreach no método Main pelo seguinte código:

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

Em seguida, pressione o botão "Executar" para ver os resultados. Esta versão final testa a entrada em relação a um tipo.

A correspondência de padrões fornece um vocabulário para comparar uma expressão com características. Os padrões podem incluir o tipo da expressão, valores de tipos, valores de propriedade e combinações deles. Comparar expressões com um padrão pode ser mais claro do que comparações múltiplas if . Você explorou alguns dos padrões que pode usar para combinar expressões. Há muitas outras maneiras de usar a correspondência de padrões em seus aplicativos. Primeiro, visite o site do .NET para baixar o SDK do .NET, criar um projeto em sua máquina e continuar codificando. Ao explorar, você pode aprender mais sobre a correspondência de padrões em C# nos seguintes artigos: