C# 9.0 : de nombreuses nouveautés, souvent inspirées de la programmation fonctionnelle

De F# à C#
C# 9.0 : de nombreuses nouveautés, souvent inspirées de la programmation fonctionnelle

Avant de plonger dans les nouveautés annoncées récemment par Microsoft pour Azure, voici un détour par C#, dont la version 9.0 a été lancée durant la conférence Build. Les nouveautés sont nombreuses, aussi bien pour la pratique usuelle que dans des cas beaucoup plus spécifiques.

Le C# reste aujourd'hui le langage fétiche de Microsoft. Il s'agit pour rappel d'un dérivé du C++, mais de plus haut niveau, puisqu'il reprenait initialement nombre de concepts de Java.

L'éditeur en assure toujours autant la promotion, puisqu'il peut servir aussi bien au développement d'applications natives et .NET que de pages web via ASP.NET. Aussi l'arrivée d'une nouvelle version majeure est-elle toujours un évènement, surtout dans le contexte d'une conférence dédiée aux développeurs.

Comme les connaisseurs pourront s'en apercevoir, beaucoup des apports de C# 9, qui arrive un peu plus d'un an après la version 8, ont trait à la programmation fonctionnelle et sont en fait directement inspirés de F#. Au point que l'on peut se demander aujourd'hui si ce dernier ne sert tout simplement pas de laboratoire d'essai à C#.

Notre dossier sur la Build 2020 de Microsoft :

Place aux propriétés Init-only

On commence avec les propriétés Init-only, qui viennent remédier à certaines limitations des initialiseurs d’objets. Ces derniers offrent en théorie un format flexible, étant particulièrement adaptés à la création imbriquée, pour une arborescence générée d'une traite par exemple. Mais ses propriétés se doivent d'être modifiables.

Les propriétés Init-only corrigent cette situation, les développeurs pouvant désormais se servir d’accesseurs init, variantes des accesseurs set :

public class Personne
{
public string Prénom { get; init; }
public string Nom { get; init; }
}

Dans l’exemple donné par Microsoft, toute affectation ultérieure aux propriétés Prénom et Nom renverra une erreur. Ces accesseurs init ne pouvant être appelés que durant l’initialisation, ils peuvent muter les champs readonly de la classe englobante, comme un développeur le pourrait avec un constructeur.

Cette particularité est utile pour rendre des propriétés individuelles immuables. Dans le cas cependant où il faudrait rendre immuable tout l’objet, on peut utiliser les Records. Dans l’exemple précédent, passer de « public class Person » à « public data class Personne » marque la classe comme Record, lui conférant des comportements de type value.

Ces Records sont davantage définis par leur contenu que par leur identité. Et puisque leur contenu est prévu pour être immuable, les changements dans le temps doivent être représentés par de nouveaux Records. Ils représentent en fait l’état d’un objet à un instant donné.

Les expressions with

Une manière de développer différente, conçue pour des besoins spécifiques, et accompagnée d’un nouveau type d’expressions nommé with. Elles utilisent la syntaxe des initialiseurs d’objets pour marquer la différence d'un nouvel objet pa rapport à un existant. Par exemple, si l’on souhaite créer une nouvelle entrée autrePersonne en récupérant les propriétés de Personne mais en en changeant simplement Nom, on obtient :

var autrePersonne = personne with { Nom = "Dupont" };

En arrière-plan, un constructeur de copie labellisé protected s’occupe de gérer l’opération.

Nouveaux patterns et opérateurs

Plusieurs types de nouveaux patterns font leur apparition dans C# 9.0.

public static decimal CalculateToll(object vehicle) =>
vehicle switch
{
...
DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m,
DeliveryTruck _ => 10.00m,
_ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle))
};

L’exemple fourni montre la manière dont le code devait être écrit jusqu’à présent pour le pattern matching. Dans C# 9.0, plusieurs éléments peuvent être notablement simplifiés. Par exemple, plus besoin de déclarer un identifiant de pattern, même un _. Le développeur peut ainsi écrire directement le type : DeliveryTruck => 10.00m,

Du changement également pour les opérateurs relationnels de type < ou <=. La partie DeliveryTruck peut alors s’écrire comme une expression switch imbriquée, > 5000 et < 3000 étant des patterns relationnels :

DeliveryTruck t when t.GrossWeightClass switch
{
> 5000 => 10.00m + 5.00m,
< 3000 => 10.00m - 2.00m,
_ => 10.00m,
},

Des opérateurs logiques and, or et not apparaissent dans la foulée. On peut par exemple écrire >= 3000 and <= 5000 => 10.00m, pour représenter un intervalle. L’opérateur not parle de lui-même et peut s’appliquer à de multiples situations. Par exemple pour définir des cas, en accompagnement de null.

Autre cas classique, dans l’utilisation d’expressions if, pour spécifier une action si une variable n’a pas la valeur spécifiquement définie, sans requérir de double parenthèses : if (e is not Customer) { ... }

Target typing, retours covariants et autres améliorations

C# 9.0 améliore le target typing, qui survient lorsqu’une expression obtient un type depuis le contexte de son utilisation. La nouvelle version allonge la liste des expressions prises en charge. Dans new par exemple, plus besoin de spécifier le type dans la plupart des cas, comme dans Point p = new (3, 5);

On note d’autres nouveautés, comme les retours covariants, s’adressant à des cas particuliers, pour signaler qu’un forçage (override) de méthode dans une classe dérivée a un type de retour plus spécifique que la déclaration dans le type de base. Signalons aussi les fonctions lambdas statiques. La déclaration peut ainsi être accompagnée d’un mot servant de préfixe, la fonction lambda se comportant alors comme une méthode statique. Cela évite notamment la capture de paramètres.

Dans la liste des simplifications, on note celle du code de base d'une application passant de :

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

À une solution plus légère, où il est possible de se contenter de débuter le code directement après les using, mais avant toute déclaration d'un espace de nom, précise Microsoft :

using System;
Console.WriteLine("Hello World!");

N'importe quelle fonctionnalité est disponible, des retours de code de statut à await en passant par les arguments (args), tant que tout est contenu dans un seul et même fichier. De quoi éviter des déclarations comme celles d'une méthode principale et d'une classe, inutiles pour une application très basique. 

On pourrait également citer une simplification de la validation null standard par l’ajout d’une petite annotation, les initialiseurs de modules, la reconnaissance de la méthode GetEnumerator dans les boucles foreach ou encore l’extension des méthodes Partial, en supprimant notamment toutes les restrictions autour de leur signature.

Les développeurs intéressés pourront se rendre par ici où les changements sont détaillés. Mais aussi lire la liste complète des nouveautés depuis la page de statut du langage sur le dépôt GitHub associé. Une vidéo a également été mise en ligne à l'occasion de l'édition numérique de la Build 2020 :

Vous n'avez pas encore de notification

Page d'accueil
Options d'affichage
Abonné
Actualités
Abonné
Des thèmes sont disponibles :
Thème de baseThème de baseThème sombreThème sombreThème yinyang clairThème yinyang clairThème yinyang sombreThème yinyang sombreThème orange mécanique clairThème orange mécanique clairThème orange mécanique sombreThème orange mécanique sombreThème rose clairThème rose clairThème rose sombreThème rose sombre

Vous n'êtes pas encore INpactien ?

Inscrivez-vous !