C# 7 – Stand der Dinge und Ausblick | heise online

C# 7 – Stand der Dinge und Ausblick

Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt.

vorlesen Druckansicht 14 Kommentare lesen
Lesezeit: 22 Min.
Von
  • Robin Sedlaczek
Inhaltsverzeichnis

Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt.

Microsoft hat nicht nur den Quellcode der .NET-Compiler-Plattform mit den Compilern für C# und VB.NET offengelegt, vielmehr ist der Softwarehersteller auch transparent hinsichtlich des Entwicklungsprozesses. So lassen sich die Fortschritte des Teams rund um die Compiler-Plattform auf GitHub beobachten. heise Developer berichtete bereits im März 2015 über die neuen Sprachfeatures, die das Roslyn-Team für die kommende Generation seiner .NET-Sprachen diskutiert.

Wie erwartet hat Microsoft auf der diesjährigen Connect();-Konferenz den ersten Release Candidate von .NET Core freigegeben. Inzwischen hat Microsoft die in Abbildung 1 gezeigte Roadmap auf GitHub angepasst. Der Release Candidate hat eine Go-Live-Lizenz, die bedeutet, dass .NET Core ab sofort in Produktivszenarien eingesetzt werden kann und der Softwarehersteller aus Redmond dafür sogar Support liefert. Das RTM soll im ersten Quartal 2016 erscheinen. Damit gibt es .NET nun offiziell auf Linux, Mac und Windows. Aber nicht nur die Laufzeitumgebung und die Basisbibliotheken stehen plattformübergreifend zur Verfügung: Mit Visual Studio Code, dessen Quelltext nun ebenfalls auf GitHub liegt, gibt es eine stark wachsende IDE auf allen Plattformen. Unter der Haube benutzt Visual Studio Code die .NET Compiler Platform für die Codeanalyse und das Kompilieren von C#-Code. Der Einsatz von C# war auf anderen Plattformen bisher nur mit Mono und MonoDevelop möglich.

Die .NET Core Roadmap macht Hoffnung auf eine baldige Veröffentlichung der RTM-Version. (Abb. 1)

(Bild: Microsoft)

Mit diesen plattformübergreifenden Technologien steht die .NET-Welt und C# einer breiteren Masse an Entwicklern zur Verfügung. Das ist ein Grund mehr, sich die nächste Generation der Sprache C# einmal genauer anzusehen. Die Liste der Neuerungen, die es in die neue Sprachversion schaffen werden, steht noch nicht komplett fest. Dennoch lässt sich von den Issues im Roslyn-Repository auf GitHub ein Überblick ableiten. Aktuell gibt es 21 aktive Vorschläge für Spracherweiterungen für den C#-7-Meilenstein. Nach dem Filtern der Liste danach, zu welchen Vorschlägen es bereits eine Sprachspezifikation gibt, bleiben nur noch neun Vorschläge übrig. Auch die Design Meeting Notes, die das Team in regelmäßigen Abständen veröffentlicht, zeigen, auf welche Bereiche es den aktuellen Fokus setzt. Und nicht zuletzt lohnt sich ein Blick in den Quellcode. Dort finden sich prototypische Implementierungen einzelner Features.

Somit zeichnet sich jetzt ab, welche Sprachmerkmale ihren Weg voraussichtlich in die neue Version von C# finden werden.

Eine von der Community seit langem geforderte Erweiterung ist die Unterstützung lokaler Funktionen. Sehr oft nutzen Entwickler private Hilfsmethoden, die nur an einer Stelle Verwendung finden. Dennoch sind diese Methoden im Sichtbarkeitsbereich der ganzen Klasse implementiert. Lokale Funktionen erlauben künftig die Implementierung der Methoden an der Stelle, an der sie benötigt werden.

Als Beispiel dient eine Funktion, welche die Fibonacci-Folge berechnet. Sie kann in C# 6 folgendermaßen implementiert und aufgerufen werden:

class Program
{
static int Fibonacci(int n) => (n < 2) ? 1 : Fibonacci(n - 1)
+ Fibonacci(n - 2);
static void Main(string[] args)
{
Console.WriteLine(Fibonacci(23));
Console.ReadKey();
}
}

In C# 7 wird es möglich sein, die Fibonacci-Funktion lokal zu implementieren – im konkreten Fall also innerhalb der Main-Methode. Die Funktion ist somit ausschließlich innerhalb von Main sichtbar.

class Program
{
static void Main(string[] args)
{
int firstFibonacci = 1;
int Fibonacci(int n) => (n < 2) ? firstFibonacci : Fibonacci(n - 1)
+ Fibonacci(n - 2);
Console.WriteLine(Fibonacci(23));
Console.ReadKey();
}
}

Innerhalb der lokalen Funktionsimplementierung ist dabei der Zugriff auf Variablen möglich, die im umschließenden Sichtbarkeitsbereich definiert wurden. Das zeigt obiges Beispiel anhand der Variable firstFibonacci. Ihre Definition erfolgt im Sichtbarkeitsbereich der Main-Methode und ihre Verwendung in der Implementierung der Fibonacci-Funktion. Wäre die Fibonacci-Funktion im Scope der Klasse beziehungsweise des Typs implementiert, benötigte die Funktion die Variable als Übergabeparameter. Mit lokalen Funktionen wird das überflüssig, weil der Zugriff auf den Zustand der umschließenden Methode direkt möglich ist. Das ist schon aus Sicht der Konsistenz sinnvoll: Innerhalb von nichtlokalen Funktionen ist schließlich der Zugriff auf den Zustand des umschließenden Sichtbarkeitsbereichs gegeben, nämlich auf den der Klasse beziehungsweise des Typs.

Theoretisch wäre die Implementierung lokaler Funktionen auch mit Bordmitteln von C# 6 möglich. Unter Verwendung eines Delegaten und eines Lambda-Ausdrucks könnte das folgendermaßen aussehen:

class Program
{
static void Main(string[] args)
{
var c = 3;
Func<int, int, int> localFunction = (int a, int b) => a + b + c;
    Console.WriteLine(localFunction(1, 2));
Console.ReadKey();
}
}

Dieser Ansatz bringt jedoch Schwierigkeiten mit sich. Obiger Code unterscheidet sich bewusst von der Berechnung der Fibonacci-Zahlen, damit er überhaupt funktioniert. Der rekursive Aufruf der Fibonacci-Funktion würde an einem Lambda-Ausdruck scheitern. Der Compiler würde einen Fehler melden, wenn die Lambda-Funktion versucht, sich selbst aufzurufen, weil die Methode in dem Fall noch nicht definiert ist. Weiterhin existiert kein allgemeingültiger Delegat, der als Typ für die Variable localFunction zum Einsatz kommen darf. Entwickler müssten somit eigene Delegat-Typen für jede lokale Funktion definieren – obiges Beispiel verwendet Func<int, int, int>. Das hat auch Auswirkungen auf die Performance, weil das System für den Delegaten in dem Fall auf dem Heap ein neues Objekt erzeugt. Das bringt einen entsprechenden Overhead mit sich, der bei lokalen Funktionen nicht existiert. Mit anderen Worten: Lokale Funktionen sind schneller und effizienter.

Ein offener Punkt im Roslyn-Team ist, ob die Variable aus dem umschließenden Sichtbarkeitsbereich vor der lokalen Funktion definiert werden muss oder ob die Definition auch nach der lokalen Funktion möglich ist. Das zeigt, wie wichtig jedes Detail bei der Implementierung solcher Sprachmerkmale sein kann.

Einen starken Fokus setzt das Roslyn-Team auf Konzepte aus der Welt der funktionalen Programmiersprachen. Daher werden algebraische Datentypen Einzug in die neue Sprachversion halten. In der objektorientierten Welt werden Methoden, die auf Datentypen arbeiten, im Vorfeld definiert. Wenn Entwickler die Methoden auf bestimmte Typen von Objekten anwenden, markieren sie die entsprechenden Methoden als virtuell, was zur Weiterleitung der Aufrufe an entsprechende Subtypen führt. Die Menge der Methoden muss daher im Vorfeld definiert sein. Damit können Entwickler anschließend eine unbestimmte Anzahl verschiedener Typen auf unterschiedlichen Abstraktionsniveaus definieren.

In der funktionalen Welt sieht es etwas anders aus: Hier definieren die Entwickler Daten als eine Menge von Typen. Die Funktionen, die auf diesen Daten arbeiten, definieren sie separat, wobei jede Operation eine Implementierung für jeden Typ in der Typ-Hierarchie anbietet. Das ist sinnvoll, wenn der Autor von Datentypen sämtliche Ausprägungen im Vorfeld definieren kann oder muss. Daraus resultiert die Möglichkeit, eine offene, unbestimmte Menge von Operationen definieren zu können, die auf diesen Daten arbeiten. Genau diese oft nützliche Option bietet C# bisweilen nicht an.

Algebraische Datentypen und Call-by-Pattern-Evaluierung (siehe Pattern Matching) wurden erstmals in der Sprache NPL vorgestellt. Bedeutung erlangten die Konzepte jedoch erst durch die Sprache Hope, die in den 1970er Jahren an der Edinburgh Universität veröffentlicht wurde. Heutzutage sind algebraische Datentypen fester Bestandteil vieler funktionaler Sprachen wie Miranda, Haskell und F#.

Algebraische Datentypen sind eine Art Kompositionstyp. Man kann ihn sich als eine Kombination aus mehreren Typen vorstellen. Es existieren zwei Klassen algebraischer Datentypen. Auf der einen Seite gibt es Produkttypen wie Tupel und Records. Die Werte eines Produkttyps enthalten in der Regel mehrere Werte, die Felder heißen. Alle Werte dieses Typs haben die gleiche Kombination von Feldtypen. Die Menge aller möglichen Werte eines Produkttypen ist das sogenannte mengentheoretische Produkt der Mengen aller möglichen Werte seiner Feldtypen.

Auf der anderen Seite gibt es die Summentypen, auch bekannt als Varianttypen. Die Werte dieses Typs sind in der Regel in verschiedene Klassen gruppiert, die Varianten genannt werden. Sein Wert wird normalerweise mit einem Konstruktor erstellt. Jede Variante hat dabei einen eigenen Konstruktor, der eine bestimmte Anzahl typisierter Argumente entgegennimmt. Die Menge aller möglichen Werte eines Summentyps ist die mengentheoretische Summe.

Nach der theoretischen Übersicht stellt sich die Frage, wie die Umsetzung in C# 7 erfolgen soll.

Das Entwicklerteam der .NET Compiler Platform diskutiert für Tupel-Typen syntaktische Darstellungen wie die folgende:

(int x, int y) t1 = (23, 45);
(int a, int b) t2 = t1; // Identitätskonvertierung
(int, int) t3 = t1; // Identitätskonvertierung

Mit der obigen Darstellung können Tupel von Daten dargestellt werden. Dabei spielen die Namen der einzelnen Werte nur eine zweitrangige Rolle, denn es gibt keinen wirklich guten Grund, warum (int x, int y) einen anderen Typ als (int a, int b) darstellen soll. Lediglich der Zugriff auf die Werte über die Namen hat eine Bedeutung:

Console.WriteLine("{0}, {1}", t1.x, t1.y);

Wie bei Parameterlisten von Methoden spielt die Reihenfolge der Typen eine größere Rolle. Eine Methode wird von einer anderen Methode überschrieben, wenn die Parameterliste dieselben Typen an der gleichen Stelle besitzt. Und so sollen auch Tupel mit denselben Typen an der gleichen Stelle als äquivalent betrachtet werden. Diese Art der Betrachtung erlaubt es ebenfalls, anonyme Tupel zu definieren. t3 im obigen Beispiel zeigt solch eine anonyme Darstellung. Unter der Haube stellt das System die Tupel mit demselben Typ dar. Dazu übersetzt der Compiler eine Tupel-Definition in einen generischen Typ, genauer in einen generischen Wertetyp.

Bezüglich der Typinferenz sehen die Regeln folgendermaßen aus: Stimmen die Namen, Typen und die Reihenfolge aller Parameter der betrachteten Tupel überein, werden die Namen für den abgeleiteten Typ übernommen. Andernfalls bleiben sie unbenannt.

var a1 = new[] { t1, t1 };    // leitet (int x, int y)[] ab,
// da alle Namen übereinstimmen
var a2 = new[] { t1, t2 }; // leitet (int, int)[] ab,
// da nicht alle Namen übereinstimmen
var a3 = new[] { t1, t3 }; // leitet (int x, int y)[] ab,
// da alle mit einem Namen übereinstimmen

Zusätzlich können Tupel in benannter und unbenannter Form verwendet werden. Sie können Zieltypen für die einzelnen Werte definieren, oder mit einem Wert daherkommen, aus dem dann der Typ abgeleitet wird. Der folgende Code soll das veranschaulichen:

var t1 = ("Hello", "World");           // leitet (string, string) ab
var t2 = (first: "John", last: "Doe"); // leitet (string first,
// string last) ab
var t3 = ("Hello", null); // führt zu einem Fehler,
// da null keinen Typ besitzt
var t4 = (first: "John", last: null); // führt zu einem Fehler,
//da null keinen Typ besitzt
(string, string) t5 = ("Hello", null); // setzt den resultierenden Typ
// auf (string, string)
(string first, string last) t6 = ("John", null); // setzt den
// resultierenden Typ auf (string first, string last)

Zwar gibt es bereits seit dem .NET Framework 4 die generische Klasse Tuple. Sie ist allerdings auf Tupel der Größe 8 beschränkt. Die native Unterstützung von Tupeln als Sprachelement in C# 7 ermöglicht eine bessere Lesbarkeit durch kürzeren Code und weitere Optimierungen durch den Compiler.

Die Einführung von Record-Typen ist noch interessanter. Entwickler können damit Daten als ein Set von Typen definieren und darauf beliebig viele Funktionen implementieren. Folgende Klassenhierarchie sei gegeben:

class Person
{
public Person(string name)
{
Name = name;
}
  public string Name
{
get;
}
}
class Student : Person
{
public Student(string name, double gpa)
: base(name)
{
Gpa = gpa;
}
  public double Gpa
{
get;
}
}
class Teacher : Person
{
public Teacher(string name, string subject)
: base(name)
{
Subject = subject;
}
  public string Subject
{
get;
}
}

Das Roslyn-Team zieht eine Syntax wie die folgende zur Darstellung der Klasssenhierarchie mittels Records in Betracht:

class Person(string Name);
class Student(string Name, double Gpa) : Person(Name);
class Teacher(string Name, string Subject) : Person(Name);

Record-Typen werden über eine Art Ableitung definiert. Die konkrete Syntax kann sich noch deutlich ändern. Dennoch sollen die beiden Beispiele im folgenden Abschnitt für die Erläuterung des Pattern Matching herangezogen werden.

Ein interessantes neues Feature, das im Kontext der Record-Typen diskutiert wird, ist das with-Schlüsselwort:

Teacher t1 = new Teacher(Name: "Shaldon", Subject: "String Theory");
Teacher t2 = t1 with { Subject = "Engineering" };

Mit diesem Konstrukt können Entwickler ein neues Objekt erzeugen, das dem Ausgangsobjekt gleicht, bei dem aber die Initialisierungsliste einige Werte überschreibt. Unter der Haube generiert das System Code, der folgendermaßen aussieht:

Teacher t2 = new Teacher(Name: t1.Name, Subject: "Engineering");

Programme können Werte algebraischer Typen mittels Pattern Matching analysieren. Dabei identifizieren sie einen Wert anhand seines Konstruktors oder seiner Feldnamen und extrahieren ihn aus dem Kompositionstypen. Zu diesem Zweck führt Microsoft neue Schlüsselworte und Sprachkonstrukte ein und erweitert vorhandene.

Folgender Code macht Gebrauch von der Klassenhierarchie beziehungsweise der neuen Record-Typdefinition aus dem vorherigen Abschnitt. Ziel ist es, den Code mittels Pattern Matching zu vereinfachen.

static string PrintedForm(Person p)
{
Student s;
Teacher t;
if ((s = p as Student) != null && s.Gpa > 3.5)
{
return $"Honor Student {s.Name} ({s.Gpa})";
}
else if (s != null)
{
return $"Student {s.Name} ({s.Gpa})";
}
else if ((t = p as Teacher) != null)
{
return $"Teacher {t.Name} of {t.Subject}";
}
else
{
return $"Person {p.Name}";
}
}

Dabei werden die Variablen s und t vor ihrer Benutzung definiert, obwohl sie nicht in allen Zweigen der if-Anweisung zum Einsatz kommen. Sämtliche Variablen müssen im Vorfeld mit eindeutigen Namen feststehen.

Das Roslyn-Team sieht für das Pattern Matching eine Erweiterung des is-Operators vor. Dadurch kann er ein Muster (Pattern) auf der rechten Seite entgegennehmen. Ein Teil davon ist eine Variablen-Deklaration. Damit sähe der vereinfachte Code folgendermaßen aus:

static string PrintedForm(Person p)
{
if (p is Student s && s.Gpa > 3.5)
{
return $"Honor Student {s.Name} ({s.Gpa})";
}
else if (p is Student s)
{
return $"Student {s.Name} ({s.Gpa})";
}
else if (p is Teacher t)
{
return $"Teacher {t.Name} of {t.Subject}";
}
else
{
return $"Person {p.Name}";
}
}

Der is-Operator ist in der Form erweitert, dass er den auf der rechten Seite angegebenen Typen aus der Variablen auf der linken Seite (p) extrahiert und das Ergebnis der Extraktion in die Variable zur rechten Seite (s und t) steckt. Die neu eingeführten Variablen sind nur in dem folgenden Scope gültig. Somit entfällt die Deklaration vor der if-Anweisung, was Ressourcen spart. Ein Problem, das an dieser Stelle besteht, ist, dass eine mehrfache Prüfung gegen den Typen Student erfolgt.

In der Kommenden C#-Version will das Team das switch-Statement erweitern. Das erklärte Ziel ist, dass die case-Zweige Muster darstellen, sodass switch zu einer Art Typ-Schalter werden kann. Der Compiler wird intelligent genug agieren, dass der generierte Code nicht – wie im obigen Beispiel – mehrfach gegen denselben Typen testet. Zusätzlich kommt im Kontext das Schlüsselwort when hinzu. Der resultierende Code könnte dann so aussehen:

static string PrintedForm(Person p)
{
switch (p)
{
case Student s when s.Gpa > 3.5:
return $"Honor Student {s.Name} ({s.Gpa})";
case Student s:
return $"Student {s.Name} ({s.Gpa})";
case Teacher t:
return $"Teacher {t.Name} of {t.Subject}";
default:
return $"Person {p.Name}"
}
}


Das Konstrukt extrahiert aus der Variablen im switch-Statement (p) den entsprechenden Typ und weist ihn anschließend einer Variablen zu (s und t). Das neue Schlüsselwort when sorgt für eine bedingte Ausführung des entsprechenden case-Zweigs. Der Code im Bedingungsausdruck kann auf die neu eingeführte Variable direkt zugreifen

Ein weiteres neues Schlüsselwort beziehungsweise Konzept ist das match-Statement. Es soll eine ähnliche Semantik wie der ?-Operator aufweisen, aber auch multiple Zweige kennen. Dadurch entfällt unter anderem die Notwendigkeit für return in den case-Zweigen. Das Resultat ist kürzerer und übersichtlicherer Code. Das folgende vereinfachte Beispiel zeigt einen Anwendungsfall:

return p match(
case Student s when s.Gpa > 3.5:
$"Honor Student {s.Name} ({s.Gpa})"
case Student { Name is "Shaldon" }:
"A Nerd"
case Student s:
$"Student {s.Name} ({s.Gpa})"
case Teacher t:
$"Teacher {t.Name} of {t.Subject}"
case null:
throw new ArgumentNullException(nameof(p))
case *:
$"Person {p.Name}"
);

In disem Zusammenhang erlaubt der is-Operator den expliziten Test auf einen Member des Typs. Für den Fall, dass die getestete Variable null ist, kann der entsprechende case-Zweig eine Exception werfen. Das Sternsymbol (*) ist in der Form der Anwendung ebenfalls neu. Als Wildcard kommt der Zweig in allen Fällen zum Tragen, die nicht mit case abgefangen wurden.

Das abschließende Beispiel erweitert den Code um die ebenfalls neue =>-Schreibweise für Methoden:

static string PrintedForm(Person p) => p match(
case Student s when s.Gpa > 3.5:
$"Honor Student {s.Name} ({s.Gpa})"
case Student { Name is "Shaldon" }:
"A Nerd"
case Student s:
$"Student {s.Name} ({s.Gpa})"
case Teacher t:
$"Teacher {t.Name} of {t.Subject}"
case null:
throw new ArgumentNullException(nameof(p))
case *:
$"Person {p.Name}"
);

Momentan genießen die gezeigten Erweiterungen für C# 7 die höchste Aufmerksamkeit. Dennoch gibt es zahlreiche kleinere, aber durchaus nützliche Features, die das Roslyn-Team für die kommende Version der Sprache diskutiert und ausprobiert. Interessierte Entwickler können die lange Liste auf GitHub einsehen.

Verschiedene Erweiterungen für Attribute stehen im Raum. Die Unterstützung generischer Attribute in C# ist denkbar. Auch Attribute, die nur zur Kompilierzeit existent sind, stehen zur Diskussion. Sie sind für die Verwendung vor der Erstellung des IL-Codes (Intermediate Language) gedacht. Für sie können auch gelockerte Vorgaben wie Typrestriktionen gelten. Das Einsatzgebiet von Attributen könnte auch dahingehend erweitert werden, dass die Sprachelemente auf weitere Code-Elemente wie auf Lambda-Ausdrücke angewandt werden.

Microsoft sieht vor, weitere Sprachkonstrukte in C# asynchron zu machen. Unter anderem sollen asynchrone Sequenzen beziehungsweise Streams Einzug in die Sprache halten. Vorstellbar wäre dann ein asynchrones Foreaching. Das würde folgendes Interface erfordern:

public interface IAsyncEnumerable<T>
{
public IAsyncEnumerator<T> GetEnumerator();
}
public interface IAsyncEnumerator<T>
{
public T Current { get; }
public Task<bool> MoveNextAsync();
}

Die Methode MoveNext würde in dem Fall kein bool mehr zurückgeben, sondern Task<bool>. Asynchrones Foreaching bringt aber verschiedene Probleme mit sich. Dazu gehört, dass die Asynchronität einer Foreach-Sequenz nicht immer im Code sichtbar ist, was zu unvorhersehbarem und schwer zu wartendem Verhalten führen kann. Um dennoch die Vorteile nutzen zu können, will das Team unter anderem die foreach-Syntax erweitern, wie folgender Code-Schnipsel zeigt:

foreach (string s in asyncStream) { ... } 
foreach async (string s in asyncStream) { ... }
foreach (await string s in asyncStream) { ... }

Ein IAsyncDisposable-Interface, das auch die Asynchronität des using-Statements erlaubt, ist ebenso denkbar.

Das Team um die Microsoft .NET Compiler Platform ist recht umtriebig. Es schaut sich andere Sprachen an und versucht, erfolgreiche und nützliche Konzepte abzuleiten oder lässt sich zumindest inspirieren. Unter anderem hat das Roslyn-Team einen Blick auf Go geworfen. Das Interesse richtet sich vor allem auf die Channels in der von Google entwickelten Sprache.

Dabei handelt es sich um Kommunikations-Pipelines zwischen Threads. Programme können Inhalte in die Pipelines hingeben und wieder herausholen. In Go muss der aufrufende Code warten, wenn er versucht, ein Element aus einer leeren Pipeline herauszuholen. Ebenso muss das Programm warten, wenn es ein Element hineingeben will, während die Pipeline voll ist. Das Roslyn-Team würde diese Endpunkte gerne awaitable machen, sodass der aufrufende Code asynchron weiterarbeiten kann. Ein derartiges Feature befindet sich vorerst aber nur in der Diskussion. Microsoft möchte zuerst Erfahrungen sammeln und schauen, in welchem Zusammenhang Channels mit asynchronen Sequenzen und Konzepten aus der DataFlow-Bibliothek stehen.

Interessierte Entwickler können die neuen Features bereits ausprobieren. Dazu müssen sie aus Git den entsprechenden Branch auschecken. In dem Patterns-Branch ((https://github.com/gafter/roslyn/tree/features/patterns)) befinden sich beispielsweise viele der neuen Erweiterungen wie Local Functions und einige Prototypen für algebraischen Datentypen und Pattern Matching. Die Aktivierung der Features erfolgt mittels Compiler Flags wie /features:patterns. Der oben genannte Branch aktiviert bei Verwendung der Präprozessordirektive __DEMO__ sämtliche Features.

Offensichtlich gehen dem Team rund um die .NET-Compiler-Plattform die Ideen für neue Sprachkonzepte nicht aus. Schließlich kommen aus der Community ein umfangreiches Feedback und viele Anfragen für Erweiterungen. Das Team schaut zudem ständig, wie es Code vereinfachen kann und welche sich häufig wiederholende Code-Segmente es in LOB-Anwendungen (line of business) findet. Noch steht aus, welche der beschriebenen Konstrukte es in die nächste Generation von C# tatsächlich schaffen werden, wobei einige Features größeres Interesse genießen.

Auffällig ist das Einfließen vieler Sprachkonzepte aus anderen Welten, darunter die funktionalen und Skript-Sprachen. C# wird damit immer mehr zu einem Allrounder. Betrachtet man demgegenüber JavaScript und die Evolution zu ECMA6 sowie TypeScript, mit denen objektorientierte Konzepte Einzug in die nicht typisierte Sprache halten, kann man feststellen, dass die Mainstream-Sprachen sich zunehmend aufeinander zu bewegen.

Zu hoffen bleibt, dass C# nicht in seiner Syntaktik erstickt, sondern Entwickler auch mit der kommenden Generation guten, überschaubaren Code schreiben können – Code, den auch unerfahrene Entwickler noch verstehen und warten können.

Robin Sedlaczek
hat mehr als fünfzehn Jahre Erfahrung als professioneller Softwareentwickler und Projektleiter. Seit vier Jahren ist er als CTO bei der Fairmas GmbH tätig und ist dort zuständig für den gesamten Entwicklungsbereich. In seiner Freizeit organisiert er die .NET User Group Berlin-Brandenburg und vermittelt sein Wissen auf Konferenzen, in User Groups, Onlinekursen, Fachartikeln und in seinem Blog. Er ist erreichbar via Twitter.
(rme)