Mit der Offenlegung des C#-Compilers können neue C#-Features bereits heute verwendet werden. Da dieser Prozess jedoch aufwändig und nicht unbedingt geeignet für ein produktives Umfeld ist, gibt es mit C# 7 wieder ein Abbild des aktuellen Stands. Dabei wird C# 7 zusammen mit Visual Studio 2017 ausgerollt. Da eine Beschreibung aller Features von C# 7 den Rahmen eines Blog Posts sprengen würde, gibt es hier den ersten Teil.
Binary literals
Mit Binary literals wird eine Möglichkeit geboten, um Binärzahlen im Code besser darzustellen. Dies ist vor allem nützlich für jeden, der hardwarenah in C# programmiert.
Beispiel
In diesem Beispiel soll die Zahl 8 als Binärformat dargestellt werden, zum einen über die neue Technik und zum anderen im Vergleich, wie es bereits heute funktioniert. In jedem der dargestellten Fälle wird am Ende in der Variablen EnableFlag die Zahl 8 stehen.
// Integer representation public const int EnableFlag = 8; // Shift representation public const int EnableFlag = 1 << 3; // Convert representation public const int EnableFlag = Convert.ToInt32("1000", 2); // Binary literal representation (C#7) public const int EnableFlag = 0b1000;
Digit separators
Mit der neuen Binärdarstellung geht ein weiteres Problem einher. Was, wenn längere Binärzahlen oder z.B. große Dezimalzahlen dargestellt werden sollen. Ein Trennzeichen z.B. für Tausenderstellen oder Halbbytes, um die Zahlen besser lesbar zu machen, würde die Lesbarkeit verbessern. Auch hier bietet C# 7 eine interessante Neuerung.
Das Trennzeichen wurde auf den Unterstrich _ festgelegt. Mit diesem ist es möglich, beliebige Zahlenwerte zu trennen. Dabei kann es beliebig oft wiederholt werden (siehe Beispiel SpecialNumber) und wird später beim Kompilieren einfach ignoriert. Zu Bedenken gilt: Das Trennzeichen kann nur zwischen Ziffern stehen und nicht zwischen Sonderzeichen (z.B. der Dezimalpunkt oder am Anfang oder am Ende einer Zahl).
Beispiel
public const int EnableFlag = 0b0000_1000; public const decimal IntervalInMilliseconds = 1_000_000.500_50; public const int WhiteColor = 0xFF_FF_FF; public const decimal SpecialNumber = 10___543__245_123.500_241;
Ref locals
Vielen C#-Entwicklern dürfte ref von Methoden-Parametern bekannt vorkommen. Durch dieses Keyword können Parameter als Referenz statt als Wert an eine Methode übergeben werden.
Ref locals ermöglicht es nun, eine Referenz auf eine Variable zu deklarieren. Wird die lokale Referenz angepasst, so verändert sich damit auch der Ursprungswert. Ref locals ergeben alleine wenig Sinn, mit dem nächsten Feature in der Liste (ref returns) wird der Anwendungsfall hierfür deutlich klarer.
Beispiel
int var1 = 5; ref int refVar = ref var1; refVar = 15; // var = 15 int var2 = 5; var refVar2 = var2; refVar2 = 15; // var2 = 5;
Ref returns
Eine Referenz als Parameter übergeben gibt es bereits, was noch fehlt ist als Rückgabewert einer Funktion eine Referenz statt eines Werts zu definieren. Dieses fehlende Feature wird nun auch in C# 7 abgedeckt.
Als Vorteil ergibt sich, dass der Rückgabe nicht kopiert werden muss, sondern eine Referenz zurückgegeben wird. Besonders bei großen Strukturen ergibt sich hier ein großer Performance- und Speichervorteil. Zusätzlich kann der Rückgabewert bearbeitet werden, wodurch sich der Ursprungswert verändert.
Beispiel
ref int Max(ref int x, ref int y) { if (x > y) return ref x; else return ref y; } int a = 5; int b = 10; ref int max = ref Max(ref a, ref b); max = 0; // b = 0
Local functions
Lokale Funktionen bieten die Möglichkeit, innerhalb einer Funktion eine weitere Funktion zu definieren. Was heute schon über die Func- und Action-Klassen möglich ist, kann nun auch direkt als lokale Funktion deklariert werden.
Durch die Nutzung von lokalen Funktionen ergeben sich mehrere Vorteile:
- ref und out Parameter können nun in lokalen Funktionen verwendet werden
- Beim Anlegen einer Action oder Func wird ein Objekt auf dem Heap allokiert. Dies passiert bei einer lokalen Funktion nicht. Diese wird durch den Compiler entweder in eine statische oder in eine Klassen-Methode umgewandelt, je nachdem ob Konstrukte der Klasse verwendet wurden.
- Werden Variablen außerhalb der Funktion verwendet (im Beispiel wäre das offsetX), so generiert der C#-Compiler im Falle einer Func oder Action eine separate Klasse mit einem Feld pro verwendeter Variablen. Bei einer lokalen Funktion wird jedoch ein struct, also ein Value Type, generiert. Dadurch wird kein Speicher auf dem Heap benötigt und es gibt auch keine Bedenken bei parallelem Zugriff.
Beispiel
int Compute()
{
int offsetX = 5; var addOffset = new Func<int, int>(x => x + offsetX); addOffset(5); // 10 int AddOffset(int x) => x + offsetX; AddOffset(5); // 10
}
Tuple
Wer mehr als einen Rückgabewert benötigt oder eine Sammlung an Daten erstellen wollte, die zu kurzlebig für eine eigene Klasse sind, musste entweder auf out-Parameter oder eine Tuple-Klasse zurückgreifen.
Die Tuple-Klasse gibt es seit dem .NET Framework 4. In dieser kann eine beliebige Kombination an Daten abgelegt werden. Das Problem an der Tuple-Klasse ist, dass die zugewiesenen Properties keine sprechenden Namen haben. Diese werden mit Item1 bis ItemN abgerufen.
Das Tuple-Feature in C# 7 ermöglicht es nun, benannte Tuples zu erstellen und führt hierzu sogar eine eigenen Syntax ein.
Zu Bedenken gibt es hier nur, dass Tuple kurzlebig und klein sein sollten, da die Syntax den Code doch recht schnell unleserlich macht. Alles andere sollte in eine eigene Klasse oder Struktur ausgelagert werden.
Beispiel
(int count, decimal sum) Compute(IEnumerable<int> items) { var result = new (int count, decimal sum) { count = 0, sum = 0 }; foreach (var item in items) { result.count++; result.sum += item; } } (int c, decimal s) x = Compute(new[] { 1, 2, 3, 4, 5 }); Console.WriteLine($"count = {c}, sum = {x.s}"); // count = 5, sum = 15
Out Parameter
Wer eine Funktion mit Out-Parametern verwendet (das beste Beispiel ist die TryParse-Methode), der musste sich zuerst mit einer Variablendeklaration herumschlagen, bevor man die Funktion verwenden konnte. Doch C# 7 bietet nun Abhilfe, indem man diese Variablendeklaration direkt mit dem Out-Parameter verbinden kann.
Beispiel
if (int.TryParse("500", out int cost)) Console.WriteLine(cost); // 500
Persönlich freue ich mich bereits auf Features wie Local Functions, Tuples und Out Parameter, da der Anwendungsfall hierfür doch recht häufig vorkommt. Die anderen Features sind zwar nicht unwichtig, hier ist die Häufigkeit der Anwendung aber eher gering.
Im nächsten Teil soll auf die weiteren Feature wie Pattern Matching, Throw Expressions, Expression bodied members und Generalized async return types eingegangen werden. Da sich Visual Studio zum Zeitpunkt des Blogpost noch in der RC-Version befindet, können unter Umständen bestimmte C# 7 Features nicht in der finalen Version enthalten sein.