Der neue Compiler Roslyn wirft bereits die ersten Schatten voraus. Auf der diesjährigen Build-Konferenz in San Francisco erklärte sein Schöpfer Andreas Hejlsberg das Projekt als Open Source und gab den Startschuss zur Veröffentlichung auf Codeplex per Knopfdruck. Durch den quelloffenen Code ist der Entwickler nun in der Lage, den Compiler nach Belieben zu erweitern und neue Sprachfeatures eigenständig einzubauen. Um zu zeigen, wie einfach dies möglich ist, veröffentlichte Microsoft auch einige neue Sprachfeatures für C# 6, die mithilfe von Roslyn umgesetzt wurden. Vermutlich werden diese die Versionsnummer 6 erhalten, allerdings gibt es dazu noch keine definitive Aussage von Microsoft.
Im Juni ist dann die End-User-Preview von Roslyn und der neuen C#-Version erschienen. Bei der Installation integriert sich diese direkt in Visual Studio 2013. Auch lässt sich die CTP-Version von Visual Studio „14“ mit integrierter End-User-Preview herunterladen. Einige Features, die Bestandteil der Finalversion sein sollen, sind in dieser Preview noch nicht enthalten. Insbesondere unter dem Aspekt der Stabilität ist einem produktiven Einsatz eher abzuraten. Ein Erscheinungstermin für die finale Version hat Microsoft indes noch nicht bekanntgegeben. Dieser Artikel gibt einen detaillierten Überblick über die kommende C#-Version. Er erläutert, welche neuen Features es gibt und was sich im Vergleich zu früheren C#-Versionen geändert hat.
C# 6: Initialisierer für Auto-Properties
Auto-Properties können mit der neuen Version C# 6 bereits in der Deklaration mit einem initialen Wert versehen werden:
public string Name { get; set; } = "Mein Name" public ListCollection { get; } = new List (); public DateTime CreationDate { get; } = DateTime.Now;
Im Hintergrund wird die private Variable der Property mit dem Default-Wert versehen. Auch kann eine read-only Auto-Property, die lediglich einen Getter besitzt, auf diese Weise erzeugt werden. Besonders in Kombination mit dem ebenfalls neuen primären Konstruktor ergibt dies eine interessante Option. Eine kleine Einschränkung besteht allerdings: Ein Zugriff mit dem this-Zeiger ist nicht möglich. Der Grund liegt darin, dass die Zuweisung der Properties erfolgt, bevor das Objekt vollständig erzeugt wurde.
Primärer Konstruktor
Eine der vielleicht interessantesten und wichtigsten Neuerungen in C# 6 ist der primäre Konstruktor. Er erleichtert das Anlegen von Konstruktoren deutlich, indem er es ermöglicht, direkt bei der Deklaration einer Klasse einen Konstruktor zu definieren. Zum Vergleich zunächst ein beispielhafter Konstruktor der aktuellen C#-Version:
public class Person { public string Vorname { get; set; } public string Nachname { get; set; } public Person (string vorname, string nachname) { this.Vorname = vorname; this.Nachname = nachname; } }
In der neuen Version sieht das Beispiel so aus:
public class Person (string vorname, string nachname) { public string Vorname { get; set; } = vorname; public string Nachname{ get; set; } = nachname; }
Die Parameter des Konstruktors werden direkt an die Deklaration der Klasse angehängt, das Anlegen eines Konstruktors entfällt dabei völlig. Durch die Kombination des primären Konstruktors und des Initialisierers für Auto-Properties ergibt sich ein schlankerer, gut lesbarer Code. Wenn die Klasse weitere Konstruktoren besitzt, müssen sie den primären Konstruktor aufrufen. Dieser Aufruf muss nicht direkt über this(…) erfolgen, sondern kann auch indirekt über weitere Konstruktoren geschehen. Da der primäre Konstruktor als einziger den Basiskonstruktor aufrufen kann, muss er am Ende der Kette aufgerufen werden. Abseits der Klassen lässt sich der primäre Konstruktor auch für Structs verwenden. Bei mit partial verteilten Klassen kann nur ein Teil einen primären Konstruktor besitzen und die Initialisierung der Felder muss auch innerhalb dieses Teils erfolgen.
Field-Parameter
Eine weitere Neuerung, die im Zusammenhang mit dem primären Konstruktor in C# 6 eingeführt wird, sind die Field-Parameter. Die Parameter eines primären Konstruktors sind normalerweise lediglich in der Initialisierung verfügbar. Um sie auch innerhalb der Klasse verwenden zu können, werden sie normalerweise in einem neu definierten Feld oder einer Property abgespeichert:
public class Person (string vorname, string nachname, string ort) { private string _ort = ort; }
Das Unschöne an dieser Variante ist, dass sich der Parameter und das Feld vom Namen unterscheiden müssen. Darauf basierend wurde die Möglichkeit geschaffen, die Parameter auch außerhalb der Initialisierung zu verwenden. Dies geschieht explizit, indem dem Parameter ein Zugriffsmodifizierer vorangestellt wird:
public class Person (string vorname, string nachname, private string ort) { }
Durch den Zugriffsmodifizierer im primären Konstruktor wird intern ein neues Feld mit dem gleichen Namen erstellt, das dann auch außerhalb der Initialisierung verwendet werden kann. Auch wenn private wohl der häufigste Fall sein wird, ist natürlich auch die Verwendung aller anderen Zugriffsmodifizierer möglich, genauso wie die Verwendung von Attributen.
„Using“ mit statischen Klassen
Bislang erlaubte das Schlüsselwort using lediglich den Import von Namespaces. Mit der neuen Version ist nun auch der Import von statischen Klassen möglich. Dadurch kann einiges an Code eingespart werden, was die Lesbarkeit deutlich erhöht:
using System.Console; public static main (string[] args) { WriteLine("New School of IT"); }
Zum einen funktioniert das aber lediglich für statische Typen. ConsoleColor.Black als Enumeration kann beispielsweise nicht in Black verkürzt werden. Zum anderen muss man darauf achten, dass es nicht zu Mehrdeutigkeiten kommt. Das wäre etwa der Fall, wenn innerhalb des Codes eine weitere Methode namens WriteLine mit der gleichen Syntax existieren würde. Momentan ist es noch nicht möglich, den Import von Typen mit Extension-Methoden zu regeln:
using System.Linq.Enumerable; public static main (string[] args) { var range = Range(1,10); var odd = Where (range, item => item % 2 == 1) var odd = range.Where (item => item % 2 == 1) }
Die ersten beiden Codezeilen funktionieren, beides sind normale Aufrufe einer statischen Methode. Die letzte Zeile dagegen funktioniert nicht bzw. erst dann, wenn der komplette Namespace System.Linq importiert wird. Ob sich das mit diesem Release ändern wird, ist aktuell noch nicht abzusehen.
„await/async“ in „Catch“- und „Finally“-Blöcken
Um das Entwickeln mit dem asynchronen Muster zu vereinfachen, wurden mit C# 5.0 die beiden Schlüsselworte await und async eingeführt. Da deren Verwendung in Catch– und Finally-Blöcken während der Entwicklung zu größeren Schwierigkeiten geführt hatte, wurde ihr Einsatz innerhalb dieser Blöcke nicht unterstützt. Diese Restriktion führte jedoch bei vielen Entwicklern zu Problemen und erhöhtem Aufwand, weshalb sie mit der neuen Version wieder aufgehoben wird: Sowohl await als auch async können jetzt in Catch– und Finally-Blöcken verwendet werden:
Resource res = null; try { res = await Resource.OpenAsync(...); } catch(ResourceException e) { await Resource.LogAsync(res, e); } finally { await res.CloseAsync(); }
[ header = Seite 2: Exception-Filter ]
Exception-Filter
Ebenfalls interessant sind die neuen Bedingungen zum Filtern von Exceptions:
try { ... } catch(SqlException e) if(e.Number == 4711) { ... }
Der Catch-Block wird nur dann betreten, wenn die if-Bedingung zu true evaluiert wurde, andernfalls bleibt der Stack im Gegensatz zu einem Catch und Rethrow unangetastet. Damit ist es auch möglich, mehrere Catch-Blöcke zu definieren, die die gleichen Exception-Typen behandeln – sofern diese sich durch ihre Filter unterscheiden:
try { ... } catch(SqlException e) if(e.Number == 4711) { ... } catch(SqlException e) if(e.Number == 42) { ... }
Null-Propagating-Operator
Ein Feature, das den Code reduziert und deutlich lesbarer macht, ist der Null-Propagating-Operator (auch „Monadic Null Checking“ genannt). Mit ihm lassen sich Überprüfungen von Nullwerten deutlich vereinfachen. Zunächst ein Beispiel aus der alten Version:
if (points != null) { var next = points.FirstOrDefault(); if (next != null && next.X != null) return next.X; } return -1;
Zur Verkürzung wurde der bereits bekannte ?-Operator erweitert. Das gleiche Beispiel lässt sich mit der neuen Version auf einen Einzeiler verkürzen:
var value = points?.FirstOrDefault()?.X ?? -1;
Die Methoden oder Properties, die auf den ?-Operator folgen, werden nur dann ausgewertet, wenn der entsprechende Wert ungleich Null ist. Der Operator lässt sich sowohl auf Properties als auch auf Methoden anwenden.
Binary Literals
Ähnlich wie bis dato für hexadezimale Zahlen möglich, lassen sich jetzt auch binäre Zahlen anlegen:
var bits = 0b11110000 // 0xF0
Besonders bei der Verwendung von binärer Logik und als Flags markierten Enumerations kann dies die Wartbarkeit erhöhen. Zudem fällt es leichter, das Ergebnis einer Operation nachzuvollziehen.
Digit Separators
Numerische Werte lassen sich im Code zur besseren Lesbarkeit formatieren. Sie können mit einem Unterstrich gruppiert werden:
int number = 1_000_000
Diese Formatierung lässt sich für alle Darstellungsformen von numerischen Werten (binär, hexadezimal oder dezimal) anwenden. Vor allem bei den neuen binären Werten kann dies von Vorteil sein, da sie relativ schnell sehr lang und schwer lesbar werden können. Allerdings sind sowohl die binäre Darstellung als auch die neuen Formatierungen momentan noch nicht fertiggestellt und somit noch nicht in der Preview enthalten. In das fertige Release sollten sie es allerdings noch schaffen.
Expression-bodied Members
Expression-bodied Members erleichtern und vereinfachen das Anlegen von Properties und Methoden, indem sie direkt bei der Deklaration mit dem eigentlichen Inhalt versehen werden können. Die Syntax ähnelt dabei der von LINQ. In der aktuellen Version sieht das folgendermaßen aus:
public double Volume { get{ return a * b * c;} } public double CalculateVolume(double a, double b, double c) { return a * b * c; }
Mit C# 6.0 kann dies vereinfacht werden:
public double Volume => a * b * c; public double CalculateVolume(double a, double b, double c) => a * b * c;
Allerdings ist auch diese Funktion noch nicht fertig implementiert (Stand Juli 2014) – laut Microsoft soll das allerdings noch in dieser Version geschehen.
[ header = Seite 3: Declaration Expressions ]
Declaration Expressions
Declaration Expressions ermöglichen die Deklaration von Variablen innerhalb anderer Ausdrücke. Dies stellt besonders bei out-Parametern eine Vereinfachung dar, da nicht mehr vorher eine passende Variable deklariert werden muss, sondern dies direkt im Aufruf passieren kann. Damit ist die folgende Syntax möglich:
Int.TryParse("4711", out int i)
Ein weiterer interessanter Anwendungsfall sind LINQ-Queries:
String[] stringArr = { "4711", "ABC", "42", "1A"); from s in stringArr select int.TryParse(s, out int i) ? i : -1
Eine solche Variable ist nur innerhalb des Codeblocks gültig, in dem sie definiert wurde. Das bedeutet, dass sie im Beispiel mit einem Try-Parse sowohl im if- als auch im else-Zweig gültig ist (obgleich eine Verwendung im else-Zweig wohl nur selten sinnvoll ist). Auch die Nutzung von implizit typisierten Variablen mit var ist möglich.
Dictionary Initializer
Um Dictionaries etwas eleganter initialisieren zu können, wurde ihnen eine neue Syntax spendiert:
Dictionarydict = new Dictionary { [2] = "TestValue", [5] = "TestValue2" }
Ein JSON-ähnlicher Zugriff mit dem $-Operator war zwar auch geplant, ist allerdings erst einmal zurückgestellt worden und wird voraussichtlich nicht mehr in das Release einfließen.
Event Initializers
Bislang konnten Events nicht direkt im Objekt-Initialisierer zugewiesen werden. Diese Einschränkung gibt es nun nicht mehr. Events lassen sich ab der neuen Version ähnlich wie Properties im Objekt-Initialisierer zuweisen:
var myClass = new MyClass { MyEvent += MyEventhandler }
Resümee
Der große Wurf ist die neue Version von C# eher nicht, die Neuerungen der vorherigen Majorversionen waren umfassender und hatten größere Auswirkungen. Als Beispiel seien hier die Generics in C# 2.0, LINQ in C# 3.0 und die Verbesserung im Handling von asynchronen Methoden in C# 5.0 erwähnt. Aber es sind die vielen kleineren Änderungen und Erweiterungen, die in die Kategorie „Syntactic Sugar“ fallen und das Leben des Entwicklers doch angenehmer gestalten können. Insbesondere der primäre Konstruktor und all das, was damit in Verbindung steht, erspart einem unter Umständen viel Code und erhöht damit die Lesbarkeit und Wartbarkeit des Codes. Neben diesen rein syntaktischen Verbesserungen sind auch einige Erweiterungen hinzugekommen, die vorher nicht oder nur umständlich möglich waren – beispielsweise die Exception-Filter und Erweiterungen von await und async.
Gegenwärtig befindet sich die neue Version noch in der Entwicklung. Die meisten der hier erwähnten Neuerungen sind bereits fertig implementiert, einige sind geplant und sollen noch in dieses Release einfließen. Ein Überblick über den aktuellen Status kann hier eingesehen werden – ein Ausschnitt zeigt Tabelle 1.
Feature | Example | C# | VB |
Primary constructors | class Point(int x, int y) { … } | Done | Maybe |
Auto-property initializers | public int X { get; set; } = x; | Done | Exists |
Getter-only auto-properties | public int Y { get; } = y; | Done | Done |
Using static members | using System.Console; … Write(4); | Done | Exists |
Dictionary initializer | new JObject { [„x“] = 3, [„y“] = 7 } | Done | Planned |
Indexed member initializer | new JObject { $x = 3, $y = 7 } | Withdrawn | Planned |
Indexed member access | c.$name = c.$first + “ “ + c.$last; | Withdrawn | Exists |
Declaration expressions | int.TryParse(s, out var x); | Done | Maybe |
Await in catch/finally | try … catch { await … } finally { await … } | Done | Planned |
Exception filters | catch(E e) if (e.Count > 5) { … } | Done | Exists |
Binary literals | 0b00000100 | Planned | Planned |
Digit separators | 0xEF_FF_00_A0 | Planned | Planned |
Expression-bodied members | public double Dist => Sqrt(X * X + Y * Y); | Planned | No |
Event initializers | new Customer { Notify += MyHandler }; | Planned | Planned |
Null propagation | customer?.Orders?[5]?.$price | Done | Planned |
Semicolon operator | (var x = Foo(); Write(x); x * x) | Maybe | Maybe |
Private protected | private protected string GetId() { … } | Withdrawn | Withdrawn |
Params IEnumerable | int Avg(params IEnumerable |
Planned | Planned |
Constructor Inference | new Tuple(3, „three“, true); | Maybe | Maybe |
String interpolation | „{p.First} {p.Last} is {p.Age} years old.“ | Maybe | Maybe |
NameOf operator | string s = nameof(Console.Write); | Planned | Planned |
Die neuen Features entstanden im Wesentlichen neben der Entwicklung des Hauptprojekts Roslyn, auf das Microsoft seinen Fokus legt. Roslyn erleichtert das Entwickeln und Nachrüsten von weiteren Sprachfeatures deutlich, und Microsoft möchte dies mit C# 6.0 demonstrieren. Man darf gespannt sein, was die Zukunft noch mit sich bringt.