C# 6– Übersicht über neue Features

Version 6 der C#-Sprache entwickelt die Sprache weiter, um weniger Codebausteine, mehr Klarheit und mehr Konsistenz zu erhalten. Übersichtlichere Initialisierungssyntax, die Möglichkeit, await in Catch/Finally-Blöcken zu verwenden, und die NULL-bedingten ? -Operator sind besonders nützlich.

Hinweis

Informationen zur neuesten Version der C#-Sprache – Version 7 – finden Sie im Artikel Neuerungen in C# 7.0.

In diesem Dokument werden die neuen Features von C# 6 vorgestellt. Es wird vollständig vom Monocompiler unterstützt, und Entwickler können mit der Verwendung der neuen Features auf allen Xamarin-Zielplattformen beginnen.

Neuerungen in C# 6-Video

Verwenden von C# 6

Der C# 6-Compiler wird in allen aktuellen Versionen von Visual Studio für Mac verwendet. Benutzer, die Befehlszeilencompiler verwenden, sollten bestätigen, dass mcs --version 4.0 oder höher zurückgegeben wird. Visual Studio für Mac Benutzer können überprüfen, ob Mono 4 (oder höher) installiert ist, indem Sie sich über Informationen Visual Studio für Mac > Visual Studio für Mac > Details anzeigen informieren.

Weniger Boilerplate

verwendet statische

Enumerationen und bestimmte Klassen wie System.Mathsind in erster Linie Besitzer statischer Werte und Funktionen. In C# 6 können Sie alle statischen Member eines Typs mit einer einzelnen using static Anweisung importieren. Vergleichen Sie eine typische trigonometrische Funktion in C# 5 und C# 6:

// Classic C#
class MyClass
{
    public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
    {
        var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
        return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
    }
}

// C# 6
using static System.Math;

class MyClass
{
    public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
    {
        var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
        return Tuple.Create (Asin (tmp), Acos (tmp));
    }
}

using static macht öffentliche const Felder wie Math.PI und Math.Enicht direkt zugänglich:

for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ... 
//PI is const, not static, so requires Math.PI

Verwenden von statisch mit Erweiterungsmethoden

Die using static Einrichtung funktioniert mit Erweiterungsmethoden etwas anders. Obwohl Erweiterungsmethoden mit staticgeschrieben werden, sind sie ohne eine instance, mit der sie betrieben werden sollen, nicht sinnvoll. Wenn using static also mit einem Typ verwendet wird, der Erweiterungsmethoden definiert, werden die Erweiterungsmethoden für ihren Zieltyp (den Typ der Methode this ) verfügbar. Für instance kann verwendet werden, um die API von IEnumerable<T> Objekten zu erweitern, using static System.Linq.Enumerable ohne alle LINQ-Typen einzubringen:

using static System.Linq.Enumerable;
using static System.String;

class Program
{
    static void Main()
    {
        var values = new int[] { 1, 2, 3, 4 };
        var evenValues = values.Where (i => i % 2 == 0);
        System.Console.WriteLine (Join(",", evenValues));
    }
}

Im vorherigen Beispiel wird der Unterschied im Verhalten veranschaulicht: Die Erweiterungsmethode Enumerable.Where ist dem Array zugeordnet, während die statische Methode String.Join ohne Verweis auf den String Typ aufgerufen werden kann.

nameof Ausdrücke

Manchmal möchten Sie auf den Namen verweisen, den Sie einer Variablen oder einem Feld zugewiesen haben. In C# 6 nameof(someVariableOrFieldOrType) gibt die Zeichenfolge "someVariableOrFieldOrType"zurück. Wenn Sie instance auslösenArgumentException, möchten Sie sehr wahrscheinlich benennen, welches Argument ungültig ist:

throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))

Der Hauptvorteil von nameof Ausdrücken besteht darin, dass sie typgeprüft sind und mit toolgestütztem Refactoring kompatibel sind. Die Typüberprüfung von nameof Ausdrücken ist besonders in Situationen willkommen, in denen ein string verwendet wird, um Typen dynamisch zuzuordnen. Für instance wird in iOS ein string verwendet, um den Typ anzugeben, der zum Prototyp UITableViewCell von Objekten in einem UITableViewverwendet wird. nameof kann sicherstellen, dass diese Zuordnung nicht aufgrund einer Falschschreibung oder eines schlampigen Refactorings fehlschlägt:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
    var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
    cell.TextLabel.Text = objects [indexPath.Row].ToString ();
    return cell;
}

Obwohl Sie einen qualifizierten Namen an nameofübergeben können, wird nur das letzte Element (nach dem letzten .) zurückgegeben. Für instance können Sie eine Datenbindung in Xamarin.Forms hinzufügen:

var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));

Die beiden Aufrufe von SetBinding übergeben identische Werte: nameof(ReactiveType.StringField) ist "StringField", nicht "ReactiveType.StringField" , wie Sie anfänglich erwarten.

Nullbedingter Operator

In früheren Aktualisierungen von C# wurden die Konzepte von Nullable-Typen und der NULL-Koalescing-Operator ?? eingeführt, um die Menge an Codebausteinen bei der Verarbeitung nullabler Werte zu reduzieren. C# 6 setzt dieses Design mit dem "NULL-bedingten Operator" ?.fort. Bei Verwendung für ein Objekt auf der rechten Seite eines Ausdrucks gibt der NULL-bedingte Operator den Elementwert zurück, wenn das Objekt nicht null ist und null andernfalls:

var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]

(Sowohl als length1 auch length0 werden abgeleitet, dass sie vom Typ int?sind)

Die letzte Zeile im vorherigen Beispiel zeigt den ? NULL-bedingten Operator in Kombination mit dem ?? NULL-Sammeloperator. Der neue nullbedingte C# 6-Operator gibt für das 2. Element im Array zurück null , an dem der NULL-Koalescierungsoperator eingeht und dem lengths Array eine 0 bereitstellt (ob dies geeignet ist oder nicht, ist natürlich problemspezifisch).

Der NULL-bedingte Operator sollte die Menge an NULL-Überprüfungen, die in vielen Anwendungen erforderlich sind, erheblich reduzieren.

Es gibt einige Einschränkungen für den NULL-bedingten Operator aufgrund von Mehrdeutigkeiten. Sie können nicht sofort einer ? mit einer in Klammern geordneten Argumentliste folgen, wie Sie es vielleicht mit einem Delegaten tun möchten:

SomeDelegate?("Some Argument") // Not allowed

Kann jedoch verwendet werden, Invoke um die ? von der Argumentliste zu trennen, und ist immer noch eine deutliche Verbesserung gegenüber einem null-check-Block des Kessels:

public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);
    return true;
}

Zeichenfolgeninterpolierung

Die String.Format Funktion verwendet traditionell Indizes als Platzhalter in der Formatzeichenfolge, z. B. String.Format("Expected: {0} Received: {1}.", expected, received). Natürlich war das Hinzufügen eines neuen Werts immer eine lästige kleine Aufgabe, Argumente aufzuzählen, Platzhalter neu zu nummerieren und das neue Argument in der richtigen Reihenfolge in die Argumentliste einzufügen.

Die neue Zeichenfolgeninterpolationsfunktion von C# 6 verbessert sich erheblich.String.Format Jetzt können Sie Variablen in einer Zeichenfolge mit dem Präfix direkt $benennen. Beispiel:

$"Expected: {expected} Received: {received}."

Variablen werden natürlich überprüft, und eine falsch geschriebene oder nicht verfügbare Variable verursacht einen Compilerfehler.

Die Platzhalter müssen keine einfachen Variablen sein, sie können ein beliebiger Ausdruck sein. Innerhalb dieser Platzhalter können Sie Anführungszeichen verwenden, ohne diese Anführungszeichen zu umgehen. Beachten Sie für instance folgendes"s":

var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"

Die Zeichenfolgeninterpolation unterstützt die Ausrichtungs- und Formatierungssyntax von String.Format. Genau wie Sie zuvor geschrieben haben {index, alignment:format}, schreiben {placeholder, alignment:format}Sie in C# 6 :

using static System.Linq.Enumerable;
using System;

class Program
{
    static void Main ()
    {
        var values = new int[] { 1, 2, 3, 4, 12, 123456 };
        foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
            Console.WriteLine (s);
        }
    Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
    }
}

führt zu:

The value is       1.00.
The value is       2.00.
The value is       3.00.
The value is       4.00.
The value is      12.00.
The value is 123,456.00.
Minimum is 1.00.

Die Zeichenfolgeninterpolation ist syntaktischer Zucker für String.Format: Sie kann nicht mit @"" Zeichenfolgenliteralen verwendet werden und ist nicht kompatibel mit const, auch wenn keine Platzhalter verwendet werden:

const string s = $"Foo"; //Error : const requires value

Im allgemeinen Anwendungsfall des Erstellens von Funktionsargumenten mit Zeichenfolgeninterpolation müssen Sie bei Flucht-, Codierungs- und Kulturproblemen dennoch vorsichtig sein. SQL- und URL-Abfragen sind natürlich entscheidend für die Bereinigung. Wie bei String.Formatverwendet die Zeichenfolgeninterpolation den CultureInfo.CurrentCulture. Die Verwendung CultureInfo.InvariantCulture ist etwas wortlicher:

Thread.CurrentThread.CurrentCulture  = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"

Initialisierung

C# 6 bietet eine Reihe präziser Möglichkeiten zum Angeben von Eigenschaften, Feldern und Membern.

Initialisierung der automatischen Eigenschaft

Automatische Eigenschaften können jetzt auf die gleiche präzise Weise initialisiert werden wie Felder. Unveränderliche automatische Eigenschaften können nur mit einem Getter geschrieben werden:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;

Im Konstruktor können Sie den Wert einer rein getter-auto-Eigenschaft festlegen:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;
    public string Description { get; }

    public ToDo (string description)
    {
        this.Description = description; //Can assign (only in constructor!)
    }

Diese Initialisierung von automatischen Eigenschaften ist sowohl ein allgemeines platzsparendes Feature als auch ein Segen für Entwickler, die die Unveränderlichkeit ihrer Objekte hervorheben möchten.

Indexinitialisierer

C# 6 führt Indexinitialisierer ein, mit denen Sie sowohl den Schlüssel als auch den Wert in Typen festlegen können, die über einen Indexer verfügen. In der Regel gilt dies für DictionaryDatenstrukturen im -Stil:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = new NSMutableDictionary {
        ["Created"] = NSDate.Now,
        ["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
        ["Task"] = Description
    };
    UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
    statusLabel.SetText ("Check phone");
}

Ausdruckskörperfunktionsmember

Lambda-Funktionen haben mehrere Vorteile, von denen einer einfach Platz spart. Ebenso ermöglichen Ausdruckskörper-Klassenmember, dass kleine Funktionen etwas prägnanter ausgedrückt werden, als dies in früheren Versionen von C# 6 möglich war.

Ausdruckskörperfunktionsmember verwenden die Lambdapfeilsyntax anstelle der herkömmlichen Blocksyntax:

public override string ToString () => $"{FirstName} {LastName}";

Beachten Sie, dass die Lambdapfeilsyntax keine explizite returnverwendet. Für Funktionen, die zurückgeben void, muss der Ausdruck auch eine -Anweisung sein:

public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");

Ausdruckskörpermember unterliegen weiterhin der Regel, die async für Methoden, aber nicht für Eigenschaften unterstützt wird:

//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;

Ausnahmen

Es gibt keine zwei Möglichkeiten: Die Ausnahmebehandlung ist schwierig, richtig zu werden. Neue Features in C# 6 machen die Ausnahmebehandlung flexibler und konsistenter.

Ausnahmefilter

Standardmäßig treten Ausnahmen unter ungewöhnlichen Umständen auf, und es kann sehr schwierig sein, alle Arten einer Ausnahme eines bestimmten Typs zu ermitteln und zu programmieren. C# 6 bietet die Möglichkeit, einen Ausführungshandler mit einem zur Laufzeit ausgewerteten Filter zu schützen. Dies erfolgt durch Hinzufügen eines when (bool) Musters nach der normalen catch(ExceptionType) Deklaration. Im Folgenden unterscheidet ein Filter einen Analysefehler, der sich auf den date Parameter bezieht, im Gegensatz zu anderen Analysefehlern.

public void ExceptionFilters(string aFloat, string date, string anInt)
{
    try
    {
        var f = Double.Parse(aFloat);
        var d = DateTime.Parse(date);
        var n = Int32.Parse(anInt);
    } catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
        Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
    } catch (FormatException x) {
        Console.WriteLine ("Problem parsing some other argument");
    }
}

await in catch... Schließlich...

Die async in C# 5 eingeführten Funktionen haben die Sprache verändert. In C# 5 await war in catch und finally Blöcken nicht zulässig, ein Ärger angesichts des Werts der async/await Funktion. C# 6 entfernt diese Einschränkung, sodass asynchrone Ergebnisse konsistent über das Programm erwartet werden können, wie im folgenden Codeausschnitt gezeigt:

async void SomeMethod()
{
    try {
        //...etc...
    } catch (Exception x) {
        var diagnosticData = await GenerateDiagnosticsAsync (x);
        Logger.log (diagnosticData);
    } finally {
        await someObject.FinalizeAsync ();
    }
}

Zusammenfassung

Die C#-Sprache wird weiterentwickelt, um Entwickler produktiver zu machen und gleichzeitig bewährte Methoden und unterstützende Tools zu fördern. Dieses Dokument bietet einen Überblick über die neuen Sprachfeatures in C# 6 und zeigt kurz, wie sie verwendet werden.