Conventions de code C# courantes
Les conventions de codage sont essentielles pour maintenir la lisibilité et la cohérence du code ainsi que la collaboration au sein d’une équipe de développement. Un code qui suit les pratiques de l’industrie et les lignes directrices établies est plus facile à comprendre, à maintenir et à étendre. La plupart des projets appliquent un style cohérent à l'aide de conventions de code. Les projets dotnet/docs
et dotnet/samples
ne font pas exception. Dans cette série d'articles, vous allez découvrir nos conventions de codage et les outils que nous utilisons pour les appliquer. Vous pouvez utiliser nos conventions en l'état ou les modifier en fonction des besoins de votre équipe.
Nous avons choisi nos conventions en fonction des objectifs suivants :
- Correction : nos exemples sont copiés et collés dans vos applications. Il s'agit de quelque chose d'attendu, et nous devons donc garantir la résilience et la correction du code, même après plusieurs modifications.
- Enseignement : l'objectif de nos exemples est d'enseigner tout ce qui touche au codage .NET et C#. Pour cette raison, nous n'avons pas de restrictions concernant une quelconque fonctionnalité de langage ou API. Au lieu de cela, ces exemples montrent quand une fonctionnalité est un bon choix.
- Cohérence : les lecteurs attendent une expérience cohérente avec notre contenu. Tous les exemples doivent être conformes au même style.
- Adoption : nous mettons à jour de manière agressive nos exemples pour utiliser de nouvelles fonctionnalités de langage. Cette pratique permet de sensibiliser aux nouvelles fonctionnalités et les rend plus familières à tous les développeurs C#.
Important
Ces recommandations sont utilisées par Microsoft pour développer les exemples et la documentation. Elles ont été adoptées à partir des recommandations de .NET Runtime, Style de codage C# et du compilateur C# (roslyn). Nous avons choisi ces recommandations, car elles ont été testées au cours de plusieurs années de développement open source. Elles ont aidé les membres de la communauté à participer aux projets runtime et de compilateur. Elles servent d’exemples de conventions C# courantes, et non de liste faisant autorité (pour cela, voir Règles de conception de .NET Framework).
Les objectifs d'enseignement et d'adoption sont les raisons pour lesquelles la convention de codage de la documentation diffère des conventions du runtime et du compilateur. Le runtime et le compilateur ont des métriques de performances strictes pour les chemins chauds. De nombreuses autres applications n'en ont pas. Notre objectif d'enseignement nous oblige à n'interdire aucune construction. Au lieu de cela, les exemples indiquent quand les constructions doivent être utilisées. Nous mettons à jour les exemples de manière plus agressive que la plupart des applications de production. Notre objectif d'adoption nous oblige à afficher le code que vous devez écrire aujourd'hui, même lorsque le code écrit l'année dernière n'a pas besoin de modifications.
Cet article explique nos recommandations. Celles-ci ont évolué au fil du temps et vous trouverez des exemples qui ne les suivent pas. Nous accueillons favorablement les demandes de tirage qui mettent ces exemples en conformité ou les signalements de problème qui attirent notre attention sur les exemples que nous devrions mettre à jour. Nos recommandations sont en open source et nous accueillons favorablement les demandes de tirage et signalements de problème. Si toutefois votre soumission viendrait à modifier ces recommandations, signalez d'abord un problème pour discussion. Nous vous invitons à utiliser nos recommandations ou à les adapter à vos besoins.
Outils et analyseurs
Des outils peuvent aider votre équipe à appliquer vos conventions. Vous pouvez activer l’analyse du code pour appliquer les règles que vous préférez. Vous pouvez également créer un editorconfig pour que Visual Studio applique automatiquement vos recommandations relatives au style. En guise de point de départ, vous pouvez copier le fichier du référentiel dotnet/docs pour utiliser notre style.
Ces outils facilitent l'adoption de vos recommandations par votre équipe. Visual Studio applique les règles de tous les fichiers .editorconfig
dans l'étendue pour mettre en forme votre code. Vous pouvez utiliser plusieurs configurations pour appliquer des conventions à l’échelle de l’entreprise, des conventions au sein d’une équipe et même des conventions spécifiques à un projet.
L’analyse du code génère des avertissements et des diagnostics lorsque les règles activées sont violées. Vous configurez les règles que vous souhaitez appliquer à votre projet. Ensuite, chaque build CI avertit les développeurs lorsqu'ils enfreignent l'une des règles.
Identificateurs de diagnostic
- Choisissez les ID de diagnostic appropriés lors de la génération de vos propres analyseurs
Directives du langage
Les sections suivantes décrivent les pratiques que l'équipe responsable de la documentation .NET suit pour préparer les exemples de code et autres. En général, suivez ces pratiques :
- Utilisez des fonctionnalités de langage et des versions C# modernes dans la mesure du possible.
- Évitez les constructions de langage obsolètes.
- Interceptez uniquement les exceptions qui peuvent être gérées correctement ; évitez d'intercepter les exceptions génériques.
- Utilisez des types d'exceptions spécifiques pour fournir des messages d'erreur significatifs.
- Utilisez des requêtes et des méthodes LINQ pour la manipulation de collection afin d'améliorer la lisibilité du code.
- Utilisez la programmation asynchrone avec async et await pour les opérations liées aux E/S.
- Faites attention aux interblocages et utilisez Task.ConfigureAwait le cas échéant.
- Utilisez les mots clés de langage pour les types de données au lieu des types de runtime. Par exemple, utilisez
string
plutôt que System.String ouint
plutôt que System.Int32. - Utilisez
int
plutôt que les types non signés. L'utilisation deint
est commune en C# et il est plus facile d'interagir avec d'autres bibliothèques lorsque vous utilisezint
. Les exceptions concernent la documentation spécifique aux types de données non signés. - Utilisez
var
uniquement lorsqu'un lecteur peut déduire le type de l'expression. Les lecteurs affichent nos exemples sur la plateforme de documentation. Ils n'ont pas de messages lors du pointage ou d'info-bulles qui affichent le type de variables. - Écrivez du code avec clarté et simplicité.
- Évitez les logiques de code trop complexes.
Des instructions plus spécifiques sont fournies ci-dessous.
Données de chaîne
Utilisez une interpolation de chaîne pour concaténer les chaînes courtes, comme illustré dans le code suivant.
string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
Pour ajouter des chaînes dans des boucles, en particulier lorsque vous travaillez avec une quantité de texte importante, utilisez un objet System.Text.StringBuilder.
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala"; var manyPhrases = new StringBuilder(); for (var i = 0; i < 10000; i++) { manyPhrases.Append(phrase); } //Console.WriteLine("tra" + manyPhrases);
Tableaux
- Utilisez la syntaxe concise lorsque vous initialisez des tableaux sur la ligne de déclaration. Dans l'exemple suivant, vous ne pouvez pas utiliser
var
à la place destring[]
.
string[] vowels1 = { "a", "e", "i", "o", "u" };
- Si vous utilisez l’instanciation explicite, vous pouvez utiliser
var
.
var vowels2 = new string[] { "a", "e", "i", "o", "u" };
Délégués
- Utilisez
Func<>
etAction<>
au lieu de définir des types délégués. Dans une classe, définissez la méthode déléguée.
Action<string> actionExample1 = x => Console.WriteLine($"x is: {x}");
Action<string, string> actionExample2 = (x, y) =>
Console.WriteLine($"x is: {x}, y is {y}");
Func<string, int> funcExample1 = x => Convert.ToInt32(x);
Func<int, int, int> funcExample2 = (x, y) => x + y;
- Appelez la méthode à l’aide de la signature définie par le délégué
Func<>
ouAction<>
.
actionExample1("string for x");
actionExample2("string for x", "string for y");
Console.WriteLine($"The value is {funcExample1("1")}");
Console.WriteLine($"The sum is {funcExample2(1, 2)}");
Pour créer des instances d'un type délégué, utilisez la syntaxe concise. Dans une classe, définissez le type délégué et une méthode qui a une signature correspondante.
public delegate void Del(string message); public static void DelMethod(string str) { Console.WriteLine("DelMethod argument: {0}", str); }
Créez une instance du type délégué et appelez-la. La déclaration suivante montre la syntaxe condensée.
Del exampleDel2 = DelMethod; exampleDel2("Hey");
La déclaration suivante utilise la syntaxe complète.
Del exampleDel1 = new Del(DelMethod); exampleDel1("Hey");
Instructions try-catch
et using
dans la gestion des exceptions
Utilisez une instruction try-catch pour la plus grande part de la gestion des exceptions.
static double ComputeDistance(double x1, double y1, double x2, double y2) { try { return Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } catch (System.ArithmeticException ex) { Console.WriteLine($"Arithmetic overflow or underflow: {ex}"); throw; } }
Simplifiez votre code à l’aide de l’instruction using C#. Si vous avez une instruction try-finally dans laquelle le seul code du bloc
finally
est un appel à la méthode Dispose, utilisez à la place une instructionusing
.Dans l’exemple suivant, l’instruction
try-finally
appelle uniquementDispose
dans le blocfinally
.Font bodyStyle = new Font("Arial", 10.0f); try { byte charset = bodyStyle.GdiCharSet; } finally { if (bodyStyle != null) { ((IDisposable)bodyStyle).Dispose(); } }
Vous pouvez faire la même chose avec une instruction
using
.using (Font arial = new Font("Arial", 10.0f)) { byte charset2 = arial.GdiCharSet; }
Utilisez la nouvelle
using
syntaxe qui ne nécessite pas d’accolades :using Font normalStyle = new Font("Arial", 10.0f); byte charset3 = normalStyle.GdiCharSet;
Opérateurs &&
et ||
Utilisez
&&
plutôt que&
et||
plutôt que|
lorsque vous effectuez des comparaisons, comme illustré dans l'exemple suivant.Console.Write("Enter a dividend: "); int dividend = Convert.ToInt32(Console.ReadLine()); Console.Write("Enter a divisor: "); int divisor = Convert.ToInt32(Console.ReadLine()); if ((divisor != 0) && (dividend / divisor) is var result) { Console.WriteLine("Quotient: {0}", result); } else { Console.WriteLine("Attempted division by 0 ends up here."); }
Si le diviseur est 0, la deuxième clause de l’instruction if
provoque une erreur d’exécution. Mais l’opérateur && court-circuite quand la première expression a la valeur false. Autrement dit, elle n’évalue pas la deuxième expression. L’opérateur & évalue les deux, ce qui entraîne une erreur d’exécution quand divisor
est 0.
new
opérateur
Utilisez l’une des formes concises d’instanciation d’objet, comme indiqué dans les déclarations suivantes.
var firstExample = new ExampleClass();
ExampleClass instance2 = new();
Les déclarations précédentes sont équivalentes à la déclaration suivante.
ExampleClass secondExample = new ExampleClass();
Utilisez des initialiseurs d’objets pour simplifier la création d’objets, comme illustré dans l’exemple suivant.
var thirdExample = new ExampleClass { Name = "Desktop", ID = 37414, Location = "Redmond", Age = 2.3 };
L’exemple suivant définit les mêmes propriétés que l’exemple précédent, mais il n’utilise pas d’initialiseurs.
var fourthExample = new ExampleClass(); fourthExample.Name = "Desktop"; fourthExample.ID = 37414; fourthExample.Location = "Redmond"; fourthExample.Age = 2.3;
Gestion des événements
- Utilisez une expression lambda pour définir un gestionnaire d'événements que vous n'aurez pas besoin de supprimer ultérieurement :
public Form2()
{
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
L’expression lambda raccourcit la définition traditionnelle suivante.
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
void Form1_Click(object? sender, EventArgs e)
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}
Membres static
Appelez les membres static en utilisant le nom de la classe : Nom_classe.Membre_statique. Cette pratique rend le code plus lisible en clarifiant l'accès aux membres static. Ne qualifiez pas un membre statique défini dans une classe de base avec le nom d'une classe dérivée. Lorsque ce code est compilé, la lisibilité du code est trompeuse et le code peut s'interrompre à l'avenir si vous ajoutez à la classe dérivée un membre static de même nom.
Requêtes LINQ
Utilisez des noms explicites pour les variables de requête. L'exemple suivant utilise
seattleCustomers
pour les clients qui se trouvent à Seattle.var seattleCustomers = from customer in customers where customer.City == "Seattle" select customer.Name;
Utilisez des alias pour vous assurer que les noms de propriétés des types anonymes sont correctement écrits en majuscules à l'aide de la casse Pascal.
var localDistributors = from customer in customers join distributor in distributors on customer.City equals distributor.City select new { Customer = customer, Distributor = distributor };
Renommez les propriétés lorsque les noms de propriétés dans le résultat sont ambigus. Par exemple, si votre requête retourne un nom de client et un ID de distributeur, au lieu de les laisser sous la forme
Name
etID
dans le résultat, renommez-les pour montrer clairement queName
est le nom d'un client etID
l'ID d'un distributeur.var localDistributors2 = from customer in customers join distributor in distributors on customer.City equals distributor.City select new { CustomerName = customer.Name, DistributorID = distributor.ID };
Utilisez le typage implicite dans la déclaration des variables de requête et des variables de portée. Cette aide sur le typage implicite dans les requêtes LINQ remplace les règles générales pour les variables locales implicitement typées. Les requêtes LINQ utilisent souvent des projections qui créent des types anonymes. D'autres expressions de requête créent des résultats avec des types génériques imbriqués. Les variables implicitement typées sont souvent plus lisibles.
var seattleCustomers = from customer in customers where customer.City == "Seattle" select customer.Name;
Alignez les clauses de requête sous la clause
from
, comme illustré dans les exemples précédents.Utilisez les clauses
where
avant les autres clauses de requête pour garantir que les clauses de requête ultérieures opèrent sur l’ensemble de données réduit et filtré.var seattleCustomers2 = from customer in customers where customer.City == "Seattle" orderby customer.Name select customer;
Utilisez plusieurs clauses
from
au lieu d’une clausejoin
pour accéder aux collections internes. Par exemple, une collection d'objetsStudent
peut contenir chacune une collection de scores de test. Lorsque la requête suivante est exécutée, elle retourne chaque score supérieur à 90, avec le nom de l'étudiant correspondant.var scoreQuery = from student in students from score in student.Scores! where score > 90 select new { Last = student.LastName, score };
Variables locales implicitement typées
Utilisez le typage implicite pour les variables locales quand le type de la variable est évident à droite de l'assignation.
var message = "This is clearly a string."; var currentTemperature = 27;
N'utilisez pas var quand le type n'est pas apparent à droite de l'assignation. Ne supposez pas que le type est clair à partir du nom de méthode. Un type de variable est considéré comme clair s'il s'agit d'un opérateur
new
, d'un cast explicite ou d'une assignation à une valeur littérale.int numberOfIterations = Convert.ToInt32(Console.ReadLine()); int currentMaximum = ExampleClass.ResultSoFar();
N'utilisez pas le nom de la variable pour spécifier son type. Il peut ne pas être correct. Utilisez plutôt le type pour spécifier son type et utilisez le nom de la variable pour indiquer ses informations sémantiques. L'exemple suivant doit utiliser
string
pour le type et quelque chose commeiterations
pour indiquer la signification des informations lues à partir de la console.var inputInt = Console.ReadLine(); Console.WriteLine(inputInt);
Évitez d’utiliser
var
à la place de dynamic. Utilisezdynamic
quand vous souhaitez une inférence de type d’exécution. Pour plus d’informations, consultez Utilisation du type dynamic (Guide de programmation C#).Utilisez le typage implicite pour la variable de boucle dans les boucles
for
.L'exemple suivant utilise un typage implicite dans une instruction
for
.var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala"; var manyPhrases = new StringBuilder(); for (var i = 0; i < 10000; i++) { manyPhrases.Append(phrase); } //Console.WriteLine("tra" + manyPhrases);
N’utilisez pas le typage implicite pour déterminer le type de la variable de boucle dans les boucles
foreach
. Dans la plupart des cas, le type d’éléments de la collection n’est pas immédiatement évident. Le nom de la collection ne doit pas être uniquement utilisé pour déduire le type de ses éléments.L'exemple suivant utilise un typage explicite dans une instruction
foreach
.foreach (char ch in laugh) { if (ch == 'h') { Console.Write("H"); } else { Console.Write(ch); } } Console.WriteLine();
Utilisez un type implicite pour les séquences de résultats dans les requêtes LINQ. La section sur LINQ explique que de nombreuses requêtes LINQ entraînent des types anonymes où des types implicites doivent être utilisés. D'autres requêtes entraînent des types génériques imbriqués où
var
est plus lisible.Remarque
Veillez à ne pas modifier accidentellement un type d’élément de la collection itérable. Par exemple, il est facile de passer de System.Linq.IQueryable à System.Collections.IEnumerable dans une instruction
foreach
, ce qui modifie l’exécution d’une requête.
Certains de nos exemples expliquent le type naturel d'une expression. Ces exemples doivent utiliser var
afin que le compilateur sélectionne le type naturel. Même si ces exemples sont moins évidents, l'utilisation de var
est nécessaire pour chacun. Le texte doit expliquer le comportement.
Placer les directives using en dehors de la déclaration d’espace de noms
Lorsqu’une directive using
se trouve en dehors d’une déclaration d’espace de noms, cet espace de noms importé représente son nom complet. Le nom qualifié complet est plus clair. Lorsque la directive using
se trouve à l'intérieur de l'espace de noms, elle peut être relative à cet espace de noms ou à son nom complet.
using Azure;
namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
En supposant qu'il existe une référence (directe ou indirecte) à la classe WaitUntil.
Nous allons maintenant légèrement modifier le code :
namespace CoolStuff.AwesomeFeature
{
using Azure;
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
Il compile aujourd’hui. Et demain. Mais la semaine suivante, le code précédent (non modifié) échoue en générant deux erreurs :
- error CS0246: The type or namespace name 'WaitUntil' could not be found (are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context
L’une des dépendances a introduit cette classe dans un espace de noms, puis se termine par .Azure
:
namespace CoolStuff.Azure
{
public class SecretsManagement
{
public string FetchFromKeyVault(string vaultId, string secretId) { return null; }
}
}
Une directive using
placée à l’intérieur d’un espace de noms respecte le contexte et complique la résolution de noms. Dans cet exemple, c'est le premier espace de nom qu'elle trouve.
CoolStuff.AwesomeFeature.Azure
CoolStuff.Azure
Azure
Ajout d’un nouvel espace de noms qui correspond à CoolStuff.Azure
ou à CoolStuff.AwesomeFeature.Azure
avant l’espace de noms global Azure
. Vous pouvez le résoudre en ajoutant le modificateur global::
à la déclaration using
. Mais il est plus facile de placer des déclarations using
en dehors de l’espace de noms.
namespace CoolStuff.AwesomeFeature
{
using global::Azure;
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
Recommandations sur le style
En général, utilisez le format suivant pour les exemples de code :
- Utilisez quatre espaces pour la mise en retrait. N'utilisez pas d'onglets.
- Alignez le code de manière cohérente pour améliorer la lisibilité.
- Limitez les lignes à 65 caractères pour améliorer la lisibilité du code sur les documents, en particulier sur les écrans d'appareil mobile.
- Décomposez les instructions longues en plusieurs lignes pour améliorer la clarté.
- Utilisez le style « Allman » pour les accolades : les accolades ouvrantes et fermantes ouvrent et ferment leur propre nouvelle ligne. Les accolades s'alignent sur le niveau de retrait actuel.
- Les sauts de ligne doivent être insérés avant les opérateurs binaires, si nécessaire.
Style de commentaire
Utilisez des commentaires à ligne unique (
//
) pour de brèves explications.Évitez les commentaires multilignes (
/* */
) pour les explications plus longues.
Les commentaires dans les exemples de code ne sont pas localisés. Cela signifie que les explications intégrées dans le code ne seront pas traduites. Les textes explicatifs plus longs devraient être placés dans l’article compagnon afin qu’ils puissent être localisés.Pour décrire les méthodes, les classes, les champs et tous les membres publics, utilisez les commentaires XML.
Placez le commentaire sur une ligne séparée, pas à la fin d'une ligne de code.
Commencez le commentaire par une lettre majuscule.
Terminez le texte de commentaire par un point.
Insérez un espace entre le délimiteur de commentaire (
//
) et le texte du commentaire, comme le montre l’exemple suivant.// The following declaration creates a query. It does not run // the query.
Conventions de disposition
Une bonne disposition utilise la mise en forme pour souligner la structure de votre code et en faciliter la lecture. Les exemples Microsoft et autres se conforment aux conventions suivantes :
Utilisez les paramètres par défaut de l'éditeur de code (retrait intelligent, retrait de quatre caractères, tabulations enregistrées en tant qu'espaces). Pour plus d’informations, consultez Options, Éditeur de texte, C#, Mise en forme.
Écrivez une seule instruction par ligne.
Écrivez une seule déclaration par ligne.
Si les lignes de continuation ne sont pas mises en retrait automatiquement, indentez-les à l'aide d'un taquet de tabulation (quatre espaces).
Ajoutez au moins une ligne blanche entre les définitions des méthodes et les définitions des propriétés.
Utilisez des parenthèses pour rendre apparentes les clauses d'une expression, comme illustré dans le code suivant.
if ((startX > endX) && (startX > previousX)) { // Take appropriate action. }
Il y a exception lorsque l'exemple explique la priorité de l'opérateur ou de l'expression.
Sécurité
Suivez les instructions indiquées dans Instructions de codage sécurisé.