Condividi tramite


Trovare la corrispondenza dei dati con i modelli

Questo tutorial illustra come usare il match di pattern per esaminare i dati in C#. Si scrivono piccole quantità di codice, quindi si compila ed esegue tale codice. L'esercitazione contiene una serie di lezioni che esplorano diverse categorie di tipi in C#. Queste lezioni illustrano i concetti fondamentali del linguaggio C#.

Suggerimento

Quando un blocco di frammenti di codice include il pulsante "Esegui", il pulsante apre la finestra interattiva o sostituisce il codice esistente nella finestra interattiva. Quando il frammento di codice non include un pulsante "Esegui", è possibile copiare il codice e aggiungerlo alla finestra interattiva corrente.

Le esercitazioni precedenti hanno dimostrato i tipi predefiniti e i tipi che definisci tu stesso come tuple o record. Le istanze di questi tipi possono essere controllate in base a un modello. Se un'istanza corrisponde a un modello determina le azioni eseguite dal programma. Negli esempi seguenti si noterà ? dopo i nomi dei tipi. Questo simbolo consente che il valore di questo tipo sia null (ad esempio, bool? può essere trueo falsenull). Per altre informazioni, vedere Tipi di valore annullabile. Iniziamo a esplorare come usare i modelli.

Abbina un valore

Tutti gli esempi di questa esercitazione usano l'input di testo che rappresenta una serie di transazioni bancarie come input con valori delimitati da virgole (CSV). In ognuno degli esempi è possibile trovare una corrispondenza tra il record e un criterio usando un'espressione is o switch . Questo primo esempio divide ogni riga sul carattere , e quindi confronta il primo campo di stringa con il valore "DEPOSIT" o "WITHDRAWAL" utilizzando un'espressione . Quando corrisponde, l'importo della transazione viene aggiunto o dedotto dal saldo corrente del conto. Per visualizzare il funzionamento, premere il pulsante "Esegui":

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

Esaminare l'output. È possibile notare che ogni riga viene elaborata confrontando il valore del testo nel primo campo. L'esempio precedente potrebbe essere costruito in modo analogo usando l'operatore == per verificare che due string valori siano uguali. Confrontare una variabile con una costante è un elemento fondamentale per la corrispondenza di modelli. Esploriamo insieme altri elementi fondamentali che fanno parte dell'abbinamento di modelli.

Corrispondenze delle enumerazioni

Un altro uso comune per la corrispondenza dei criteri è la corrispondenza con i valori di un enum tipo. Questo esempio successivo elabora i record di input per creare una tupla in cui il primo valore è un enum valore che annota un deposito o un prelievo. Il secondo valore è il valore della transazione. Per visualizzare il funzionamento, premere il pulsante "Esegui":

Avvertimento

Non copiare e incollare. Per eseguire gli esempi seguenti, è necessario reimpostare la finestra interattiva. Se si commette un errore, la finestra si blocca ed è necessario aggiornare la pagina per continuare.

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
}

Nell'esempio precedente viene usata anche un'istruzione if per controllare il valore di un'espressione enum . Un'altra forma di corrispondenza di schemi utilizza un'espressione switch. Esploriamo la sintassi e vediamo come usarla.

Corrispondenze complete con switch

Una serie di if espressioni può testare una serie di condizioni. Tuttavia, il compilatore non è in grado di stabilire se una serie di if istruzioni è esaustiva o se le condizioni successive if vengono sommate da condizioni precedenti. L'espressione switch garantisce che entrambe le caratteristiche vengano soddisfatte, con un minor numero di bug nelle app. Proviamoci e sperimentiamo. Copiare il codice seguente. Sostituire le due if istruzioni nella finestra interattiva con l'espressione switch copiata. Dopo aver modificato il codice, premere il pulsante "Esegui" nella parte superiore della finestra interattiva per eseguire il nuovo esempio.

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

Quando si esegue il codice, si noterà che funziona allo stesso modo. Per dimostrare la sussunzione, riordinare i rami dello switch come mostrato nel frammento di codice seguente.

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

Dopo aver riordinato le leve del commutatore, premi il pulsante "Esegui". Il compilatore genera un errore perché il arm con _ corrisponde a ogni valore. Di conseguenza, quel braccio finale con TransactionType.Withdrawal non viene mai eseguito. Il compilatore indica che nel codice si è verificato un errore.

Il compilatore genera un avviso se l'espressione testata in un'espressione switch potrebbe contenere valori che non corrispondono ad alcun arm switch. Se alcuni valori potrebbero non corrispondere a qualsiasi condizione, l'espressione switch non è esaustiva. Il compilatore genera anche un avviso se alcuni valori dell'input non corrispondono ad alcuna delle braccia del commutatore. Ad esempio, se si rimuove la riga con _ => 0.0,, tutti i valori non validi non corrispondono. In fase di esecuzione, l'operazione avrà esito negativo. Dopo aver installato .NET SDK e aver compilato programmi nell'ambiente, è possibile testare questo comportamento. L'esperienza online non visualizza avvisi nella finestra di output.

Modelli di tipo

Per completare questa esercitazione, esaminiamo un'altra componente fondamentale per la corrispondenza delle strutture: il modello di tipo. Un criterio di tipo verifica un'espressione in fase di esecuzione per verificare se è il tipo specificato. È possibile usare un test di tipo con un'espressione is o un'espressione switch . Modifichiamo l'esempio corrente in due modi. Prima di tutto, invece di una tupla, creiamo i tipi di record Deposit e Withdrawal che rappresentano le transazioni. Aggiungere le dichiarazioni seguenti nella parte inferiore della finestra interattiva:

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

Aggiungere quindi questo metodo dopo il Main metodo per analizzare il testo e restituire una serie di record:

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

Sostituire infine il foreach ciclo nel Main metodo con il codice seguente:

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

Premere quindi il pulsante "Esegui" per visualizzare i risultati. Questa versione finale testa l'input rispetto a un tipo.

Il pattern matching fornisce un vocabolario per confrontare un'espressione con delle proprietà. I modelli possono includere il tipo dell'espressione, i valori di tipi, i valori delle proprietà e le combinazioni di tali tipi. Il confronto delle espressioni con un modello può essere più chiaro di molti if confronti. Sono stati esaminati alcuni dei modelli che è possibile usare per trovare le corrispondenze con le espressioni. Esistono molti altri modi per utilizzare il matching dei pattern nelle vostre applicazioni. Per prima cosa, visitare il sito .NET per scaricare .NET SDK, creare un progetto nel computer e continuare a scrivere codice. Durante l'esplorazione, è possibile ottenere altre informazioni sui criteri di ricerca in C# negli articoli seguenti: