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!
