Convenzioni comuni per il codice C#
Le convenzioni per la scrittura del codice sono essenziali per garantire la leggibilità e la coerenza del codice e la collaborazione in 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:
- 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.
- 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.
- Coerenza: i lettori si aspettano un'esperienza coerente in tutto il contenuto. Tutti gli esempi devono essere conformi allo stesso stile.
- 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 le convenzioni definite. È 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 convenzioni a livello aziendale, di team e persino granulari di progetto.
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
- Scegliere ID di diagnostica appropriati durante la creazione di analizzatori personalizzati
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 oint
anziché System.Int32. - Usare
int
anziché tipi non firmati. L'uso diint
è comune in C# ed è più facile interagire con altre librerie quando si usaint
. 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<>
eAction<>
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<>
oAction<>
.
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'istruzioneusing
.Nell'esempio seguente l'istruzione
try-finally
chiama soloDispose
nel bloccofinally
.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
eID
nei risultati, rinominarli per spiegare cheName
è il nome di un cliente eID
è 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 clausolajoin
per accedere a raccolte interne. Ad esempio, ogni raccolta di oggettiStudent
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 aiterations
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. Usaredynamic
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 negli esempi di codice non sono tradotti. Ciò significa che le spiegazioni integrate nel codice non saranno tradotte. Il testo esplicativo più lungo deve essere inserito nell'articolo complementare, in modo che possa essere tradotto.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.