BLOG

Achtergronden

Krijg meer gedaan met C# pattern matching

16 november 2023

C# bestaat inmiddels ruim twintig jaar en is in die tijd flink verder geëvolueerd met diverse language features. Denk aan Generics, Async & await, Linq, auto properties, Records, Top level statements, global usings, etc. Code die in de begintijd geschreven is zal overigens nog prima compileren en functioneren, maar de ontwikkeling van C# heeft de afgelopen jaren zeker niet stilgestaan. Als je jaren geleden begonnen bent met C# en je kunt alles oplossen met de syntax die je gewend bent, dan heb je mogelijk de nieuwe features een beetje over het hoofd gezien. Terwijl er zeer interessante en productieve mogelijkheden zijn toegevoegd. Een mooi voorbeeld daarvan is het Pattern matching dat vaak gebruikt wordt in combinatie met switch statements.

Switch syntax

Laten we eerst nog eens kijken naar de notatie van het switch statement. Net als bij methodes bijvoorbeeld, is het mogelijk om een hoop accolades en andere boilerplate code weg te laten door gebruik te maken van de arrow notation oftewel “switch expressions” die in C# 8 geïntroduceerd zijn.

Hieronder zie je twee keer dezelfde implementatie, maar de switch expression is veel korter en leesbaarder dan het originele switch statement.

De code zal zichzelf redelijk verklaren, maar misschien nog even aandacht voor de “_” underscore in de onderste switch. De underscore in C# is een discard. Een discard is een opzettelijk ongebruikte variabele. Deze kun je bijvoorbeeld gebruiken wanneer je een methode aanroept, maar niet geïnteresseerd bent in de return value. In de switch statement is een discard een match op alles (inclusief null). Dus als number niet matcht met 1 of 2, dan matcht het met de discard en zal “unknown” retourneren, vergelijkbaar met de default in het bovenste voorbeeld. Discards zullen we later ook nog op een andere manier terug zien bij list pattern matching.

Type pattern matching

Wat is pattern matching? Met pattern matching ga je kijken of een expressie (bijvoorbeeld een variabele) voldoet aan bepaalde karakteristieken. Een karakteristiek kan een waarde zijn, maar ook andere eigenschappen van de expressie. In het bovenstaande voorbeeld vergelijken we een variabele met 1 waarde. Maar je kunt dus ook matchen op andere karakteristieken, bijvoorbeeld of de variabele van een bepaald type is.

Afhankelijk van het type van de variabele item, wordt een andere methode aangeroepen. Door achter het type een naam te zetten ontstaat er automatisch een variabele van dit type. Dus in de variabele someType zit een referentie naar hetzelfde object als in de variabele item, maar dan gecast naar het type SomeType.

Relational en Logical pattern matching

Wat als we niet op 1 specifieke waarde willen controleren, maar of een expressie groter of kleiner is dan een bepaalde waarde? Met relational pattern matching is dat mogelijk. In het onderstaande voorbeeld vergelijken we een expressie, in dit geval result, door middel van <, >, <=, >=  statements. Dit kunnen we dan combineren met logical pattern matching, waarbij we met “and” en “or” combinaties kunnen maken van matches en op die manier kunnen we controleren of de waarde binnen een range valt. 

Property pattern matching

Tot nu toe hebben we gekeken naar eenvoudige waardes. Door middel van Property pattern matching kunnen we ook properties gebruiken van de objecten die we onder de loep nemen. In het voorbeeld hieronder kijken we bijvoorbeeld naar de Length property van de string name.

Dit kunnen we nog een stap verder brengen door een complexer type te bekijken. In het voorbeeld hieronder gebruiken we een Order object. Deze order bevat de properties Cost en Items. De property Items is een Array van het Type OrderDetails wat op zijn beurt ook weer 2 properties heeft.


Bij het pattern matching kunnen we die properties gebruiken om business rules uit te voeren. Hieronder zie je bijvoorbeeld een manier waarbij de korting berekend wordt op basis van het aantal orderdetails en de waarde van Cost.

Positional pattern matching

In bovenstaande voorbeelden zijn we telkens uitgegaan van 1 expressie waar we een patroon op willen matchen. Het is ook mogelijk om hiervoor meerdere inputs te gebruiken, door middel van positional pattern matching. De verschillende inputs vormen samen feitelijk een tuple en op de posities binnen deze tuple kunnen we dan het patroon matchen.

In het onderstaande voorbeeld hebben we 2 variabelen groupSize en visitDate. De eerste input voor de switch is de groupSize en van de visitDate gebruiken we de DayOfWeek property als tweede input voor de switch.

In de match patterns zien we nu dus ook 2 posities waarop gematcht wordt.

  • Als de groep kleiner of gelijk is aan 0, ongeacht de dag van de week, dan is er een foutmelding.
  • Ongeacht de groepsgrootte, in het weekend geen korting.
  • Groepen tussen de 5 en 9 personen krijgen op maandag 20% korting en grotere groepen zelfs 30%.
  • Tussen de 5 en 9 personen krijg je op de overige dagen 12% korting en met grotere groepen 15%.
  • In alle andere gevallen krijg je geen korting.

Aangezien er maar 1 “;” puntkomma staat, kun je stellen dat in deze one-liner een behoorlijk geavanceerd stukje business logic is opgenomen.

Var pattern matching

We kunnen het positional pattern matching combineren met het var pattern matching. Door het keyword var te gebruiken ontstaat er een nieuwe variabele die je vervolgens kunt gebruiken. Bijvoorbeeld om tussentijdse berekeningen op te slaan en te gebruiken in de match. We hebben eerder gezien dat de order 2 properties bevat. De eerste property slaan we op in de variabele items en de tweede property in de variabele cost. De Amount property van de items wordt vervolgens gebruikt om te aggregeren en te vergelijken met de cost variabele.

Voor de volledigheid nog een korte uitleg van het statement achter de => arrow.

Order is gedefinieerd als Record. Door middel van het “with” keyword creëer je een copy van order, maar wordt de cost property vervangen door een nieuwe waarde. In het eerste geval dus met de som van de bedragen en in het tweede geval met 0. Op deze manier hebben we eenvoudig een transformatie uitgevoerd van het order object. 

List pattern matching

Naast waardes en objecten kunnen we ook collecties matchen op bepaalde patronen. Dit noemen we List pattern matching. Deze mogelijkheid is toegevoegd in C# 11. Hieronder zie je hoe we een array numbers matchen met een vijftal patronen. Je ziet dat dit te combineren is met de relational en logical pattern matching waardoor geavanceerde patronen gecreëerd kunnen worden.

Dit werkt uiteraard ook met andere types dan getallen. Hieronder zie je een voorbeeld met strings. Bij het tweede patroon wordt de discard “_” gebruikt om één enkele waarde aan te geven. De 2 punten “..” is de range operator waarbij 0 of meer items verwacht worden. Bij het derde patroon wordt de range tussen het eerste en het laatste item opgeslagen in de variabele tussenvoegsels, die we achter de arrow weer kunnen gebruiken.

Hieronder zie je welke output hoort bij de verschillende aanroepen van deze ProcessName methode.


De laatste stap die we gaan bespreken is een voorbeeld waarbij we een CSV (Comma Separated Value) gaan parsen. En de input data is ook nog eens van slechte kwaliteit. Zo hebben regels met een “WITHDRAWAL” extra kolommen, terwijl regels met “Interest” en “FEE” juist minder kolommen hebben. Toch gaan we onderstaande CSV verwerken door de bedragen op te tellen of juist af te trekken en zo het saldo te berekenen.

In onderstaande methode worden de regels opgesplitst en gescheiden op komma. Door middel van list pattern matching wordt gecontroleerd welke bewerking het betreft en wordt de laatste waarde in de variabele amount opgeslagen. Deze wordt vervolgens geparst naar een decimaal en opgeteld bij de variabele balance.



Super krachtig en goed leesbaar.

Hopelijk heeft dit overzicht je een goed idee gegeven van de kracht van pattern matching. Door het inzetten van pattern matching maak je leesbare en onderhoudbare code. Bij bijna elke versie van C# worden er weer nieuwe pattern matching features toegevoegd. In ons vak zijn we dus nooit uitgeleerd. En dat is precies wat het zo mooi maakt!

De voorbeelden in deze blog zijn ook terug te vinden op GitHub.

 

Meer informatie over Microsoft oplossingen, C# of .NET?

Neem dan contact op met Christian. Hij wil je er graag alles over vertellen.

Interesse in een gesprek?

neem contact op met Christian Peeters

Laat uw gegevens achter

We nemen contact met u op!



Zie onze privacyverklaring.