Поделиться через


Общие соглашения о коде C#

Соглашения по программированию необходимы для поддержания удобочитаемости кода, согласованности и совместной работы в команде разработчиков. Код, который соответствует отраслевым методикам и установленным рекомендациям, проще понимать, поддерживать и расширять. Большинство проектов применяют согласованный стиль с помощью соглашений о коде. И dotnet/docsdotnet/samples проекты не являются исключением. В этой серии статей вы узнаете о наших соглашениях по программированию и средствах, которые мы используем для их применения. Вы можете принять наши соглашения как есть или изменить их в соответствии с потребностями вашей команды.

Мы выбрали наши соглашения на основе следующих целей:

  1. Правильность. Наши примеры копируются и вставляются в приложения. Мы ожидаем, что поэтому нам нужно сделать код устойчивым и правильным, даже после нескольких изменений.
  2. Обучение. Цель наших примеров — научить все .NET и C#. По этой причине мы не размещаем ограничения на какие-либо языковые функции или API. Вместо этого эти примеры учат, когда функция является хорошим выбором.
  3. Согласованность. Читатели ожидают согласованный интерфейс в нашем содержимом. Все образцы должны соответствовать одному и тому же стилю.
  4. Внедрение: мы агрессивно обновляем наши примеры, чтобы использовать новые языковые функции. Эта практика повышает осведомленность о новых функциях и делает их более знакомыми для всех разработчиков C#.

Внимание

Эти рекомендации используются корпорацией Майкрософт для разработки примеров и документации. Они были приняты из правил среды выполнения .NET, стиля написания кода C# и компилятора C# (roslyn). Мы выбрали эти рекомендации, так как они были протестированы в течение нескольких лет разработки с открытым кодом. Они помогли членам сообщества участвовать в проектах среды выполнения и компилятора. Они предназначены для того, чтобы быть примером общих соглашений C#, а не авторитетным списком (см . рекомендации по проектированию платформы для этого).

Цели обучения и внедрения определяют, почему соглашение о кодировании документов отличается от соглашений среды выполнения и компилятора. Среда выполнения и компилятор имеют строгие метрики производительности для горячих путей. Многие другие приложения не делают. Наша цель обучения требует, чтобы мы не запрещали какую-либо конструкцию. Вместо этого примеры показывают, когда должны использоваться конструкции. Мы обновляем примеры более агрессивно, чем большинство рабочих приложений. Наша цель внедрения требует, чтобы мы отображали код, который вы должны написать сегодня, даже если код, написанный в прошлом году, не нуждается в изменениях.

В этой статье описываются наши рекомендации. Рекомендации развивались со временем, и вы найдете примеры, которые не соответствуют нашим рекомендациям. Мы приветствуем PR, которые приносят эти образцы в соответствие или проблемы, которые обращают внимание на примеры, которые мы должны обновить. Наши рекомендации — это Открытый исходный код, и мы приветствуем PR и проблемы. Однако если ваша отправка изменит эти рекомендации, сначала откройте вопрос для обсуждения. Вы можете использовать наши рекомендации или адаптировать их к вашим потребностям.

Средства и анализаторы

Средства могут помочь вашей команде обеспечить соблюдение соглашений. Вы можете включить анализ кода для применения предпочитаемого правила. Вы также можете создать конфигурацию редактора , чтобы Visual Studio автоматически применяла рекомендации по стилю. В качестве отправной точки можно скопировать файл репозитория dotnet/docs, чтобы использовать наш стиль.

Эти средства упрощают работу вашей команды по принятию предпочитаемых рекомендаций. Visual Studio применяет правила во всех .editorconfig файлах в области для форматирования кода. Можно использовать несколько конфигураций для применения корпоративных соглашений, соглашений о команде и даже детализированных соглашений о проекте.

Анализ кода создает предупреждения и диагностика при нарушении включенных правил. Вы настраиваете правила, примененные к проекту. Затем каждая сборка CI уведомляет разработчиков, когда они нарушают какие-либо правила.

Идентификаторы диагностики

Рекомендации по использованию языка

В следующих разделах описаны рекомендации, которые команда документации .NET следует для подготовки примеров кода и примеров. Как правило, следуйте приведенным ниже рекомендациям.

  • По возможности используйте современные языковые функции и версии C#.
  • Избегайте устаревших или устаревших конструкций языка.
  • Перехват исключений, которые можно правильно обрабатывать; избегайте перехвата универсальных исключений.
  • Используйте определенные типы исключений для предоставления значимых сообщений об ошибках.
  • Используйте запросы и методы LINQ для обработки коллекций, чтобы улучшить удобочитаемость кода.
  • Используйте асинхронное программирование с асинхронным и ожиданием операций с привязкой ввода-вывода.
  • Будьте осторожны с взаимоблокировками и используйте Task.ConfigureAwait при необходимости.
  • Используйте ключевые слова языка для типов данных вместо типов среды выполнения. Например, используйте string вместо System.Stringнее или int вместо System.Int32.
  • Используйте int вместо неподписанных типов. Использование int часто используется на C#, и при использовании intпроще взаимодействовать с другими библиотеками. Исключения предназначены для документации, конкретной для типов данных без знака.
  • Используйте var только в том случае, если средство чтения может вывести тип из выражения. Читатели просматривают наши примеры на платформе документов. У них нет подсказок по наведении указателя мыши или инструментов, отображающих тип переменных.
  • Напишите код с четкостью и простотой.
  • Избегайте чрезмерно сложной и запутанной логики кода.

Более конкретные рекомендации следуют.

Строковые данные

  • Для сцепления коротких строк рекомендуется использовать интерполяцию строк, как показано в следующем коде.

    string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
    
  • Для добавления строк в циклах, особенно при работе с текстами больших размеров, используйте объект System.Text.StringBuilder.

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

Массивы

  • При инициализации массивов в строке объявления рекомендуется использовать сокращенный синтаксис. В следующем примере вместо этого нельзя использовать varstring[].
string[] vowels1 = { "a", "e", "i", "o", "u" };
  • Если экземпляр создается явно, можно использовать var.
var vowels2 = new string[] { "a", "e", "i", "o", "u" };

Делегаты

  • Используйте Func<> и Action<> вместо определения типов делегатов. В классе определите метод делегата.
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;
  • Вызывайте метод с помощью сигнатуры, которую определяет делегат Func<> или 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)}");
  • Если вы создаете экземпляры типа делегата, используйте сокращенный синтаксис. В классе определите тип делегата и метод с соответствующей сигнатурой.

    public delegate void Del(string message);
    
    public static void DelMethod(string str)
    {
        Console.WriteLine("DelMethod argument: {0}", str);
    }
    
  • Создайте экземпляр типа делегата и вызовите его. В следующем объявлении используется сокращенный синтаксис.

    Del exampleDel2 = DelMethod;
    exampleDel2("Hey");
    
  • В следующем объявлении используется полный синтаксис.

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

Операторы try-catch и using при обработке исключений

  • Рекомендуется использовать оператор 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;
        }
    }
    
  • Использование оператора C# using упрощает код. При наличии оператора try-finally, код которого в блоке finally содержит только вызов метода Dispose, вместо него рекомендуется использовать оператор using.

    В следующем примере оператор try-finally вызывает Dispose только в блоке finally.

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

    То же самое можно сделать с помощью оператора using.

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

    Используйте новый using синтаксис , который не требует фигурных скобок:

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

Операторы && и ||

  • Используйте && вместо того, ||& чтобы вместо | выполнения сравнений, как показано в следующем примере.

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

Если делитель равен нулю, второе условие в операторе if вызовет ошибку времени выполнения. При этом оператор && сразу прекращает выполнение, если первое выражение ложно. Это означает, что второе выражение не будет вычисляться. Оператор & всегда вычисляет оба выражения, и если значение divisor равно 0, возникает ошибка времени выполнения.

Оператор new

  • Используйте одну из сокращенных форм создания экземпляров объектов, как показано в следующих объявлениях.

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

    Предыдущие объявления эквивалентны следующему объявлению.

    ExampleClass secondExample = new ExampleClass();
    
  • Используйте инициализаторы объектов, чтобы упростить создание объектов, как показано в следующем примере.

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

    В следующем примере задаются точно такие же свойства, как и в предыдущем, но без использования инициализаторов.

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

Обработка событий

  • Используйте лямбда-выражение для определения обработчика событий, который не требуется удалить позже:
public Form2()
{
    this.Click += (s, e) =>
        {
            MessageBox.Show(
                ((MouseEventArgs)e).Location.ToString());
        };
}

Лямбда-выражение сокращает приведенное ниже традиционное определение.

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

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

Статические участники

Для вызова статических членов следует использовать имя класса: ClassName.StaticMember. В этом случае код становится более удобочитаемым за счет четкого доступа. Не присваивайте статическому элементу, определенному в базовом классе, имя производного класса. Во время компиляции кода его читаемость нарушается, и если добавить статический член с тем же именем в производный классе, код может быть поврежден.

Запросы LINQ

  • Используйте значимые имена для переменных запроса. В следующем примере используется seattleCustomers для клиентов, находящихся в Сиэтле.

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • Рекомендуется использовать псевдонимы для уверенности в том, что в именах свойств анонимных типов верно используются прописные буквы при помощи правил использования прописных и строчных букв языка Pascal.

    var localDistributors =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { Customer = customer, Distributor = distributor };
    
  • Переименуйте свойства, если имена свойств в результате могут быть неоднозначными. Например, если запрос возвращает имя клиента и идентификатор распространителя, не оставляйте имена в виде Name и ID, а переименуйте их, чтобы было ясно, что Name — имя клиента и ID — идентификатор распространителя.

    var localDistributors2 =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { CustomerName = customer.Name, DistributorID = distributor.ID };
    
  • Рекомендуется использовать неявное типизирование в объявлении переменных запроса и переменных диапазона. Это руководство по неявным вводу в запросах LINQ переопределяет общие правила для неявно типизированных локальных переменных. Запросы LINQ часто используют проекции, которые создают анонимные типы. Другие выражения запросов создают результаты с вложенными универсальными типами. Неявные типизированные переменные часто доступны для чтения.

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • Выровняйте предложения запросов в соответствии с from предложением, как показано в предыдущих примерах.

  • Используйте where предложения перед другими предложениями запросов, чтобы гарантировать, что более поздние предложения запросов работают на сокращенном отфильтрованном наборе данных.

    var seattleCustomers2 = from customer in customers
                            where customer.City == "Seattle"
                            orderby customer.Name
                            select customer;
    
  • Используйте несколько from предложений вместо join предложения для доступа к внутренним коллекциям. Например, коллекция объектов Student может содержать коллекцию результатов тестирования. При выполнении следующего запроса возвращаются результаты, превышающие 90 балов, а также фамилии учащихся, получивших такие оценки.

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

Неявно типизированные локальные переменные

  • Используйте неявное ввод для локальных переменных, если тип переменной очевиден с правой стороны назначения.

    var message = "This is clearly a string.";
    var currentTemperature = 27;
    
  • Не используйте var , если тип не отображается в правой части назначения. Не полагайтесь на то, что имя метода предоставляет информацию о типе. Тип переменной считается понятным, если это new оператор, явный приведение или назначение литерального значения.

    int numberOfIterations = Convert.ToInt32(Console.ReadLine());
    int currentMaximum = ExampleClass.ResultSoFar();
    
  • Не используйте имена переменных для указания типа переменной. Имя может быть неверным. Вместо этого используйте тип, чтобы указать тип и использовать имя переменной, чтобы указать семантические сведения переменной. Следующий пример должен использоваться string для типа и что-то подобное iterations , чтобы указать значение информации, считываемой из консоли.

    var inputInt = Console.ReadLine();
    Console.WriteLine(inputInt);
    
  • Рекомендуется избегать использования var вместо dynamic. Используйте dynamic, если требуется определение типа во время выполнения. Дополнительные сведения см. в статье Использование типа dynamic (руководство по программированию на C#).

  • Используйте неявное ввод для переменной цикла в for циклах.

    В следующем примере неявное типизирование используется в операторе for.

    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    
  • Не используйте неявное ввод, чтобы определить тип переменной цикла в foreach циклах. В большинстве случаев тип элементов в коллекции не сразу очевиден. Имя коллекции не должно быть исключительно зависеть от вывода типа его элементов.

    В следующем примере явное типизирование используется в операторе foreach.

    foreach (char ch in laugh)
    {
        if (ch == 'h')
            Console.Write("H");
        else
            Console.Write(ch);
    }
    Console.WriteLine();
    
  • используйте неявный тип для последовательностей результатов в запросах LINQ. В разделе LINQ объясняется, что многие запросы LINQ приводят к анонимным типам, в которых должны использоваться неявные типы. Другие запросы приводят к вложенным универсальным типам, где var более удобочитаемым.

    Примечание.

    Следите за тем, чтобы случайно не изменить тип элемента итерируемой коллекции. Например, можно легко переключиться с System.Linq.IQueryable на System.Collections.IEnumerable в инструкции foreach, изменяющей выполнение запроса.

Некоторые из наших примеров объясняют естественный тип выражения. Эти примеры должны использовать var , чтобы компилятор выбрал естественный тип. Несмотря на то, что эти примеры менее очевидны, для примера требуется использование var . Текст должен объяснить поведение.

Размещение директив using за пределами объявления пространства имен

Если директива using находится за пределами объявления пространства имен, импортированное пространство имен является его полным именем. Полное имя является более понятным. Если директива using находится внутри пространства имен, она может быть либо относительно этого пространства имен, либо его полное имя.

using Azure;

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

Предположим, что класс имеет ссылку (прямую или непрямую).WaitUntil

Теперь давайте немного изменим его:

namespace CoolStuff.AwesomeFeature
{
    using Azure;

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

И он компилируется сегодня. И завтра. Но когда-нибудь на следующей неделе предыдущий код (нетронутый) завершается сбоем с двумя ошибками:

- 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

Один из зависимостей представил этот класс в пространстве имен, а затем заканчивается следующим образом .Azure:

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

Директива, размещенная using в пространстве имен, учитывает контекст и усложняет разрешение имен. В этом примере это первое пространство имен, которое он находит.

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

Добавление нового пространства имен, соответствующего либо CoolStuff.AzureCoolStuff.AwesomeFeature.Azure совпадающего с глобальным Azure пространством имен. Его можно устранить, добавив global:: модификатор в using объявление. Однако вместо этого проще размещать using объявления за пределами пространства имен.

namespace CoolStuff.AwesomeFeature
{
    using global::Azure;

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

Рекомендации по стилю

В общем случае используйте следующий формат для примеров кода:

  • Используйте четыре пробела для отступа. Не используйте вкладки.
  • Согласованное выравнивание кода для повышения удобства чтения.
  • Ограничить строки до 65 символов, чтобы повысить удобочитаемость кода в документах, особенно на мобильных экранах.
  • Прервать длинные операторы на несколько строк, чтобы повысить ясность.
  • Используйте стиль Allman для фигурных скобок: открытый и закрывающий фигурные скобки своей собственной новой линии. Фигурные скобки соответствуют текущему уровню отступа.
  • Разрывы строк должны возникать перед двоичными операторами при необходимости.

Стиль комментариев

  • Используйте одно строковый комментарий (//) для кратких объяснений.

  • Избегайте нескольких строковый комментарий (/* */) для более длинных объяснений. Комментарии не локализованы. Вместо этого более длинные объяснения находятся в статье-компаньоне.

  • Для описания методов, классов, полей и всех общедоступных элементов используются xml-комментарии.

  • Комментарий размещается на отдельной строке, а не в конце строки кода.

  • Текст комментария начинается с заглавной буквы.

  • Текст комментария завершается точкой.

  • Вставьте одно пространство между разделителем комментариев (//) и текстом комментария, как показано в следующем примере.

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

Соглашения о макете

Чтобы выделить структуру кода и облегчить чтение кода, в хорошем макете используется форматирование. Примеры и образцы корпорации Майкрософт соответствуют следующим соглашениям.

  • Использование параметров редактора кода по умолчанию (логичные отступы, отступы по четыре символа, использование пробелов для табуляции). Дополнительные сведения см. в разделе "Параметры", "Текстовый редактор", C#, "Форматирование".

  • Запись только одного оператора в строке.

  • Запись только одного объявления в строке.

  • Если строки продолжения не отступываются автоматически, отступите их на одну остановку табуляции (четыре пробела).

  • Добавление по крайней мере одной пустой строки между определениями методов и свойств.

  • Использование скобок для ясности предложений в выражениях, как показано в следующем коде.

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

Исключения возникают, когда в примере объясняется приоритет оператора или выражения.

Безопасность

Следуйте указаниям, изложенным в правилах написания безопасного кода.