Convenções comuns de código C#

As convenções de codificação são essenciais para manter a legibilidade, consistência e colaboração do código dentro de uma equipe de desenvolvimento. O código que segue as práticas do setor e as diretrizes estabelecidas é mais fácil de entender, manter e ampliar. A maioria dos projetos impõe um estilo consistente por meio de convenções de código. Os dotnet/docs projetos e dotnet/samples não são exceção. Nesta série de artigos, você aprende nossas convenções de codificação e as ferramentas que usamos para aplicá-las. Você pode tomar nossas convenções como estão, ou modificá-las para atender às necessidades da sua equipe.

Escolhemos as nossas convenções com base nos seguintes objetivos:

  1. Correção: Nossas amostras são copiadas e coladas em seus aplicativos. Esperamos isso, então precisamos criar um código resiliente e correto, mesmo depois de várias edições.
  2. Ensino: O objetivo de nossos exemplos é ensinar todo o .NET e C#. Por esse motivo, não colocamos restrições a nenhum recurso de idioma ou API. Em vez disso, esses exemplos ensinam quando um recurso é uma boa escolha.
  3. Consistência: os leitores esperam uma experiência consistente em todo o nosso conteúdo. Todas as amostras devem obedecer ao mesmo estilo.
  4. Adoção: atualizamos agressivamente nossos exemplos para usar novos recursos de idioma. Essa prática aumenta a conscientização sobre novos recursos e os torna mais familiares para todos os desenvolvedores de C#.

Importante

Essas diretrizes são usadas pela Microsoft para desenvolver exemplos e documentação. Eles foram adotados a partir das diretrizes .NET Runtime, C# Coding Style e compilador C# (roslyn). Escolhemos essas diretrizes porque foram testadas ao longo de vários anos de desenvolvimento Open Source. Eles ajudaram os membros da comunidade a participar dos projetos de tempo de execução e compiladores. Eles devem ser um exemplo de convenções comuns em C#, e não uma lista autorizada (consulte Framework Design Guidelines para isso).

Os objetivos de ensino e adoção são o motivo pelo qual a convenção de codificação docs difere das convenções de tempo de execução e compilador. Tanto o tempo de execução quanto o compilador têm métricas de desempenho rígidas para caminhos quentes. Muitas outras aplicações não. Nosso objetivo de ensino determina que não proíbamos nenhuma construção. Em vez disso, os exemplos mostram quando as construções devem ser usadas. Atualizamos as amostras de forma mais agressiva do que a maioria dos aplicativos de produção. Nossa meta de adoção exige que mostremos o código que você deve escrever hoje, mesmo quando o código escrito no ano passado não precisa de alterações.

Este artigo explica nossas diretrizes. As diretrizes evoluíram ao longo do tempo, e você encontrará exemplos que não seguem nossas diretrizes. Congratulamo-nos com PRs que coloquem essas amostras em conformidade ou questões que chamem a nossa atenção para amostras que devemos atualizar. As nossas diretrizes são Open Source e damos as boas-vindas a RPs e questões. No entanto, se o seu envio alterar essas recomendações, abra um problema para discussão primeiro. Você pode usar nossas diretrizes ou adaptá-las às suas necessidades.

Ferramentas e analisadores

As ferramentas podem ajudar a sua equipa a aplicar as suas convenções. Você pode habilitar a análise de código para impor as regras de sua preferência. Você também pode criar um editorconfig para que o Visual Studio imponha automaticamente suas diretrizes de estilo. Como ponto de partida, você pode copiar o arquivo do repositório dotnet/docs para usar nosso estilo.

Essas ferramentas tornam mais fácil para sua equipe adotar suas diretrizes preferidas. O Visual Studio aplica as regras em todos os .editorconfig arquivos no escopo para formatar seu código. Você pode usar várias configurações para impor convenções corporativas, convenções de equipe e até convenções de projeto granulares.

A análise de código produz avisos e diagnósticos quando as regras habilitadas são violadas. Você configura as regras que deseja aplicar ao seu projeto. Em seguida, cada compilação de CI notifica os desenvolvedores quando eles violam qualquer uma das regras.

IDs de diagnóstico

Orientações linguísticas

As seções a seguir descrevem as práticas que a equipe de documentos do .NET segue para preparar exemplos de código e exemplos. Em geral, siga estas práticas:

  • Utilize recursos de linguagem moderna e versões em C# sempre que possível.
  • Evite construções de linguagem obsoletas ou desatualizadas.
  • Apanhe apenas exceções que possam ser devidamente tratadas; evite apanhar exceções genéricas.
  • Use tipos de exceção específicos para fornecer mensagens de erro significativas.
  • Use consultas LINQ e métodos para manipulação de coleção para melhorar a legibilidade do código.
  • Use programação assíncrona com assíncrono e aguarde operações vinculadas a E/S.
  • Tenha cuidado com impasses e use Task.ConfigureAwait quando apropriado.
  • Use as palavras-chave de idioma para tipos de dados em vez dos tipos de tempo de execução. Por exemplo, use string em vez de System.String, ou int em vez de System.Int32.
  • Use int em vez de tipos não assinados. O uso de int é comum em todo o C#, e é mais fácil interagir com outras bibliotecas quando você usa into . As exceções são para documentação específica para tipos de dados não assinados.
  • Use var somente quando um leitor puder inferir o tipo a partir da expressão. Os leitores visualizam as nossas amostras na plataforma de documentos. Eles não têm foco ou dicas de ferramentas que exibem o tipo de variáveis.
  • Escreva código com clareza e simplicidade em mente.
  • Evite lógicas de código excessivamente complexas e complicadas.

Seguem-se orientações mais específicas.

Dados de cadeia de caracteres

  • Use a interpolação de cadeia de caracteres para concatenar cadeias curtas, conforme mostrado no código a seguir.

    string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
    
  • Para acrescentar cadeias de caracteres em loops, especialmente quando você estiver trabalhando com grandes quantidades de texto, use um System.Text.StringBuilder objeto.

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

Matrizes

  • Use a sintaxe concisa ao inicializar matrizes na linha de declaração. No exemplo a seguir, você não pode usar var em vez de string[].
string[] vowels1 = { "a", "e", "i", "o", "u" };
  • Se você usar instanciação explícita, poderá usar varo .
var vowels2 = new string[] { "a", "e", "i", "o", "u" };

Delegados

  • Use Func<> e Action<> em vez de definir tipos de delegados. Em uma classe, defina o método delegado.
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;
  • Chame o método usando a assinatura definida pelo Func<> ou Action<> delegado.
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 você criar instâncias de um tipo delegado, use a sintaxe concisa. Em uma classe, defina o tipo de delegado e um método que tenha uma assinatura correspondente.

    public delegate void Del(string message);
    
    public static void DelMethod(string str)
    {
        Console.WriteLine("DelMethod argument: {0}", str);
    }
    
  • Crie uma instância do tipo de delegado e chame-a. A declaração a seguir mostra a sintaxe condensada.

    Del exampleDel2 = DelMethod;
    exampleDel2("Hey");
    
  • A declaração a seguir usa a sintaxe completa.

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

try-catch e using declarações no tratamento de exceções

  • Use uma instrução try-catch para a maioria do tratamento de exceções.

    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;
        }
    }
    
  • Simplifique seu código usando a instrução using C#. Se você tiver uma instrução try-finally na qual o finally único código no bloco é uma chamada para o Dispose método, use uma using instrução em vez disso.

    No exemplo a seguir, a try-finally instrução só chama Dispose no finally bloco .

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

    Você pode fazer a mesma coisa com uma using declaração.

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

    Use a nova using sintaxe que não requer chaves:

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

&& e || operadores

  • Use && em vez de & e || em vez de | quando você executa comparações, como mostrado no exemplo a seguir.

    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 o divisor for 0, a segunda cláusula na if instrução causaria um erro em tempo de execução. Mas o operador && entra em curto-circuito quando a primeira expressão é falsa. Ou seja, não avalia a segunda expressão. O operador & avaliaria ambos, resultando em um erro em tempo de execução quando divisor é 0.

new Operador

  • Use uma das formas concisas de instanciação de objeto, conforme mostrado nas declarações a seguir.

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

    As declarações anteriores equivalem à declaração seguinte.

    ExampleClass secondExample = new ExampleClass();
    
  • Use inicializadores de objeto para simplificar a criação de objetos, conforme mostrado no exemplo a seguir.

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

    O exemplo a seguir define as mesmas propriedades do exemplo anterior, mas não usa inicializadores.

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

Manipulação de eventos

  • Use uma expressão lambda para definir um manipulador de eventos que você não precisa remover posteriormente:
public Form2()
{
    this.Click += (s, e) =>
        {
            MessageBox.Show(
                ((MouseEventArgs)e).Location.ToString());
        };
}

A expressão lambda encurta a seguinte definição tradicional.

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

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

Membros estáticos

Chame membros estáticos usando o nome da classe: ClassName.StaticMember. Essa prática torna o código mais legível, tornando o acesso estático claro. Não qualifique um membro estático definido em uma classe base com o nome de uma classe derivada. Enquanto esse código é compilado, a legibilidade do código é enganosa, e o código pode quebrar no futuro se você adicionar um membro estático com o mesmo nome à classe derivada.

Consultas LINQ

  • Use nomes significativos para variáveis de consulta. O exemplo a seguir usa seattleCustomers para clientes que estão localizados em Seattle.

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • Use aliases para certificar-se de que os nomes de propriedade de tipos anônimos estão corretamente em maiúsculas, usando a caixa Pascal.

    var localDistributors =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { Customer = customer, Distributor = distributor };
    
  • Renomeie propriedades quando os nomes de propriedade no resultado forem ambíguos. Por exemplo, se sua consulta retornar um nome de cliente e um ID de distribuidor, em vez de deixá-los como Name e ID no resultado, renomeie-os para esclarecer que Name é o nome de um cliente e ID é o ID de um distribuidor.

    var localDistributors2 =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { CustomerName = customer.Name, DistributorID = distributor.ID };
    
  • Use a digitação implícita na declaração de variáveis de consulta e variáveis de intervalo. Esta orientação sobre digitação implícita em consultas LINQ substitui as regras gerais para variáveis locais digitadas implicitamente. As consultas LINQ geralmente usam projeções que criam tipos anônimos. Outras expressões de consulta criam resultados com tipos genéricos aninhados. As variáveis digitadas implícitas são geralmente mais legíveis.

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • Alinhe as cláusulas de consulta sob a from cláusula, conforme mostrado nos exemplos anteriores.

  • Use where cláusulas antes de outras cláusulas de consulta para garantir que cláusulas de consulta posteriores operem no conjunto reduzido e filtrado de dados.

    var seattleCustomers2 = from customer in customers
                            where customer.City == "Seattle"
                            orderby customer.Name
                            select customer;
    
  • Use várias from cláusulas em vez de uma join cláusula para acessar coleções internas. Por exemplo, uma coleção de Student objetos pode conter uma coleção de pontuações de teste. Quando a consulta a seguir é executada, ela retorna cada pontuação acima de 90, juntamente com o sobrenome do aluno que recebeu a pontuação.

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

Variáveis locais escritas implicitamente

  • Use a digitação implícita para variáveis locais quando o tipo da variável for óbvio do lado direito da atribuição.

    var message = "This is clearly a string.";
    var currentTemperature = 27;
    
  • Não use var quando o tipo não estiver aparente do lado direito da atribuição. Não assuma que o tipo é claro a partir de um nome de método. Um tipo de variável é considerado claro se for um new operador, uma conversão explícita ou atribuição a um valor literal.

    int numberOfIterations = Convert.ToInt32(Console.ReadLine());
    int currentMaximum = ExampleClass.ResultSoFar();
    
  • Não use nomes de variáveis para especificar o tipo da variável. Pode não estar correto. Em vez disso, use o tipo para especificar o tipo e use o nome da variável para indicar as informações semânticas da variável. O exemplo a seguir deve ser usado string para o tipo e algo como iterations indicar o significado das informações lidas do console.

    var inputInt = Console.ReadLine();
    Console.WriteLine(inputInt);
    
  • Evite o uso de var no lugar de dinâmica. Use dynamic quando quiser inferência de tipo em tempo de execução. Para obter mais informações, consulte Usando o tipo dinâmico (Guia de Programação em C#).

  • Use a digitação implícita para a variável de loop em for loops.

    O exemplo a seguir usa a digitação implícita em uma for instrução.

    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    
  • Não use digitação implícita para determinar o tipo da variável de loop em foreach loops. Na maioria dos casos, o tipo de elementos na coleção não é imediatamente óbvio. O nome da coleção não deve ser usado apenas para inferir o tipo de seus elementos.

    O exemplo a seguir usa a digitação explícita em uma foreach instrução.

    foreach (char ch in laugh)
    {
        if (ch == 'h')
        {
            Console.Write("H");
        }
        else
        {
            Console.Write(ch);
        }
    }
    Console.WriteLine();
    
  • use o tipo implícito para as sequências de resultados em consultas LINQ. A seção sobre LINQ explica que muitas consultas LINQ resultam em tipos anônimos onde tipos implícitos devem ser usados. Outras consultas resultam em tipos genéricos aninhados onde var é mais legível.

    Nota

    Tenha cuidado para não alterar acidentalmente um tipo de elemento da coleção iterável. Por exemplo, é fácil alternar de System.Linq.IQueryable para System.Collections.IEnumerable em uma foreach instrução, o que altera a execução de uma consulta.

Algumas das nossas amostras explicam o tipo natural de uma expressão. Essas amostras devem ser usadas var para que o compilador escolha o tipo natural. Embora esses exemplos sejam menos óbvios, o uso de var é necessário para a amostra. O texto deve explicar o comportamento.

Coloque as diretivas using fora da declaração de namespace

Quando uma using diretiva está fora de uma declaração de namespace, esse namespace importado é seu nome totalmente qualificado. O nome totalmente qualificado é mais claro. Quando a using diretiva está dentro do namespace, ela pode ser relativa a esse namespace ou seu nome totalmente qualificado.

using Azure;

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

Supondo que haja uma referência (direta ou indireta) à WaitUntil classe.

Agora, vamos mudá-lo um pouco:

namespace CoolStuff.AwesomeFeature
{
    using Azure;

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

E compila hoje. E amanhã. Mas então, em algum momento da próxima semana, o código anterior (intocado) falha com dois erros:

- 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

Uma das dependências introduziu essa classe em um namespace e termina com .Azure:

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

Uma using diretiva colocada dentro de um namespace é sensível ao contexto e complica a resolução de nomes. Neste exemplo, é o primeiro namespace encontrado.

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

Adicionar um novo namespace que corresponda CoolStuff.Azure ou CoolStuff.AwesomeFeature.Azure corresponda antes do namespace global Azure . Você pode resolvê-lo adicionando o global:: modificador à using declaração. No entanto, é mais fácil colocar using declarações fora do namespace.

namespace CoolStuff.AwesomeFeature
{
    using global::Azure;

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

Diretrizes de estilo

Em geral, use o seguinte formato para exemplos de código:

  • Use quatro espaços para recuo. Não utilize separadores.
  • Alinhe o código consistentemente para melhorar a legibilidade.
  • Limite as linhas a 65 caracteres para melhorar a legibilidade do código em documentos, especialmente em telas de dispositivos móveis.
  • Divida instruções longas em várias linhas para melhorar a clareza.
  • Use o estilo "Allman" para chaves: abra e feche sua própria nova linha. As chaves alinham-se com o nível de recuo atual.
  • As quebras de linha devem ocorrer antes dos operadores binários, se necessário.

Estilo do comentário

  • Use comentários de linha única (//) para breves explicações.

  • Evite comentários com várias linhas (/* */) para explicações mais longas.
    Os comentários nos exemplos de código não estão localizados. Isso significa que as explicações incorporadas no código não serão traduzidas. Texto explicativo mais longo deve ser colocado no artigo complementar, para que possa ser localizado.

  • Para descrever métodos, classes, campos e todos os membros públicos usam comentários XML.

  • Coloque o comentário em uma linha separada, não no final de uma linha de código.

  • Comece o texto do comentário com uma letra maiúscula.

  • Termine o texto do comentário com um ponto.

  • Insira um espaço entre o delimitador de comentários (//) e o texto do comentário, conforme mostrado no exemplo a seguir.

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

Convenções de layout

Um bom layout usa a formatação para enfatizar a estrutura do seu código e para tornar o código mais fácil de ler. Os exemplos e exemplos da Microsoft estão em conformidade com as seguintes convenções:

  • Use as configurações padrão do Editor de Códigos (recuo inteligente, recuos de quatro caracteres, guias salvas como espaços). Para obter mais informações, consulte Opções, Editor de texto, C#, Formatação.

  • Escreva apenas uma instrução por linha.

  • Escreva apenas uma declaração por linha.

  • Se as linhas de continuação não forem recuadas automaticamente, recue-as uma parada de tabulação (quatro espaços).

  • Adicione pelo menos uma linha em branco entre definições de método e definições de propriedade.

  • Use parênteses para tornar as cláusulas em uma expressão aparente, conforme mostrado no código a seguir.

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

As exceções são quando o exemplo explica a precedência do operador ou da expressão.

Segurança

Siga as diretrizes em Diretrizes de codificação segura.