Alles neu macht Roslyn

C# 6: das sind die neuen Codefeatures
Kommentare

Im Zuge des Roslyn-Projekts wird von Microsoft auch eine neue C#-Version veröffentlicht. C# 6 bietet einige interessante neue Sprachfeatures, die es lohnen, genauer beleuchtet zu werden.

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 List Collection { 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:

Dictionary dict = 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 numbers) { … } 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.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -