Auf dem Weg zu C# 7: Sprachfeatures und Entwicklungsprozess

Seite 2: Kommende Sprachfeatures

Inhaltsverzeichnis

Folgende Themenbereiche hat das Team isoliert:

Objektorientierte Sprachen sind verhaltensorientiert. Betrachtet man moderne Anwendungen, hat sich der Umgang mit Daten relativ stark verändert. Längst werden Daten nicht mehr nur aus großen Datenbanken gelesen, von einem Benutzer bearbeitet und wieder gespeichert. Vielmehr ist das verteilte Produzieren und Konsumieren kleinerer Datenhäppchen Thema – in Services, IoT-Anwendungen, mobilen Apps, Desktop-Anwendungen und so weiter. Daraus ergeben sich neue Anforderungen an den Umgang mit Daten. Inspiriert von funktionalen Sprachen, möchte man etwa Konzepte wie Pattern Matching, Unveränderbarkeit, Slicing und Tupel in die Sprache aufnehmen.

Der engen Beziehung zwischen C# und .NET geschuldet, adressiert das Design-Team Performance- und Zuverlässigkeitsprobleme der Sprachplattform. So erfordern zum Beispiel Strukturtypen und generische Typen oftmals Kopiervorgänge und lassen sich nicht per Referenz verarbeiten. Das verursacht Performance-Einbußen, insbesondere auf kleineren Geräten.

Die Zuverlässigkeit der Sprachen hingegen spiegelt sich etwa im Speichermanagement wider. C# und VB.NET sind verwaltete Sprachen und gehen weitgehend sicher mit dem Speicher um. Probleme treten auf, wenn man sich das Löschen und Finalisieren von Objekten anschaut – insbesondere an der Grenze zu nicht verwaltetem Code. Microsoft Research beschäftigt sich intensiv mit derartigen Themen. Ergebnisse aus der Forschungsarbeit sollen in beide Sprachen einfließen.

Microsoft veröffentlichte jüngst den Quellcode der CoreCLR. Dabei handelt es sich um eine abgespeckte .NET-Laufzeitumgebung, speziell für den Einsatz in der Cloud optimiert. Nicht nur daran lässt sich ablesen, dass das .NET Framework flexibler wird. Auch die Anzahl der auf GitHub gehosteten Repositories spricht eine deutliche Sprache. Aus derartigen Entwicklungen ergeben sich neue Anforderung für die Modularisierung von Code und die Komposition von Softwareeinheiten. Generalisierte Erweiterungsmethoden sind dabei eines der wenigen Sprachelemente, mit denen Microsoft dem begegnen möchte. Vielmehr hält das Unternehmen in dem Zusammenhang Arbeit am Framework und Tooling für nötig. Statisches Linken statt IL Merge, Determinismus, Versionierung und Unterstützung für NuGet sind nur ein paar Themen, die dabei eine Rolle spielen.

Die verteilte Natur des modernen Rechnens soll ebenfalls bei den Neuerungen in C# Beachtung finden. Präziser geht es um die Verteilung und parallele Ausführung datenverarbeitender Prozesse sowie Berechnungen im weitesten Sinne. Parallelität und Asynchronität sind hier die Schlagworte. Entsprechende Konzepte müssen sich in den Sprachen wiederfinden, um den Umgang mit der Parallelität zu vereinfachen und verteiltes Rechnen überhaupt erst zu ermöglichen. Dazu wurden mit C# 5 beispielsweise async[i] und [i]await eingeführt. Die Schlüsselworte vereinfachen den Umgang mit nebenläufigen Prozessen, indem der Compiler entsprechenden Code zum Erzeugen und Synchronisieren von Threads generiert.

Bisher bietet das async/await-Feature lediglich die sogenannte Single-Value Asynchrony. Das bedeutet, dass sich nur genau ein Wert zurückgeben lässt. Das manifestiert sich im Typ Task<T>, den asynchrone Methoden an den Aufrufer liefern müssen. Um das verteilte Rechnen besser zu unterstützen, sollen kommende Versionen der .NET-Sprachen asynchrone Sequenzen und Streams beherrschen. Ein weiteres Thema ist die Serialisierung von Daten: In Zukunft soll es keine direkt eingebaute Serialisierung mehr geben. Stattdessen wird der Fokus auf die Unterstützung für benutzerdefinierte Serialisierung gerichtet, wobei Performance unter Vermeidung von Reflexion im Vordergrund steht.

Metaprogrammierung bedeutet vereinfacht gesagt, dass Programme andere Programme schreiben. Code generiert also Code. Die Idee dazu kommt aus dem Bedürfnis nach adaptiven Programmen, die sich an ihre jeweilige Umgebung anpassen können. Microsoft plant in dem Bereich keine umfassende Unterstützung statischer Metaprogrammierung (also solche, die zur Kompilierzeit stattfindet) in den .NET-Sprachen. Als Grund führt das Design-Team Bedenken bezüglich der Performance im Tooling auf, da Änderungen an einer Code-Datei Änderungen an anderen verursachen könnten. Mit dem Anspruch, innerhalb von 20 Millisekunden auf Tastendrücke in Visual Studio zu reagieren, wäre das nicht vereinbar.

Das Design-Team möchte den Fokus daher auf Sprachelemente richten, die das Softwaredesign vereinfachen und das Bedürfnis nach Metaprogrammierung verringern. Sprachfeatures, mit denen sich das umsetzen lässt, nennt das Team ebenfalls. Darunter fallen zum Beispiel virtuelle Erweiterungsmethoden, generische Einschränkungen für Konstruktoren, Einschränkungen für Aufzählungen und Delegaten, Mixins und Traits. Unter Letzteren versteht man zusammenhängende, wiederverwendbare Funktionsbündel, die sich in Klassen einhängen lassen. Diese Konzepte der objektorientierten Programmierung sind beispielsweise aus den Sprachen Ruby und Scala bekannt.

Um C#-Programme weniger fehleranfällig zu gestalten, treiben die Entwickler die Unterstützung von Null-toleranten Operationen voran. So gibt es ab C# 6 den Null-Bedingungsoperator (?.). Mit ihm werden Tests auf Null-Werte für Referenzen erleichtert. Zum Beispiel bewirkt der Ausdruck myObject?.DoSomething(), dass die Methode DoSomething nur aufgerufen wird, wenn die Referenz in myObject ungleich Null ist. Für C# 7 werden nun Konzepte evaluiert, die an der Stelle ein Stück weiter gehen sollen.

Ein großes Thema dabei sind Referenzen, die als non-nullable bezeichnet werden. Das bedeutet, dass eine Variable eines Referenztypen zur gesamten Laufzeit des Programms nicht Null werden kann. Sie einzusetzen würde Tests auf Null im Code ersparen und könnte viele Fehler im Vorfeld ausschließen. Leider ist Microsoft selbst ein schwerwiegender Fehler unterlaufen: Deratige Referenzen waren zu Beginn der Entwicklung von .NET nicht im Design vorgesehen. Das ist beim Entwurf eines Typsystems aber notwendig, da das Hinzufügen des Konstruktes im Nachhinein zu Inkompatibilitäten in existierenden Anwendungen führt. Zusätzlich sind Referenzen, die nicht Null werden, technisch recht schwierig umzusetzen. Was passiert zum Beispiel, wenn man auf ein solches Feld im Konstruktor zugegreift? Die Memory Allocator Common Language Runtime initialisiert alle Referenzfelder einer Klasse mit Null, bevor das Programm den Konstruktor aufruft. Ein weiteres zu lösendes Problem ist das Konvertieren von Referenzen, die Null zulassen, in solche, die non-nullable ist. In was würde konvertiert, wenn die Referenz Null wäre? Wird die Zielvariable kanonisch initialisiert oder soll die CLR eine Exception werfen? Im Laufe der kommenden Design Meetings zu C# 7 wird man sehen, wie Microsoft diese Themen adressiert.