C# 7 – Stand der Dinge und Ausblick

Seite 4: Pattern Matching

Inhaltsverzeichnis

Programme können Werte algebraischer Typen mittels Pattern Matching analysieren. Dabei identifizieren sie einen Wert anhand seines Konstruktors oder seiner Feldnamen und extrahieren ihn aus dem Kompositionstypen. Zu diesem Zweck führt Microsoft neue Schlüsselworte und Sprachkonstrukte ein und erweitert vorhandene.

Folgender Code macht Gebrauch von der Klassenhierarchie beziehungsweise der neuen Record-Typdefinition aus dem vorherigen Abschnitt. Ziel ist es, den Code mittels Pattern Matching zu vereinfachen.

static string PrintedForm(Person p)
{
Student s;
Teacher t;
if ((s = p as Student) != null && s.Gpa > 3.5)
{
return $"Honor Student {s.Name} ({s.Gpa})";
}
else if (s != null)
{
return $"Student {s.Name} ({s.Gpa})";
}
else if ((t = p as Teacher) != null)
{
return $"Teacher {t.Name} of {t.Subject}";
}
else
{
return $"Person {p.Name}";
}
}

Dabei werden die Variablen s und t vor ihrer Benutzung definiert, obwohl sie nicht in allen Zweigen der if-Anweisung zum Einsatz kommen. Sämtliche Variablen müssen im Vorfeld mit eindeutigen Namen feststehen.

Das Roslyn-Team sieht für das Pattern Matching eine Erweiterung des is-Operators vor. Dadurch kann er ein Muster (Pattern) auf der rechten Seite entgegennehmen. Ein Teil davon ist eine Variablen-Deklaration. Damit sähe der vereinfachte Code folgendermaßen aus:

static string PrintedForm(Person p)
{
if (p is Student s && s.Gpa > 3.5)
{
return $"Honor Student {s.Name} ({s.Gpa})";
}
else if (p is Student s)
{
return $"Student {s.Name} ({s.Gpa})";
}
else if (p is Teacher t)
{
return $"Teacher {t.Name} of {t.Subject}";
}
else
{
return $"Person {p.Name}";
}
}

Der is-Operator ist in der Form erweitert, dass er den auf der rechten Seite angegebenen Typen aus der Variablen auf der linken Seite (p) extrahiert und das Ergebnis der Extraktion in die Variable zur rechten Seite (s und t) steckt. Die neu eingeführten Variablen sind nur in dem folgenden Scope gültig. Somit entfällt die Deklaration vor der if-Anweisung, was Ressourcen spart. Ein Problem, das an dieser Stelle besteht, ist, dass eine mehrfache Prüfung gegen den Typen Student erfolgt.

In der Kommenden C#-Version will das Team das switch-Statement erweitern. Das erklärte Ziel ist, dass die case-Zweige Muster darstellen, sodass switch zu einer Art Typ-Schalter werden kann. Der Compiler wird intelligent genug agieren, dass der generierte Code nicht – wie im obigen Beispiel – mehrfach gegen denselben Typen testet. Zusätzlich kommt im Kontext das Schlüsselwort when hinzu. Der resultierende Code könnte dann so aussehen:

static string PrintedForm(Person p)
{
switch (p)
{
case Student s when s.Gpa > 3.5:
return $"Honor Student {s.Name} ({s.Gpa})";
case Student s:
return $"Student {s.Name} ({s.Gpa})";
case Teacher t:
return $"Teacher {t.Name} of {t.Subject}";
default:
return $"Person {p.Name}"
}
}


Das Konstrukt extrahiert aus der Variablen im switch-Statement (p) den entsprechenden Typ und weist ihn anschließend einer Variablen zu (s und t). Das neue Schlüsselwort when sorgt für eine bedingte Ausführung des entsprechenden case-Zweigs. Der Code im Bedingungsausdruck kann auf die neu eingeführte Variable direkt zugreifen

Ein weiteres neues Schlüsselwort beziehungsweise Konzept ist das match-Statement. Es soll eine ähnliche Semantik wie der ?-Operator aufweisen, aber auch multiple Zweige kennen. Dadurch entfällt unter anderem die Notwendigkeit für return in den case-Zweigen. Das Resultat ist kürzerer und übersichtlicherer Code. Das folgende vereinfachte Beispiel zeigt einen Anwendungsfall:

return p match(
case Student s when s.Gpa > 3.5:
$"Honor Student {s.Name} ({s.Gpa})"
case Student { Name is "Shaldon" }:
"A Nerd"
case Student s:
$"Student {s.Name} ({s.Gpa})"
case Teacher t:
$"Teacher {t.Name} of {t.Subject}"
case null:
throw new ArgumentNullException(nameof(p))
case *:
$"Person {p.Name}"
);

In disem Zusammenhang erlaubt der is-Operator den expliziten Test auf einen Member des Typs. Für den Fall, dass die getestete Variable null ist, kann der entsprechende case-Zweig eine Exception werfen. Das Sternsymbol (*) ist in der Form der Anwendung ebenfalls neu. Als Wildcard kommt der Zweig in allen Fällen zum Tragen, die nicht mit case abgefangen wurden.

Das abschließende Beispiel erweitert den Code um die ebenfalls neue =>-Schreibweise für Methoden:

static string PrintedForm(Person p) => p match(
case Student s when s.Gpa > 3.5:
$"Honor Student {s.Name} ({s.Gpa})"
case Student { Name is "Shaldon" }:
"A Nerd"
case Student s:
$"Student {s.Name} ({s.Gpa})"
case Teacher t:
$"Teacher {t.Name} of {t.Subject}"
case null:
throw new ArgumentNullException(nameof(p))
case *:
$"Person {p.Name}"
);