Amigo do Dev
C#
18 min de leitura

Boas Práticas de Programação em C# — Guia Completo 2026

Aprenda as melhores práticas de programação em C# para escrever código limpo, performático e manutenível. Convenções de nomenclatura, SOLID, async/await, LINQ, tratamento de erros e muito mais.

LinkedIn

1. Introdução

C# é uma das linguagens mais utilizadas no mundo, presente em aplicações web, desktop, mobile, jogos e microserviços. Com a evolução do .NET 8+ e as features modernas do C# 12, escrever código limpo e performático nunca foi tão importante.

Este guia reúne as melhores práticas de programação em C# para 2026 — desde convenções básicas de nomenclatura até padrões avançados de arquitetura. Seja você um desenvolvedor iniciante ou experiente, estas práticas vão elevar a qualidade do seu código.

2. Convenções de Nomenclatura

A nomenclatura correta é a base de um código legível. O C# segue convenções bem definidas pela Microsoft:

PascalCase

Use para classes, métodos, propriedades, eventos, enums e namespaces:

// ✅ Correto
public class UserService
{
    public string FirstName { get; set; }
    public async Task<User> GetUserByIdAsync(int userId) { ... }
}

// ❌ Errado
public class userService
{
    public string first_name { get; set; }
    public async Task<User> getUserById(int user_id) { ... }
}

camelCase

Use para parâmetros de método, variáveis locais e campos privados (com prefixo _):

public class OrderService
{
    private readonly IOrderRepository _orderRepository; // campo privado
    private readonly ILogger<OrderService> _logger;

    public async Task ProcessOrderAsync(int orderId, string customerName)
    {
        var order = await _orderRepository.GetByIdAsync(orderId);
        var totalAmount = CalculateTotal(order);
    }
}

Interfaces e Prefixo I

// ✅ Sempre prefixe interfaces com "I"
public interface IUserRepository { }
public interface IEmailService { }
public interface IPaymentGateway { }

// ❌ Nunca faça
public interface UserRepository { }
public interface EmailServiceInterface { } // redundante

Constantes e Enums

// Constantes em PascalCase
public const int MaxRetryAttempts = 3;
public const string DefaultConnectionString = "Server=...";

// Enums em PascalCase (singular)
public enum OrderStatus
{
    Pending,
    Processing,
    Completed,
    Cancelled
}

// ❌ Evite
public const int MAX_RETRY_ATTEMPTS = 3; // estilo C/Java, não C#
public enum OrderStatuses { } // não use plural

3. Princípios SOLID

Os princípios SOLID são fundamentais para escrever código C# extensível e testável:

S — Single Responsibility Principle (SRP)

Cada classe deve ter apenas uma razão para mudar:

// ❌ Errado: classe faz tudo
public class UserManager
{
    public void CreateUser(User user) { ... }
    public void SendWelcomeEmail(User user) { ... }
    public void GenerateReport(User user) { ... }
    public void ValidatePassword(string password) { ... }
}

// ✅ Correto: responsabilidades separadas
public class UserService
{
    public async Task<User> CreateUserAsync(CreateUserDto dto) { ... }
}

public class EmailService
{
    public async Task SendWelcomeEmailAsync(User user) { ... }
}

public class ReportService
{
    public async Task<byte[]> GenerateUserReportAsync(int userId) { ... }
}

public class PasswordValidator
{
    public ValidationResult Validate(string password) { ... }
}

O — Open/Closed Principle (OCP)

Classes devem ser abertas para extensão, fechadas para modificação:

// ✅ Extensível sem modificar código existente
public interface IPaymentProcessor
{
    Task<PaymentResult> ProcessAsync(Payment payment);
}

public class CreditCardProcessor : IPaymentProcessor
{
    public async Task<PaymentResult> ProcessAsync(Payment payment) { ... }
}

public class PixProcessor : IPaymentProcessor
{
    public async Task<PaymentResult> ProcessAsync(Payment payment) { ... }
}

public class BoletoProcessor : IPaymentProcessor
{
    public async Task<PaymentResult> ProcessAsync(Payment payment) { ... }
}

// Para adicionar um novo método de pagamento, basta criar uma nova classe
// sem alterar as existentes!

D — Dependency Inversion Principle (DIP)

Dependa de abstrações, não de implementações concretas:

// ✅ Correto: injeção de dependência via interface
public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly IEmailService _emailService;
    private readonly ILogger<OrderService> _logger;

    public OrderService(
        IOrderRepository repository,
        IEmailService emailService,
        ILogger<OrderService> logger)
    {
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }
}

// Registro no DI Container (.NET 8+)
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.AddScoped<OrderService>();

4. Async/Await — Programação Assíncrona

A programação assíncrona é essencial no C# moderno. Siga estas regras:

Regras de Ouro

// ✅ SEMPRE use "Async" no nome de métodos assíncronos
public async Task<User> GetUserByIdAsync(int id) { ... }
public async Task SendEmailAsync(string to, string body) { ... }

// ✅ Use CancellationToken para operações canceláveis
public async Task<List<Product>> SearchProductsAsync(
    string query, 
    CancellationToken cancellationToken = default)
{
    return await _dbContext.Products
        .Where(p => p.Name.Contains(query))
        .ToListAsync(cancellationToken);
}

// ❌ NUNCA use .Result ou .Wait() — causa deadlock!
var user = GetUserByIdAsync(1).Result; // DEADLOCK!
GetUserByIdAsync(1).Wait();            // DEADLOCK!

// ❌ NUNCA use async void (exceto em event handlers)
public async void ProcessOrder() { } // impossível capturar exceções!

// ✅ Sempre use async Task
public async Task ProcessOrderAsync() { }

Task.WhenAll para operações paralelas

// ✅ Executa em paralelo — muito mais rápido!
public async Task<DashboardData> GetDashboardAsync(int userId)
{
    var userTask = _userService.GetByIdAsync(userId);
    var ordersTask = _orderService.GetRecentAsync(userId);
    var notificationsTask = _notificationService.GetUnreadAsync(userId);

    await Task.WhenAll(userTask, ordersTask, notificationsTask);

    return new DashboardData
    {
        User = userTask.Result,
        RecentOrders = ordersTask.Result,
        Notifications = notificationsTask.Result
    };
}

// ❌ Sequencial — 3x mais lento
var user = await _userService.GetByIdAsync(userId);
var orders = await _orderService.GetRecentAsync(userId);
var notifications = await _notificationService.GetUnreadAsync(userId);

5. LINQ — Boas Práticas

LINQ é uma das features mais poderosas do C#. Use com sabedoria:

// ✅ Prefira a sintaxe de método (method syntax)
var activeUsers = users
    .Where(u => u.IsActive && u.Age >= 18)
    .OrderBy(u => u.Name)
    .Select(u => new UserDto(u.Id, u.Name, u.Email))
    .ToList();

// ✅ Use Any() em vez de Count() > 0 para verificar existência
if (orders.Any(o => o.Status == OrderStatus.Pending))
{
    // existe pelo menos um pedido pendente
}

// ❌ Ineficiente — percorre toda a coleção
if (orders.Count(o => o.Status == OrderStatus.Pending) > 0) { }

// ✅ Use FirstOrDefault com predicado
var admin = users.FirstOrDefault(u => u.Role == "Admin");

// ✅ Use pattern matching com null check
if (users.FirstOrDefault(u => u.Id == id) is { } user)
{
    Console.WriteLine(user.Name);
}

6. Tratamento de Erros e Exceções

Um bom tratamento de erros diferencia código amador de código profissional:

// ✅ Use exceções específicas, nunca genéricas
public async Task<User> GetUserAsync(int id)
{
    var user = await _repository.FindByIdAsync(id);
    
    return user ?? throw new NotFoundException(
        $"Usuário com ID {id} não foi encontrado.");
}

// ✅ Crie exceções customizadas com significado
public class NotFoundException : Exception
{
    public NotFoundException(string message) : base(message) { }
}

public class BusinessRuleException : Exception
{
    public string ErrorCode { get; }
    
    public BusinessRuleException(string errorCode, string message) 
        : base(message)
    {
        ErrorCode = errorCode;
    }
}

// ✅ Use Result Pattern para operações que podem falhar
public record Result<T>
{
    public bool IsSuccess { get; init; }
    public T? Value { get; init; }
    public string? Error { get; init; }

    public static Result<T> Success(T value) => 
        new() { IsSuccess = true, Value = value };
    
    public static Result<T> Failure(string error) => 
        new() { IsSuccess = false, Error = error };
}

// Uso:
public async Task<Result<Order>> CreateOrderAsync(CreateOrderDto dto)
{
    if (dto.Items.Count == 0)
        return Result<Order>.Failure("Pedido deve ter pelo menos um item.");

    var order = new Order(dto);
    await _repository.AddAsync(order);
    
    return Result<Order>.Success(order);
}

7. Records, Pattern Matching e Features Modernas

Aproveite os recursos modernos do C# 12 para escrever código mais expressivo:

Records para DTOs e Value Objects

// ✅ Records são perfeitos para DTOs — imutáveis e com igualdade por valor
public record UserDto(int Id, string Name, string Email);

public record CreateOrderCommand(
    int CustomerId,
    List<OrderItemDto> Items,
    string? CouponCode = null
);

public record Address(
    string Street, 
    string City, 
    string State, 
    string ZipCode
);

// Uso
var user = new UserDto(1, "André", "andre@email.com");
var updated = user with { Name = "André Neiva" }; // cria cópia com alteração

Pattern Matching Avançado

// ✅ Switch expressions com pattern matching
public decimal CalculateDiscount(Customer customer) => customer switch
{
    { Type: CustomerType.Premium, YearsActive: > 5 } => 0.25m,
    { Type: CustomerType.Premium } => 0.15m,
    { Type: CustomerType.Regular, OrderCount: > 100 } => 0.10m,
    { Type: CustomerType.Regular } => 0.05m,
    _ => 0m
};

// ✅ Pattern matching com tipos
public string FormatDocument(object document) => document switch
{
    Invoice { IsPaid: true } inv => $"Fatura {inv.Number} — Paga",
    Invoice inv => $"Fatura {inv.Number} — Pendente: {inv.Amount:C}",
    Receipt r => $"Recibo {r.Number} — {r.Date:dd/MM/yyyy}",
    null => throw new ArgumentNullException(nameof(document)),
    _ => throw new ArgumentException("Tipo de documento desconhecido")
};

8. Entity Framework Core — Boas Práticas

O EF Core é o ORM mais usado no .NET. Evite os erros mais comuns:

// ✅ Use AsNoTracking para consultas read-only (muito mais rápido!)
public async Task<List<ProductDto>> GetProductsAsync()
{
    return await _context.Products
        .AsNoTracking()
        .Where(p => p.IsActive)
        .Select(p => new ProductDto(p.Id, p.Name, p.Price))
        .ToListAsync();
}

// ✅ Use Include seletivo — carregue apenas o que precisa
var orders = await _context.Orders
    .AsNoTracking()
    .Include(o => o.Customer)
    .Include(o => o.Items)
        .ThenInclude(i => i.Product)
    .Where(o => o.Date >= startDate)
    .ToListAsync();

// ❌ NUNCA carregue tudo sem filtro
var allProducts = await _context.Products.ToListAsync(); // carrega TUDO na memória

// ✅ Use paginação
public async Task<PagedResult<Product>> GetPagedAsync(int page, int pageSize)
{
    var query = _context.Products.AsNoTracking().Where(p => p.IsActive);
    
    var total = await query.CountAsync();
    var items = await query
        .OrderBy(p => p.Name)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();
    
    return new PagedResult<Product>(items, total, page, pageSize);
}

// ✅ Use transações para operações que devem ser atômicas
public async Task TransferFundsAsync(int fromId, int toId, decimal amount)
{
    await using var transaction = await _context.Database.BeginTransactionAsync();
    try
    {
        var fromAccount = await _context.Accounts.FindAsync(fromId);
        var toAccount = await _context.Accounts.FindAsync(toId);

        fromAccount!.Balance -= amount;
        toAccount!.Balance += amount;

        await _context.SaveChangesAsync();
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

9. Segurança

Proteger sua aplicação C# é essencial. Veja as práticas fundamentais:

// ✅ NUNCA armazene senhas em texto puro
public class PasswordService
{
    public string HashPassword(string password)
    {
        return BCrypt.Net.BCrypt.HashPassword(password, workFactor: 12);
    }

    public bool VerifyPassword(string password, string hash)
    {
        return BCrypt.Net.BCrypt.Verify(password, hash);
    }
}

// ✅ Use parameterized queries — NUNCA concatene strings SQL
// ❌ SQL Injection vulnerável
var query = $"SELECT * FROM Users WHERE Name = '{userName}'";

// ✅ Seguro com EF Core
var user = await _context.Users
    .FirstOrDefaultAsync(u => u.Name == userName);

// ✅ Seguro com Dapper
var user = await connection.QueryFirstOrDefaultAsync<User>(
    "SELECT * FROM Users WHERE Name = @Name", 
    new { Name = userName });

// ✅ Use secrets e variáveis de ambiente
// appsettings.json — NUNCA coloque senhas aqui em produção!
// Use: Azure Key Vault, AWS Secrets Manager, ou User Secrets para dev
var connectionString = builder.Configuration.GetConnectionString("Default");
var apiKey = builder.Configuration["ExternalApi:ApiKey"];

10. Logging e Observabilidade

// ✅ Use structured logging com ILogger
public class OrderService
{
    private readonly ILogger<OrderService> _logger;

    public async Task<Order> CreateOrderAsync(CreateOrderDto dto)
    {
        _logger.LogInformation(
            "Criando pedido para cliente {CustomerId} com {ItemCount} itens",
            dto.CustomerId, dto.Items.Count);

        try
        {
            var order = await ProcessOrder(dto);
            
            _logger.LogInformation(
                "Pedido {OrderId} criado com sucesso. Total: {Total:C}",
                order.Id, order.Total);
            
            return order;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Erro ao criar pedido para cliente {CustomerId}",
                dto.CustomerId);
            throw;
        }
    }
}

// ❌ NUNCA faça log de dados sensíveis
_logger.LogInformation("Login: {Email}, Senha: {Password}", email, password);

// ✅ Use log levels corretamente
_logger.LogDebug("Detalhes internos para debugging");
_logger.LogInformation("Eventos de negócio importantes");
_logger.LogWarning("Algo inesperado, mas não crítico");
_logger.LogError(ex, "Erro que precisa de atenção");
_logger.LogCritical(ex, "Sistema comprometido — ação imediata necessária");

11. Testes Unitários

Testes são a rede de segurança do seu código. Use o padrão AAA (Arrange, Act, Assert):

public class OrderServiceTests
{
    private readonly Mock<IOrderRepository> _repositoryMock;
    private readonly Mock<IEmailService> _emailServiceMock;
    private readonly OrderService _sut; // System Under Test

    public OrderServiceTests()
    {
        _repositoryMock = new Mock<IOrderRepository>();
        _emailServiceMock = new Mock<IEmailService>();
        _sut = new OrderService(
            _repositoryMock.Object, 
            _emailServiceMock.Object);
    }

    [Fact]
    public async Task CreateOrderAsync_WithValidData_ReturnsSuccessResult()
    {
        // Arrange
        var dto = new CreateOrderDto(
            CustomerId: 1,
            Items: new List<OrderItemDto>
            {
                new(ProductId: 10, Quantity: 2, UnitPrice: 49.90m)
            }
        );

        _repositoryMock
            .Setup(r => r.AddAsync(It.IsAny<Order>()))
            .Returns(Task.CompletedTask);

        // Act
        var result = await _sut.CreateOrderAsync(dto);

        // Assert
        Assert.True(result.IsSuccess);
        Assert.NotNull(result.Value);
        Assert.Equal(99.80m, result.Value.Total);
        
        _repositoryMock.Verify(
            r => r.AddAsync(It.IsAny<Order>()), 
            Times.Once);
    }

    [Fact]
    public async Task CreateOrderAsync_WithEmptyItems_ReturnsFailure()
    {
        // Arrange
        var dto = new CreateOrderDto(CustomerId: 1, Items: new());

        // Act
        var result = await _sut.CreateOrderAsync(dto);

        // Assert
        Assert.False(result.IsSuccess);
        Assert.Contains("pelo menos um item", result.Error);
    }
}

12. Dicas de Performance

// ✅ Use StringBuilder para concatenação em loops
var sb = new StringBuilder();
foreach (var item in items)
{
    sb.AppendLine($"- {item.Name}: {item.Price:C}");
}
var result = sb.ToString();

// ❌ String concatenation em loop é O(n²)
var result = "";
foreach (var item in items)
{
    result += $"- {item.Name}: {item.Price:C}\n"; // cria nova string a cada iteração
}

// ✅ Use Span<T> e Memory<T> para operações de alto desempenho
public static int CountWords(ReadOnlySpan<char> text)
{
    int count = 0;
    bool inWord = false;
    
    foreach (var c in text)
    {
        if (char.IsWhiteSpace(c))
            inWord = false;
        else if (!inWord)
        {
            count++;
            inWord = true;
        }
    }
    
    return count;
}

// ✅ Use IMemoryCache para dados frequentemente acessados
public class ProductService
{
    private readonly IMemoryCache _cache;
    private readonly IProductRepository _repository;

    public async Task<List<Category>> GetCategoriesAsync()
    {
        return await _cache.GetOrCreateAsync("categories", async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
            return await _repository.GetAllCategoriesAsync();
        });
    }
}

13. Conclusão

Escrever código C# de qualidade é uma jornada contínua. As práticas apresentadas neste guia são fundamentais para qualquer desenvolvedor .NET:

  • Nomenclatura consistente — PascalCase para públicos, camelCase com _ para privados
  • SOLID — classes pequenas, interfaces bem definidas, injeção de dependência
  • Async/Await — nunca bloqueie, use CancellationToken, aproveite Task.WhenAll
  • LINQ eficiente — Any() em vez de Count(), AsNoTracking, paginação
  • Tratamento de erros — exceções específicas, Result Pattern
  • Segurança — parameterized queries, hash de senhas, secrets management
  • Testes — padrão AAA, mocks, cobertura das regras de negócio
  • Performance — StringBuilder, Span<T>, cache

Continue praticando, revisando seu código e aprendendo com a comunidade. O Amigo do Dev está aqui para ajudar com ferramentas que aceleram seu dia a dia como desenvolvedor!

Tags

C#.NETBoas PráticasClean CodeSOLIDAsync/AwaitLINQDesign Patterns

© 2026 Amigo do Dev — Ferramentas gratuitas para desenvolvedores