Compartilhar via


Comparar 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#. Essas lições ensinam os conceitos básicos da linguagem C#.

Dica

Quando um bloco de snippet 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 snippet não inclui um botão "Executar", você pode copiar o código e adicioná-lo à janela interativa atual.

Os tutoriais anteriores demonstraram tipos embutidos e tipos que você define como tuplas ou tipos de 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 seu programa executa. Nos exemplos abaixo, você observará ? após os nomes de tipo. Esse símbolo permite que o valor desse tipo seja nulo (por exemplo, bool? pode ser true, false ou null). Para obter mais informações, consulte tipos de valor anuláveis. Vamos começar a explorar como você pode usar padrões.

Igualar a um valor

Todos os exemplos neste tutorial usam a entrada de texto que representa uma série de transações bancárias como entrada de CSV (valores separados por vírgulas). Em cada uma das amostras, você pode corresponder o registro com um padrão usando uma expressão is ou uma expressão switch. Este primeiro exemplo divide cada linha no caractere , e, em seguida, verifica o primeiro campo da string contra o valor "DEPOSIT" ou "WITHDRAWAL" usando uma expressão is. Quando corresponde, o valor da transação é adicionado ou deduzido do saldo da conta atual. 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}");
    }
}

Verifique a saída. Você pode ver que cada linha é processada comparando o valor do texto no primeiro campo. O exemplo anterior pode ser construído da mesma forma 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.

Correspondências de Enum

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

Aviso

Não copie e cole. A janela interativa deve ser redefinida para executar os exemplos a seguir. Se você cometer um erro, a janela será interrompida e você precisará 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 instrução if para verificar o valor de uma expressão enum . Outra forma de correspondência de padrões usa uma switch expressão. Vamos explorar essa sintaxe e como usá-la.

Resultados exaustivos com switch

Uma série de if declarações pode avaliar uma sequência de condições. No entanto, o compilador não pode dizer se uma série de if instruções são exaustivas ou se as condições posteriores if são 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 if declarações na janela interativa pela expressão switch que você copiou. 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,
};

Ao executar o código, você verá que ele funciona da mesma forma. Para demonstrar a subsumpição, reordene os braços do comutador, conforme mostrado no trecho de código a seguir.

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

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

O compilador emitirá um aviso se a expressão testada em uma switch expressão puder conter valores que não correspondem a nenhum braço de comutador. Se alguns valores não corresponderem a qualquer condição, a switch expressão não será exaustiva. O compilador também emitirá um aviso se alguns valores da entrada não corresponderem a nenhum dos braços do comutador. 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ê poderá 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 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 criar Deposit e Withdrawal tipos de registro 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 esse 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;
    }
}

Por fim, substitua o foreach loop no Main método 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 várias if comparações. Você explorou alguns dos padrões que pode usar para corresponder a 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 seu computador e continuar codificando. Ao explorar, você pode saber mais sobre a correspondência de padrões em C# nos seguintes artigos: