Padrões Modernos de Codificação C#

Escreva código C# moderno e de alto desempenho usando records, pattern matching, value objects, async/await, Span<T>/Memory<T> e padrões de design de API de melhores práticas. Enfatiza programação funcional com recursos do C# 12+.

O que é Padrões Modernos de Codificação C#?

Esta skill define padrões modernos de codificação C# para desenvolvedores que desejam escrever código robusto, de alto desempenho e de fácil manutenção usando os recursos mais recentes da linguagem (C# 9-12). Ela se concentra em imutabilidade, segurança de tipos, pattern matching, programação assíncrona e padrões de alocação zero usando Span<T>/Memory<T>. A skill é útil para projetar APIs públicas, implementar modelos de domínio, otimizar código crítico de desempenho e construir aplicações pesadas em async/await. Ela fornece diretrizes práticas com exemplos de código concretos, links para arquivos de referência aprofundados e uma lista clara de 'faça' e 'não faça'. As plataformas suportadas incluem qualquer ambiente .NET (console, ASP.NET Core, bibliotecas, microsserviços).

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/csharp-coding-standards

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

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

Princípios Básicos

  1. Imutabilidade por Padrão - Use tipos record e propriedades init-only
  2. Segurança de Tipos - Aproveite nullable reference types e value objects
  3. Pattern Matching Moderno - Use switch expressions e patterns extensivamente
  4. Async em Todo Lugar - Prefira APIs assíncronas com suporte adequado a cancelamento
  5. Padrões de Alocação Zero - Use Span<T> e Memory<T> para código crítico de desempenho
  6. Design de API - Aceite abstrações, retorne tipos apropriadamente específicos
  7. Composição Sobre Herança - Evite classes base abstratas, prefira composição
  8. Value Objects como Structs - Use readonly record struct para 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 propriedades
  • record 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 CancellationToken com = default
  • Use ConfigureAwait(false) em código de biblioteca
  • Nunca bloqueie código async (sem .Result ou .Wait())
  • Use CancellationTokenSource vinculado 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 record para DTOs, mensagens e entidades de domínio
  • Use readonly record struct para value objects
  • Aproveite pattern matching com switch expressions
  • Habilite e respeite nullable reference types
  • Use async/await para todas as operações de I/O
  • Aceite CancellationToken em todos os métodos async
  • Use Span<T> e Memory<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[] quando Span<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

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

Habilidades Relacionadas