Friday, March 24, 2017

Was ist neu in C # 7.0




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})");
}
In C # 7.0 fügen wir out -Variablen; Die Möglichkeit, eine Variable direkt an dem Punkt zu deklarieren, an dem sie als out-Argument übergeben wird:
public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    WriteLine($"({x}, {y})");
}
Da die out-Variablen direkt als Argumente für out-Parameter deklariert werden, kann der Compiler normalerweise sagen, was sein Typ sein sollte (es sei denn, es gibt widersprüchliche Überladungen), so ist es gut, var anstelle eines Typs zu verwenden, um sie zu deklarieren:
p.GetCoordinates(out var x, out var y);
Eine allgemeine Verwendung von out-Parametern ist das Try ... -Muster, wobei ein boolescher Rückgabewert Erfolg anzeigt, und out-Parameter die erhaltenen Ergebnisse:
public void PrintStars(string s)
{
    if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
    else { WriteLine("Cloudy - no stars tonight!"); }
}
Wir planen, " Wildcards" als Out-Parameter auch in Form eines * zuzulassen, damit Sie Parameter ignorieren können, die Sie nicht interessieren:
p.GetCoordinates(out int x, out *); // I only care about x
Musterabgleich
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:
      // ...
}
Interessanterweise würde die Zulassung von Ausdrücken als „case“-Anweisungen es auch erforderlich machen, Ausdrücke als Argumente in „goto case“-Anweisungen zuzulassen.
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) {...}
Beachten Sie, dass „case Point(490, 42)“ ohne den „where“-Ausdruck nicht erreicht werden könnte, was zur Ausgabe eines Fehlers oder einer Warnung durch den Compiler führen würde.
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 }
Analog dazu könnte auch der „is“-Operator Musterabgleich unterstützen, was nicht nur eine Typprüfung ermöglichen sondern auch Unterstützung für eine allgemeinere Abfrage bedeuten würde, ob für einen Typ bestimmte Member vorhanden sind.

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;
  }
}
Wie aus dem Listing zu ersehen ist, kann ein Typ mithilfe von Tupelunterstützung als Tupel deklariert werden – das heißt, er kann zwei (oder mehr) Werte aufweisen. Das kann überall da genutzt werden, wo ein Datentyp genutzt werden kann – auch als Feld, Parameter, Variablendeklaration oder sogar Rückgabewert von Methoden. Beispielsweise gibt der folgende Codeausschnitt ein Tupel als Rückgabewert einer Methode zurück:
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");
  // ...
}
In diesem Listing gibt es eine Methode, die ein Tupel zurückgibt, und eine Variablendeklaration des ersten und des letzten Elements, dem das Ergebnis von „GetNames“ zugewiesen ist. Beachten Sie, dass die Zuweisung auf der Reihenfolge innerhalb des Tupels (nicht auf den Namen der empfangenden Variablen) beruht. In Anbetracht der Alternativen, die wir heute verwenden müssen – ein Array oder eine Auflistung, einen benutzerdefinierter Typ oder einen out-Parameter – stellen Tupel eine attraktive Möglichkeit dar.
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")
und hier:
var name = (first: "John", last: "Doe")
Das Ergebnis könnte in einem anonymen Typ oder in expliziten Variablen bestehen, wie hier:
var name = (first: "John", last: "Doe")
oder hier:
(string first, string last) = GetNames("Inigo Montoya")
Potenziell könnte ein Array in ein Tupel konvertiert werden, wie hier:
var names = new[]{ "Inigo", "Montoya" }
Auf die einzelnen Elemente eines Tupels könnte anhand des Namens zugegriffen werden:
Console.WriteLine($”My name is { names.first } { names.last }.”);
Datentypen könnten abgeleitet werden, wo sie nicht explizit identifiziert werden (wobei der gleiche Ansatz wie bei anonymen Datentypen im Allgemeinen verfolgt wird)
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);
Diese einfache Anweisung würde automatisch Folgendes generieren:
Einen Konstruktor:
public Person(string Name, int Age)
{
  this.Name = Name;
  this.Age = Age;
}
Schreibgeschützte Eigenschaften, wodurch ein unveränderlicher Typ erstellt würde
Gleichheitsimplementierungen (wie etwaGetHashCode“, „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 );
Ein Alternativvorschlag wäre, statt sich in dem „with“-Ausdruck auf die Signatur des Konstruktors zu beziehen, ihn in einen Aufruf einer „With“-Methode zu übersetzen, wie hier:
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}.");
In einer dekonstruierenden Deklaration können Sie für die einzelnen deklarierten Variablen var verwenden:
(var first, var middle, var last) = LookupName(id1); // var inside
Oder sogar ein einzelnes var außerhalb der Klammern als Abkürzung:
var (first, middle, last) = LookupName(id1); // var outside
Sie können auch in vorhandene Variablen mit dekonstruierender Zuweisung dekonstruieren:
(first, middle, last) = LookupName(id2); // deconstructing assignment
Dekonstruktion ist nicht nur für Tupel. Jeder Typ kann dekonstruiert werden, solange er eine (Instanz oder Erweiterung) De-Konstruktor-Methode des Formulars hat:
public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }
Die Out-Parameter bilden die Werte, die sich aus der Dekonstruktion ergeben. (Warum verwendet es Parameter, anstatt ein Tupel zurückzugeben? Das ist so, dass Sie mehrere Überladungen für verschiedene Wertezahlen haben können).
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);
Es wird ein gemeinsames Muster sein, dass Konstruktoren und Dekonstrukteure so "symmetrisch" sind. Wie für Variablen, planen wir, "Wildcards" in Dekonstruktion zu ermöglichen, für Dinge, die Sie nicht interessieren:
(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);
    }
}
Lokale Funktionen sind für dieses Szenario perfekt:
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;
Außerdem führt C # 7.0 Binärliterale ein, sodass Sie Bitmuster direkt angeben können, anstatt hexadezimale Notationen auswendig kennen zu müssen.
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();
}
Hinweis: Throw Ausdrücke noch nicht in Preview 4.
Das sind die coolen Features von C# 7.0
Viel Spaß

No comments:

Post a Comment