Conventions de code C# courantes

Une norme de code est essentielle 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 :

  1. 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.
  2. 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.
  3. Cohérence : les lecteurs attendent une expérience cohérente avec notre contenu. Tous les exemples doivent être conformes au même style.
  4. 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

Les outils peuvent aider votre équipe à appliquer vos normes. 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 normes à l’échelle de l’entreprise, des normes d’équipe et même des normes de projet granulaires.

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

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 ou int plutôt que System.Int32.
  • Utilisez int plutôt que les types non signés. L'utilisation de int est commune en C# et il est plus facile d'interagir avec d'autres bibliothèques lorsque vous utilisez int. 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 de string[].
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<> et Action<> 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<> ou Action<>.
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 instruction using.

    Dans l’exemple suivant, l’instruction try-finallyappelle uniquement Dispose dans le bloc finally.

    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 usingsyntaxe 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 et ID dans le résultat, renommez-les pour montrer clairement que Name est le nom d'un client et ID 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 clause join pour accéder aux collections internes. Par exemple, une collection d'objets Student 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 comme iterations 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. Utilisez dynamic 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 ne sont pas localisés. Au lieu de cela, les explications plus longues sont données dans l'article complément.

  • 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é.