Typowe konwencje kodu języka C#
Konwencje kodowania są niezbędne do utrzymania czytelności kodu, spójności i współpracy w zespole deweloperów. Kod zgodny z praktykami branżowymi i ustalonymi wytycznymi jest łatwiejszy do zrozumienia, utrzymania i rozszerzania. Większość projektów wymusza spójny styl za pomocą konwencji kodu. Projekty dotnet/docs
i dotnet/samples
nie są wyjątkiem. W tej serii artykułów poznasz nasze konwencje kodowania i narzędzia, których używamy do ich wymuszania. Możesz podjąć nasze konwencje zgodnie z oczekiwaniami lub zmodyfikować je zgodnie z potrzebami twojego zespołu.
Wybraliśmy nasze konwencje na podstawie następujących celów:
- Poprawność: nasze przykłady są kopiowane i wklejane do aplikacji. Oczekujemy, że dlatego musimy zrobić kod, który jest odporny i poprawny, nawet po wielu edycjach.
- Nauczanie: Celem naszych przykładów jest nauczenie wszystkich platform .NET i C#. Z tego powodu nie nakładamy ograniczeń dotyczących żadnej funkcji językowej ani interfejsu API. Zamiast tego te przykłady uczą się, gdy funkcja jest dobrym wyborem.
- Spójność: Czytelnicy oczekują spójnego środowiska w naszej zawartości. Wszystkie próbki powinny być zgodne z tym samym stylem.
- Wdrażanie: agresywnie aktualizujemy nasze przykłady, aby korzystały z nowych funkcji językowych. Ta praktyka zwiększa świadomość nowych funkcji i sprawia, że są bardziej znane wszystkim deweloperom języka C#.
Ważne
Te wytyczne są używane przez firmę Microsoft do opracowywania przykładów i dokumentacji. Zostały one przyjęte z wytycznych dotyczących środowiska uruchomieniowego .NET, stylu kodowania w języku C# i kompilatora języka C# (roslyn). Wybraliśmy te wytyczne, ponieważ zostały przetestowane w ciągu kilku lat opracowywania rozwiązań typu open source. Pomogli członkom społeczności uczestniczyć w projektach środowiska uruchomieniowego i kompilatora. Są one przeznaczone do bycia przykładem typowych konwencji języka C#, a nie listy autorytatywnej (zobacz Wytyczne dotyczące projektowania struktury).
Cele nauczania i wdrażania są powodem, dla których konwencja kodowania dokumentów różni się od konwencji środowiska uruchomieniowego i kompilatora. Zarówno środowisko uruchomieniowe, jak i kompilator mają ścisłe metryki wydajności dla ścieżek gorących. Wiele innych aplikacji nie. Nasz cel nauczania nakazuje, że nie zabraniamy żadnej konstrukcji. Zamiast tego przykłady pokazują, kiedy należy używać konstrukcji. Aktualizujemy próbki bardziej agresywnie niż większość aplikacji produkcyjnych. Nasz cel wdrożenia nakazuje pokazanie kodu, który należy napisać dzisiaj, nawet jeśli kod napisany w zeszłym roku nie wymaga zmian.
W tym artykule wyjaśniono nasze wytyczne. Wytyczne ewoluowały wraz z upływem czasu i znajdziesz przykłady, które nie są zgodne z naszymi wytycznymi. Z zadowoleniem przyjmujemy żądania ściągnięcia, które zapewniają zgodność tych przykładów lub problemy, które zwracają uwagę na próbki, które należy zaktualizować. Nasze wytyczne to open source i mile widziane żądania ściągnięcia i problemy. Jeśli jednak twoje przesłanie zmieni te zalecenia, najpierw otwórz problem do dyskusji. Zachęcamy do korzystania z naszych wytycznych lub dostosowania ich do Twoich potrzeb.
Narzędzia i analizatory
Narzędzia mogą pomóc zespołowi w wymuszaniu konwencji. Możesz włączyć analizę kodu, aby wymusić preferowane reguły. Możesz również utworzyć konfigurację edytora, aby program Visual Studio automatycznie wymuszał wytyczne dotyczące stylu. Jako punkt wyjścia możesz skopiować plik repozytorium dotnet/docs, aby użyć naszego stylu.
Te narzędzia ułatwiają zespołowi przyjęcie preferowanych wytycznych. Program Visual Studio stosuje reguły we wszystkich .editorconfig
plikach w zakresie do formatowania kodu. Można użyć wielu konfiguracji, aby wymusić konwencje dla całej firmy, konwencje zespołu, a nawet szczegółowe konwencje projektu.
Analiza kodu generuje ostrzeżenia i diagnostykę, gdy włączone reguły zostaną naruszone. Skonfiguruj reguły, które chcesz zastosować do projektu. Następnie każda kompilacja ciągłej integracji powiadamia deweloperów, gdy naruszają one dowolne reguły.
Identyfikatory diagnostyczne
- Wybieranie odpowiednich identyfikatorów diagnostycznych podczas tworzenia własnych analizatorów
Wskazówki dotyczące języka
W poniższych sekcjach opisano praktyki, które zespół dokumentacji platformy .NET wykonuje w celu przygotowania przykładów i przykładów kodu. Ogólnie rzecz biorąc, postępuj zgodnie z następującymi rozwiązaniami:
- Korzystaj z nowoczesnych funkcji językowych i wersji języka C#, jeśli to możliwe.
- Unikaj przestarzałych lub przestarzałych konstrukcji językowych.
- Przechwytuj tylko wyjątki, które mogą być prawidłowo obsługiwane; unikaj przechwytywania wyjątków ogólnych.
- Użyj określonych typów wyjątków, aby zapewnić znaczące komunikaty o błędach.
- Użyj zapytań LINQ i metod do manipulowania kolekcją, aby zwiększyć czytelność kodu.
- Używanie programowania asynchronicznego z asynchronicznym i oczekiwaniem na operacje związane z operacjami we/wy.
- Zachowaj ostrożność w przypadku zakleszczeń i używaj Task.ConfigureAwait , jeśli jest to konieczne.
- Użyj słów kluczowych języka dla typów danych zamiast typów środowiska uruchomieniowego. Na przykład użyj polecenia
string
zamiast System.String, lubint
zamiast System.Int32. - Użyj
int
zamiast niepodpisanych typów. Korzystanie z programuint
jest powszechne w języku C#i jest łatwiejsze do interakcji z innymi bibliotekami w przypadku korzystania z programuint
. Wyjątki dotyczą dokumentacji specyficznej dla niepodpisanych typów danych. - Użyj
var
tylko wtedy, gdy czytelnik może wywnioskować typ z wyrażenia. Czytelnicy wyświetlają nasze przykłady na platformie docs. Nie mają one wskaźnika myszy ani wskazówek narzędzi, które wyświetlają typ zmiennych. - Napisz kod z jasnością i prostotą.
- Unikaj nadmiernie złożonej i splotowej logiki kodu.
Postępuj zgodnie z bardziej szczegółowymi wytycznymi.
Dane ciągu
Użyj interpolacji ciągów, aby połączyć krótkie ciągi, jak pokazano w poniższym kodzie.
string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
Aby dołączyć ciągi w pętlach, szczególnie podczas pracy z dużą ilością tekstu, użyj System.Text.StringBuilder obiektu.
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala"; var manyPhrases = new StringBuilder(); for (var i = 0; i < 10000; i++) { manyPhrases.Append(phrase); } //Console.WriteLine("tra" + manyPhrases);
Tablice
- Użyj zwięzłej składni podczas inicjowania tablic w wierszu deklaracji. W poniższym przykładzie nie można użyć funkcji
var
zamiaststring[]
.
string[] vowels1 = { "a", "e", "i", "o", "u" };
- Jeśli używasz jawnego wystąpienia, możesz użyć polecenia
var
.
var vowels2 = new string[] { "a", "e", "i", "o", "u" };
Delegaci
- Użyj poleceń
Func<>
iAction<>
zamiast definiować typy delegatów. W klasie zdefiniuj metodę delegata.
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;
- Wywołaj metodę przy użyciu podpisu zdefiniowanego przez delegata
Func<>
lubAction<>
.
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)}");
Jeśli tworzysz wystąpienia typu delegata, użyj zwięzłej składni. W klasie zdefiniuj typ delegata i metodę, która ma zgodny podpis.
public delegate void Del(string message); public static void DelMethod(string str) { Console.WriteLine("DelMethod argument: {0}", str); }
Utwórz wystąpienie typu delegata i wywołaj je. Poniższa deklaracja przedstawia skondensowaną składnię.
Del exampleDel2 = DelMethod; exampleDel2("Hey");
Poniższa deklaracja używa pełnej składni.
Del exampleDel1 = new Del(DelMethod); exampleDel1("Hey");
try-catch
instrukcje i using
w obsłudze wyjątków
W przypadku większości obsługi wyjątków użyj instrukcji try-catch.
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; } }
Uprość kod przy użyciu instrukcji języka C#. Jeśli masz instrukcję try-finally , w której jedynym kodem w
finally
bloku jest wywołanie Dispose metody, użyj instrukcjiusing
.W poniższym przykładzie instrukcja
try-finally
wywołujeDispose
tylko wfinally
bloku.Font bodyStyle = new Font("Arial", 10.0f); try { byte charset = bodyStyle.GdiCharSet; } finally { if (bodyStyle != null) { ((IDisposable)bodyStyle).Dispose(); } }
Tę samą czynność można wykonać za pomocą instrukcji
using
.using (Font arial = new Font("Arial", 10.0f)) { byte charset2 = arial.GdiCharSet; }
Użyj nowej
using
składni , która nie wymaga nawiasów klamrowych:using Font normalStyle = new Font("Arial", 10.0f); byte charset3 = normalStyle.GdiCharSet;
&&
operatory i ||
Użyj
&&
wartości zamiast&
i||
zamiast|
podczas przeprowadzania porównań, jak pokazano w poniższym przykładzie.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."); }
Jeśli dzielnika ma wartość 0, druga klauzula w instrukcji if
spowoduje błąd czasu wykonywania. Jednak skróty operatorów &&, gdy pierwsze wyrażenie jest fałszywe. Oznacza to, że nie oblicza drugiego wyrażenia. Operator & oceni oba, co spowoduje błąd czasu wykonywania, gdy divisor
ma wartość 0.
new
operator
Użyj jednej z zwięzłych form tworzenia wystąpienia obiektu, jak pokazano w poniższych deklaracjach.
var firstExample = new ExampleClass();
ExampleClass instance2 = new();
Powyższe deklaracje są równoważne następującej deklaracji.
ExampleClass secondExample = new ExampleClass();
Użyj inicjatorów obiektów, aby uprościć tworzenie obiektów, jak pokazano w poniższym przykładzie.
var thirdExample = new ExampleClass { Name = "Desktop", ID = 37414, Location = "Redmond", Age = 2.3 };
Poniższy przykład ustawia te same właściwości co w poprzednim przykładzie, ale nie używa inicjatorów.
var fourthExample = new ExampleClass(); fourthExample.Name = "Desktop"; fourthExample.ID = 37414; fourthExample.Location = "Redmond"; fourthExample.Age = 2.3;
Obsługa zdarzeń
- Użyj wyrażenia lambda, aby zdefiniować procedurę obsługi zdarzeń, której nie trzeba usuwać później:
public Form2()
{
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
Wyrażenie lambda skraca następującą tradycyjną definicję.
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
void Form1_Click(object? sender, EventArgs e)
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}
Statyczne elementy członkowskie
Wywołaj statyczne elementy członkowskie przy użyciu nazwy klasy ClassName.StaticMember. Dzięki temu kod jest bardziej czytelny, dzięki czemu dostęp statyczny jest przejrzysty. Nie należy kwalifikować statycznej składowej zdefiniowanej w klasie bazowej o nazwie klasy pochodnej. Podczas kompilowania tego kodu czytelność kodu jest myląca, a kod może ulec awarii w przyszłości, jeśli dodasz statyczny element członkowski o tej samej nazwie do klasy pochodnej.
zapytania LINQ
Użyj znaczących nazw zmiennych kwerendy. W poniższym przykładzie użyto
seattleCustomers
klientów, którzy znajdują się w Seattle.var seattleCustomers = from customer in customers where customer.City == "Seattle" select customer.Name;
Użyj aliasów, aby upewnić się, że nazwy właściwości typów anonimowych są poprawnie wielkich liter przy użyciu liter Pascal.
var localDistributors = from customer in customers join distributor in distributors on customer.City equals distributor.City select new { Customer = customer, Distributor = distributor };
Zmień nazwę właściwości, gdy nazwy właściwości w wyniku będą niejednoznaczne. Jeśli na przykład zapytanie zwraca nazwę klienta i identyfikator dystrybutora, zamiast pozostawiać je jako
Name
iID
w wyniku, zmień ich nazwę, aby wyjaśnić, żeName
jest to nazwa klienta iID
jest identyfikatorem dystrybutora.var localDistributors2 = from customer in customers join distributor in distributors on customer.City equals distributor.City select new { CustomerName = customer.Name, DistributorID = distributor.ID };
Użyj niejawnego pisania w deklaracji zmiennych zapytania i zmiennych zakresu. Te wskazówki dotyczące niejawnego wpisywania w zapytaniach LINQ zastępują ogólne reguły niejawnie typizowane zmiennych lokalnych. Zapytania LINQ często używają projekcji, które tworzą typy anonimowe. Inne wyrażenia zapytania tworzą wyniki z zagnieżdżonym typami ogólnymi. Niejawne zmienne typizowane są często bardziej czytelne.
var seattleCustomers = from customer in customers where customer.City == "Seattle" select customer.Name;
Wyrównaj klauzule zapytania do klauzuli
from
, jak pokazano w poprzednich przykładach.Użyj
where
klauzul przed innymi klauzulami zapytania, aby upewnić się, że późniejsze klauzule zapytania działają na zredukowanym, filtrowanym zestawie danych.var seattleCustomers2 = from customer in customers where customer.City == "Seattle" orderby customer.Name select customer;
Użyj wielu
from
klauzul zamiastjoin
klauzuli , aby uzyskać dostęp do kolekcji wewnętrznych. Na przykład kolekcjaStudent
obiektów może zawierać kolekcję wyników testów. Po wykonaniu następującego zapytania zwraca on każdy wynik powyżej 90, wraz z nazwiskiem ucznia, który otrzymał wynik.var scoreQuery = from student in students from score in student.Scores! where score > 90 select new { Last = student.LastName, score };
Niejawnie typizowane zmienne lokalne
Użyj niejawnego wpisywania zmiennych lokalnych, gdy typ zmiennej jest oczywisty z prawej strony przypisania.
var message = "This is clearly a string."; var currentTemperature = 27;
Nie używaj wartości var , gdy typ nie jest widoczny po prawej stronie przypisania. Nie zakładaj, że typ jest jasny z nazwy metody. Typ zmiennej jest uznawany za jasny, jeśli jest
new
to operator, jawne rzutowanie lub przypisanie do wartości literału.int numberOfIterations = Convert.ToInt32(Console.ReadLine()); int currentMaximum = ExampleClass.ResultSoFar();
Nie używaj nazw zmiennych do określania typu zmiennej. To może nie być poprawne. Zamiast tego użyj typu, aby określić typ, i użyj nazwy zmiennej, aby wskazać informacje semantyczne zmiennej. Poniższy przykład powinien być używany
string
dla typu i coś podobnegoiterations
do wskazania znaczenia informacji odczytanych z konsoli.var inputInt = Console.ReadLine(); Console.WriteLine(inputInt);
Unikaj używania
var
zamiast dynamicznego. Użyjdynamic
polecenia , jeśli chcesz wnioskować o typie czasu wykonywania. Aby uzyskać więcej informacji, zobacz Using type dynamic (Przewodnik programowania w języku C#).Użyj niejawnego pisania dla zmiennej pętli w
for
pętlach.W poniższym przykładzie użyto niejawnego pisania w instrukcji
for
.var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala"; var manyPhrases = new StringBuilder(); for (var i = 0; i < 10000; i++) { manyPhrases.Append(phrase); } //Console.WriteLine("tra" + manyPhrases);
Nie używaj niejawnego pisania, aby określić typ zmiennej pętli w
foreach
pętlach. W większości przypadków typ elementów w kolekcji nie jest natychmiast oczywisty. Nazwa kolekcji nie powinna polegać wyłącznie na wnioskowaniu typu jej elementów.W poniższym przykładzie użyto jawnego wpisywania w instrukcji
foreach
.foreach (char ch in laugh) { if (ch == 'h') { Console.Write("H"); } else { Console.Write(ch); } } Console.WriteLine();
użyj typu niejawnego dla sekwencji wyników w zapytaniach LINQ. W sekcji linQ wyjaśniono, że wiele zapytań LINQ powoduje anonimowe typy, w których należy używać typów niejawnych. Inne zapytania powodują zagnieżdżone typy ogólne, w których
var
jest bardziej czytelny.Uwaga
Należy zachować ostrożność, aby przypadkowo zmienić typ elementu iterowalnej kolekcji. Na przykład łatwo jest przełączyć się z System.Linq.IQueryable do System.Collections.IEnumerable w instrukcji
foreach
, która zmienia wykonywanie zapytania.
Niektóre z naszych przykładów wyjaśniają naturalny typ wyrażenia. Te przykłady muszą być używane var
, aby kompilator wybierał typ naturalny. Mimo że te przykłady są mniej oczywiste, użycie metody var
jest wymagane dla próbki. Tekst powinien wyjaśnić zachowanie.
Umieść dyrektywy using poza deklaracją przestrzeni nazw
using
Gdy dyrektywa znajduje się poza deklaracją przestrzeni nazw, zaimportowana przestrzeń nazw jest w pełni kwalifikowaną nazwą. W pełni kwalifikowana nazwa jest jaśniejsza. using
Gdy dyrektywa znajduje się wewnątrz przestrzeni nazw, może być względna względem tej przestrzeni nazw lub jej w pełni kwalifikowana nazwa.
using Azure;
namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
Zakładając, że istnieje odwołanie (bezpośrednie lub pośrednie) do WaitUntil klasy.
Teraz zmieńmy to nieco:
namespace CoolStuff.AwesomeFeature
{
using Azure;
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
I kompiluje się dzisiaj. A jutro. Jednak czasami w przyszłym tygodniu poprzedni kod (nietknięty) kończy się niepowodzeniem z dwoma błędami:
- 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
Jedna z zależności wprowadziła tę klasę w przestrzeni nazw, a następnie kończy się ciągiem .Azure
:
namespace CoolStuff.Azure
{
public class SecretsManagement
{
public string FetchFromKeyVault(string vaultId, string secretId) { return null; }
}
}
Dyrektywa umieszczona using
wewnątrz przestrzeni nazw jest wrażliwa na kontekst i komplikuje rozpoznawanie nazw. W tym przykładzie jest to pierwsza przestrzeń nazw, którą znajduje.
CoolStuff.AwesomeFeature.Azure
CoolStuff.Azure
Azure
Dodanie nowej przestrzeni nazw zgodnej CoolStuff.Azure
z przestrzeń nazw lub CoolStuff.AwesomeFeature.Azure
będzie zgodna z globalną Azure
przestrzenią nazw. Można go rozwiązać, dodając global::
modyfikator do deklaracji using
. Jednak łatwiej jest umieścić using
deklaracje poza przestrzenią nazw.
namespace CoolStuff.AwesomeFeature
{
using global::Azure;
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
Wskazówki dotyczące stylu
Ogólnie rzecz biorąc, użyj następującego formatu dla przykładów kodu:
- Użyj czterech spacji do wcięcia. Nie używaj kart.
- Spójne dopasowywanie kodu w celu zwiększenia czytelności.
- Ogranicz wiersze do 65 znaków, aby zwiększyć czytelność kodu w dokumentacji, zwłaszcza na ekranach mobilnych.
- Podziel długie instrukcje na wiele wierszy, aby zwiększyć przejrzystość.
- Użyj stylu "Allman" dla nawiasów klamrowych: otwórz i zamyka nawias klamrowy własnego nowego wiersza. Nawiasy klamrowe są wyrównane z bieżącym poziomem wcięcia.
- Podziały wierszy powinny występować przed operatorami binarnymi, jeśli to konieczne.
Styl komentarza
W celu uzyskania krótkich wyjaśnień użyj komentarzy jednowierszowych (
//
).Unikaj komentarzy wielowierszowych (
/* */
) w celu uzyskania dłuższych wyjaśnień.
Komentarze w przykładach kodu nie są zlokalizowane. Oznacza to, że wyjaśnienia osadzone w kodzie nie zostaną przetłumaczone. Dłuższy tekst objaśniający powinien zostać umieszczony w artykule towarzyszącym, aby można go było lokalizować.Do opisywania metod, klas, pól i wszystkich publicznych elementów członkowskich należy używać komentarzy XML.
Umieść komentarz w osobnym wierszu, a nie na końcu wiersza kodu.
Rozpocznij tekst komentarza z wielką literą.
Zakończ tekst komentarza kropką.
Wstaw jedną spację między ogranicznikiem komentarza (
//
) i tekstem komentarza, jak pokazano w poniższym przykładzie.// The following declaration creates a query. It does not run // the query.
Konwencje układu
Dobry układ używa formatowania, aby podkreślić strukturę kodu i ułatwić odczytywanie kodu. Przykłady i przykłady firmy Microsoft są zgodne z następującymi konwencjami:
Użyj domyślnych ustawień Edytora kodu (inteligentne wcięcia, wcięcia czteroznakowe, karty zapisane jako spacje). Aby uzyskać więcej informacji, zobacz Opcje, Edytor tekstu, C#, Formatowanie.
Napisz tylko jedną instrukcję na wiersz.
Napisz tylko jedną deklarację na wiersz.
Jeśli wiersze kontynuacji nie są automatycznie wcięte, wcięcie ich do jednego zatrzymania karty (cztery spacje).
Dodaj co najmniej jedną pustą linię między definicjami metod i definicjami właściwości.
Użyj nawiasów, aby tworzyć klauzule w wyrażeniu widocznym, jak pokazano w poniższym kodzie.
if ((startX > endX) && (startX > previousX)) { // Take appropriate action. }
Wyjątki są wtedy, gdy przykład objaśnia pierwszeństwo operatora lub wyrażenia.
Zabezpieczenia
Postępuj zgodnie z wytycznymi w temacie Wytyczne dotyczące bezpiecznego kodowania.