Overzicht van patroonkoppeling

Patroonkoppeling is een techniek waarbij u een expressie test om te bepalen of deze bepaalde kenmerken heeft. C#-patroonkoppeling biedt beknoptere syntaxis voor het testen van expressies en het ondernemen van actie wanneer een expressie overeenkomt. De expressie ondersteuntis patroonkoppeling om een expressie te testen en een nieuwe variabele voorwaardelijk te declareren voor het resultaat van die expressie. switch Met de expressie kunt u acties uitvoeren op basis van het eerste overeenkomende patroon voor een expressie. Deze twee expressies ondersteunen een rijke vocabulaire aan patronen.

Dit artikel bevat een overzicht van scenario's waarin u patroonkoppeling kunt gebruiken. Deze technieken kunnen de leesbaarheid en juistheid van uw code verbeteren. Zie het artikel over patronen in de taalreferentie voor een volledige bespreking van alle patronen die u kunt toepassen.

Null-controles

Een van de meest voorkomende scenario's voor patroonkoppeling is ervoor te zorgen dat waarden niet nullzijn. U kunt een waardetype met null-waarde testen en converteren naar het onderliggende type tijdens het testen null voor het volgende voorbeeld:

int? maybe = 12;

if (maybe is int number)
{
    Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{
    Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}

De voorgaande code is een declaratiepatroon om het type van de variabele te testen en deze toe te wijzen aan een nieuwe variabele. De taalregels maken deze techniek veiliger dan vele andere. De variabele number is alleen toegankelijk en toegewezen in het werkelijke gedeelte van de if component. Als u deze ergens anders probeert te openen, in de else component of na het if blok, treedt er een fout op in de compiler. Ten tweede, omdat u de == operator niet gebruikt, werkt dit patroon wanneer een type de == operator overbelast. Dit maakt het een ideale manier om null-verwijzingswaarden te controleren, waarbij het not patroon wordt toegevoegd:

string? message = ReadMessageOrDefault();

if (message is not null)
{
    Console.WriteLine(message);
}

In het voorgaande voorbeeld is een constant patroon gebruikt om de variabele te vergelijken met null. Het not is een logisch patroon dat overeenkomt wanneer het negated patroon niet overeenkomt.

Typetests

Een ander veelvoorkomend gebruik voor patroonkoppeling is het testen van een variabele om te zien of deze overeenkomt met een bepaald type. Met de volgende code wordt bijvoorbeeld getest of een variabele niet null is en de System.Collections.Generic.IList<T> interface wordt geïmplementeerd. Als dit het geval is, wordt de ICollection<T>.Count eigenschap in die lijst gebruikt om de middelste index te vinden. Het declaratiepatroon komt niet overeen met een null waarde, ongeacht het type compileertijd van de variabele. De onderstaande code beveiligt naast nullhet beschermen tegen een type dat niet wordt geïmplementeerd IList.

public static T MidPoint<T>(IEnumerable<T> sequence)
{
    if (sequence is IList<T> list)
    {
        return list[list.Count / 2];
    }
    else if (sequence is null)
    {
        throw new ArgumentNullException(nameof(sequence), "Sequence can't be null.");
    }
    else
    {
        int halfLength = sequence.Count() / 2 - 1;
        if (halfLength < 0) halfLength = 0;
        return sequence.Skip(halfLength).First();
    }
}

Dezelfde tests kunnen in een switch expressie worden toegepast om een variabele te testen op meerdere verschillende typen. U kunt deze informatie gebruiken om betere algoritmen te maken op basis van het specifieke runtimetype.

Discrete waarden vergelijken

U kunt ook een variabele testen om een overeenkomst op specifieke waarden te vinden. In de volgende code ziet u een voorbeeld waarin u een waarde test op basis van alle mogelijke waarden die in een opsomming zijn gedeclareerd:

public State PerformOperation(Operation command) =>
   command switch
   {
       Operation.SystemTest => RunDiagnostics(),
       Operation.Start => StartSystem(),
       Operation.Stop => StopSystem(),
       Operation.Reset => ResetToReady(),
       _ => throw new ArgumentException("Invalid enum value for command", nameof(command)),
   };

In het vorige voorbeeld ziet u een methodeverzending op basis van de waarde van een opsomming. Het laatste _ geval is een verwijderingspatroon dat overeenkomt met alle waarden. Het verwerkt eventuele foutvoorwaarden waarbij de waarde niet overeenkomt met een van de gedefinieerde enum waarden. Als u die schakelarm weglaat, waarschuwt de compiler dat uw patroonexpressie niet alle mogelijke invoerwaarden verwerkt. Tijdens runtime genereert de switch expressie een uitzondering als het object dat wordt onderzocht niet overeenkomt met een van de schakelarmen. U kunt numerieke constanten gebruiken in plaats van een set enumwaarden. U kunt deze vergelijkbare techniek ook gebruiken voor constante tekenreekswaarden die de opdrachten vertegenwoordigen:

public State PerformOperation(string command) =>
   command switch
   {
       "SystemTest" => RunDiagnostics(),
       "Start" => StartSystem(),
       "Stop" => StopSystem(),
       "Reset" => ResetToReady(),
       _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
   };

In het voorgaande voorbeeld ziet u hetzelfde algoritme, maar worden tekenreekswaarden gebruikt in plaats van een opsomming. U gebruikt dit scenario als uw toepassing reageert op tekstopdrachten in plaats van een normale gegevensindeling. Vanaf C# 11 kunt u ook een Span<char> of a ReadOnlySpan<char>gebruiken om te testen op constante tekenreekswaarden, zoals wordt weergegeven in het volgende voorbeeld:

public State PerformOperation(ReadOnlySpan<char> command) =>
   command switch
   {
       "SystemTest" => RunDiagnostics(),
       "Start" => StartSystem(),
       "Stop" => StopSystem(),
       "Reset" => ResetToReady(),
       _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
   };

In al deze voorbeelden zorgt het verwijderingspatroon ervoor dat u elke invoer verwerkt. De compiler helpt u door ervoor te zorgen dat elke mogelijke invoerwaarde wordt verwerkt.

Relationele patronen

U kunt relationele patronen gebruiken om te testen hoe een waarde zich verhoudt tot constanten. De volgende code retourneert bijvoorbeeld de toestand van water op basis van de temperatuur in Fahrenheit:

string WaterState(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        (> 32) and (< 212) => "liquid",
        < 32 => "solid",
        > 212 => "gas",
        32 => "solid/liquid transition",
        212 => "liquid / gas transition",
    };

De voorgaande code demonstreert ook het logische patroon van het cluster om andte controleren of beide relationele patronen overeenkomen. U kunt ook een disjunctive-patroon or gebruiken om te controleren of beide patroons overeenkomen. De twee relationele patronen worden omgeven door haakjes, die u kunt gebruiken rond elk patroon voor duidelijkheid. De laatste twee schakelarmen verwerken de zaken voor het smeltpunt en het kookpunt. Zonder die twee armen waarschuwt de compiler u dat uw logica niet alle mogelijke invoer bedekt.

De voorgaande code demonstreert ook een andere belangrijke functie die de compiler biedt voor patroonkoppelingsexpressies: de compiler waarschuwt u als u niet elke invoerwaarde verwerkt. De compiler geeft ook een waarschuwing uit als het patroon voor een schakelarm wordt gedekt door een eerder patroon. Dat geeft u vrijheid om switchexpressies te herstructureren en opnieuw te ordenen. Een andere manier om dezelfde expressie te schrijven, kan het volgende zijn:

string WaterState2(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        < 32 => "solid",
        32 => "solid/liquid transition",
        < 212 => "liquid",
        212 => "liquid / gas transition",
        _ => "gas",
};

De belangrijkste les in het voorgaande voorbeeld en andere herstructureringen of volgordes is dat de compiler valideert dat uw code alle mogelijke invoer verwerkt.

Meerdere invoer

Alle patronen die tot nu toe zijn behandeld, hebben één invoer gecontroleerd. U kunt patronen schrijven die meerdere eigenschappen van een object onderzoeken. Houd rekening met de volgende Order record:

public record Order(int Items, decimal Cost);

Het voorgaande positionele recordtype declareert twee leden op expliciete posities. Als eerste wordt weergegeven, is de Items, dan de volgorde Cost. Zie Records voor meer informatie.

De volgende code onderzoekt het aantal items en de waarde van een order om een kortingsprijs te berekenen:

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        { Items: > 10, Cost: > 1000.00m } => 0.10m,
        { Items: > 5, Cost: > 500.00m } => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

De eerste twee armen onderzoeken twee eigenschappen van de Order. De derde onderzoekt alleen de kosten. De volgende controles en nullde finale komt overeen met een andere waarde. Als het Order type een geschikte Deconstruct methode definieert, kunt u de eigenschapsnamen weglaten uit het patroon en deconstruction gebruiken om eigenschappen te onderzoeken:

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        ( > 10,  > 1000.00m) => 0.10m,
        ( > 5, > 50.00m) => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

In de voorgaande code ziet u het positionele patroon waarin de eigenschappen voor de expressie worden gedeconstrueerd.

Lijstpatronen

U kunt elementen in een lijst of matrix controleren met behulp van een lijstpatroon. Een lijstpatroon biedt een manier om een patroon toe te passen op een element van een reeks. Daarnaast kunt u het verwijderingspatroon (_) toepassen op overeenkomst met elk element of een segmentpatroon toepassen op nul of meer elementen.

Lijstpatronen zijn een waardevol hulpmiddel wanneer gegevens geen normale structuur volgen. U kunt patroonkoppeling gebruiken om de shape en waarden van de gegevens te testen in plaats van deze te transformeren in een set objecten.

Bekijk het volgende fragment uit een tekstbestand met banktransacties:

04-01-2020, DEPOSIT,    Initial deposit,            2250.00
04-15-2020, DEPOSIT,    Refund,                      125.65
04-18-2020, DEPOSIT,    Paycheck,                    825.65
04-22-2020, WITHDRAWAL, Debit,           Groceries,  255.73
05-01-2020, WITHDRAWAL, #1102,           Rent, apt, 2100.00
05-02-2020, INTEREST,                                  0.65
05-07-2020, WITHDRAWAL, Debit,           Movies,      12.57
04-15-2020, FEE,                                       5.55

Het is een CSV-indeling, maar sommige rijen hebben meer kolommen dan andere. Nog erger voor verwerking bevat één kolom in het type door de WITHDRAWAL gebruiker gegenereerde tekst en kan een komma in de tekst bevatten. Een lijstpatroon dat het verwijderingspatroon, het constante patroon en het var-patroon bevat om de waardegegevens in deze indeling vast te leggen:

decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
{
    balance += transaction switch
    {
        [_, "DEPOSIT", _, var amount]     => decimal.Parse(amount),
        [_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
        [_, "INTEREST", var amount]       => decimal.Parse(amount),
        [_, "FEE", var fee]               => -decimal.Parse(fee),
        _                                 => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"),
    };
    Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}");
}

In het voorgaande voorbeeld wordt een tekenreeksmatrix gebruikt, waarbij elk element één veld in de rij is. De switch expressiesleutels in het tweede veld, waarmee het soort transactie en het aantal resterende kolommen worden bepaald. Elke rij zorgt ervoor dat de gegevens de juiste indeling hebben. Met het verwijderingspatroon (_) wordt het eerste veld overgeslagen, met de datum van de transactie. Het tweede veld komt overeen met het type transactie. Het resterende element komt overeen met het veld met het bedrag. De laatste overeenkomst maakt gebruik van het var-patroon om de tekenreeksweergave van de hoeveelheid vast te leggen. De expressie berekent het bedrag dat moet worden optellen of afgetrokken van het saldo.

Met lijstpatronen kunt u overeenkomen met de vorm van een reeks gegevenselementen. U gebruikt de patronen voor verwijderen en segmenten om overeen te komen met de locatie van elementen. U gebruikt andere patronen om kenmerken van afzonderlijke elementen te vergelijken.

In dit artikel vindt u een rondleiding door de soorten code die u kunt schrijven met patroonkoppeling in C#. In de volgende artikelen ziet u meer voorbeelden van het gebruik van patronen in scenario's en de volledige woordenlijst met patronen die beschikbaar zijn voor gebruik.

Zie ook