Vue d’ensemble des nouvelles fonctionnalités C# 6

La version 6 du langage C# continue de faire évoluer le langage pour avoir un code moins chaud, une clarté améliorée et plus de cohérence. Syntaxe d’initialisation plus propre, possibilité d’utiliser l’attente dans les blocs catch/finally, et le conditionnel null ? les opérateurs sont particulièrement utiles.

Notes

Pour plus d’informations sur la dernière version du langage C#, version 7, reportez-vous à l’article Nouveautés de C# 7.0

Ce document présente les nouvelles fonctionnalités de C# 6. Il est entièrement pris en charge par le compilateur mono et les développeurs peuvent commencer à utiliser les nouvelles fonctionnalités sur toutes les plateformes cibles Xamarin.

Nouveautés de la vidéo C# 6

Utilisation de C# 6

Le compilateur C# 6 est utilisé dans toutes les versions récentes de Visual Studio pour Mac. Ceux qui utilisent des compilateurs de ligne de commande doivent confirmer que mcs --version retourne 4.0 ou une version ultérieure. Visual Studio pour Mac utilisateurs peuvent case activée s’ils ont installé Mono 4 (ou version ultérieure) en faisant référence à À propos Visual Studio pour Mac > Visual Studio pour Mac > Afficher les détails.

Moins chaudronnerie

using static

Les énumérations, et certaines classes telles que System.Math, sont principalement des détenteurs de valeurs et de fonctions statiques. En C# 6, vous pouvez importer tous les membres statiques d’un type avec une instruction unique using static . Comparez une fonction trigonométrique classique en C# 5 et 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 ne rend pas les champs publics const , tels que Math.PI et Math.E, directement accessibles :

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

utilisation de statiques avec des méthodes d’extension

L’installation using static fonctionne un peu différemment avec les méthodes d’extension. Bien que les méthodes d’extension soient écrites à l’aide staticde , elles n’ont pas de sens sans une instance sur laquelle fonctionner. Par conséquent, quand using static est utilisé avec un type qui définit des méthodes d’extension, les méthodes d’extension deviennent disponibles sur leur type cible (le type de this la méthode). Par instance, using static System.Linq.Enumerable peut être utilisé pour étendre l’API d’objets IEnumerable<T> sans apporter tous les types LINQ :

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));
    }
}

L’exemple précédent illustre la différence de comportement : la méthode Enumerable.Where d’extension est associée au tableau, tandis que la méthode String.Join statique peut être appelée sans référence au String type.

nameof Expressions

Parfois, vous souhaitez faire référence au nom que vous avez donné à une variable ou à un champ. En C# 6, nameof(someVariableOrFieldOrType) retourne la chaîne "someVariableOrFieldOrType". Par instance, lors de la levée d’unArgumentException, vous êtes très susceptible de vouloir nommer l’argument qui n’est pas valide :

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

Le principal avantage des nameof expressions est qu’elles sont vérifiées par type et qu’elles sont compatibles avec la refactorisation basée sur les outils. La vérification de type des nameof expressions est particulièrement bienvenue dans les situations où un string est utilisé pour associer dynamiquement des types. Par instance, dans iOS, un string est utilisé pour spécifier le type utilisé pour prototyper UITableViewCell des objets dans un UITableView. nameof peut garantir que cette association n’échoue pas en raison d’une faute d’orthographe ou d’une refactorisation bâclée :

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

Bien que vous puissiez passer un nom qualifié à nameof, seul l’élément final (après le dernier .) est retourné. Par instance, vous pouvez ajouter une liaison de données dans Xamarin.Forms :

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));

Les deux appels à passent des SetBinding valeurs identiques : nameof(ReactiveType.StringField) est "StringField", pas "ReactiveType.StringField" comme prévu initialement.

Opérateur conditionnel Null

Les mises à jour antérieures de C# ont introduit les concepts des types nullables et de l’opérateur ?? null-coalescing pour réduire la quantité de code réutilisable lors de la gestion des valeurs nullables. C# 6 continue ce thème avec l'« opérateur conditionnel null ». ?. Lorsqu’il est utilisé sur un objet à droite d’une expression, l’opérateur conditionnel null retourne la valeur membre si l’objet n’est pas null et null sinon :

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]

(Et length0length1 sont déduits comme étant de type int?)

La dernière ligne de l’exemple précédent montre l’opérateur ? conditionnel null en combinaison avec l’opérateur ?? null-coalescing. Le nouvel opérateur conditionnel null C# 6 retourne null sur le 2e élément du tableau, à partir duquel l’opérateur de fusion null entre en jeu et fournit un 0 au lengths tableau (que ce soit approprié ou non, bien sûr, spécifique au problème).

L’opérateur conditionnel null doit réduire considérablement la quantité de vérification null réutilisable nécessaire dans de nombreuses applications.

L’opérateur conditionnel null présente certaines limitations en raison d’ambiguïtés. Vous ne pouvez pas suivre immédiatement un ? avec une liste d’arguments entre parenthèses, comme vous pouvez l’espérer avec un délégué :

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

Toutefois, Invoke peut être utilisé pour séparer le ? de la liste d’arguments et constitue toujours une amélioration marquée par rapport à un nullbloc de vérification de la plaque réutilisable :

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

Interpolation de chaîne

La String.Format fonction a traditionnellement utilisé des index comme espaces réservés dans la chaîne de format, par exemple , String.Format("Expected: {0} Received: {1}.", expected, received). Bien sûr, l’ajout d’une nouvelle valeur a toujours impliqué une petite tâche ennuyeuse de comptage des arguments, de renumérotage des espaces réservés et d’insertion du nouvel argument dans la séquence appropriée dans la liste d’arguments.

La nouvelle fonctionnalité d’interpolation de chaîne de C# 6 améliore considérablement .String.Format À présent, vous pouvez nommer directement les variables dans une chaîne précédée d’un $. Exemple :

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

Les variables sont, bien sûr, vérifiées et une variable mal orthographiée ou non disponible provoque une erreur du compilateur.

Les espaces réservés n’ont pas besoin d’être des variables simples, ils peuvent être n’importe quelle expression. Dans ces espaces réservés, vous pouvez utiliser des guillemets sans échapper à ces guillemets. Pour instance, notez le "s" dans les éléments suivants :

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

L’interpolation de chaîne prend en charge la syntaxe d’alignement et de mise en forme de String.Format. Comme vous l’avez écrit {index, alignment:format}précédemment, en C# 6, vous écrivez {placeholder, alignment:format}:

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}.");
    }
}

a pour résultat :

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.

L’interpolation de chaîne est un sucre syntaxique pour String.Format: elle ne peut pas être utilisée avec @"" des littéraux de chaîne et n’est pas compatible avec const, même si aucun espace réservé n’est utilisé :

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

Dans le cas d’usage courant de la génération d’arguments de fonction avec interpolation de chaîne, vous devez toujours faire attention aux problèmes d’échappement, d’encodage et de culture. Les requêtes SQL et URL sont, bien sûr, essentielles à assainir. Comme avec String.Format, l’interpolation de chaîne utilise .CultureInfo.CurrentCulture L’utilisation CultureInfo.InvariantCulture est un peu plus moty :

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"

Initialisation

C# 6 fournit un certain nombre de façons concises de spécifier des propriétés, des champs et des membres.

Initialisation automatique de propriété

Les propriétés automatiques peuvent désormais être initialisées de la même manière concise que les champs. Les propriétés automatiques immuables peuvent être écrites uniquement avec un getter :

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

Dans le constructeur, vous pouvez définir la valeur d’une propriété automatique getter-only :

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!)
    }

Cette initialisation des propriétés automatiques est à la fois une fonctionnalité d’économie d’espace générale et une aubaine pour les développeurs qui souhaitent mettre l’accent sur l’immuabilité dans leurs objets.

Initialiseurs d’index.

C# 6 introduit des initialiseurs d’index, qui vous permettent de définir à la fois la clé et la valeur dans les types qui ont un indexeur. En règle générale, il s’agit des Dictionarystructures de données de style :

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");
}

Membres de la fonction expression-bodied

Les fonctions lambda présentent plusieurs avantages, dont l’un consiste simplement à économiser de l’espace. De même, les membres de classe expression-bodied permettent aux petites fonctions d’être exprimées un peu plus succinctement que ce qui était possible dans les versions précédentes de C# 6.

Les membres de la fonction expression-bodied utilisent la syntaxe de flèche lambda plutôt que la syntaxe de bloc traditionnelle :

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

Notez que la syntaxe lambda-arrow n’utilise pas de .return Pour les fonctions qui retournent void, l’expression doit également être une instruction :

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

Les membres expression-bodied sont toujours soumis à la règle prise async en charge pour les méthodes, mais pas pour les propriétés :

//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;

Exceptions

Il n’y a pas deux façons de le faire : la gestion des exceptions est difficile à obtenir correctement. Les nouvelles fonctionnalités de C# 6 rendent la gestion des exceptions plus flexible et cohérente.

Filtres d’exception

Par définition, les exceptions se produisent dans des circonstances inhabituelles, et il peut être très difficile de raisonner et de coder toutes les façons dont une exception d’un type particulier peut se produire. C# 6 introduit la possibilité de protéger un gestionnaire d’exécution avec un filtre évalué au runtime. Pour ce faire, ajoutez un when (bool) modèle après la déclaration normale catch(ExceptionType) . Dans ce qui suit, un filtre distingue une erreur d’analyse relative au date paramètre par opposition à d’autres erreurs d’analyse.

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");
    }
}

attendent dans la prise... Enfin...

Les async fonctionnalités introduites dans C# 5 ont changé la donne pour le langage. En C# 5, await n’était pas autorisé dans catch les blocs et finally , ce qui est une gêne étant donné la valeur de la async/await capacité. C# 6 supprime cette limitation, ce qui permet d’attendre systématiquement les résultats asynchrones via le programme, comme illustré dans l’extrait de code suivant :

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

Résumé

Le langage C# continue d’évoluer pour rendre les développeurs plus productifs tout en promouvant les bonnes pratiques et en prenant en charge les outils. Ce document a donné une vue d’ensemble des nouvelles fonctionnalités de langage en C# 6 et a brièvement démontré comment elles sont utilisées.