O que Padrões Modernos de Codificação C# faz?
Quando Usar Esta Skill
Use esta skill quando:
- Escrevendo novo código C# ou refatorando código existente
- Projetando APIs públicas para bibliotecas ou serviços
- Otimizando caminhos de código críticos para desempenho
- Implementando modelos de domínio com tipagem forte
- Construindo aplicações pesadas em async/await
- Trabalhando com dados binários, buffers ou cenários de alta vazão
Arquivos de Referência
- value-objects-and-patterns.md: Exemplos completos de value objects e código de pattern matching
- performance-and-api-design.md: Exemplos de Span/Memory e princípios de design de API
- composition-and-error-handling.md: Composição sobre herança, tipo Result, padrões de teste
- anti-patterns-and-reflection.md: Evitando reflexão e anti-padrões comuns
Princípios Básicos
- Imutabilidade por Padrão - Use tipos
recorde propriedadesinit-only - Segurança de Tipos - Aproveite nullable reference types e value objects
- Pattern Matching Moderno - Use
switchexpressions e patterns extensivamente - Async em Todo Lugar - Prefira APIs assíncronas com suporte adequado a cancelamento
- Padrões de Alocação Zero - Use
Span<T>eMemory<T>para código crítico de desempenho - Design de API - Aceite abstrações, retorne tipos apropriadamente específicos
- Composição Sobre Herança - Evite classes base abstratas, prefira composição
- Value Objects como Structs - Use
readonly record structpara value objects
Padrões de Linguagem
Records para Dados Imutáveis (C# 9+)
Use tipos record para DTOs, mensagens, eventos e entidades de domínio.
// DTO imutável simples
public record CustomerDto(string Id, string Name, string Email);
// Record com validação no construtor
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Endereço de email inválido", nameof(value));
Value = value;
}
}
// Records com coleções - use IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
Quando usar record class vs record struct:
record class(padrão): Tipos de referência, use para entidades, agregados, DTOs com múltiplas propriedadesrecord struct: Tipos de valor, use para value objects (veja próxima seção)
Value Objects como readonly record struct
Value objects devem ser sempre readonly record struct para desempenho e semântica de valor. Use conversões explícitas, nunca operadores implícitos.
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId não pode ser vazio", nameof(value)))
{ }
public override string ToString() => Value;
}
public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
Veja value-objects-and-patterns.md para exemplos completos incluindo objetos de múltiplos valores, padrões de fábrica e a regra de nenhuma conversão implícita.
Pattern Matching (C# 8-12)
Use switch expressions, property patterns, relational patterns e list patterns para código mais limpo.
public decimal CalculateDiscount(Order order) => order switch
{
{ Total: > 1000m } => order.Total * 0.15m,
{ Total: > 500m } => order.Total * 0.10m,
{ Total: > 100m } => order.Total * 0.05m,
_ => 0m
};
Veja value-objects-and-patterns.md para exemplos completos de pattern matching.
Nullable Reference Types (C# 8+)
Habilite nullable reference types em seu projeto e lide com nulos explicitamente.
// Em .csproj
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Anulabilidade explícita
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// Pattern matching com verificações nulas
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// Guard clauses com ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// order agora é não anulável neste escopo
Console.WriteLine(order.Id);
}
Composição Sobre Herança
Evite classes base abstratas. Use interfaces + composição. Use helpers estáticos para lógica compartilhada. Use records com métodos de fábrica para variantes.
Veja composition-and-error-handling.md para exemplos completos.
Padrões de Desempenho
Melhores Práticas de Async/Await
// Async até o fim - sempre aceite CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// ValueTask para métodos frequentemente chamados, muitas vezes síncronos
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(orderId, out var order))
return ValueTask.FromResult<Order?>(order);
return GetFromDatabaseAsync(orderId, cancellationToken);
}
// IAsyncEnumerable para streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
string customerId,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var order in _repository.StreamAllAsync(cancellationToken))
{
if (order.CustomerId == customerId)
yield return order;
}
}
Regras principais:
- Sempre aceite
CancellationTokencom= default - Use
ConfigureAwait(false)em código de biblioteca - Nunca bloqueie código async (sem
.Resultou.Wait()) - Use
CancellationTokenSourcevinculado para timeouts
Span e Memory
Use Span<T> para operações síncronas de alocação zero, Memory<T> para async e ArrayPool<T> para buffers temporários grandes.
Veja performance-and-api-design.md para exemplos completos de Span/Memory e a seção de design de API.
Tratamento de Erros: Tipo Result
Para erros esperados, use Result<T, TError> em vez de exceções. Use exceções apenas para erros inesperados/de sistema.
Veja composition-and-error-handling.md para a implementação completa do tipo Result e exemplos de uso.
Evite Metaprogramação Baseada em Reflexão
Banidos: AutoMapper, Mapster, ExpressMapper. Use métodos de extensão de mapeamento explícitos. Use UnsafeAccessorAttribute (.NET 8+) quando precisar genuinamente de acesso a membros privados.
Veja anti-patterns-and-reflection.md para orientação completa.
Organização do Código
// Arquivo: Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. Tipo de domínio primário
public record Order(
OrderId Id,
CustomerId CustomerId,
Money Total,
OrderStatus Status,
IReadOnlyList<OrderItem> Items
)
{
public bool IsCompleted => Status is OrderStatus.Completed;
public Result<Order, OrderError> AddItem(OrderItem item)
{
if (Status is not OrderStatus.Draft)
return Result<Order, OrderError>.Failure(
new OrderError("ORDER_NOT_DRAFT", "Só é possível adicionar itens a pedidos rascunho"));
var newItems = Items.Append(item).ToList();
var newTotal = new Money(
Items.Sum(i => i.Total.Amount) + item.Total.Amount,
Total.Currency);
return Result<Order, OrderError>.Success(
this with { Items = newItems, Total = newTotal });
}
}
// 2. Enums para estado
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. Tipos relacionados
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. Value objects
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. Erros
public readonly record struct OrderError(string Code, string Message);
Resumo de Melhores Práticas
FAÇA
- Use
recordpara DTOs, mensagens e entidades de domínio - Use
readonly record structpara value objects - Aproveite pattern matching com
switchexpressions - Habilite e respeite nullable reference types
- Use async/await para todas as operações de I/O
- Aceite
CancellationTokenem todos os métodos async - Use
Span<T>eMemory<T>para cenários de alto desempenho - Aceite abstrações (
IEnumerable<T>,IReadOnlyList<T>) - Use
Result<T, TError>para erros esperados - Faça pooling de buffers com
ArrayPool<T>para grandes alocações - Prefira composição sobre herança
NÃO FAÇA
- Não use classes mutáveis quando records funcionam
- Não use classes para value objects (use
readonly record struct) - Não crie hierarquias de herança profundas
- Não ignore avisos de nullable reference types
- Não bloqueie código async (
.Result,.Wait()) - Não use
byte[]quandoSpan<byte>for suficiente - Não esqueça parâmetros
CancellationToken - Não retorne coleções mutáveis de APIs
- Não lance exceções para erros de negócio esperados
- Não aloque grandes arrays repetidamente (use
ArrayPool)
Veja anti-patterns-and-reflection.md para exemplos detalhados de anti-padrões.
Recursos Adicionais
- Especificação da Linguagem C#: https://learn.microsoft.com/en-us/dotnet/csharp/
- Pattern Matching: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
- Span e Memory: https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/
- Melhores Práticas Async: https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
- Dicas de Desempenho .NET: https://learn.microsoft.com/en-us/dotnet/framework/performance/