Convenções de codificação em C#

As convenções de codificação atendem às seguintes finalidades:

  • Criam uma aparência consistente para o código, para que os leitores possam se concentrar no conteúdo e não no layout.
  • Permitem que os leitores entendam o código com mais rapidez, fazendo suposições com base na experiência anterior.
  • Facilitam a cópia, a alteração e a manutenção do código.
  • Demonstram as práticas recomendadas do C#.

Importante

As diretrizes neste artigo são usadas pela Microsoft para desenvolver amostras e documentação. Elas foram adotadas das diretrizes de Estilo de Codificação do C# do .NET Runtime. Você pode usá-las ou adaptá-las às suas necessidades. Os principais objetivos são consistência e legibilidade no código-fonte do projeto, da equipe, da organização ou da empresa.

Convenções de nomenclatura

Há várias convenções de nomenclatura a serem consideradas ao gravar o código do C#.

Nos exemplos a seguir, qualquer uma das diretrizes relativas aos elementos marcados public também é aplicável ao trabalhar com os elementos protected e protected internal. Todos eles se destinam a ficar visíveis para chamadores externos.

Pascal Case

Use Pascal Case ("PascalCasing") ao nomear um class, record ou struct.

public class DataService
{
}
public record PhysicalAddress(
    string Street,
    string City,
    string StateOrProvince,
    string ZipCode);
public struct ValueCoordinate
{
}

Ao nomear um interface, use Pascal Case, além de aplicar ao nome um prefixo I. Isso indica claramente aos consumidores que é um interface.

public interface IWorkerQueue
{
}

Ao nomear membros de tipos public, como campos, propriedades, eventos, métodos e funções locais, use Pascal Case.

public class ExampleEvents
{
    // A public field, these should be used sparingly
    public bool IsValid;

    // An init-only property
    public IWorkerQueue WorkerQueue { get; init; }

    // An event
    public event Action EventProcessing;

    // Method
    public void StartEventProcessing()
    {
        // Local function
        static int CountQueueItems() => WorkerQueue.Count;
        // ...
    }
}

Ao gravar registros posicionais, use Pascal Case para parâmetros, pois são as propriedades públicas do registro.

public record PhysicalAddress(
    string Street,
    string City,
    string StateOrProvince,
    string ZipCode);

Para obter mais informações sobre registros posicionais, confira Sintaxe posicional para definição de propriedade.

Camel Case

Use Camel Case ("camelCasing") ao nomear os campos private ou internal e aplique a eles o prefixo _.

public class DataService
{
    private IWorkerQueue _workerQueue;
}

Dica

Ao editar o código do C# que segue essas convenções de nomenclatura em um IDE que permite a conclusão da instrução, digitar _ mostrará todos os membros no escopo do objeto.

Ao trabalhar com os campos static que são private ou internal, use o prefixo s_ e, para thread estático, use t_.

public class DataService
{
    private static IWorkerQueue s_workerQueue;

    [ThreadStatic]
    private static TimeSpan t_timeSpan;
}

Ao gravar os parâmetros de método, use Camel Case.

public T SomeMethod<T>(int someNumber, bool isValid)
{
}

Para obter mais informações sobre as convenções de nomenclatura do C#, confira Estilo de Codificação em C#.

Outras convenções de nomenclatura

  • Em exemplos que não incluem o uso de diretivas, use as qualificações do namespace. Se você souber que um namespace é importado por padrão em um projeto, não precisará qualificar totalmente os nomes desse namespace. Nomes qualificados podem ser interrompidos após um ponto (.) se forem muito longos para uma única linha, conforme mostrado no exemplo a seguir.

    var currentPerformanceCounterCategory = new System.Diagnostics.
        PerformanceCounterCategory();
    
  • Não é necessário alterar os nomes de objetos que foram criados usando as ferramentas de designer do Visual Studio para adequá-los a outras diretrizes.

Convenções de layout

Um bom layout usa formatação para enfatizar a estrutura do código e para facilitar a leitura de código. Exemplos e amostras 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.

  • Gravar apenas uma instrução por linha.

  • Gravar apenas uma declaração por linha.

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

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

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

    if ((val1 > val2) && (val1 > val3))
    {
        // Take appropriate action.
    }
    

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

Quando uma diretiva using está fora de uma declaração de namespace, esse namespace importado é seu nome totalmente qualificado. Assim fica mais claro. Quando a diretiva using está dentro do namespace, ela pode ser referente a esse namespace ou ao nome totalmente qualificado. Assim fica ambíguo.

using Azure;

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

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

Agora, vamos fazer uma pequena alteração:

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

E ela será compilada hoje. E amanhã. Porém, em algum momento da próxima semana, esse código (intocado) falhará 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 esta classe em um namespace e termina com .Azure:

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

Uma diretiva using colocada dentro de um namespace diferencia contexto e complica a resolução do nome. Neste exemplo, é o primeiro namespace que ela encontra.

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

Adicionar um novo namespace que corresponda a CoolStuff.Azure ou CoolStuff.AwesomeFeature.Azure seria combinado antes do namespace Azure global. Você poderia resolver isso adicionando o modificador global:: à declaração using. No entanto, é mais fácil colocar declarações using fora do namespace.

namespace CoolStuff.AwesomeFeature
{
    using global::Azure;
    
    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            …
        }
    }
}

Convenções de comentário

  • 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 final.

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

    // The following declaration creates a query. It does not run
    // the query.
    
  • Não crie blocos de asteriscos formatados em torno dos comentários.

  • Verifique se todos os membros públicos têm os comentários XML necessários, fornecendo as devidas descrições sobre o comportamento.

Diretrizes de linguagem

As seções a seguir descrevem práticas que a equipe de C# segue para preparar exemplos e amostras do código.

Tipos de dados de cadeia de caracteres

  • Use a interpolação de cadeia de caracteres para concatenar cadeias de caracteres curtas, como é 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 objeto StringBuilder.

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

Variáveis locais de tipo implícito

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

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

    int var3 = Convert.ToInt32(Console.ReadLine()); 
    int var4 = ExampleClass.ResultSoFar();
    
  • Não se baseie no nome da variável para especificar o tipo dela. Ele pode não estar correto. No exemplo a seguir, o nome da variável inputInt está equivocado. É uma cadeia de caracteres.

    var inputInt = Console.ReadLine();
    Console.WriteLine(inputInt);
    
  • Evite o uso de var em vez de dynamic. Use dynamic quando quiser uma inferência de tipo em tempo de execução. Para obter mais informações, confira Como usar o tipo dinâmico (Guia de Programação do C#).

  • Use a digitação implícita para determinar o tipo da variável em loop nos loops for.

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

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

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

    foreach (char ch in laugh)
    {
        if (ch == 'h')
            Console.Write("H");
        else
            Console.Write(ch);
    }
    Console.WriteLine();
    

    Observação

    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 instrução foreach, o que altera a execução de uma consulta.

Tipos de dados não assinados

Em geral, use int em vez de tipos sem assinatura. O uso de int é comum em todo o C#, e é mais fácil interagir com outras bibliotecas ao usar int.

Matrizes

Use a sintaxe concisa ao inicializar matrizes na linha da declaração. No exemplo a seguir, observe que você não pode usar var em vez de string[].

string[] vowels1 = { "a", "e", "i", "o", "u" };

Se você usar uma instanciação explícita, poderá usar var.

var vowels2 = new string[] { "a", "e", "i", "o", "u" };

Delegados

Use Func<> e Action<>, em vez de definir tipos delegados. Em uma classe, defina o método delegado.

public static Action<string> ActionExample1 = x => Console.WriteLine($"x is: {x}");

public static Action<string, string> ActionExample2 = (x, y) => 
    Console.WriteLine($"x is: {x}, y is {y}");

public static Func<string, int> FuncExample1 = x => Convert.ToInt32(x);

public static Func<int, int, int> FuncExample2 = (x, y) => x + y;

Chame o método usando a assinatura definida pelo delegado Func<> ou 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 você criar instâncias de um tipo delegado, use a sintaxe concisa. Em uma classe, defina o tipo 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 delegado e a chame. 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- Instruções catch e using no tratamento de exceções

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

    static string GetValueFromArray(string[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (System.IndexOutOfRangeException ex)
        {
            Console.WriteLine("Index is out of range: {0}", index);
            throw;
        }
    }
    
  • Simplifique o código usando a instrução using do #C. Se você tiver uma instrução try-finally na qual o único código do bloco finally é uma chamada para o método Dispose, use, em vez disso, uma instrução using.

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

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

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

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

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

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

Operadores && e ||

Para evitar exceções e melhorar o desempenho ignorando comparações desnecessárias, use &&, em vez de &, e ||, em vez de |, ao executar comparações, conforme 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 > 0))
{
    Console.WriteLine("Quotient: {0}", dividend / divisor);
}
else
{
    Console.WriteLine("Attempted division by 0 ends up here.");
}

Se o divisor for 0, a segunda cláusula na instrução if causará um erro em tempo de execução. Mas o operador && entra em curto-circuito quando a primeira expressão é falsa. Ou seja, ele não avalia a segunda expressão. O operador & avalia ambas, resultando em um erro em tempo de execução quando divisor é 0.

Operador new

  • Use uma das formas concisas de instanciação de objeto, conforme mostrado nas declarações a seguir. O segundo exemplo mostra a sintaxe disponível a partir do C# 9.

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

    As declarações anteriores são equivalentes à declaração a seguir.

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

    var instance3 = 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 instance4 = new ExampleClass();
    instance4.Name = "Desktop";
    instance4.ID = 37414;
    instance4.Location = "Redmond";
    instance4.Age = 2.3;
    

Manipulação de eventos

Se você estiver definindo um manipulador de eventos que não necessita ser removido posteriormente, use uma expressão lambda.

public Form2()
{
    this.Click += (s, e) =>
        {
            MessageBox.Show(
                ((MouseEventArgs)e).Location.ToString());
        };
}

A expressão lambda reduz a definição convencional a seguir.

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 de classe: ClassName.StaticMember. Essa prática torna o código mais legível, tornando o acesso estático limpo. 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 leitura do código fica incorreta e o código poderá ser danificado no futuro se você adicionar um membro estático com o mesmo nome da classe derivada.

Consultas LINQ

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

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

    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 propriedades no resultado forem ambíguos. Por exemplo, se a sua consulta retornar um nome de cliente e uma 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 é a identificação 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 };
    
  • Usa a digitação implícita na declaração de variáveis de consulta e de intervalo.

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

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

    var seattleCustomers2 = from customer in customers
                            where customer.City == "Seattle"
                            orderby customer.Name
                            select customer;
    
  • Use várias cláusulas from, em vez de uma cláusula join, para acessar as coleções internas. Por exemplo, cada coleção de objetos Student pode conter um conjunto de pontuações no teste. Quando a próxima consulta for executada, ela retorna cada pontuação que seja acima de 90, juntamente com o sobrenome do estudante 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 };
    

Segurança

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

Confira também