Über unsMediaKontaktImpressum
Rainer Stropek 28. Januar 2015

Neuerungen in C# 6

Was C#-Entwickler von Visual Studio 2015 erwarten können

C# nimmt seit vielen Jahren einen der Spitzenplätze in den diversen Ranglisten bezüglich Verbreitung und Beliebtheit von Programmiersprachen ein. Die kommende Version 2015 von Visual Studio wird daher von vielen schon mit Spannung erwartet. Es handelt sich schließlich um einen Meilenstein für C#: Die gesamte Compiler-Infrastruktur wird ausgetauscht, das jahrelang vorbereitete Projekt Roslyn [1] geht endlich in Betrieb. Neugierig, welche Änderungen Visual Studio 2015 für C# bereit hält? In diesem Artikel gehen wir die Spracherweiterungen anhand von Codebeispielen durch.

Bevor wir in das Thema einsteigen, ein paar Tipps für Leser, die selbst C# 6 ausprobieren oder ihre bestehenden Lösungen mit der neuen C#-Version testweise kompilieren möchten:

  1. Visual Studio 2015 und C# 6 sind Preview-Versionen. Das bedeutet, dass sie nicht für den produktiven Einsatz freigegeben sind. Bei einigen der hier vorgestellten Sprachneuerungen sind auch noch da oder dort kleinere Anpassungen bis zur finalen Version zu erwarten.
  2. Die aktuelle Preview-Version von Visual Studio 2015 unterstützt bereits die Side-by-Side-Installation mit den Vorgängerversionen 2013 und 2012 [2]. Wer möchte kann also parallel zu seiner Produktivumgebung die Visual Studio 2015 Preview installieren [3].
  3. Viele Entwickler zögern, wenn es darum geht, auf der produktiven Entwicklungsmaschine Preview-Software zu installieren. Hier hilft die Cloud: Man kann mit wenigen Klicks eine virtuelle Maschine (VM) in Microsoft Azure mit fertig installiertem Visual Studio 2015 anlegen [4].

Null-Conditional Operator

Die Codebeispiele aus diesem Artikel stehen auf GitHub zum Download bereit [5].

Grundlagen

Beginnen wir mit der Neuerung, die aus meiner Sicht im C# Entwickleralltag am häufigsten zu finden sein wird: Dem Null-Conditional Operator „?“. Er ist verwandt mit „??“, dem Null-Coalescing Operator [6], den es in C# schon seit den Anfängen gibt. Der neue Operator ermöglicht es, Member-Zugriffe nur dann durchzuführen, wenn der Empfänger des Aufrufs nicht Null ist. An einem Beispiel kann man die Praxisrelevanz gut zeigen. Der folgende Code ist ohne Null-Conditional Operator geschrieben. Er steckt voller Prüfungen auf Null.


private static void DoSomethingWithCSharp6()
{
    // Read theme from application settings and look it up in theme directory.
    var themeConfig = ConfigurationManager.AppSettings["Theme"];
    if (themeConfig == null)
    {
        // If setting is missing, use light theme
        themeConfig = "light";
    }
    else
    {
        // Ignore casing so confert theme setting to lowercase
        themeConfig = themeConfig.ToLower();
    }
  

    const ConsoleColor defaultForegroundColor = ConsoleColor.Black;
    const ConsoleColor defaultBackgroundColor = ConsoleColor.White;
  
    Theme theme;
    if (themes == null || (theme = themes.FirstOrDefault(
        p => p.Name != null && p.Name.ToLower() == themeConfig)) == null)
    {
        // If themes have not been set up or requested theme could not be found, 
        // use default values
        Console.ForegroundColor = defaultForegroundColor;
        Console.BackgroundColor = defaultBackgroundColor;
    }
    else
    {
        Console.ForegroundColor = theme.ForegroundColor;
        Console.BackgroundColor = theme.BackgroundColor;
    }
}

Lassen Sie uns als nächstes einen Blick auf die geänderte Implementierung mit der neuen C# Sprachfunktion werfen. Wie man sieht, wird der Code wesentlich kürzer und klarer.


private static void DoSomethingWithCSharp6()
{
    // Read theme from application settings and look it up in theme directory.
    // Note the use of the null conditional and null coalesce operators.
    var themeConfig = (ConfigurationManager.AppSettings["Theme"]?.ToLower()) ?? "light";
      
    const ConsoleColor defaultForegroundColor = ConsoleColor.Black;
    const ConsoleColor defaultBackgroundColor = ConsoleColor.White;
      
    // Note the use of multiple null conditional operators.
    var theme = themes?.FirstOrDefault(p => p.Name?.ToLower() == themeConfig);
      
    // If themes have not been set up or requested theme could not be found, 
    // use default values.
    // Note that the null conditional operator in "theme?.ForegroundColor" changes
    // the expression's type from "ConsoleColor" to "Nullable". So
    // the following line would result in a compiler error:
    // ConsoleColor foreground = theme?.ForegroundColor;
    Console.ForegroundColor = theme?.ForegroundColor ?? defaultForegroundColor;
    Console.BackgroundColor = theme?.BackgroundColor ?? defaultBackgroundColor;
}

Generierter IL Code

Wirft man einen Blick auf den Intermediate Language Code (IL-Code), der bei Übersetzung des letzten Beispiels entsteht, sieht man, dass der Null-Conditional Operator keine Funktion der Common Language Runtime (CLR) ist. Der C#-Compiler ist für die notwendige „Magie“ verantwortlich. Er nutzt bereits vorhandene IL Anwendungen, um den neuen Operator umzusetzen. Das bedeutet auch, dass man ihn ohne weiteres in Verbindung mit älteren .NET Versionen verwenden kann.

Null-Conditional Operator und Value Types

Es ist wichtig zu wissen, dass der Null-Conditional Operator den Typ eines Ausdrucks verändert, wenn man ihn in Zusammenhang mit Value Types verwendet. Die letzten beiden Zeilen im oben gezeigten Codebeispiel demonstrieren dieses Verhalten. theme.ForegroundColor wäre eigentlich ein Value Type (ConsoleColor). Da über theme?. zugegriffen wird, muss sich das Ergebnis von ConsoleColor auf ConsoleColor?, also Nullable, ändern, denn schließlich könnte das Ergebnis Null sein, falls theme Null ist.

Anwendung bei Events

Sehr praktisch ist der Null-Conditional Operator auch in Verbindung mit Events. Das folgende Codebeispiel zeigt typischen Code für eine Eigenschaft in einer Klasse, die INotifyPropertyChanged implementiert:


public string Name
{
    get { return NameValue; }
    set
    {
        if (NameValue != value)
        {
            NameValue = value;
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
                this.PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
            }
        }
    }
}

Statt der Prüfung des Events auf Null im if-Statement können wir wieder den neuen Operator einsetzen:


public string Name
{
    get { return NameValue; }
    set
    {
        if (NameValue != value)
        {
            NameValue = value;
  
            // Note the use of the null conditional and nameof Operators here
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Theme.Name)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FullName)));
        }
    }
}

Der nameof Operator

Ist Ihnen die zweite Neuerung von C# 6 im letzten Codebeispiel aufgefallen? Richtig, der nameof Operator. Er gibt den Namen eines Programmelements (z. B. Klassen, Methode etc.) als Zeichenkette zurück.

Achten Sie im oben gezeigten Code darauf, dass zu Demonstrationszwecken einmal ein mehrteiliger (Theme.Name) und einmal ein einteiliger Name (FullName) verwendet wurde. Für das Ergebnis ist das egal. Es wird immer nur der letzte Teil verwendet. Die Ergebnisse sind also „Name“ und „FullName“.

Index Initializers

Während wir uns als C# Entwickler schon lange an die bequemen Collection Initializers [7] gewöhnt haben, hat diese Funktion bei Arrays und Dictionaries noch gefehlt. C# 6 behebt diesen Mangel. Das folgende Codebeispiel zeigt die Anwendung beim Anlegen eines Dictionaries:


// Collection Initializers
themes = new List
{
  new Theme("dark", ConsoleColor.Black, ConsoleColor.White),
  new Theme("light", ConsoleColor.White, ConsoleColor.Black),
  new Theme("winter", ConsoleColor.Gray, ConsoleColor.Gray)
  // everything's gray in Austrian winter.
};
    
// Index Initializers
themesIndex = new Dictionary
{
  ["dark"] = themes[0],
  ["light"] = themes[1],
  [themes[2].Name] = themes[2]
  // Note the usage of an expression as the index here
};

Verbesserungen bei Auto-Implemented Properties

Auto-Implemented Properties (Auto-Implemented Properties auf MSDN, 2015) wurden in C# 3 eingeführt und sind eine äußerst bequeme Angelegenheit. Sie sparen uns Entwicklern eine Menge Tippaufwand und machen Code leichter lesbar. Leider hatten sie in der Vergangenheit einige wesentliche Nachteile. Der folgende Beispielcode zeigt das (achten Sie bitte auf die Kommentare im Code):


// Note that we cannot make BackgroundColor readonly if we use 
// auto-implemented properties. Also note that prior to C# 6 we
// could not initialize value of BackgroundColor here.
public ConsoleColor BackgroundColor { get; private set; }
  
// Note that we cannot use auto-implemented properties if we want
// to use readonly.
private readonly ConsoleColor ForegroundColorValue = ConsoleColor.Black;
public ConsoleColor ForegroundColor { get { return this.ForegroundColorValue; } }

public Theme(string name, ConsoleColor backgroundColor, ConsoleColor foregroundColor)
{
  [...]
  this.BackgroundColor = backgroundColor;
  this.ForegroundColorValue = foregroundColor;
}
  
public Theme()
{
  // Set default colors
  this.ForegroundColorValue = ConsoleColor.Black;
}

C# 6 behebt beide Mängel, die im letzten Codebeispiel zu sehen waren. Erstens kann man Getter-Only Properties erstellen. Bei ihnen erzeugt der Compiler automatisch einen readonly Setter. Zweitens kann man jetzt auch Auto-Implemented Properties wie Felder initialisieren.


// Note the use of a getter-only property with initializer here.
public ConsoleColor BackgroundColor { get; } = ConsoleColor.White;
  
public ConsoleColor ForegroundColor { get; } = ConsoleColor.Black;
  
public Theme(string name, ConsoleColor backgroundColor, ConsoleColor foregroundColor)
{
  [...]
  this.BackgroundColor = backgroundColor;
  this.ForegroundColor = foregroundColor;
}
  
public Theme()
{
  // No need to set default values any more.
}

In frühen Vorversionen von Roslyn war für C# eine weitere Neuerung geplant: Primary Constructors. Ziel war es, eine kürzere Schreibweise für das Generieren von Konstruktoren zu erstellen. Auf die Parameter des generierten Konstruktors hätte man beim Initialisieren von Properties zugreifen können. Allerdings hat sich das C# Team im Lauf des letzten Jahres entschlossen, Primary Constructors zurückzustellen, um nochmals grundlegend über diese Funktion nachzudenken. Diese Erweiterung ist daher in Visual Studio 2015 nicht zu finden.

Expression Bodied Function Members

Haben Sie sich schon an die Lambda-Ausdrücke [8], die C# mit Version 3 gebracht hat, gewöhnt? Falls nicht empfehle ich Ihnen, diese Wissenslücke möglichst rasch zu schließen. Ihr Code wird, speziell wenn Sie Aspekte der funktionalen Programmierung verwenden (alle aktuellen APIs von Microsoft zwingen mittlerweile dazu), deutlich kompakter. Mit C# 6 kann man jetzt auch Funktionen und Getter-Only Properties als Lambda-Ausdrücke schreiben.

Ich möchte Ihnen den Vorteil der neuen Schreibweise an folgendem Codebeispiel demonstrieren. Es enthält ein Getter-Only Property und eine Funktion, die beide aus jeweils nur einem Ausdruck bestehen:


public string FullName
{
  get
  {
    return string.Format("Theme: {0}, Background: {1}, Foreground: {2}",
      this.Name, this.BackgroundColor, this.ForegroundColor);
  }
}
  
public Theme Clone()
{
  return new Theme(this.Name, this.BackgroundColor, this.ForegroundColor);
}

Genau solchen Code macht die neue Lambda-Ausdrucksschreibweise kompakter:


// Note the use of Lambda bodied property here.
// The exact syntax of string interpolations will change until RTM of VS2015.
public string FullName => 
  "Theme: \{Name}, Background: \{BackgroundColor}, Foreground: \{ForegroundColor}";
  
// Note the use of Lambda bodied function here.
public Theme Clone() => new Theme(this.Name, this.BackgroundColor, this.ForegroundColor);

String Interpolation

Haben Sie bemerkt, dass sich in das letzte Codebeispiel eine weitere Neuerung eingeschlichen hat? String Interpolation heißt die Möglichkeit, die Sie in Zukunft statt string.Format einsetzen können. Fehler durch falsche Parameterindizes im Format-String gehören damit der Vergangenheit an.

Die Syntax für String Interpolation wird sich bis zur finalen Version von Visual Studio 2015 noch verändern. Die Syntax, die aller Voraussicht nach gewählt wird, sieht wie folgt aus:


public string FullName => 
  $"Theme: {Name}, Background: {BackgroundColor}, Foreground: {ForegroundColor}";

Verbesserungen beim Exception Handling

Die Verbesserungen beim Exception Handling habe ich mir bis zum Ende aufgehoben, um mit einem echten Knaller abschließen zu können: Catch-Blöcke erlauben jetzt die Verwendung von await. Lassen Sie uns an einem Beispiel ansehen, wie unbequem asynchrone Methoden (z. B. Logging) im Exception Handling waren:


private static async Task InitializeThemesAsync()
{
  var exceptionMessage = string.Empty;
  try
  {
    InitializeThemes();
  }
  catch (Exception ex)
  {
    if (ex.InnerException != null)
    {
      // For demo purposes, lets say we are just interested in the
      // inner exception. If there is no inner exception, we do not
      // want to handle the exception.
      exceptionMessage = ex.InnerException.Message;
    }
    else
    {
      throw;
    }
  }
  
  // Now that we are ouside of catch, we can use await.
  await LogAsync(exceptionMessage);
}

Dieses Codebeispiel wurde bewusst einfach gehalten. Wenn man mehr Information als nur die Message braucht, musste man oft auch auf die .NET-Klasse ExceptionDispatchInfo zurückgreifen [9].

Mit C# 6 lässt sich die oben gezeigte Ausnahmebehandlung wesentlich kürzer und kompakter schreiben. Neben der asynchronen Methode im Catch-Block zeigt das folgende Codebeispiel auch die Verwendung der neuen Exception Filter.


private static async Task InitializeThemesAsync()
{
  var exceptionMessage = string.Empty;
  try
  {
    InitializeThemes();
  }
  // Note the exception filter here.
  catch (Exception ex) if (ex.InnerException != null)
  {
    // Note the usage of await inside of catch
    await LogAsync(exceptionMessage);
  }
}

Sonstige, kleinere Neuerungen

Neben den oben gezeigten Neuigkeiten bringt C# 6 noch einige weitere Detailverbesserungen mit:

  • Using Static: Sie können jetzt mit using nicht nur Namensräume sondern auch Klassen referenzieren. Dadurch können Sie statische Member ohne Angabe der Klassennamen referenzieren.
  • Strukturen unterstützen jetzt parameterlose Konstruktoren, die bei Anlegen einer Instanz mit new ausgeführt werden.
  • Die Namensauflösung in Verbindung mit Overloads wurde verbessert.
  • Zusammenfassung

C# 6 bringt keine revolutionär neuen Funktionen wie es die vorherige Version durch async/await tat. Es handelt sich aber um eine solide Weiterentwicklung.
Die eigentliche Revolution fand diesmal unter der Haube statt. Der Austausch der kompletten Compiler-Infrastruktur bei einer Mainstream-Programmiersprache wie C# ist keine Kleinigkeit. Die Anforderungen hinsichtlich Performance, Kompatibilität, Stabilität etc. sind enorm hoch. Aus diesem Grund mussten wir auch so lange auf die Fertigstellung von Roslyn warten.

Sie fragen sich, was die Umstellung des C#-Unterbaus für Sie in der Praxis bringt? Der C#-Compiler steht Ihnen jetzt als Open Source-Klassenbibliothek zur Verfügung. Das Analysieren und Verändern von C#-Quelltext wird damit so einfach wie der Umgang mit z. B. XML- oder JSON-Dateien. Das ändert vieles falls Sie im Geschäft mit Visual Studio Erweiterungen sind. Aber auch als normale C#-Entwickler müssen wir darüber nachdenken, wie wir den „Compiler-as-a-Service“ im Alltag verwenden. Automatisierte Prüfungen auf Code Issues und zugehörige Code Fixes, Codegeneratoren und vieles mehr rücken in den Bereich des Mach- und Finanzierbaren.

Zu guter Letzt ist noch die neue Offenheit von Microsoft zu erwähnen. Microsoft hat die Liebe zu Open Source und Linux entdeckt. Als eine Konsequenz ist Roslyn und damit der gesamte C#-Compiler offen und plattformunabhängig. Auch wenn wahrscheinlich nur wenige den Drang verspüren werden, sich aktiv an der Compilerentwicklung zu beteiligen, von der Verbreiterung des Einsatzbereichs von C# profitieren wir alle. Schließlich können wir unser über die Jahre gewonnenes C#-Wissen jetzt nicht mehr nur unter Windows einsetzen.

Autor

Rainer Stropek

Rainer Stropek entwickelt im Augenblick in seiner Firma "software architects" mit seinem Team die preisgekrönte Software time cockpit. Seine technischen Schwerpunkte sind C# und das .NET Framework, XAML, die Windows Azure...
>> Weiterlesen
botMessage_toctoc_comments_9210