Site icon Blog ARC Optimizer

Le changeur de jeu de C# 14 pour un code plus propre

Le changeur de jeu de C# 14 pour un code plus propre


Cet article présente les propriétés d’extension, une nouvelle fonctionnalité de C# 14 qui vous permet d’utiliser des propriétés dans vos méthodes d’extension.

Depuis la sortie de C# 3 en 2007, les développeurs C# utilisent des méthodes d’extension pour ajouter des fonctionnalités aux types sans toucher à leur code source. Que vous étendiez des types à partir de la BCL .NET, de bibliothèques tierces ou même de votre propre code existant, les méthodes d’extension ont été essentielles à la façon dont nous écrivons du code C#.

Mais il y a toujours eu une grande limitation : vous ne pouvez étendre que les méthodes, pas les propriétés. La communauté j’attends avec impatience pour que ce jour vienne.

Avec C# 14, cela change. Avec .NET 10, Microsoft a introduit membres d’extensionune extension du modèle d’extension qui apporte des propriétés d’extension, des indexeurs d’extension et même des membres d’extension statiques au langage.

Une introduction rapide aux méthodes d’extension

Assurons-nous que nous sommes tous sur la même longueur d’onde. Les méthodes d’extension vous permettent « d’ajouter » des méthodes aux types existants sans les modifier ni créer de types dérivés.

Voici un exemple classique :

public static class StringExtensions
{
    
    public static string Capitalize(this string value)
    {
        if (string.IsNullOrEmpty(value))
            return value;
        return char.ToUpper(value[0]) + value.Substring(1).ToLower();
    }
}


string name = "sara";
string capitalized = name.Capitalize(); 

La magie opère avec le this mot-clé sur le premier paramètre, qui indique au compilateur qu’il s’agit d’une méthode d’extension. Même si Capitalize est une méthode statique dans une classe statique, vous l’appelez comme s’il s’agissait d’une méthode d’instance sur le type chaîne (que nous appelons le type récepteur). IntelliSense le récupère et tout le monde est content.

Les méthodes d’extension sont au cœur de l’écosystème .NET et peuvent être trouvées dans pratiquement toutes les bases de code. Par exemple, les méthodes LINQ classiques comme Where, Select et OrderBy sont toutes des méthodes d’extension qui donnent l’impression que les collections disposent de fonctionnalités de requête intégrées. Pour autant, ils ne sont pas sans limites.

Limites des méthodes d’extension

Nous aimons tous les méthodes d’extension. Cependant, les méthodes d’extension ont toujours été limitées à… enfin, aux méthodes.

Disons que vous travaillez avec un client API et que vous travaillez avec des réponses HTTP toute la journée. Vous vous retrouvez constamment à vérifier les codes d’état :

var response = await httpClient.GetAsync(url);

if ((int)response.StatusCode >= 200 && (int)response.StatusCode < 300)
{
    Console.WriteLine("Success!");
}
else if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500)
{
    Console.WriteLine("Client error - check your request");
}

Ce n’est… pas génial. Vous faites donc ce que tout bon développeur C# ferait : créer des méthodes d’extension.

public static class HttpStatusCodeExtensions
{
    public static bool IsSuccess(this HttpStatusCode status)
    {
        return (int)status >= 200 && (int)status < 300;
    }
    
    public static bool IsClientError(this HttpStatusCode status)
    {
        return (int)status >= 400 && (int)status < 500;
    }
}

if (response.StatusCode.IsSuccess())
{
    Console.WriteLine("Success!");
}

C’est mieuxmais quelque chose ne va pas. Tu appelles IsSuccess() avec des parenthèses, comme si ça fonctionnait. Mais ce n’est pas vraiment une action ; c’est une caractéristique du code d’état. Il crie : « Qu’est-ce que tu es ? » et non « Que pouvez-vous faire ? »

Au risque de revenir au CS101, les méthodes représentent des actions et les propriétés représentent des attributs et des caractéristiques. Quand tu veux savoir si un numéro est positif, tu n’appelles pas number.IsPositive(). Vous vous attendriez naturellement number.IsPositive.

Depuis près de deux décennies, nous écrivons des méthodes lorsque nous recherchons des propriétés. Nous avons ajouté des préfixes Get, utilisé des parenthèses là où elles n’ont pas leur place et avons généralement contourné cette limitation.

Membres d’extension en C# 14

C# 14 résout cette limitation avec les membres d’extension. Cette nouvelle syntaxe introduit un extension bloc qui vous permet de définir plusieurs types de membres d’extension pour un type, y compris des propriétés, des indexeurs et des membres statiques.

Note: Vos méthodes d’extension ne vont nulle part et continueront à fonctionner en C# 14 au-delà. Vous pourrez utiliser des méthodes d’extension statiques, et le this mot-clé comme vous l’avez fait auparavant.

Voici comment cela fonctionne. Au lieu de méthodes d’extension individuelles, vous créez un extension bloc.

public static class HttpStatusCodeExtensions
{
    extension(HttpStatusCode status)
    {
        public bool IsSuccess => (int)status >= 200 && (int)status < 300;
        public bool IsRedirect => (int)status >= 300 && (int)status < 400;
        public bool IsClientError => (int)status >= 400 && (int)status < 500;
        public bool IsServerError => (int)status >= 500 && (int)status < 600;
    }
}

var response = await httpClient.GetAsync(url);
if (response.StatusCode.IsSuccess)
{
    Console.WriteLine("Success!");
}
else if (response.StatusCode.IsClientError)
{
    Console.WriteLine("Client error - check your request");
}

Regardez ça. Pas de parenthèses. Ça se lit comme IsSuccess est une propriété réelle de HttpStatusCode. Parce que maintenant c’est est.

Le extension la déclaration crée un bloc où status est votre récepteur (l’instance que vous étendez). Tout à l’intérieur peut accéder status tout comme tu accéderais this dans une classe ordinaire.

À propos de cette syntaxe…

J’aurais probablement dû te prévenir du extension bloc. Il faudra peut-être un certain temps pour s’y habituer, et je ne suis pas seul. De nombreux développeurs le trouvent moche et verbeux, et c’était mon cas au début. Mais tout comme le changer la syntaxe de l’expressionça grandit sur moi. (Et après avoir attendu près de deux décennies pour cela, je prendrai ce que je peux obtenir.)

De plus, une fois que vous regroupez les membres d’extension associés, cela commence à avoir du sens :

extension(HttpStatusCode status)
{
    
    public bool IsSuccess => (int)status >= 200 && (int)status < 300;
    public bool IsClientError => (int)status >= 400 && (int)status < 500;
    
    
    public string Category => status switch
    {
        _ when status.IsSuccess => "Success",
        _ when status.IsClientError => "Client Error",
        _ => "Unknown"
    };
}

Dans cet exemple, tout ce qui concerne HttpStatusCode les extensions vivent au même endroit.

Selon à l’équipe C#ils ont exploré de nombreuses considérations de conception différentes : placer le récepteur sur chaque membre (trop répétitif), attacher le récepteur à la classe statique (brise les modèles existants), étendre this pour les propriétés (juste, non) et d’autres approches qui ont subi des modifications importantes ou des implémentations compliquées.

Le extension le bloc a gagné car il ne casse pas le code existant (vos méthodes d’extension habituelles continuent de fonctionner) ; de plus, il est flexible, permet le regroupement et offre un endroit où vivre au récepteur. Après tout, puisque les propriétés n’ont pas de paramètres, où d’autre déclareriez-vous un type que vous étendez ?

Encore mieux : les membres d’extension statiques

Pourquoi s’arrêter là ? Cette nouvelle fonctionnalité prend en charge les membres d’extension statiques, ouvrant de nouveaux modèles pour étendre les types.

Vous pouvez définir des membres d’extension statiques dans un bloc d’extension sans instance de récepteur.

Ajoutons quelques méthodes et constantes d’usine à HttpStatusCode:

public static class HttpStatusCodeExtensions
{
    
    extension(HttpStatusCode)
    {
        public static HttpStatusCode OK => HttpStatusCode.OK;
        public static HttpStatusCode NotFound => HttpStatusCode.NotFound;
        public static HttpStatusCode BadRequest => HttpStatusCode.BadRequest;
        
        public static HttpStatusCode FromInt(int code) => (HttpStatusCode)code;
    }
}


var status = HttpStatusCode.OK;
var notFound = HttpStatusCode.NotFound;
var teapot = HttpStatusCode.FromInt(418); 

Vous remarquez la différence ici ? Aucune instance du récepteur n’est dans la déclaration d’extension, juste extension(HttpStatusCode). Cela vous permet d’ajouter des membres statiques qui semblent appartenir au type lui-même.

C’est parfait pour les modèles d’usine, les constantes spécifiques à un type ou les méthodes utilitaires qui ne nécessitent pas d’instance.

Construire une solution complète

Rassemblons tout cela et construisons quelque chose d’utile. Nous allons créer un ensemble complet de membres d’extension pour travailler avec les codes d’état HTTP :

public static class HttpStatusCodeExtensions
{
    
    extension(HttpStatusCode)
    {
        public static HttpStatusCode OK => HttpStatusCode.OK;
        public static HttpStatusCode NotFound => HttpStatusCode.NotFound;
        public static HttpStatusCode BadRequest => HttpStatusCode.BadRequest;
        public static HttpStatusCode FromInt(int code) => (HttpStatusCode)code;
    }
    
    
    extension(HttpStatusCode status)
    {
        public bool IsSuccess => (int)status >= 200 && (int)status < 300;
        public bool IsRedirect => (int)status >= 300 && (int)status < 400;
        public bool IsClientError => (int)status >= 400 && (int)status < 500;
        public bool IsServerError => (int)status >= 500 && (int)status < 600;
        
        public string Category => status switch
        {
            _ when status.IsSuccess => "Success",
            _ when status.IsRedirect => "Redirect",
            _ when status.IsClientError => "Client Error",
            _ when status.IsServerError => "Server Error",
            _ => "Unknown"
        };
    }
}

Regardez maintenant à quel point votre code client HTTP devient propre :

var response = await httpClient.GetAsync(url);

if (response.StatusCode.IsSuccess)
{
    var data = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Success! Category: {response.StatusCode.Category}");
}
else if (response.StatusCode.IsClientError)
{
    Console.WriteLine("Client error - check your request");
}
else if (response.StatusCode.IsServerError)
{
    Console.WriteLine("Server error - try again later");
}

Plus d’exemples du « monde réel »

Une fois que vous aurez commencé à travailler sur les propriétés des extensions, vous trouverez des utilisations partout.

Validation de chaîne

public static class StringExtensions
{
    extension(string str)
    {
        public bool IsEmpty => string.IsNullOrEmpty(str);
        public bool IsWhitespace => string.IsNullOrWhiteSpace(str);
        public bool IsValidEmail => !str.IsEmpty && 
            str.Contains("@") && str.Contains(".");
        public bool IsNumeric => !str.IsEmpty && str.All(char.IsDigit);
    }
}

string email = "sara@peloton.com";
if (!email.IsEmpty && email.IsValidEmail)
{
    Console.WriteLine("Valid email!");
}

Aides à la collecte

public static class CollectionExtensions
{
    extension<T>(ICollection<T> collection)
    {
        public bool IsEmpty => collection.Count == 0;
        public bool HasMultipleItems => collection.Count > 1;
        public bool HasSingleItem => collection.Count == 1;
    }
}

var items = new List<string> { "apple", "banana" };


if (items.HasMultipleItems)
{
    Console.WriteLine("Multiple items found");
}

Vérification de l’état des tâches asynchrones

public static class TaskExtensions
{
    extension<T>(Task<T>)
    {
        public static Task<T> Cancelled => 
            Task.FromCanceled<T>(new CancellationToken(true));
        
        public static Task<T> Completed(T result) => 
            Task.FromResult(result);
    }
    
    extension<T>(Task<T> task)
    {
        public bool IsCompleted => task.IsCompletedSuccessfully;
        public bool HasFailed => task.IsFaulted;
        public Exception Error => task.Exception?.InnerException;
    }
}

var task = Task<int>.Completed(42);
if (task.IsCompleted)
{
    Console.WriteLine("All done!");
}

Qu’en est-il des performances ?

Vous avez raison de remettre en question les performances avec les nouvelles fonctionnalités linguistiques. Heureusement, les membres d’extension sont du sucre syntaxique qui se compile en appels de méthodes réguliers, tout comme les méthodes d’extension traditionnelles. Le CLR ne sait pas (ou ne se soucie pas) que vous avez utilisé la syntaxe d’extension.

Accès response.StatusCode.IsSuccess génère un code IL identique à l’appel d’un IsSuccess() méthode.

Quelques pièges

Bien que les membres d’extension soient formidables, vous devez connaître quelques limitations :

  • Aucun champ d’extension. Vous ne pouvez pas ajouter de champs aux types via des extensions ; cela signifie des propriétés automatiques comme public string Status { get; set; } ne fonctionnera pas car ils nécessitent techniquement des champs de sauvegarde.
  • Ordre des paramètres de type générique. Si le type n’est pas dans l’ordre « le récepteur d’abord, la méthode ensuite », il ne peut pas utiliser la nouvelle syntaxe. Je dirais que c’est rare, mais si vous avez quelque chose comme SelectLessThan<TResult, T> cela devra rester comme méthode d’extension.

Ce que cela signifie pour votre code

Pour moi, les membres d’extension ne sont pas seulement une fonctionnalité intéressante. Ils changent notre façon de penser à l’extension des types C#.

Pensez à la conception d’API : lorsque vous écrivez des bibliothèques, vous pouvez fournir des points d’extension qui semblent finalement faire partie du type. Les utilisateurs n’auront pas à se rappeler si quelque chose est une méthode ou une propriété. Ce sera ce qui a le plus de sens.

Conclusion

Avec C# 14 et .NET 10, nous disposons enfin de propriétés d’extension. Nous pouvons désormais étendre les types avec la syntaxe qui a du sens. Nous pouvons cesser de prétendre que les caractéristiques sont des actions.

Est-ce que le extension bloquer bizarre et différent ? Oui. Est-ce qu’on s’y habituera ? Probablement.

Comment allez-vous utiliser les membres de l’extension ? Faites-le-moi savoir dans les commentaires et bon codage !




Source link
Quitter la version mobile