Deklarieren von Verweistypen, die NULL-Werte zulassen und NULL-Werte nicht zulassen
Einige der vorherrschenden Ideen in der Erörterung von C# 7 betreffen weitere Verbesserungen im Umgang mit NULL – den gleichen Gedanken folgend, die auch dem NULL-bedingten Operator von C# 6.0 zugrunde lagen. Eine der einfachsten Verbesserungen besteht möglicherweise in einer Analyse- oder Konformitätsprüfung, die dem Zugriff auf die Instanz eines Typs, der NULL-Werte zulässt, vorangestellt wird und prüft, ob die Instanz nicht eben doch NULL ist.
Out-variablen
Derzeit in C #, Ausnutzung Parameter ist nicht so flüssig wie wir möchten. Bevor Sie eine Methode ohne Parameter aufrufen können, müssen Sie zuerst Variable deklarieren. Da Sie diese Variablen normalerweise nicht initialisieren (sie werden von der Methode schließlich überschrieben), können Sie auch nicht var verwenden, um sie zu deklarieren, müssen aber den vollständigen Typ angeben:
public void PrintCoordinates(Point p) {int x, y; // have to "predeclare" p.GetCoordinates(out x, out y); WriteLine($"({x}, {y})");} |
|---|
public void PrintCoordinates(Point p) {p.GetCoordinates(out int x, out int y); WriteLine($"({x}, {y})");} |
|---|
| p.GetCoordinates(out var x, out var y); |
|---|
public void PrintStars(string s) { if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); } else { WriteLine("Cloudy - no stars tonight!"); }} |
|---|
| p.GetCoordinates(out int x, out *); // I only care about x |
|---|
C # 7.0 führt die Vorstellung von Mustern ein, die abstrakt gesprochen syntaktische Elemente sind, die testen können, dass ein Wert eine bestimmte "Form" aufweist und Informationen aus dem Wert extrahiert, wenn dies der Fall ist.
Is-expressions mit patterns
Hier ist ein Beispiel für die Verwendung von Ausdrücken mit konstanten Mustern und Mustermustern:
public void PrintStars(object o) {if (o is null) return; // constant pattern "null" if (!(o is int i)) return; // type pattern "int i" WriteLine(new string('*', i));} |
|---|
Switch statements mit patterns
Hier ist ein einfaches Beispiel:
switch(shape) {case Circle c: WriteLine($"circle with radius {c.Radius}");break; case Rectangle s when (s.Length == s.Height): WriteLine($"{s.Length} x {s.Height} square");break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} rectangle");break; default: WriteLine("<unknown shape>");break; case null: throw new ArgumentNullException(nameof(shape)); case 42: // ... case Color.Red: // ... case string s: // ... case Point(int x, 42) where (Y > 42): // ... case Point(490, 42): // fine // ... default: // ... } |
|---|
Um den Fall vom Typ „Point“ zu unterstützen, müsste auch ein Member vom Typ „Point“ vorhanden sein, das den Musterabgleich ausführt. In diesem Fall wird ein Member benötigt, das zwei Argumente vom Typ „int“ akzeptiert. Ein Member etwa dieser Art:
public static bool operator is (Point self out int x, out int y) {...} |
|---|
Eine der Einschränkungen der „switch“-Anweisung besteht darin, dass sie keinen Wert zurückgibt, sondern vielmehr einen Codeblock ausführt. Ein zusätzliches Feature des Musterabgleichs könnte in der Unterstützung von „switch“-Ausdrücken bestehen, die einen Wert zurückgeben, wie hier:
string text = match (e) { pattern => expression; ... ; default => expression } |
|---|
Tuples
Tupel ist ein weiteres Feature, das für C# 7 in der Diskussion steht. Dieses Thema ist schon bei früheren Gelegenheiten für frühere Versionen der Sprache aufgekommen, aber bisher ist es nicht in eine Produktionsversion aufgenommen worden. Die Grundidee ist, dass es möglich wäre, Typen in Form einer Menge zu deklarieren, sodass eine Deklaration mehrere Werte enthalten könnte und Methoden demgemäß auch mehrere Werte zurückgeben könnten. Der folgende Beispielcode soll das Konzept veranschaulichen:public class Person {public readonly (string firstName, int lastName) Names; // a tuple public Person((string FirstName, string LastName)) names, int Age) {Names = names; } } |
|---|
public (string FirstName, string LastName) GetNames(string! fullName) { string[] names = fullName.Split(" ", 2);return (names[0], names[1]); } public void Main() {// ... (string first, string last) = GetNames("Inigo Montoya");// ... } |
|---|
Mit Tupeln könnte eine Vielzahl von Optionen einhergehen. Hier sind ein paar, die zur Diskussion stehen:
Tupel könnten benannte oder unbenannte Eigenschaften aufweisen, wie hier:
var name = ("Inigo", "Montoya") |
|---|
var name = (first: "John", last: "Doe") |
|---|
var name = (first: "John", last: "Doe") |
|---|
(string first, string last) = GetNames("Inigo Montoya") |
|---|
var names = new[]{ "Inigo", "Montoya" } |
|---|
Console.WriteLine($”My name is { names.first } { names.last }.”); |
|---|
Zwar gibt es Komplikationen mit Tupeln, aber größtenteils folgen sie Strukturen, die bereits gut in der Sprache eingeführt sind, und daher stößt ihre Aufnahme in C# 7 auf breite Unterstützung.
Datensätze
In Fortführung der verkürzten Konstruktor-deklarations-syntax, die für C# 6.0 diskutiert (aber letztlich verworfen) wurde, gibt es Unterstützung für ein Einbetten der Konstruktor-deklaration innerhalb der Klassendefinition, ein Konzept, das als „Datensätze“ bekannt ist. Betrachten Sie zum Beispiel die folgende Deklaration:
class Person(string Name, int Age); |
|---|
Einen Konstruktor:
| public Person(string Name, int Age) { this.Name = Name; this.Age = Age; } |
|---|
Gleichheitsimplementierungen (wie etwa „GetHashCode“, „Equals“, Operator „==“, Operator „!=“ usw.)
Eine Standardimplementierung von „ToString“
Unterstützung für den Musterabgleich des „is“-Operators
Zwar würde eine erhebliche Menge Code generiert (wenn man bedenkt, dass dazu nur eine kurze Zeile Code erforderlich war), doch knüpft sich daran vor allem die Hoffnung, dass dies eine deutliche Abkürzung gegenüber der manuellen Codeerstellung bedeutet, was für die Boiler-plate-implementierung wichtig ist.
Ferner könnte dieser gesamte Code als „standardmäßig“ angesehen werden, in dem Sinn, dass seine explizite Implementierung Vorrang vor dem gleichen Member hätte und dessen Generierung damit ausschließen würde.
Einer der problematischsten Aspekte im Zusammenhang mit Datensätzen ist aber der Umgang mit Serialisierung. Vermutlich stellt die Nutzung von Datensätzen als Datenübertragungsobjekte (DTOs, Data Transfer Objects) einen ziemlich typischen Fall dar, aber es ist keineswegs klar, wie die Serialisierung solcher Datensätze unterstützt werden kann, wenn das überhaupt möglich ist.
Mit Datensätzen geht die Unterstützung von „with“-Ausdrücken einher. „With“-Ausdrücke ermöglichen die Instanziierung eines neuen Objekts auf der Basis eines vorhandenen Objekts. Bei einer vorhandenen Objektdeklaration „person“ könnten Sie beispielsweise mithilfe des folgenden „with“-Ausdrucks eine neue Instanz erstellen:
Person inigo = new Person("Inigo Montoya", 42);Person humperdink = inigo with { Name = "Prince Humperdink" };Der generierte Code, der dem „with“-Ausdruck entspricht, sähe etwa so aus: Person humperdink = new Person(Name: "Prince Humperdink", Age: inigo.42 ); |
|---|
Person humperdink = inigo.With(Name: "Prince Humperdink", Age: inigo.42); |
|---|
Dekonstruktion
Eine andere Weise, Tupel zu verbrauchen ist, sie zu dekonstruieren. Eine dekonstruierende Deklaration ist eine Syntax für die Aufteilung eines Tupels (oder eines anderen Wertes) in seine Teile und die Zuordnung dieser Teile zu den neuen Variablen einzeln:
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration WriteLine($"found {first} {last}."); |
|---|
(var first, var middle, var last) = LookupName(id1); // var inside |
|---|
var (first, middle, last) = LookupName(id1); // var outside |
|---|
(first, middle, last) = LookupName(id2); // deconstructing assignment |
|---|
public void Deconstruct(out T1 x1, ..., out Tn xn) { ... } |
|---|
class Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; }} (var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY); |
|---|
(var myX, *) = GetPoint(); // I only care about myX |
|---|
Lokale Funktionen
Sie können diese Funktionen nun in einer anderen Funktionsbibliothek als lokale Funktion deklarieren:
public int Fibonacci(int x) { if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));return Fib(x).current; (int current, int previous) Fib(int i) {if (i == 0) return (1, 0); var (p, pp) = Fib(i - 1); return (p + pp, p); } } |
|---|
public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter) {if (source == null) throw new ArgumentNullException(nameof(source)); if (filter == null) throw new ArgumentNullException(nameof(filter)); return Iterator(); IEnumerable<T> Iterator() {foreach (var element in source) { if (filter(element)) { yield return element; }} } } |
|---|
Literale Verbesserungen
C # 7.0 erlaubt _ als Zifferntrennzeichen innerhalb der Zahl Literale auftreten:
var d = 123_456; var x = 0xAB_CD_EF; |
|---|
var b = 0b1010_1011_1100_1101_1110_1111; |
|---|
Ref-Rückkehr und Einheimischen
Genau wie Sie Dinge durch Verweis (mit dem ref-Modifikator) in C # übergeben können, können Sie sie jetzt als Referenz zurückgeben und sie auch als Referenz in lokalen Variablen speichern.
public ref int Find(int number, int[] numbers) {for (int i = 0; i < numbers.Length; i++) {if (numbers[i] == number) {return ref numbers[i]; // return the storage location, not the value } } throw new IndexOutOfRangeException($"{nameof(number)} not found");} int[] array = { 1, 15, -39, 0, 7, 14, -12 };ref int place = ref Find(7, array); // aliases 7's place in the array place = 9; // replaces 7 with 9 in the array WriteLine(array[4]); // prints 9 |
|---|
Mehr Ausdruck(Expression)
Expression bodied Methoden, Eigenschaften etc. sind ein großer Hit in C # 6.0, aber wir haben nicht zulassen, dass sie in allen Arten von Mitgliedern. C # 7.0 fügt Accessoire, Konstruktoren und Finalerer zur Liste der Dinge hinzu, die Ausdruckskörper haben können:
class Person {private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>(); private int id = GetId(); public Person(string name) => names.TryAdd(id, name); // constructors ~Person() => names.TryRemove(id, out *); // destructors public string Name {get => names[id]; // getters set => names[id] = value; // setters } } |
|---|
Throw Ausdrücke
Es ist leicht, eine Ausnahme in der Mitte eines Ausdrucks zu werfen: rufen Sie einfach eine Methode, die es für Sie tut! Aber in C # 7.0 sind wir direkt erlauben Wurf als Ausdruck in bestimmten Orten:
class Person { public string Name { get; }public Person(string name) => Name = name ?? throw new ArgumentNullException(name); public string GetFirstName() { var parts = Name.Split(" "); return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");} public string GetLastName() => throw new NotImplementedException(); } |
|---|
Das sind die coolen Features von C# 7.0
Viel Spaß
No comments:
Post a Comment