Weitere Funktionen mit Mustern in C # 8.0

In jüngerer Zeit wurde Visual Studio 2019 Preview 2 veröffentlicht. Damit stehen Ihnen einige zusätzliche C # 8.0-Funktionen zum Ausprobieren zur Verfügung. Dies ist hauptsächlich ein Vergleich mit der Stichprobe, obwohl ich am Ende auf einige andere Neuigkeiten und Änderungen eingehen werde.


Dieser Artikel ist in Englisch.




Vielen Dank für die Übersetzung unseres MSP, Lev Bulanov .

Mehr Muster an mehr Orten


Als der Mustervergleich in C # 7.0 erschien, stellten wir fest, dass in Zukunft eine Zunahme der Anzahl von Mustern an mehr Stellen erwartet wird. Diese Zeit ist gekommen! Wir fügen so genannte rekursive Muster sowie eine kompaktere Form von Switch- Ausdrücken hinzu, die als Switch- Ausdrücke bezeichnet werden (Sie haben es erraten).


Hier ist ein einfaches Beispiel für C # 7.0-Muster:


class Point { public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); } static string Display(object o) { switch (o) { case Point p when pX == 0 && pY == 0: return "origin"; case Point p: return $"({pX}, {pY})"; default: return "unknown"; } } 

Ausdrücke wechseln


Beachten Sie zunächst, dass viele Schalterausdrücke in Fallkörpern tatsächlich nicht viel interessante Arbeit leisten. Oft erstellen alle einfach einen Wert, indem sie ihn entweder einer Variablen zuweisen oder zurückgeben (wie oben angegeben). In all diesen Situationen scheint der Schalter fehl am Platz zu sein. Dies ähnelt einer fünfzig Jahre alten Sprachfunktion.


Wir beschlossen, dass es Zeit war, ein Formular für die switch-Anweisung hinzuzufügen. Dies gilt für das folgende Beispiel:


 static string Display(object o) { return o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; } 

Es gibt einige Dinge, die sich im Vergleich zu switch-Anweisungen geändert haben. Lassen Sie uns sie auflisten:


  • Das Schlüsselwort switch lautet "infix" zwischen dem getesteten Wert und der Liste der {...} Fälle. Dies macht es kompositorischer mit anderen Ausdrücken und erleichtert auch die visuelle Unterscheidung von der switch-Anweisung.
  • Das case- Schlüsselwort und das case- Symbol: wurden durch den Lambda-Pfeil ersetzt => kurz.
  • Die Standardeinstellung für die Kürze wurde durch das _- Reset-Muster ersetzt.
  • Körper sind Ausdrücke. Das Ergebnis des ausgewählten Körpers wird zum Ergebnis der switch-Anweisung.

Da der Ausdruck wichtig sein oder eine Ausnahme auslösen muss, löst ein ausgewählter Ausdruck, der ohne Übereinstimmung endet, eine Ausnahme aus. Der Compiler warnt Sie, wenn dies passieren kann, zwingt Sie jedoch nicht, alle select-Anweisungen mit der Funktion catch-all zu beenden.


Da unsere Display- Methode jetzt aus einer einzelnen return-Anweisung besteht, können wir sie für den Ausdruck vereinfachen:


  static string Display(object o) => o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; 

Unabhängig von den Formatierungsempfehlungen sollten diese äußerst klar und präzise sein. Mit der Kürze können Sie den Schalter wie oben beschrieben "tabellarisch" formatieren, wobei sich Muster und Körper in derselben Zeile befinden und => untereinander angeordnet sind.


Übrigens planen wir, die Verwendung eines Kommas nach dem letzten Fall gemäß allen anderen "durch Kommas in geschweiften Klammern getrennten Listen" in C # zuzulassen, dies ist jedoch in Vorschau 2 noch nicht zulässig.


Mustereigenschaften


Apropos Kürze: Muster werden plötzlich zu den schwierigsten Elementen von Auswahlausdrücken. Lassen Sie uns etwas dagegen tun.


Beachten Sie, dass der select-Ausdruck ein Muster vom Typ Point p (zweimal) verwendet und im ersten Fall zusätzliche Bedingungen hinzugefügt werden müssen .


In C # 8.0 fügen wir dem Mustertyp zusätzliche optionale Elemente hinzu, sodass das Muster selbst tiefer in den Wert eintauchen kann, der dem Muster zugeordnet ist. Sie können es zu einem Eigenschaftsmuster machen, indem Sie {...} mit verschachtelten Mustern hinzufügen und Werte auf die verfügbaren Eigenschaften oder Felder anwenden. Auf diese Weise können wir den Schalterausdruck wie folgt umschreiben:


 static string Display(object o) => o switch { Point { X: 0, Y: 0 } p => "origin", Point { X: var x, Y: var y } p => $"({x}, {y})", _ => "unknown" }; 

In beiden Fällen wird weiterhin überprüft, ob o Punkt ist . Im ersten Fall wird das Muster der Konstanten 0 rekursiv auf die Eigenschaften X und Y der Variablen p angewendet, um zu prüfen, ob sie diesen Wert haben. Somit können wir die Wann- Bedingung in diesem und anderen ähnlichen Fällen beseitigen.


Im zweiten Fall wird das var- Muster jeweils auf X und Y angewendet. Denken Sie daran, dass das var- Muster in C # 7.0 immer erfolgreich ist und deklarieren Sie einfach eine neue Variable, die den Wert enthält. X und y enthalten also int-Werte für pX und pY .


Wir verwenden p nie und können es hier tatsächlich überspringen:


  Point { X: 0, Y: 0 } => "origin", Point { X: var x, Y: var y } => $"({x}, {y})", _ => "unknown" 

Eines bleibt für alle Arten von Mustern gleich, einschließlich Eigenschaftsmuster. Dies ist eine Voraussetzung dafür, dass der Wert ungleich Null ist. Dies eröffnet die Möglichkeit, das Muster "leere" Eigenschaften {} als kompaktes Muster "ungleich Null" zu verwenden. Zum Beispiel. Wir könnten den Fallback durch die folgenden zwei Fälle ersetzen:


  {} => o.ToString(), null => "null" 

{} Behandelt die verbleibenden Nicht-Null-Objekte, und Null erhält Nullen, sodass der Schalter vollständig ist und der Compiler sich nicht über fehlende Werte beschwert.


Positionsmuster


Das Eigenschaftsmuster verkürzt den zweiten Punkt nicht . Sie müssen sich darüber keine Sorgen machen, Sie können noch mehr tun.


Beachten Sie, dass die Point- Klasse eine Deconstruct- Methode hat, den sogenannten Deconstructor. In C # 7.0 können Sie mit Dekonstruktoren einen Wert "dekonstruieren", wenn er zugewiesen ist, sodass Sie beispielsweise schreiben können:


 (int x, int y) = GetPoint(); // split up the Point according to its deconstructor 

C # 7.0 hat die Dekonstruktion nicht in Muster integriert. Dies ändert sich mit Positionsmustern, die eine zusätzliche Möglichkeit darstellen, Mustertypen in C # 8.0 zu erweitern. Wenn der übereinstimmende Typ ein Tupeltyp ist oder einen Dekonstruktor hat, können wir Positionsmuster als kompakte Methode verwenden, um rekursive Muster anzuwenden, ohne die Eigenschaften benennen zu müssen:


 static string Display(object o) => o switch { Point(0, 0) => "origin", Point(var x, var y) => $"({x}, {y})", _ => "unknown" }; 

Nach dem Abgleichen des Objekts mit Point wird der Dekonstruktor angewendet und die verschachtelten Muster werden auf die resultierenden Werte angewendet.


Dekonstruktoren sind nicht immer angemessen. Sie sollten nur zu den Typen hinzugefügt werden, bei denen wirklich klar ist, welcher der Werte welcher ist. Für die Point- Klasse können Sie beispielsweise annehmen, dass der erste Wert X und der zweite Y ist , sodass der obige Schalterausdruck klar und leicht zu lesen ist.


Tupelmuster


Ein sehr nützlicher Sonderfall von Positionsmustern ist ihre Anwendung auf Tupel. Wenn die switch-Anweisung direkt auf den Tupelausdruck angewendet wird, können wir sogar den zusätzlichen Satz von Klammern weglassen, wie in switch (x, y, z) anstelle von switch ((x, y, z)) .


Tupelmuster eignen sich hervorragend zum gleichzeitigen Testen mehrerer Eingaben. Hier ist eine einfache Implementierung der Zustandsmaschine:


 static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition) switch { (Opened, Close) => Closed, (Closed, Open) => Opened, (Closed, Lock) when hasKey => Locked, (Locked, Unlock) when hasKey => Closed, _ => throw new InvalidOperationException($"Invalid transition") }; 

Natürlich könnten wir hasKey in das Tupel aufnehmen, anstatt when- Klauseln zu verwenden - dies ist Geschmackssache:


 static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition, hasKey) switch { (Opened, Close, _) => Closed, (Closed, Open, _) => Opened, (Closed, Lock, true) => Locked, (Locked, Unlock, true) => Closed, _ => throw new InvalidOperationException($"Invalid transition") }; 

Im Allgemeinen sehen Sie, dass rekursive Muster und Schalterausdrücke zu einer klareren und deklarativeren Programmlogik führen können.


Weitere Funktionen von C # 8.0 in Vorschau 2


Trotz der Tatsache, dass in VS 2019 Preview 2 die Hauptfunktionen für die Arbeit mit Mustern am wichtigsten sind, gibt es einige kleinere, die Sie hoffentlich auch nützlich und interessant finden. Ich werde nicht auf Details eingehen, sondern nur eine kurze Beschreibung von jedem geben.


Anzeigen verwenden


Bei der Verwendung von C # erhöhen Bediener immer die Verschachtelung, was sehr ärgerlich und schlecht lesbar sein kann. In einfachen Fällen, in denen Sie die Ressource nur am Ende des Bereichs löschen müssen, werden Deklarationen verwendet. Verwenden von Deklarationen sind einfach Deklarationen lokaler Variablen mit dem Schlüsselwort using vor sich, und ihr Inhalt wird am Ende des aktuellen Anweisungsblocks platziert. Daher anstelle von:

 static void Main(string[] args) { using (var options = Parse(args)) { if (options["verbose"]) { WriteLine("Logging..."); } ... } // options disposed here } 

Du kannst einfach schreiben


 static void Main(string[] args) { using var options = Parse(args); if (options["verbose"]) { WriteLine("Logging..."); } } // options disposed here 

Einweg-Ref-Strukturen


Ref-Strukturen wurden in C # 7.2 eingeführt, und es scheint hier keinen Platz zu geben, um sie zu wiederholen. Dennoch ist etwas zu beachten: Sie weisen einige Einschränkungen auf, z. B. die Unmöglichkeit, Schnittstellen zu implementieren. Ref-Strukturen können jetzt verwendet werden, ohne die IDisposable- Schnittstelle zu implementieren, indem einfach die darin enthaltene Dispose- Methode verwendet wird.


Statische lokale Funktionen


Wenn Sie sicherstellen möchten, dass für Ihre lokale Funktion keine Laufzeitkosten für das Erfassen (Referenzieren) von Variablen aus dem Bereich anfallen, können Sie sie als statisch deklarieren. Dann verhindert der Compiler die Verknüpfung zu allem, was in den umschließenden Funktionen deklariert ist - mit Ausnahme anderer statischer lokaler Funktionen!


Änderungen gegenüber der Vorschau 1


Die Hauptfunktionen von Vorschau 1 waren nullbare Referenztypen und asynchrone Streams. Beide Funktionen haben sich in Vorschau 2 etwas geändert. Wenn Sie sie also verwenden, ist es hilfreich, Folgendes zu wissen.


Nullable Referenztypen


Wir haben weitere Optionen zum Verwalten von nullbaren Warnungen sowohl an der Quelle (über die Warnanweisungen #nullable und #pragma ) als auch auf Projektebene hinzugefügt . Wir haben auch das Abonnement für die Projektdatei in <NullableContextOptions> enable </ NullableContextOptions> geändert .


Asynchrone Threads


Wir haben die Form der IAsyncEnumerable <T> -Schnittstelle geändert, die der Compiler erwartet. Dies führt dazu, dass der Compiler nicht mit der in .NET Core 3.0 Preview 1 bereitgestellten Schnittstelle synchronisiert wird, was zu Problemen führen kann. .NET Core 3.0 Preview 2 wird jedoch bald veröffentlicht und die Synchronisierung wird zurückgegeben.

Source: https://habr.com/ru/post/de440530/


All Articles