Convenzioni comuni per il codice C#

Uno standard di codice è essenziale per mantenere la leggibilità, la coerenza e la collaborazione sul codice all'interno di un team di sviluppo. Il codice che segue le procedure del settore e le linee guida stabilite è più facile da comprendere, gestire ed estendere. La maggior parte dei progetti applica uno stile coerente tramite convenzioni per la scrittura del codice. I progetti dotnet/docs e dotnet/samples non fanno eccezione. In questa serie di articoli vengono fornite informazioni sulle convenzioni per la scrittura del codice e sugli strumenti usati per applicarle. È possibile adottare le convenzioni così come sono o modificarle in base alle esigenze del team.

Le convenzioni sono state scelte in base agli obiettivi seguenti:

  1. Correttezza: gli esempi vengono copiati e incollati nelle applicazioni. Questo è un comportamento previsto, quindi, è necessario rendere il codice resiliente e corretto, anche dopo più modifiche.
  2. Insegnamento: lo scopo degli esempi è insegnare tutti gli aspetti relativi a .NET e C#. Per questo motivo, non vengono applicate restrizioni ad alcuna funzionalità o API del linguaggio. Questi esempi insegnano invece quando una funzionalità è una buona scelta.
  3. Coerenza: i lettori si aspettano un'esperienza coerente in tutto il contenuto. Tutti gli esempi devono essere conformi allo stesso stile.
  4. Adozione: gli esempi vengono aggiornati in modo aggressivo in modo da usare nuove funzionalità del linguaggio. Questa prassi aumenta la consapevolezza della disponibilità di nuove funzionalità e le rende più familiari a tutti gli sviluppatori C#.

Importante

Queste linee guida vengono usate da Microsoft per sviluppare esempi e documentazione. Sono state adottate dalle linee guida Stile per la scrittura del codice di .NET Runtime e C# e Compilatore C# (roslyn). Queste linee guida sono state scelte perché sono state testate in diversi anni di sviluppo open source. Hanno aiutato i membri della community a partecipare ai progetti di runtime e compilatore. Sono destinate a essere un esempio di convenzioni comuni per C#, non un elenco autorevole, che è invece disponibile in Linee guida per la progettazione di framework.

Gli obiettivi di insegnamento e adozione sono i motivi per cui la convenzione per la scrittura del codice di docs è diversa rispetto alle convenzioni per il runtime e il compilatore. Sia il runtime che il compilatore hanno metriche di prestazioni rigorose per i percorsi ad accesso frequente, a differenza di molte altre applicazioni. L'obiettivo di insegnamento impone che non venga vietato alcun costrutto. Gli esempi mostrano invece quando devono essere usati i costrutti. Gli esempi vengono aggiornati in modo più aggressivo rispetto alla maggior parte delle applicazioni di produzione. L'obiettivo di adozione impone che venga mostrato il codice che un utente dovrebbe scrivere attualmente, anche quando il codice scritto lo scorso anno non richiede modifiche.

Questo articolo illustra le linee guida. Le linee guida si sono evolute nel corso del tempo e sono disponibili esempi che non seguono le linee guida. Gli utenti sono incoraggiati a inviare richieste può che consentono a tali esempi di raggiungere la conformità o a segnalare problemi per evidenziare esempi che necessitano di aggiornamento. Le linee guida sono Open Source e le richieste pull e le segnalazioni di problemi sono molto apprezzate. Se tuttavia l'invio comporterebbe modifiche per queste raccomandazioni, aprire prima di tutto un problema per la discussione. Gli utenti sono invitati a usare le linee guida disponibili o adattarle alle esigenze specifiche.

Strumenti e analizzatori

Gli strumenti consentono al team di applicare gli standard. È possibile abilitare l'analisi codice per applicare le regole preferite. È anche possibile creare un editorconfig in modo che Visual Studio applichi automaticamente le linee guida di stile. Come punto di partenza, è possibile copiare il file del repository dotnet/docs per usare lo stile disponibile.

Questi strumenti semplificano l'adozione delle linee guida preferite dal team. Visual Studio applica le regole in tutti i file .editorconfig nell'ambito per formattare il codice. È possibile usare più configurazioni per applicare standard a livello aziendale, standard del team e persino standard di progetto granulari.

L'analisi codice genera avvisi e dati di diagnostica quando le regole abilitate vengono violate. Configurare le regole da applicare al progetto. Ogni compilazione con integrazione continua invia quindi una notifica agli sviluppatori quando violano una delle regole.

ID di diagnostica

Linee guida per il linguaggio

Nelle sezioni seguenti vengono descritte le procedure che il team di docs .NET deve seguire per preparare campioni ed esempi di codice. Seguire in generale queste procedure:

  • Usare le funzionalità del linguaggio e le versioni moderne di C# quando possibile.
  • Evitare costrutti di linguaggio obsoleti o non aggiornati.
  • Rilevare solo le eccezioni che possono essere gestite correttamente. Evitare di intercettare eccezioni generiche.
  • Usare tipi di eccezione specifici per fornire messaggi di errore significativi.
  • Usare query e metodi LINQ per la manipolazione della raccolta per migliorare la leggibilità del codice.
  • Usare la programmazione asincrona con async e await per le operazioni associate a I/O.
  • Prestare attenzione ai deadlock e usare Task.ConfigureAwait quando appropriato.
  • Usare le parole chiave del linguaggio per i tipi di dati anziché i tipi di runtime. Usare ad esempio string anziché System.String o int anziché System.Int32.
  • Usare int anziché tipi non firmati. L'uso di int è comune in C# ed è più facile interagire con altre librerie quando si usa int. Le eccezioni sono relative alla documentazione specifica per i tipi di dati non firmati.
  • Usare var solo quando un lettore può dedurre il tipo dall'espressione. I lettori visualizzano gli esempi nella piattaforma docs. Non sono disponibili suggerimenti al passaggio del mouse o descrizioni comando che visualizzano il tipo delle variabili.
  • Scrivere codice con chiarezza e semplicità.
  • Evitare la logica del codice eccessivamente complessa e contorta.

Seguono linee guida più specifiche.

Dati stringa

  • Usare l'interpolazione di stringhe per concatenare stringhe brevi, come illustrato nel codice seguente.

    string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
    
  • Per accodare stringhe nei cicli, in particolare quando si lavora con grandi quantità di testo, usare un oggetto System.Text.StringBuilder.

    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    

Matrici

  • Usare la sintassi concisa quando si inizializzano le matrici nella riga della dichiarazione. Nell'esempio seguente non è possibile usare var anziché string[].
string[] vowels1 = { "a", "e", "i", "o", "u" };
  • Se si usa la creazione di un'istanza esplicita, è possibile usare var.
var vowels2 = new string[] { "a", "e", "i", "o", "u" };

Delegati

  • Usare Func<> e Action<> anziché definire tipi delegati. In una classe definire il metodo delegato.
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;
  • Chiamare il metodo usando la firma definita dal delegato Func<> o 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)}");
  • Se si creano istanze di un tipo delegato, usare la sintassi concisa. In una classe definire il tipo delegato e un metodo con una firma corrispondente.

    public delegate void Del(string message);
    
    public static void DelMethod(string str)
    {
        Console.WriteLine("DelMethod argument: {0}", str);
    }
    
  • Creare un'istanza del tipo delegato e chiamarla. La dichiarazione seguente illustra la sintassi condensata.

    Del exampleDel2 = DelMethod;
    exampleDel2("Hey");
    
  • La dichiarazione seguente usa la sintassi completa.

    Del exampleDel1 = new Del(DelMethod);
    exampleDel1("Hey");
    

Istruzioni try-catch e using nella gestione delle eccezioni

  • Usare un'istruzione try-catch per la gestione della maggior parte delle eccezioni.

    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;
        }
    }
    
  • Semplificare il codice usando l'istruzione using di C#. Se si ha un'istruzione try-finally in cui l'unico codice nel blocco finally è una chiamata al metodo Dispose, usare invece un'istruzione using.

    Nell'esempio seguente l'istruzione try-finally chiama solo Dispose nel blocco finally.

    Font bodyStyle = new Font("Arial", 10.0f);
    try
    {
        byte charset = bodyStyle.GdiCharSet;
    }
    finally
    {
        if (bodyStyle != null)
        {
            ((IDisposable)bodyStyle).Dispose();
        }
    }
    

    È possibile eseguire la stessa operazione con un'istruzione using.

    using (Font arial = new Font("Arial", 10.0f))
    {
        byte charset2 = arial.GdiCharSet;
    }
    

    Usare la nuova sintassi using che non richiede parentesi graffe:

    using Font normalStyle = new Font("Arial", 10.0f);
    byte charset3 = normalStyle.GdiCharSet;
    

Operatori && e ||

  • Usare && anziché & e || anziché | quando si eseguono confronti, come illustrato nell'esempio seguente.

    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.");
    }
    

Se il divisore è 0, la seconda clausola nell'istruzione if genererà un errore di run-time. Ma l'operatore && risulta in un corto circuito quando la prima espressione è false, ovvero non valuta la seconda espressione. L'operatore & valuta entrambe, generando un errore di run-time quando divisor è 0.

Operatore new

  • Usare una delle forme concise di creazione di un'istanza dell'oggetto, come illustrato nelle dichiarazioni seguenti.

    var firstExample = new ExampleClass();
    
    ExampleClass instance2 = new();
    

    Le dichiarazioni precedenti sono equivalenti alla dichiarazione seguente.

    ExampleClass secondExample = new ExampleClass();
    
  • Usare gli inizializzatori di oggetti per semplificare la creazione di oggetti, come illustrato nell'esempio seguente.

    var thirdExample = new ExampleClass { Name = "Desktop", ID = 37414,
        Location = "Redmond", Age = 2.3 };
    

    L'esempio seguente imposta le stesse proprietà dell'esempio precedente, ma non usa gli inizializzatori.

    var fourthExample = new ExampleClass();
    fourthExample.Name = "Desktop";
    fourthExample.ID = 37414;
    fourthExample.Location = "Redmond";
    fourthExample.Age = 2.3;
    

Gestione degli eventi

  • Usare un'espressione lambda per definire un gestore eventi che non è necessario rimuovere in un secondo momento:
public Form2()
{
    this.Click += (s, e) =>
        {
            MessageBox.Show(
                ((MouseEventArgs)e).Location.ToString());
        };
}

L'espressione lambda abbrevia la definizione tradizionale seguente.

public Form1()
{
    this.Click += new EventHandler(Form1_Click);
}

void Form1_Click(object? sender, EventArgs e)
{
    MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}

Membri statici

Chiamare i membri static usando il nome della classe: ClassName.StaticMember. Questa pratica rende più leggibile il codice semplificando l'accesso statico. Non qualificare un membro statico definito in una classe base con il nome di una classe derivata. Nonostante il codice venga compilato, la leggibilità del codice è fuorviante mentre il codice potrebbe essere interrotto in futuro, se si aggiunge un membro statico con lo stesso nome alla classe derivata.

Query LINQ

  • Usare nomi significativi per le variabili di query. Nell'esempio seguente viene usato seattleCustomers per i clienti che si trovano a Seattle.

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • Usare gli alias per assicurarsi che i nomi delle proprietà di tipi anonimi siano scritti correttamente in maiuscolo, usando la convenzione Pascal.

    var localDistributors =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { Customer = customer, Distributor = distributor };
    
  • Rinominare le proprietà quando i nomi delle proprietà nel risultato potrebbero risultare ambigui. Ad esempio, se la query restituisce un nome cliente un ID del server di distribuzione, anziché lasciarli come Name e ID nei risultati, rinominarli per spiegare che Name è il nome di un cliente e ID è l'ID di un server di distribuzione.

    var localDistributors2 =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { CustomerName = customer.Name, DistributorID = distributor.ID };
    
  • Usare la tipizzazione implicita nella dichiarazione di variabili di query e variabili di intervallo. Queste linee guida sulla tipizzazione implicita nelle query LINQ sostituiscono le regole generali per variabili locali tipizzate in modo implicito. Le query LINQ usano spesso proiezioni che creano tipi anonimi. Altre espressioni di query creano risultati con tipi generici annidati. Le variabili tipizzate in modo implicito sono spesso più leggibili.

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • Allineare le clausole di query sotto la clausola from, come illustrato negli esempi precedenti.

  • Usare le clausole where prima delle altre clausole di query per garantire che le clausole di query successive agiscano su un set di dati ridotto e filtrato.

    var seattleCustomers2 = from customer in customers
                            where customer.City == "Seattle"
                            orderby customer.Name
                            select customer;
    
  • Usare più clausole from invece di una clausola join per accedere a raccolte interne. Ad esempio, ogni raccolta di oggetti Student potrebbe contenere una raccolta di punteggi del test. Quando viene eseguita la query seguente, viene restituito ogni punteggio superiore a 90, e il cognome dello studente che ha ricevuto il punteggio.

    var scoreQuery = from student in students
                     from score in student.Scores!
                     where score > 90
                     select new { Last = student.LastName, score };
    

Variabili locali tipizzate in modo implicito

  • Usare la tipizzazione implicita per le variabili locali quando il tipo della variabile è ovvio dal lato destro dell'assegnazione.

    var message = "This is clearly a string.";
    var currentTemperature = 27;
    
  • Non usare var quando il tipo non è evidente dal lato destro dell'assegnazione. Non presupporre che il tipo sia chiaro in base al nome di metodo. Un tipo di variabile viene considerato chiaro se si tratta di un operatore new, un cast esplicito o un'assegnazione a un valore letterale.

    int numberOfIterations = Convert.ToInt32(Console.ReadLine());
    int currentMaximum = ExampleClass.ResultSoFar();
    
  • Non usare nomi di variabile per specificare il tipo della variabile. Potrebbe non essere corretto. Usare invece il tipo per specificare il tipo e usare il nome della variabile per indicare le informazioni semantiche della variabile. L'esempio seguente deve usare string per il tipo e qualcosa di simile a iterations per indicare il significato delle informazioni lette dalla console.

    var inputInt = Console.ReadLine();
    Console.WriteLine(inputInt);
    
  • Evitare l'uso di var al posto di dynamic. Usare dynamic quando si vuole l'inferenza del tipo di runtime. Per altre informazioni, vedere Uso del tipo dynamic (Guida per programmatori C#).

  • Usare la tipizzazione implicita per la variabile di ciclo nei cicli for.

    Nell'esempio seguente viene usata la tipizzazione implicita in un'istruzione for.

    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    
  • Non usare la tipizzazione implicita per determinare il tipo della variabile di ciclo nei cicli foreach. Nella maggior parte dei casi il tipo di elementi nella raccolta non è immediatamente ovvio. Non affidarsi esclusivamente al nome della raccolta per dedurre il tipo dei relativi elementi.

    Nell'esempio seguente viene usata la tipizzazione esplicita in un'istruzione foreach.

    foreach (char ch in laugh)
    {
        if (ch == 'h')
            Console.Write("H");
        else
            Console.Write(ch);
    }
    Console.WriteLine();
    
  • Usare il tipo implicito per le sequenze di risultati nelle query LINQ. La sezione relativa a LINQ spiega che molte query LINQ generano tipi anonimi in cui devono essere usati tipi impliciti. Altre query generano tipi generici annidati in cui var è più leggibile.

    Nota

    Prestare attenzione a non modificare accidentalmente un tipo di un elemento della raccolta iterabile. È ad esempio facile passare da System.Linq.IQueryable a System.Collections.IEnumerable in un'istruzione foreach, modificando quindi l'esecuzione di una query.

Alcuni esempi illustrano il tipo naturale di un'espressione. Tali esempi devono usare var in modo che il compilatore selezioni il tipo naturale. Anche se questi esempi sono meno scontati, l'uso di var è necessario per l'esempio. Il testo deve spiegare il comportamento.

Inserire le direttive using all'esterno della dichiarazione dello spazio dei nomi

Quando una direttiva using non rientra in una dichiarazione dello spazio dei nomi, tale spazio dei nomi importato è il rispettivo nome completo. Il nome completo è più chiaro. Quando la direttiva using si trova all'interno dello spazio dei nomi, può essere relativa a tale spazio dei nomi o al nome completo.

using Azure;

namespace CoolStuff.AwesomeFeature
{
    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            // ...
        }
    }
}

Supponendo che sia presente un riferimento (diretto o indiretto) alla classe WaitUntil.

A questo punto, è possibile modificarlo leggermente:

namespace CoolStuff.AwesomeFeature
{
    using Azure;

    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            // ...
        }
    }
}

E viene compilato oggi. E domani. La settimana successiva, tuttavia, il codice precedente (non modificato) ha esito negativo con due errori:

- 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

Una delle dipendenze ha introdotto questa classe in uno spazio dei nomi che termina con .Azure:

namespace CoolStuff.Azure
{
    public class SecretsManagement
    {
        public string FetchFromKeyVault(string vaultId, string secretId) { return null; }
    }
}

Una direttiva using inserita all'interno di uno spazio dei nomi è sensibile al contesto e complica la risoluzione dei nomi. In questo esempio si tratta del primo spazio dei nomi trovato.

  • CoolStuff.AwesomeFeature.Azure
  • CoolStuff.Azure
  • Azure

L'aggiunta di un nuovo spazio dei nomi corrispondente a CoolStuff.Azure o CoolStuff.AwesomeFeature.Azure risulterebbe in una corrispondenza prima dello spazio dei nomi globale Azure. È possibile risolvere questo problema aggiungendo il modificatore global:: alla dichiarazione using. È tuttavia più facile inserire le dichiarazioni using all'esterno dello spazio dei nomi.

namespace CoolStuff.AwesomeFeature
{
    using global::Azure;

    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            // ...
        }
    }
}

Linee guida per lo stile

In generale, usare il formato seguente per gli esempi di codice:

  • Usare quattro spazi per il rientro. Non usare tabulazioni.
  • Allineare il codice in modo coerente per migliorare la leggibilità.
  • Limitare le righe a 65 caratteri per migliorare la leggibilità del codice nei documenti, in particolare sugli schermi di dispositivi mobili.
  • Suddividere le istruzioni lunghe in più righe per migliorare la chiarezza.
  • Usa lo stile "Allman" per le parentesi graffe, ovvero una nuova riga per ogni parentesi di apertura e chiusura. Le parentesi graffe si allineano con il livello di rientro corrente.
  • Le interruzioni di riga devono verificarsi prima degli operatori binari, se necessario.

Stile dei commenti

  • Usare commenti a riga singola (//) per brevi spiegazioni.

  • Evitare commenti su più righe (/* */) per spiegazioni più lunghe. I commenti non vengono localizzati. Le spiegazioni più lunghe sono invece disponibili nell'articolo complementare.

  • Per descrivere metodi, classi, campi e tutti i membri pubblici usare commenti XML.

  • Posizionare il commento su una riga separata, non alla fine di una riga di codice.

  • Iniziare il commento con una lettera maiuscola.

  • Terminare il commento con un punto finale.

  • Inserire uno spazio tra i delimitatori di commento (//) e il testo del commento, come illustrato nell'esempio seguente.

    // The following declaration creates a query. It does not run
    // the query.
    

Convenzioni di layout

Un layout appropriato usa la formattazione per mettere in evidenza la struttura del codice e per facilitare la lettura del codice. Gli esempi Microsoft sono conformi alle convenzioni seguenti:

  • Usare le impostazioni dell'Editor di codice predefinite (rientri intelligenti, rientri di quattro caratteri, tabulazioni salvate come spazi). Per altre informazioni, vedere Opzioni, Editor di testo, C#, Formattazione.

  • Scrivere una sola istruzione per riga.

  • Scrivere una sola dichiarazione per riga.

  • Se le righe di continuazione non sono rientrate automaticamente, impostare un rientro con un punto di tabulazione (quattro spazi).

  • Aggiungere almeno una riga vuota tra le definizioni di metodo e proprietà.

  • Usare le parentesi per rendere visibili le clausole in un'espressione, come illustrato nel codice seguente.

    if ((startX > endX) && (startX > previousX))
    {
        // Take appropriate action.
    }
    

Le eccezioni sono consentite quando l'esempio spiega la precedenza di operatori o espressioni.

Sicurezza

Seguire le indicazioni in Linee guida per la generazione di codice sicuro.