Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 12 Min.

C# Pattern Matching für Fortgeschrittene

Die Pattern-Matching-Features von C# im Vergleich mit denen funktionaler Sprachen.
© dotnetpro
Wer funktionale Programmiersprachen kennt, ist auch mit ­Pattern Matching vertraut. Mustervergleich, auf Deutsch gesagt, ist in solchen Sprachen ein Allzweckwerkzeug, das jeder Neuling kennenlernt, ohne darüber im Sinne eines speziellen Features nachzudenken.Pattern Matching wird für Fallunterscheidungen verwendet, aber auch zur Erzeugung von überladenen Funktionen und ähnlichen Zwecken.In manchen Sprachen ist die Syntax der Patterns so weitgehend integriert, dass es auf den ersten Blick schwierig ist, sie separat zu betrachten.Um Ihnen meinen Hintergrund zum Thema zu vermitteln, möchte ich daher eingangs einige Beispiele aus ­verschiedenen anderen Programmiersprachen wie zum Beispiel der Sprache Haskell zeigen.Sollten Sie diese Code-Schnipsel zunächst beängstigend finden, machen Sie sich keine Sorgen. Nachdem Sie reichlich Beispiele in C# gesehen haben, wird Ihnen hoffentlich alles ganz klar!

Funktionaler Hintergrund von Patterns

In Haskell wird Pattern Matching dazu verwendet, mehrere überladene Versionen einer Funktion zu erzeugen. In C# bedeutet Überladung bekanntlich, dass Varianten einer Methode anhand verschiedener Typen der Übergabeparameter selektiv aufgerufen werden.In Haskell hingegen können Überladungen anhand der Inhalte von Parametern ausgewählt werden, das klappt beispielsweise so:

data Food = Pasta | Pizza | Chips 

isEqualFood :: Food -> Food -> Bool 
isEqualFood Pasta Pasta = True 
isEqualFood Pizza Pizza = True 
isEqualFood Chips Chips = True 
isEqualFood _ _ = False 
Dieses Beispiel dient allein der Demonstration, da es in der Praxis natürlich andere Möglichkeiten gibt, die Gleichheitsprüfung für den Datentyp Food zu implementieren.Klar wird allerdings eines: Alle vier Varianten der Funk­tion isEqualFood werden mit zwei Parametern vom Typ Food aufgerufen.In C# ließen sich somit verschiedene Szenarien nicht anhand von Überladung auseinanderhalten, aber in der Programmiersprache Haskell wird je eine Funktion für unterschiedliche inhaltliche Fälle erzeugt.Das gleiche Schema ist auch in wesentlich komplexeren sowie realistischeren Fällen anwendbar:

data Tree a = L 
    | N a (Tree a) (Tree a) 
    deriving(Show) 

insert :: Ord a => Tree a -> a -> Tree a 
insert L v = N v L L 
insert (N nv l r) v 
    | v < nv = N nv (insert l v) r 
    | otherwise = N nv l (insert r v) 
Es kommt nicht darauf an, dass Sie im Detail verstehen, was dieser Algorithmus tut. 
Sie können leicht aus der Syntax erkennen, dass Pattern Matching zur Aufschlüsselung relevanter Fälle bei der Übergabe verwendet wird, und im zweiten Overload von insert findet das sogar auf zwei Ebenen statt.Wie gesagt, der Mechanismus ist allgegenwärtig, und seine Integration in die Sprache tief.Haskell steht in dieser Hinsicht allerdings nicht allein da. In der Sprache Erlang gibt es folgendes Konstrukt zur Verarbeitung eingehender Nachrichten:

loop() -> 
  receive 
    {add, A, B} -> 
      io:format("adding: ~p~n", [A + B]), 
      loop(); 

    {mult, A, B} -> 
      io:format("multiplying: ~p~n", [A * B]), 
      loop() 
  end. 
 
Verschiedene Nachrichtentypen werden dort anhand des ersten Wertes, eines sogenannten Atoms, unterschieden, und die Syntax verwendet Pattern Matching.Als letztes Beispiel sei F# erwähnt, da es sich um eine mit C# kompatible .NET-Sprache handelt:

let rec listLength' = function 
    | [] -> 0 
    | _ :: xs -> 1 + (listLength' xs) 
 
Diese Syntax ist ein weiteres Beispiel für eine enge Integration von Pattern Matching.Wenn Sie genau hinsehen, werden Sie bemerken, dass die Funktion listLength‘ anscheinend gar keinen Parameter hat, auf den sich der algorithmische Match beziehen könnte.Dieser Ausdruck arbeitet so, dass ein Parameter automatisch akzeptiert und verarbeitet wird – er muss nicht namentlich erwähnt werden, da dies lediglich die Struktur verkomplizieren würde.

Wie sieht es in C# aus?

Genug der Einleitung! Mit den obigen Beispielen im Kopf stellte ich mir eine einfache Frage: Kann die Sprache C# das alles heute auch?Wir wissen, dass die Programmiersprache Pattern-Matching-Features hat und immer neue dazukommen, aber sind diese Features tatsächlich kompatibel mit denen aus der Welt ihrer Herkunft?C# hat seit Version 7 regelmäßig neue Pattern-Matching-Features erhalten. Der Anfang war einfach: In C# Version 7 gab es die Möglichkeit, den alten is-Ausdruck mit einem Variablennamen zu ergänzen.is war eigentlich auch schon vorher ein Ausdruck, der Pattern Matching anwandte, aber er war sehr eingeschränkt und wurde allein auf weiter Flur nicht offiziell so eingeordnet. Ab C# 7 also, in switch oder if-Klauseln:

Person p = ... ; 

switch(p) { 
  case Employee e: 
    // ... Zugriff auf Employee Properties: e.... 
    break; 

  case Customer c: 
    // ... nun mit Customer: c... 
    break; 
} 
 
In C# 8 wurde endlich auch die Verwendung von switch als Ausdruck möglich. Ein wichtiger Schritt auf dem Weg zu funktionalen Ideen, denn switch war zuvor nur imperativ nutzbar!

static int CalcResult(int input) => 

  input switch { 
   1 => 2, 
   2 => 3, 
   _ => throw new ArgumentException("Rien ne va plus") 

  }; 
 
Der Umschwung in Richtung von Ausdrücken war auch an dem Schlüsselwort throw für das Werfen einer Exceptions erkennbar, das nun ebenfalls außerhalb eines rein imperativen Kontexts verwendbar war.Zusätzlich können Sie an dem Beispiel ablesen, dass ein Pattern Match nun mit einem int-Wert möglich war.Und nicht nur das, auch Objekt- sowie Tupelstrukturen wurden unterstützt, zusammen mit der Extraktion von interessanten Pattern-Teilen mit dem var-Schlüsselwort.

bool IsJune(DateTime dt) => dt is { Month: 6 }; 

public class Address 
{ 
  public string? City { get; set; } 
  public string? Country { get; set; } 
} 

public class Customer 
{ 
  public Address? Address { get; set; } 
} 
... 

if (customer is { Address: { City: "Castle Douglas" } }) { 
  ... 
} 
(string, string) person = ("Oli", "Sturm"); 
if (personInfo is ("Oli", var olisLastName)) 

  Console.WriteLine($"Oli gefunden, " +
    "sein Nachname ist {olisLastName}."); 
 
Das war schon sehr interessant, und es wurde mit C# 9 noch wesentlich flexibler, da zu dem Zeitpunkt mehrere neue Operatoren eingeführt wurden.Relationale Operatoren stehen nun zur Verfügung, um Werte im Verhältnis zu Vorgaben zu vergleichen, und mit logischen Operatoren können Sie Pattern-Teile flexibel miteinander verbinden.

bool IsSummer(DateTime dt) => dt is 
  { Month: 7 or 8 } or 
  { Month: 6, Day: >= 21 } or 
  { Month: 9, Day: < 21 }; 
 
Seit C# 9 steht somit der Implementation recht komplexer Algorithmen mit Pattern Matching nichts mehr im Wege.Die Microsoft-Dokumentation zum Thema bietet immer wieder solche Beispiele, in denen es vor allem um geschäftslogische Mechanismen geht.Im folgenden Code werden mehrere verschiedene Pattern-Matching-Features kombiniert:
  • Positional Patterns basierend auf der Deconstruct-Methode im Typ Order,
  • relationale Operatoren zur Bereichsanalyse der Werte,
  • extrahierte Variablen mit einer Berechnung als Teil eines Patterns,
  • das globale Fallback mit einem einfachen Unterstrich _.

class Order { 
  public int ItemCount { get; set; } 
  public double ItemPrice { get; set; } 

  public void Deconstruct( 
    out int itemCount, out double itemPrice) { 
    itemCount = ItemCount; 
    itemPrice = ItemPrice; 
  } 
} 

public enum OrderValue { 
  ValuableDueToHighCount, 
  ValuableDueToHighItemPrice, 
  ValuableDueToHighTotal, 
  NotValuable, 
} 

static OrderValue OrderValueCategory( 
  Order o) => o switch { 
    ( < 0, _)    => throw new ArgumentException( 
      "Positive itemCounts please!"), 
    (_, < 0)     => throw new ArgumentException( 
      "Positive itemPrices please!"), 
    ( >= 100, _) => OrderValue.ValuableDueToHighCount, 
    (_, >= 1000) => 
      OrderValue.ValuableDueToHighItemPrice, 

    var (c, p) when c * p > 1000 => 
      OrderValue.ValuableDueToHighTotal, 

    _ => OrderValue.NotValuable 
}; 
 
Zur Nutzung von Patterns für den Umgang mit typischen OO-Datenstrukturen, Datenklassen oder Ähnlichem ist dieses Beispiel sehr gut.Pattern Matching erlaubt es, die möglichen Fälle übersichtlich aufzulisten und mit kompakter Syntax flexibel zu verarbeiten. Damit ist für die Programmiersprache C# ein Meilenstein erreicht!

Darf C# auch funktional sein?

Wenn Sie sich noch an die Einleitung erinnern, wird Ihnen ­allerdings auffallen, dass das Beispiel nicht sehr an den gezeigten Code aus funktionalen Sprachen erinnert. Meine Zielsetzung war daher, über die Handhabung typischer geschäftlicher Datenstrukturen hinaus die Features von Pattern Matching in C# auszuloten.Dazu verwendete ich meine Implementation einer Bibliothek von funktionalen Hilfsmitteln in C#, die ich seit circa 2009 pflege.Diese Bibliothek heißt FCSlib [1], und ich setzte mir die Aufgabe, den Code darin nach und nach zu überarbeiten und mithilfe moderner Sprachfeatures neu aufzubauen. Pattern Matching ist der umfangreichste Block solcher Features, den C# in den letzten Jahren hinzugewonnen hat!Im Zuge dieser Arbeit fand ich verschiedene Beispiele, die über die typischen Microsoft-Anwendungsfälle hinausgingen oder die sich in ihrer Vielfalt zumindest nicht aus der Dokumentation ergaben. Ein einfaches Beispiel zum Einstieg:

static int compare(string x, string y) => (x, y) switch { 
  ("one", "one") => 0, 
  ("one", "two") => -1, 
  ("one", "three") => -1, 
  ("one", _) => 1, 
  ("one", "two") => 0, 
  ... 
  (_, _) => 1 
}; 
 
Die Implementierung ist ein Delegate Comparison<T>, das in FCSlib in einem Test verwendet wird.Zu dieser einfachen Funktion habe ich mir glücklicherweise Notizen gemacht, da ich mittlerweile womöglich vergessen hätte, was für ein Aha-Erlebnis dahintersteckte! Sind Sie schon drauf gekommen?Ganz einfach: Die Funktion nimmt zwei Parameter entgegen. Alle Beispiele, die Sie bisher gesehen haben, empfingen hingegen nur einen Parameter. Wie kann ich nun beide Parameter x und y gleichzeitig prüfen?In der Microsoft-Dokumentation fand sich ein ähnliches Beispiel, aus dem für mich klar hervorging: Der switch-Ausdruck unterstützt auch den Umgang mit mehreren Werten, die ihm mit Klammern umgeben vorangestellt werden können. Nur schien dieser tolle Umstand nirgendwo dokumentiert zu sein.Letztlich wurde mir klar, dass ich auf dem Holzweg war und der Schreiber der Dokumentation es leider versäumt hatte, den Ausdruck ordentlich zu erläutern.Das Element (x, y) ist gar nicht Teil des switch-Ausdrucks! Stattdessen wird hier ­ad hoc ein Tupel erzeugt, gerade damit dann das Positional Pattern für den Match eingesetzt werden kann. Wie gesagt, ein Aha-Erlebnis — vielleicht sind Sie schneller drauf gekommen als ich seinerzeit.In manchen Teilen von FCSlib arbeite ich mit vielen generischen Parametern.In dieser Funktion etwa erzeuge ich ­einen Typ Either, der entweder einen „linken“ oder einen „rechten“ Wert darstellt – ein gängiges Pattern in der funktionalen Programmierung, um korrekte (rechte) Werte von Fehlerzuständen (den linken Werten) zu unterscheiden.Ich verwende dabei zwei anscheinend völlig unterschiedliche Typen in den Patterns, inklusive generischer Parameter.

public static RES Either&lt;R, L, RES&gt;( 
  Func&lt;R?, RES&gt; rightHandler, 
  Func&lt;L?, RES&gt; leftHandler, Either e) =&gt; 
  e switch { 

    Right&lt;R&gt; r =&gt; rightHandler(r.Value), 
    Left&lt;L&gt; l  =&gt; leftHandler(l.Value), 
    _          =&gt; throw new 
                  InvalidOperationException("...") 
}; 
 
Instinktiv implementierte ich in diesem Zusammenhang auch manchen ternären Ausdruck als Pattern Match:

public static T? FromLeft&lt;T&gt;( 
  T? defaultValue, Either e) =&gt; e switch { 

    Left&lt;T&gt; l =&gt; l.Value, 
    _ =&gt; defaultValue 
}; 
 
Das ist absolut optional und ich hatte dabei kein spezielles Ziel im Sinn. Allerdings mögen manche Programmierer ternäre Ausdrücke aufgrund ihrer Kürze nicht, und die Syntax mit dem Pattern Match wäre in Zukunft wesentlich einfacher erweiterbar.

C#-Patterns sind sehr flexibel

In der monadischen Bind-Methode für Either machte ich mir die Tatsache zunutze, dass unterschiedliche Patterns beliebig kombiniert werden dürfen.
public Either Bind&lt;T&gt;(Func&lt;T?, Either&gt; g) =&gt; 
  this switch { 

    { IsLeft: true } =&gt; this, 
    Right&lt;T&gt; r =&gt; g(r.Value), 
    _ =&gt; throw new InvalidOperationException("...") 
}; 
 
Sie erinnern sich, dass Either entweder mit einem „rechten“ generischen Typ R arbeiten kann oder mit dem „linken“ generischen Typ L.Bei Bind wird allerdings logisch nur mit dem „rechten“ Typ gearbeitet — sollte ein Fehler aufgetreten sein, tut Bind nichts (es gibt this zurück), sodass der Typ L ganz irrelevant ist. Indem ich in Bind den „linken“ Fall mit einem Property Pattern prüfe, benötigt die Funktion nur ­einen generischen Parameter, nicht zwei.Damit wird der Aufruf von Bind wesentlich einfacher, und es ist für diesen Code von großem Wert, dass C# die Verwendung unterschiedlicher Pattern-Typen in einem Block zulässt.Nun komme ich zu einem anderen Typ aus FCSlib, dem RedBlackTree. Wie der Name vermuten lässt, handelt es sich um einen rot/schwarzen Binärbaum.Die Implementation folgt den Prinzipien der unveränderbaren Daten, also immutable data.Ein kurzer Blick in die Methode Insert zeigt zunächst diesen tollen Anwendungsfall von Pattern Matching mit relationalen Operatoren:

... Comparer&lt;T&gt;.Default.Compare(value, t.Value) switch { 

     &lt; 0 =&gt; Balance(t.NodeColor, ins(t.Left), 
            t.Value, t.Right), 

     &gt; 0 =&gt; Balance(t.NodeColor, t.Left, 
            t.Value, ins(t.Right)), 

     _ =&gt; t 
}; 
 
Das Pattern des Comparer<T> kennen Sie sicher, es ist dasselbe, das oben bereits mit dem Delegate Comparison<T> implementiert wurde: Ist Wert A kleiner Wert B, wird ein negativer Wert zurückgegeben, im umgekehrten Fall ein positiver Wert, oder einfach null, falls beide Werte im Sinne des Vergleichs gleich sein sollten. Dieses Pattern gibt es schon sehr lange (mich selbst erinnert es an Quick Sort in C), aber der Umgang mit den möglichen Rückgabewerten war kaum jemals so einfach wie nun in C#.Ein wesentlich interessanterer Aspekt des rot/schwarzen Baumes soll nun als abschließendes Beispiel dieses Artikels herhalten. Den Algorithmus dieses unveränderbaren Baumes habe ich mir seinerzeit nicht selbst ausgedacht, sondern er stammt aus dem Buch „Purely Functional Data Structures“ von Chris Okasaki. Der Autor gestattete mir damals freundlicherweise, meine eigene Implementation in C# für mein Buch zu verwenden sowie gelegentlich Auszüge aus den Original-Algorithmen für Illustrationen zu nutzen. Im Buch erschienen die Algorithmen in Haskell sowie ML. Besonders interessant ist die Funktion balance – in der C#-Version im obigen Beispiel als Aufruf zu sehen –, da sie in Haskell stark vom Pattern Matching profitiert. Das sieht etwa so aus:

// data Color = R | B 
// data RedBlackSet a = 
//  E | T Color (RedBlackSet a) a (RedBlackSet a) 
// 
// balance B (T R (T R a x b) y c) z d = 
//            T R (T B a x b) y (T B c z d) 
// balance B (T R a x (T R b y c)) z d = 
//            T R (T B a x b) y (T B c z d) 
// balance B a x (T R (T R b y c) z d) = 
//            T R (T B a x b) y (T B c z d) 
// balance B a x (T R b y (T R c z d)) = 
              T R (T B a x b) y (T B c z d) 
// balance color a x b = T color a x b 
 
Die beiden einleitenden Zeilen enthalten die Definition des Datentyps selbst in Haskell, in Form von Discriminated Unions. Die Zeit mag kommen, wo wir in C# auch so kompakt arbeiten dürfen, aber bisher ist das für .NET-Programmierer nur in F# möglich.Sie können erkennen, dass die Farbe eines Knotens jeweils R oder B sein darf, also Rot oder Schwarz. Der Knoten selbst kann entweder E sein (also empty, leer) oder vom Typ T (das steht für Tree), wo dann Werte angehängt sind: die Farbe, ein linker Teilbaum, ein Wert (das kleine a ist ein generischer Parameter für den Typ des Werts) und schließlich der rechte Teilbaum.Die Funktion balance selbst verwendet auf der linken ­Seite der Gleichheitszeichen jeweils mehrere geschachtelte Patterns, mit deren Hilfe ein Übergabeparameter in seine Einzelteile zerlegt wird.Vorn steht immer B, da nur schwarze Knoten relevant für die Verarbeitung sind. Im Weiteren werden dann verschiedene Details des Baumes und seiner geschachtelten Kinder in benannte Werte extrahiert: a, x, b, y, c, z und d.Auf der rechten Seite der Gleichheitszeichen werden jeweils neue Knoten erzeugt – in Haskell kann der Typ des Baums, T, sowohl für den Match als auch als Konstruktor verwendet werden. Genialerweise ist der Algorithmus so gestaltet, dass die rechten „Hälften“ aller Overloads identisch sind.

Einfach wie Haskell machen

Meine Implementation dieser Logik aus den Jahren 2009 und 2010 war – sagen wir einmal, länger. In C# der damaligen Zeit gab es kein Pattern Matching, und so musste ich den Weg manuell gehen. Der Umfang der gesamten Funktion ist zu groß für diesen Artikel, aber hier sehen Sie den ersten von vier ähnlich langen Teilblöcken:

if (nodeColor == RedBlackTree&lt;T&gt;.Color.Black) { 
  if (!(left.IsEmpty) &amp;&amp; 
    left.NodeColor == RedBlackTree&lt;T&gt;.Color.Red &amp;&amp; 
    !(left.Left.IsEmpty) &amp;&amp; 
    left.Left.NodeColor == RedBlackTree&lt;T&gt;.Color.Red) 
    return new RedBlackTree&lt;T&gt;(Color.Red, 
      new RedBlackTree&lt;T&gt;(Color.Black, 

        left.Left.Left, left.Left.Value, 
        left.Left.Right), left.Value, 

        new RedBlackTree&lt;T&gt;(Color.Black, 
        left.Right, value, right)); 
  ... 
 
Wenn Sie den Code sorgfältig mit den Patterns aus Haskell vergleichen, können Sie dieselben Elemente darin wiederfinden. Der Mechanismus funktioniert prima!Bereits an dieser Stelle sei gesagt, dass es jedem Programmierer freisteht, Präferenzen für die eine oder andere Form der Darstellung zu haben.Fakt ist aber, dass die C#-Version der damaligen Zeit sehr mühsam und sorgfältig aus dem Haskell-Code hergeleitet werden musste und sich nun über mehrere Bildschirmseiten erstreckt. Der Algorithmus an sich ist komplex, das ist nicht zu ändern. Die Darstellung hingegen kennt offensichtlich durchaus Variation.So gern man dem Haskell-Code eine gewisse Komplexität und Wartungsfeindlichkeit vorwerfen möchte, muss man doch zugeben, dass das C#-Äquivalent eher noch schlechter dasteht.Nun also im Ernst: Kann C# heute so gut Pattern Matching, dass wir keine grundlegenden strukturellen Änderungen mehr machen müssen, um solchen Code zu portieren?Ja, das ist so. Mehrere Schritte sind dazu notwendig. Als Erstes halten Sie sich vor Augen, wie das Pattern in Haskell arbeitet, indem es verschiedene Schachtelungsebenen gleichzeitig im Match unterbringt.Die Funktion balance selbst hat vier individuelle Parameter, in C# ist das ebenso.

private static RedBlackTree&lt;T&gt; Balance( 
  Color nodeColor, RedBlackTree&lt;T&gt; left, 
  T? value, RedBlackTree&lt;T&gt; right) { 
 
Das heißt, ein äußerer Match würde sich auf ein ad hoc konstruiertes Tupel beziehen, wie es bereits zuvor demonstriert worden ist.Aber was ist mit den geschachtelten Teilen des Patterns? Die beziehen sich zum Beispiel auf den linken Teilbaum left. Also muss ein Dekonstruktor her!

public void Deconstruct( 
  out Color nodeColor, out RedBlackTree&lt;T&gt; left, 
  out T? value, out RedBlackTree&lt;T&gt; right) { 
  nodeColor = NodeColor; 
  left = Left; 
  value = Value; 
  right = Right; 
} 
Damit ist es nun möglich, die Teilbäume auf allen Ebenen mit denselben Patterns zu prüfen. 
Nun fehlt allerdings noch etwas Code, um die Syntax kompakter zu halten.Analog zu Haskell sollen die Bezeichner R und B verwendet werden, daher sind die folgenden beiden Konstanten nützlich:

const Color R = Color.Red; 
const Color B = Color.Black; 
 
Beachten Sie, dass diese Werte mit const deklariert werden müssen. Das ist notwendig, damit sie wie in Haskell im Pattern als Vergleichselement benutzt werden dürfen. Mit einer Variablen wäre dies in C# nicht erlaubt.Nebenbei: In Haskell existiert dieses Problem nicht, da die Sprache keine veränderbaren Variablen kennt. Sachen gibt’s!Als letztes Hilfsmittel soll noch eine Hilfsfunktion her, mit der neue Knoteninstanzen erzeugt werden können. Da der Name T bereits als Typparameter in C# verwendet wird, soll sie der Kürze halber TT heißen:

RedBlackTree&lt;T&gt; TT(
  Color c, 
  RedBlackTree&lt;T&gt; l, 
  T? v, 
  RedBlackTree&lt;T&gt; r) =&gt; new(c, l, v, r); 
 
Auf dieser Basis kann nun endlich das Pattern geschrieben werden. Hier ist es:

return (nodeColor, left, value, right) 
  switch { 
   (B, (R, (R, var a, var x, var b), var y, var c), 
    var z, var d) =&gt; 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)), 
    (B, (R, var a, var x, (R, var b, var y, var c)), 
    var z, var d) =&gt; 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)), 
   (B, var a, var x, (R, (R, var b, var y, var c), 
    var z, var d)) =&gt; 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)), 
    (B, var a, var x, (R, var b, var y, (R, var c, 
    var z, var d))) =&gt; 
      TT(R, TT(B, a, x, b), y, TT(B, c, z, d)), 
   (var color, var a, var x, var b) =&gt; 
      TT(color, a, x, b)
}; 
 
Die Struktur muss gegenüber Haskell angeglichen werden. Besonders fällt allerdings auf, dass die dauernde Verwendung von var in C# höchst lästig ist, da gefühlt ein Drittel des Codes nun var lautet.Warum ist das notwendig? Immerhin besteht der Compiler schon darauf, dass konstante Werte ­const deklariert werden. Warum müssen dann die variablen Elemente außerdem noch platzverschwenderisch var genannt werden? Dies sind rein rhetorische Fragen – ändern können wir das natürlich nicht, aber in diesem Fall ist es unangenehm. Wenn Sie sich einmal vorstellen, var sei nicht nötig, bliebe von der linken Seite der ersten Zeile Folgendes übrig, im Vergleich zu Haskell:

// In C# 
(B, (R, (R, a, x, b), y, c), z, d) 

-- In Haskell 
B (T R (T R a x b) y c) z d 
 
Man kann machen, was man will, Haskell ist noch immer kürzer! Aber wie klein der Unterschied ist, das ist schon bemerkenswert.Vor allem ist leicht zu erkennen, dass das Pattern tatsächlich dasselbe ist. Damit ist das Hauptziel erreicht: Die Portierung ist so einfach geworden wie nur vorstellbar.

Fazit: C# kann Pattern Matching

Wird diese neue Struktur Ihnen dabei helfen, den Algorithmus des rot/schwarzen Baumes zu verstehen? Vermutlich eher nicht.Allerdings macht sie dieses Verständnis auch nicht schwieriger, denn der Algorithmus ist in jedem Fall komplex und von jemand anderem programmiert worden, was immer problematisch ist.Es geht nicht darum, zu urteilen, welche Syntax Sie lieber mögen. Wenn es allerdings gilt, bestehenden Code von einer funktionalen Sprache nach C# zu übertragen oder das Resultat einer solchen Übertragung zu prüfen beziehungsweise zu korrigieren, dann wird es offensichtlich etwas einfacher sein, dies mit einer eng verwandten Struktur zu tun.Es sei wie immer gewarnt: Wenn Sie solchen Code schreiben wie im abschließenden Beispiel, werden Sie den Zorn Ihres Teams auf sich ziehen, solange sich nicht alle Mitarbeiter hinsichtlich der Verwendung dieser Ansätze einig sind.Glückwunsch, C#-Pattern-Matching ist gut! Ist es vollständig? Im Vergleich zu Haskell, Erlang oder F# fehlt es noch an der Integration.Features sind nach und nach auf dem gleichen Level, besonders da C# 11 auch Patterns für Listen unterstützt.Damit lassen sich noch mehr funktionale Algorithmen abbilden, die zum Beispiel auf Rekursion statt Schleifen setzen. Dazu in späteren Artikeln mehr! 

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Bausteine guter Architektur - Entwurf und Entwicklung wartbarer Softwaresysteme, Teil 2
Code sauberer gestalten anhand von wenigen Patterns und Grundhaltungen.
6 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige