Compartilhar via


Tutorial: Usar C# para corresponder 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 padrões compatíveis com o C#. Essas lições ensinam os conceitos básicos da linguagem C#.

Neste tutorial, você:

  • Inicie um Codespace do GitHub com um ambiente de desenvolvimento em C#.
  • Teste dados para valores discretos.
  • Corresponder dados de enumeração com o valor.
  • Crie correspondências exaustivas usando expressões switch.
  • Corresponda tipos utilizando padrões de tipo.

Pré-requisitos

Você deve ter um dos seguintes:

Igualar a um valor

Os tutoriais anteriores demonstraram tipos embutidos e tipos que você define como tuplas ou tipos de registros. Você pode verificar instâncias desses tipos 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ê verá os nomes de tipo seguidos por ?. Esse símbolo permite que o valor desse tipo seja nulo (por exemplo, bool? pode ser true, falseou null). Para obter mais informações, consulte tipos de valor anuláveis. Vamos começar a explorar como você pode usar padrões.

Abra uma janela do navegador e acesse os codespaces do GitHub. Crie um novo codespace com base no Modelo do .NET. Se você concluiu outros tutoriais nesta série, poderá abrir esse codespace.

  1. Quando o codespace for carregado, crie um novo arquivo na pasta de tutoriais chamada patterns.cs.

  2. Abra seu novo arquivo.

  3. 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 a um padrão usando uma expressão is ou switch. Este primeiro exemplo divide cada linha no caractere , e, em seguida, verifica se o primeiro campo de cadeia de caracteres é igual ao 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, adicione o seguinte código ao patterns.cs:

    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}");
        }
    }
    
  4. Em seguida, digite o seguinte texto na janela do terminal:

    cd tutorials
    dotnet patterns.cs
    
  5. Verifique a saída. Você pode ver que cada linha é processada comparando o valor do texto no primeiro campo.

Da mesma forma, você pode construir o exemplo anterior 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 é a correspondência nos valores de um enum tipo. O exemplo a seguir processa os registros de entrada para criar uma tupla onde o primeiro valor é um enum valor que indica se é um depósito ou um saque. O segundo valor é o valor da transação.

  1. Adicione o código a seguir ao final do arquivo de origem. Ela define a TransactionType enumeração:

    public enum TransactionType
    {
        Deposit,
        Withdrawal,
        Invalid
    }
    
  2. Adicione uma função para analisar uma transação bancária em uma tupla que contém o tipo de transação e o valor da transação. Adicione o seguinte código antes da declaração da TransactionType enumeração:

    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);
            }
        }
    }
    
  3. Adicione um novo loop para processar os dados da transação usando a TransactionType enumeração que você declarou:

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

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. Exaustivo significa que uma das cláusulas if ou else na série de testes lida com todas as entradas possíveis. Se uma série de if declarações é abrangente, toda entrada possível satisfaz pelo menos uma cláusula if ou else. Subsunção significa que uma cláusula posterior if ou else não pode ser alcançada porque cláusulas anteriores if ou else correspondem a todas as entradas possíveis. Por exemplo, no código de exemplo a seguir, uma cláusula nunca corresponde:

int n = GetNumber();

if (n < 20)
    Console.WriteLine("n is less than 20");
else if (n < 10)
    Console.WriteLine("n is less than 10"); // unreachable
else
    Console.WriteLine("n is greater than 20");

A else if cláusula nunca corresponde porque cada número menor que 10 também é menor que 20. 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.

  1. Copie o código a seguir. Substitua as duas instruções if em seu loop foreach pela expressão switch que você copiou.

    currentBalance += transaction switch
    {
        (TransactionType.Deposit, var amount) => amount,
        (TransactionType.Withdrawal, var amount) => -amount,
        _ => 0.0,
    };
    
  2. Digite dotnet patterns.cs na janela do terminal para executar o novo exemplo.

    Ao executar o código, você verá que ele funciona da mesma forma.

  3. 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 switch, digite dotnet patterns.cs na janela do terminal. 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.

  4. Remova a linha com _ => 0.0,, para que os valores inválidos não correspondam.

  5. Digite dotnet patterns.cs para ver os resultados.

    O compilador emite um aviso. Os dados de teste são válidos, portanto, o programa funciona. No entanto, qualquer dado inválido causaria uma falha no runtime.

Padrões de tipo

Para concluir este tutorial, explore 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. Modifique o exemplo atual de duas maneiras. Primeiro, em vez de uma tupla, construa tipos de registro Deposit e Withdrawal que representem as transações.

  1. Adicione as seguintes declarações no final do arquivo de código:

    public record Deposit(double Amount, string description);
    public record Withdrawal(double Amount, string description);
    
  2. Adicione esse método pouco antes da declaração da TransactionType enumeração. Ele analisa o texto e retorna uma série de registros:

    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;
        }
    }
    
  3. Adicione o seguinte código após o último foreach loop:

    currentBalance = 0.0;
    
    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}");
    }
    
  4. Digite dotnet patterns.cs na janela do terminal 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. Ao explorar, você pode saber mais sobre a correspondência de padrões em C# nos seguintes artigos:

Recursos de limpeza

O GitHub exclui automaticamente o Codespace após 30 dias de inatividade. Você concluiu todos os tutoriais desta série. Para excluir seu Codespace agora, abra uma janela do navegador e vá para seus Codespaces. Você deve ver uma lista de seus codespaces na janela. Selecione os três pontos (...) na entrada para o codespace do tutorial de aprendizado e selecione excluir.