Ce que j'aime dans C# 9

Nous allons commencer par comment j'utilise certaines des nouvelles fonctionnalités de C# 9 et pourquoi je les aime. Ensuite, nous examinerons un exemple qui utilise une classe Person
et la développe. À la fin, je peux même faire allusion aux fonctionnalités de C# 10.
Je suis ingénieur logiciel depuis plus de 20 ans, et comme le dit l'adage, Vous ne pouvez pas enseigner de nouveaux tours à un vieux chien . Cependant, s'il y a une chose que j'ai apprise au cours de ces 20+ années, c'est que j'apprends TOUJOURS . Il y a toujours de nouvelles technologies, de nouvelles langues et de nouveaux produits pour résoudre des problèmes complexes. .NET 5 a introduit C# 9, qui comportait de nombreuses nouvelles fonctionnalités de langage. Il était donc temps pour moi d'apprendre de nouvelles astuces et je me suis plongé dans les ajouts de langage C# 9 de .NET 5.
Après avoir utilisé ces nouvelles fonctionnalités de langage, mots-clés et syntaxe, j'ai remarqué qu'ils commençaient à me faire gagner du temps et des frappes. Étant donné que ces ajouts de langue m'ont aidé, je voulais les partager avec vous.
Jetons un coup d'œil à certaines des nouvelles fonctionnalités de langue.
Records
Le nouveau mot-clé record
définit une référence type qui fournit des fonctionnalités intégrées pour représenter les données. Vous pensez peut-être que cela ressemble beaucoup à une class
et vous auriez raison, c'est le cas. Cependant, l'intention est de fournir des types plus petits et plus concis pour représenter des données immuables. J'aime les considérer comme un type utilisé principalement pour transférer des données et ne pas avoir beaucoup de méthodes ou de manipulation de données. Il existe différentes manières de définir un enregistrement
. La forme la plus simple est :
public record Person(string FirstName, string LastName);
À première vue, du moins pour moi, cela me paraissait étrange. Il a un aspect et une sensation de méthode. Il y a même un point-virgule à la fin. Mais, la ligne ci-dessus crée un type Person avec les propriétés de lecture/écriture de FirstName
et LastName
. Vous pouvez accéder à la Person comme suit :
var person = new Person("Joseph", "Guadagno");
Console.WriteLine(person.FirstName); // Sorties Joseph
Console.WriteLine(person.LastName); // Sorties Guadagno
Jusqu'à présent, cela ressemble beaucoup à la classe
. Eh bien, c'est le cas, à l'exception de la déclaration. Nous avons déjà enregistré un tas de frappes. Mais approfondissons le sujet.
Une autre façon de définir l'enregistrement Person ressemble davantage à une
class
:
public record Person
{
chaîne publique Prénom { get; ensemble;}
chaîne publique LastName { get; ensemble;}
}
Création d'enregistrements par position
Vous pouvez réduire davantage la saisie et supprimer du code passe-partout à l'aide de la nouvelle syntaxe positionnelle pour les enregistrements. Par exemple, si vous vouliez déclarer une variable avec l'approche de classe et l'initialiser avec des données, vous feriez quelque chose comme ceci.
var person = new Person { FirstName = "Joseph", LastName="Guadagno"};
Avec la syntaxe positionnelle, cela ressemblerait à ceci.
Person person = new ("Joseph", "Guadagno");
C'est 26 caractères de moins. Dans les coulisses, la compilation crée une grande partie du code boilerplate pour vous. Le compilateur crée un constructeur qui correspond à la position de la déclaration d'enregistrement. Étant donné que la propriété FirstName
a été la première propriété déclarée lorsque nous avons défini la méthode, elle suppose que la valeur Joseph doit être la valeur de la propriété FirstName
. Le compilateur a également généré toutes les propriétés en tant qu'initialisation uniquement (plus d'informations à ce sujet plus tard ), ce qui signifie que les propriétés ne peuvent pas être définies après l'initialisation, ce qui les rend en lecture seule.
Égalité des valeurs
Un ensemble de fonctionnalités intégrées fournies par les enregistrements est l'égalité des valeurs. Lors de la vérification pour voir si deux enregistrements sont égaux, il examinera les valeurs de chacune des propriétés et non la référence.
En supposant la définition de :
public record Person(string FirstName, string LastName);
lors de la comparaison des enregistrements :
Personne personne1 = nouveau ("Joseph", "Guadagno");
Personne personne2 = nouveau ("Joseph", "Guadagno");
Console.WriteLine(person1 == personne2) ; // renvoie Vrai
Console.WriteLine(ReferenceEquals(person1, person2)); // renvoie Faux
Comme person2
a les mêmes FirstName
et LastName
de person2
ils sont égaux, bien que les références ne le soient pas.[19659034]Amélioration de ToString()
L'utilisation du mot-clé record
vous permet d'obtenir une autre méthode intégrée. Quelle affaire! Une méthode améliorée ToString
. Je souhaite vraiment que ce soit également la norme d'opt-in pour les classes.
La méthode ToString
affiche le format suivant.
{ = = ...}
Pour un enregistrement défini comme :
public record Person(string FirstName, string LastName);
et initialisé comme :
Person person = new {"Joseph", "Guadagno"} ;
la méthode ToString
produirait une chaîne comme :
Person { FirstName = Joseph, LastName = Guadagno }
S'il existe un type de référence comme l'une des propriétés de l'enregistrement, l'implémentation de l'enregistrement ToString
affichera le nom de type de celui-ci.
REMARQUE : N'essayez pas d'utiliser la méthode
ToString
pour déterminer les propriétés des enregistrements.
Héritage des enregistrements
Les enregistrements peuvent être hérités de la même manière que les classes, à l'exception des éléments suivants :
- Les enregistrements ne peuvent pas hériter d'une classe.[19659049]La classe ne peut pas hériter d'un enregistrement.
- Lors de la comparaison d'enregistrements, le type d'enregistrement est utilisé dans le cadre de la comparaison et pas seulement les valeurs.
Copie d'enregistrements
La copie d'enregistrements est assez simple. En prime, la syntaxe facilite la lecture du code.
Disons que nous avons un enregistrement Person défini comme :
public record Person
{
string Prénom { get; ensemble;}
string LastName { get; ensemble;}
string HomeState { get; ensemble;}
}
Disons aussi que nous voulons créer une Person et faire plusieurs copies et juste modifier quelques propriétés, comme si nous créions des variables pour toute la famille. Dans notre cas, les propriétés LastName
et HomeState
sont les mêmes et l'utilisation d'enregistrements avec le mot-clé with
facilite cette opération.
var me = new Person ("Joseph", "Guadagno", "Arizona");
var femme = moi avec {Prénom = "Deidre"} ;
var son = moi avec {FirstName = "Joseph Jr."} ;
var fille = moi avec {FirstName = "Emily"} ;
Désormais, les objets wife
son
et daughter
ont la propriété LastName
définie sur Guadagno et HomeState
défini sur Arizona.
Defining Set-Once Properties
Vous pouvez également utiliser le nouveau mot-clé init
pour rendre certaines propriétés définissables lors de l'initialisation seulement. Le mot-clé init
fonctionne avec les propriétés ou les indexeurs dans struct
class
ou record
.
Disons avec vouloir définir un Person record
avec les propriétés FirstName
LastName
et CreateOnDate
. Le CreatedOnDate
ne doit pas être modifiable une fois l'enregistrement initialisé. Nous déclarerions le record
comme ceci.
public record Person
{
chaîne publique Prénom { get; ensemble;}
chaîne publique LastName { get; ensemble;}
public DateTime CreatedOnDate { get; initialisation;}
}
Vous voyez à la ligne 5, nous avons le mot-clé init
au lieu de set
. Cela signifie que CreatedOnDate
ne peut être défini qu'une fois initialisé.
var person = new Person("Joseph", "Guadagno", DateTime.Now());
Après avoir déclaré cet enregistrement, nous sommes limités aux propriétés que nous pouvons modifier.
person.FirstName = "Joe"; // valide
personne.CreatedOnDate = DateTime.Now(); // Vous obtiendrez une erreur de compilation
La ligne 2 provoquera une erreur de compilation car la propriété CreatedOnDate
a été définie sur init uniquement.
Alternative Declaration
Vous pouvez également déclarer le setter d'une propriété avec un champ de sauvegarde comme init uniquement .
classe publique Personne
{
privé en lecture seule DateTime _dateOfBirth;
public DateHeure DateOfNaissance
{
get => _dateOfBirth;
init => (valeur ?? throw new ArgumentNullException(nameof(DateOfBirth)));
}
chaîne publique Prénom { get; ensemble;}
chaîne publique LastName { get; ensemble;}
}
Sur la ligne 7, nous définissons la classe Person avec une propriété init-only DateOfBirth
qui doit être définie à l'initialisation ou vous obtiendrez une erreur de compilation ou une exception d'exécution selon le implémentation.
Ceci est valide (en supposant la définition ci-dessus) :
var person = new Person{FirstName="Joseph", LastName="Guadagno", DateOfBirth=DateTime.Now(});
Ce n'est pas (en supposant la définition ci-dessus) :
var person = new Person{FirstName="Joseph", LastName="Guadagno"} ;
Sur la base de la définition ci-dessus, cet exemple de code lèvera une exception d'exécution. Les déclarations de niveau supérieur sont une autre des fonctionnalités.
Pour être honnête, vous n'utiliserez probablement pas beaucoup cette fonctionnalité. En fait, vous ne pouvez avoir qu'un seul fichier dans votre application qui utilise cette fonctionnalité. Il est généralement utile pour démontrer certaines fonctionnalités et supprimer toute la cérémonie supplémentaire autour du démarrage de l'application. Je me vois l'utiliser lorsque je crée des présentations.
Prenons l'exemple typique de "Hello World".
using System;
espace de noms CSharp9Features.ConsoleApp
{
programme de classe statique
{
static void Main(string[] args)
{
Console.WriteLine("Bonjour tout le monde!");
}
}
}
Il fait 12 lignes en utilisant le modèle d'application de console .NET C# par défaut. Désormais, avec les instructions de niveau supérieur, cela peut être réduit à :
System.Console.WriteLine("Hello World");
Maintenant, nous avons réduit le code de 12 lignes et 210 caractères à 1 ligne et 40 caractères.
Dans les coulisses, le compilateur a essentiellement créé les 12 lignes et 210 caractères pour nous. Mais encore une fois, C# 9 essaie de vous faciliter les choses, alors pourquoi taper ces lignes lorsque le compilateur sait que c'est ce que vous voulez ?
Dans un exemple plus «réaliste», disons pour une API Web ASP.NET Core projet, le modèle typique aurait un fichier Program.cs
qui ressemble à ceci :
en utilisant Microsoft.AspNetCore.Hosting ;
en utilisant Microsoft.Extensions.Hosting ;
en utilisant Microsoft.Extensions.Logging ;
espace de noms Contacts.Api
{
Programme des cours publics
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public statique IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
});
}
}
Maintenant, avec C# 9, nous pouvons supprimer une partie du bruit et de la cérémonie et faire en sorte que notre code soit exactement ce dont l'API a besoin pour démarrer :
à l'aide de Contacts.Api ;
en utilisant Microsoft.AspNetCore.Hosting ;
en utilisant Microsoft.Extensions.Hosting ;
en utilisant Microsoft.Extensions.Logging ;
CreateHostBuilder(args).Build().Run();
IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
});
Ce code indique désormais clairement l'intention de program.cs
sans la méthode extra namespace
ou Main
.
Nouveau Correspondance de modèle
Bien que la correspondance de modèle ne soit pas une nouveauté dans C# 9, C# 9 a ajouté quelques modèles supplémentaires.
Modèles logiques :
Modèles de relations :
Ces modèles contribuent à améliorer la lisibilité coder. Mon ajout préféré à ceci est le matcher de modèle pas
. Maintenant, nous pouvons prendre toutes les instances de :
if (!person est null)
et les rendre plus lisibles avec :
if (person is not null)
Bien que celui-ci comporte davantage de frappes, les quelques caractères supplémentaires le rendent plus lisible pour moi que l'opérateur !
.
Omission du type
Le compilateur devient plus intelligent. Il ne s'agit pas nécessairement de devenir plus intelligent, mais de mieux comprendre ce que vous essayez de faire et, encore une fois, de réduire les frappes.
La fonctionnalité C# 9 des nouvelles expressions de type cible démontre que le compilateur devient plus intelligent. Maintenant, en fonction de la déclaration de variable ou de la signature de méthode, vous pouvez omettre le type dans les déclarations de variable ou l'utilisation.
Ici, nous déclarons une variable _people
de type List
:[19659012]liste privée
Nous n'avons plus à initialiser la variable de _people
avec new List
. Le compilateur peut supposer que nous voulons une nouvelle liste de personnes.
Il en va de même pour les méthodes. Dans l'exemple ci-dessous, la méthode CalculateSalary
attend un paramètre de type PerformanceRating
.
public Person CalculateSalary(PerformanceRating rating)
{
// Omis
}
Si nous voulions initialiser un nouvel objet PerformanceRating
pour la méthode sans créer de variable, nous pouvons maintenant.
var person = person.CalculateSalary(new ());
Ou, nous pouvons transmettre un nouvel objet PerformanceRating
avec une ou plusieurs de ses propriétés initialisées.
var person = person.CalculateSalary(new () {Rating ="Rock Star"}) ;
Cette syntaxe demande un certain temps d'adaptation. Je pense qu'à long terme, cela conduit à un code plus facile à utiliser. Cependant, cela pourrait ajouter plus de carburant au débat var
contre déclaration de variable typée. 🙂
Résumé
Wow, c'était beaucoup. C#9 a ajouté Record TypesInit Only setters, Top-Level programmes, améliorations de pattern matchinget more .
J'espère que vous prendrez le temps de jouer avec ces nouvelles fonctionnalités linguistiques. Cela réduira vos frappes et aidera votre code à être lisible à long terme. prévoit d'ajouter ce qui suit à C# 10.
- Autoriser les chaînes interpolées
const
. - Les types d'enregistrement peuvent sceller
ToString()
. - Autoriser l'affectation et la déclaration dans la même déconstruction.
- Autoriser l'attribut
AsyncMethodBuilder
sur les méthodes.
Pour en savoir plus, consultez "Quoi de neuf dans C# 10.0".
Source link