Wie ich den Sommer mit C # 8 verbracht habe

In einer kürzlich veröffentlichten Version des DotNet & More Blazor-Podcasts, NetCore 3.0 Preview, C # 8, haben wir nicht nur beiläufig ein so brennendes Thema wie C # 8 erwähnt. Die Geschichte über die Erfahrung mit C # 8 war nicht groß genug, um ein separates Thema zu behandeln, daher wurde beschlossen, die Mittel des Genres der Pistole mit ihm zu teilen.


In diesem Artikel möchte ich über meine Erfahrungen mit C # 8 in der Produktion für 4 Monate sprechen. Nachfolgend finden Sie Antworten auf folgende Fragen:


  • Wie man im neuen C # "buchstabiert"
  • Welche Funktionen waren wirklich nützlich
  • Was enttäuscht

Eine vollständige Liste der C # 8-Funktionen finden Sie in der offiziellen Dokumentation von Microsoft . In diesem Artikel werde ich die Möglichkeiten auslassen, die ich aus dem einen oder anderen Grund nicht ausprobieren konnte, nämlich:


  • Readonly Mitglieder
  • Standardschnittstellenmitglieder
  • Einweg-Ref-Strukturen
  • Asynchrone Streams
  • Indizes und Bereiche

Ich schlage vor, mit einer der köstlichsten Möglichkeiten zu beginnen, wie es mir vorher schien.


Ausdrücke wechseln


In unseren Träumen präsentieren wir diese Funktion ziemlich rosig:


int Exec(Operation operation, int x, int y) => operation switch { Operation.Summ => x + y, Operation.Diff => x - y, Operation.Mult => x * y, Operation.Div => x / y, _ => throw new NotSupportedException() }; 

Leider nimmt die Realität ihre eigenen Anpassungen vor.
Erstens gibt es keine Möglichkeit, die Bedingungen zu kombinieren:


  string TrafficLights(Signal signal) { switch (signal) { case Signal.Red: case Signal.Yellow: return "stop"; case Signal.Green: return "go"; default: throw new NotSupportedException(); } } 

In der Praxis bedeutet dies, dass in der Hälfte der Fälle der Schalterausdruck in einen regulären Schalter umgewandelt werden muss, um ein Kopieren und Einfügen zu vermeiden.


Zweitens unterstützt die neue Syntax keine Anweisungen, d. H. Code, der keinen Wert zurückgibt. Es scheint gut, und es ist nicht notwendig, aber ich selbst war überrascht, als mir klar wurde, wie oft Schalter (in Verbindung mit Mustervergleich) für so etwas wie die Behauptung in Tests verwendet werden.


Drittens unterstützt der Schalterausdruck, der aus dem letzten Absatz folgt, keine mehrzeiligen Handler. Wie beängstigend wir zum Zeitpunkt des Hinzufügens der Protokolle verstehen:


  int ExecFull(Operation operation, int x, int y) { switch (operation) { case Operation.Summ: logger.LogTrace("{x} + {y}", x, y); return x + y; case Operation.Diff: logger.LogTrace("{x} - {y}", x, y); return x - y; case Operation.Mult: logger.LogTrace("{x} * {y}", x, y); return x * y; case Operation.Div: logger.LogTrace("{x} / {y}", x, y); return x / y; default: throw new NotSupportedException(); } } 

Ich möchte nicht sagen, dass der neue Schalter schlecht ist. Nein, er ist gut, nur nicht gut genug.


Eigenschafts- und Positionsmuster


Vor einem Jahr schienen sie mir die Hauptkandidaten für den Titel "Gelegenheit, die die Entwicklung verändert hat" zu sein. Und wie erwartet müssen Sie Ihren Entwicklungsansatz ändern, um die volle Leistung von Positions- und Eigenschaftsmustern nutzen zu können. Es ist nämlich notwendig, algebraische Datentypen zu imitieren.
Es scheint, was ist das Problem: Nehmen Sie die Marker-Oberfläche und gehen Sie. Leider hat diese Methode in einem großen Projekt einen schwerwiegenden Nachteil: Niemand garantiert, dass die Erweiterung Ihrer algebraischen Typen in der Entwurfszeit verfolgt wird. Es ist daher sehr wahrscheinlich, dass Änderungen am Code im Laufe der Zeit an den unerwartetsten Stellen zu vielen "Standardfehlern" führen.


Tupelmuster


Aber der "jüngere Bruder" der neuen Vergleichsmöglichkeiten mit der Stichprobe erwies sich als wirklich gut gemacht. Die Sache ist, dass das Tupelmuster keine Änderungen in der bekannten Architektur unseres Codes erfordert, sondern nur einige Fälle vereinfacht:


  Player? Play(Gesture left, Gesture right) { switch (left, right) { case (Gesture.Rock, Gesture.Rock): case (Gesture.Paper, Gesture.Paper): case (Gesture.Scissors, Gesture.Scissors): return null; case (Gesture.Rock, Gesture.Scissors): case (Gesture.Scissors, Gesture.Paper): case (Gesture.Paper, Gesture.Rock): return Player.Left; case (Gesture.Paper, Gesture.Scissors): case (Gesture.Rock, Gesture.Paper): case (Gesture.Scissors, Gesture.Rock): return Player.Right; default: throw new NotSupportedException(); } } 

Das Beste daran ist jedoch, dass diese Funktion, die vorhersehbar genug ist, mit der Deconstruct-Methode hervorragend funktioniert. Übergeben Sie einfach eine Klasse mit implementiertem Deconstruct, um die Funktionen des Tupelmusters zu wechseln und zu nutzen.


Deklarationen verwenden


Es scheint eine kleine Chance zu sein, aber es bringt so viel Freude. In allen Promos spricht Microsoft von einem Aspekt wie der Reduzierung der Verschachtelung. Aber seien wir ehrlich, nicht so sehr wichtig. Aber was wirklich ernst ist, sind die Nebenwirkungen des Ausschlusses eines Codeblocks:


  • Wenn wir using hinzufügen, müssen wir den Code häufig mit der Copy-Paste-Methode "innerhalb" des Blocks ziehen. Jetzt denken wir einfach nicht darüber nach
  • Die Variablen, die innerhalb von using deklariert und nach dem Entsorgen des using-Objekts verwendet werden, bereiten echte Kopfschmerzen. Ein Problem weniger
  • In Klassen, die häufige Dispose-Aufrufe erfordern, wäre jede Methode 2 Zeilen länger. Es scheint eine Kleinigkeit zu sein, aber unter den Bedingungen vieler kleiner Methoden erlaubt diese Kleinigkeit nicht, eine ausreichende Anzahl dieser Methoden auf einem Bildschirm anzuzeigen

Infolgedessen ändert eine so einfache Sache wie die Verwendung von Deklarationen das Gefühl der Codierung so sehr, dass Sie einfach nicht zu c # 7.3 zurückkehren möchten.


Statische lokale Funktionen


Um ehrlich zu sein, würde ich diese Möglichkeit ohne die Hilfe der Code-Analyse nicht einmal bemerken. Trotzdem hat sie sich fest in meinem Code verankert: Schließlich sind statische lokale Funktionen perfekt für die Rolle kleiner reiner Funktionen geeignet, da sie das Schließen von Methodenvariablen nicht unterstützen können. Dies schont das Herz, da Sie verstehen, dass Ihr Code einen potenziellen Fehler weniger enthält.


Nullable Referenztypen


Und zum Nachtisch möchte ich das wichtigste Merkmal von C # 8 erwähnen. In Wahrheit verdient das Parsen von nullbaren Referenztypen einen separaten Artikel. Ich möchte nur die Empfindungen beschreiben.


  • Erstens ist es wunderbar. Ich hätte früher meine ausdrückliche Absicht beschreiben können, ein Feld oder eine Eigenschaft für nullbar zu erklären, aber jetzt ist diese Funktion in die Sprache integriert.
  • Zweitens wird von NullReferenceException überhaupt nicht gespeichert. Und ich spreche nicht von dem berüchtigten "Verstopfen" bei Warnungen. Es ist nur so, dass zur Laufzeit niemand Nullargumentprüfungen für Sie generiert. Beeilen Sie sich also nicht, Code wie throw new ArgumentNullException () zu werfen.
  • Drittens gibt es ein ernstes Problem mit dem DTO. Beispielsweise kommentieren Sie eine Eigenschaft mit dem Attribut Erforderlich. Dementsprechend gelangt ein Objekt mit einer Eigenschaft von 100% nicht null in Ihren WebAPI-Controller. Es ist jedoch nicht möglich, dieses Attribut und alle ähnlichen Attribute mit prüfbaren Referenztypprüfungen zu verknüpfen. Die Sache ist, dass, wenn Sie Standard MyProperty {get; Wenn Sie eine Eigenschaft mit einem NotNull-Typ festlegen, erhalten Sie eine Warnung: "[CS8618] Die nicht nullfähige Eigenschaft 'MyProperty' ist nicht initialisiert. Erwägen Sie, die Eigenschaft als nullbar zu deklarieren . " Das ist fair genug, da Sie während des Initialisierungsprozesses keine Nullsemantik garantieren können. Das einzige Ergebnis dieser Funktion ist die Unfähigkeit, in einem DTO keine Null-Eigenschaften zu verwenden. Aber es gibt gute Nachrichten, es gibt eine einfache Problemumgehung - initialisieren Sie Ihr Feld einfach mit dem Standardwert:
     public string MyProperty { get; set; } = ""; 
  • Viertens sind Attribute, die komplexe Fälle behandeln, wie z. B. TryGetValue, selbst recht komplex. Infolgedessen ist es sehr wahrscheinlich, dass nicht sehr bewusste Entwickler Operatoren (!) Missbrauchen, wodurch die Fähigkeiten nullbarer Referenztypen ausgeglichen werden. Eine Hoffnung für Analysatoren.
  • Fünftens und vor allem persönlich hat mich diese Gelegenheit schon oft vor NullReferenceException-Fehlern bewahrt. Es stellt sich als banale Zeitersparnis heraus - viele Fehler werden beim Kompilieren abgefangen und keine Tests oder Fehlerbehebungen. Dies gilt insbesondere nicht nur für die Entwicklung komplexer Geschäftslogik, sondern auch für die triviale Arbeit mit externen Bibliotheken, DTO und anderen Abhängigkeiten, die möglicherweise null enthalten.

Zusammenfassung


Natürlich erreichen die sich bietenden Möglichkeiten keine vollwertige Revolution, aber es gibt immer weniger Lücken zwischen C # und F # / Scala. Ob es gut oder schlecht ist, wird die Zeit zeigen.


Zum Zeitpunkt der Veröffentlichung dieses Artikels hat sich C # 8 möglicherweise bereits in Ihrem Projekt niedergelassen. Ich würde mich also fragen, was Sie von der neuen Version unserer Lieblingssprache halten.

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


All Articles