Design de Tipos para Performance

Projete tipos .NET visando performance: sele classes, use structs readonly, prefira funções estáticas puras, evite enumeração prematura e escolha os tipos de coleção adequados.

O que é Design de Tipos para Performance?

Esta skill descreve princípios essenciais de design de tipos em .NET com foco em desempenho. Ela cobre práticas como selar classes para permitir desvirtualização JIT, usar structs readonly para evitar cópias defensivas, preferir funções estáticas puras para melhor performance e testabilidade, adiar a materialização de enumeráveis para reduzir alocações e escolher tipos de coleção apropriados — retornando `IReadOnlyList<T>` em limites de API e usando `FrozenDictionary` para dados estáticos. A skill também aborda o uso de `ValueTask` para caminhos quentes síncronos, `Span<T>` e `Memory<T>` para operações de baixo nível, e fornece uma tabela de referência rápida com padrões e benefícios. É indicada para desenvolvedores que trabalham com C# e desejam escrever código eficiente, seguro e de fácil manutenção, sendo aplicável a qualquer plataforma .NET (incluindo ASP.NET Core, Blazor, etc.). As orientações são baseadas em boas práticas documentadas pela Microsoft e em padrões amplamente adotados no ecossistema .NET.

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

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 Design de Tipos para Performance faz?

Quando Usar Esta Skill

Use esta skill quando:

  • Projetando novos tipos e APIs
  • Revisando código em busca de problemas de performance
  • Escolhendo entre classe, struct e record
  • Trabalhando com coleções e enumeráveis

Princípios Fundamentais

  1. Sele seus tipos - A menos que explicitamente projetados para herança
  2. Prefira structs readonly - Para tipos de valor pequenos e imutáveis
  3. Prefira funções estáticas puras - Melhor performance e testabilidade
  4. Adie a enumeração - Não materialize até que seja necessário
  5. Retorne coleções imutáveis - Dos limites da API

Sele Classes por Padrão

Selar classes permite a desvirtualização JIT e comunica a intenção da API.

// FAÇA: Sele classes não projetadas para herança
public sealed class OrderProcessor
{
    public void Process(Order order) { }
}

// FAÇA: Sele records (eles são classes)
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);

// NÃO FAÇA: Deixar sem selar sem motivo
public class OrderProcessor  // Pode ser subclassificado - intencional?
{
    public virtual void Process(Order order) { }  // Virtual = mais lento
}

Benefícios:

  • JIT pode desvirtualizar chamadas de método
  • Comunica "este não é um ponto de extensão"
  • Previne alterações de quebra acidentais

Structs Readonly para Tipos de Valor

Structs devem ser readonly quando imutáveis. Isso previne cópias defensivas.

// FAÇA: Struct readonly para tipos de valor imutáveis
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
    public override string ToString() => Value.ToString();
}

// FAÇA: Struct readonly para dados pequenos e de curta duração
public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }
}

// NÃO FAÇA: Struct mutável (causa cópias defensivas)
public struct Point  // Não é readonly!
{
    public int X { get; set; }  // Mutável!
    public int Y { get; set; }
}

Quando Usar Structs

Use Struct QuandoUse Classe Quando
Pequeno (≤16 bytes tipicamente)Objetos maiores
Curta duraçãoLonga duração
Frequentemente alocadoReferências compartilhadas necessárias
Semântica de valor necessáriaSemântica de identidade necessária
ImutávelEstado mutável

Prefira Funções Estáticas Puras

Métodos estáticos sem efeitos colaterais são mais rápidos e mais testáveis.

// FAÇA: Função estática pura
public static class OrderCalculator
{
    public static Money CalculateTotal(IReadOnlyList<OrderItem> items)
    {
        var total = items.Sum(i => i.Price * i.Quantity);
        return new Money(total, "USD");
    }
}

// Uso - previsível, testável
var total = OrderCalculator.CalculateTotal(items);

Benefícios:

  • Sem consulta à vtable (mais rápido)
  • Sem estado oculto
  • Mais fácil de testar (entrada pura → saída)
  • Thread-safe por design
  • Força dependências explícitas
// NÃO FAÇA: Método de instância escondendo dependências
public class OrderCalculator
{
    private readonly ITaxService _taxService;  // Dependência oculta
    private readonly IDiscountService _discountService;  // Dependência oculta

    public Money CalculateTotal(IReadOnlyList<OrderItem> items)
    {
        // Do que isso realmente depende?
    }
}

// MELHOR: Dependências explícitas via parâmetros
public static class OrderCalculator
{
    public static Money CalculateTotal(
        IReadOnlyList<OrderItem> items,
        decimal taxRate,
        decimal discountPercent)
    {
        // Todas as entradas visíveis
    }
}

Não exagere - Use métodos de instância quando você realmente precisar de estado ou polimorfismo.


Adie a Enumeração

Não materialize enumeráveis até que seja necessário. Evite encadeamentos excessivos de LINQ.

// RUIM: Materialização prematura
public IReadOnlyList<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .ToList()  // Materializado!
        .OrderBy(o => o.CreatedAt)  // Outra iteração
        .ToList();  // Materializado novamente!
}

// BOM: Adie até o final
public IReadOnlyList<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .OrderBy(o => o.CreatedAt)
        .ToList();  // Materialização única
}

// BOM: Retorne IEnumerable se o chamador pode não precisar de todos os itens
public IEnumerable<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .OrderBy(o => o.CreatedAt);
    // O chamador decide quando materializar
}

Enumeração Assíncrona

Tenha cuidado com async e IEnumerable:

// RUIM: Async em LINQ - alocações ocultas
var results = orders
    .Select(async o => await ProcessOrderAsync(o))  // Task por item!
    .ToList();
await Task.WhenAll(results);

// BOM: Use IAsyncEnumerable para streaming
public async IAsyncEnumerable<OrderResult> ProcessOrdersAsync(
    IEnumerable<Order> orders,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    foreach (var order in orders)
    {
        ct.ThrowIfCancellationRequested();
        yield return await ProcessOrderAsync(order, ct);
    }
}

// BOM: Processamento em lote para paralelismo
var results = await Task.WhenAll(
    orders.Select(o => ProcessOrderAsync(o)));

ValueTask vs Task

Use ValueTask para caminhos quentes que frequentemente completam de forma síncrona. Para I/O real, use apenas Task.

// FAÇA: ValueTask para caminhos em cache/síncronos
public ValueTask<User?> GetUserAsync(UserId id)
{
    if (_cache.TryGetValue(id, out var user))
    {
        return ValueTask.FromResult<User?>(user);  // Sem alocação
    }

    return new ValueTask<User?>(FetchUserAsync(id));
}

// FAÇA: Task para I/O real (mais simples, sem armadilhas)
public Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
{
    // Isso sempre acessa o banco de dados
    return _repository.CreateAsync(cmd);
}

Regras do ValueTask:

  • Nunca aguarde um ValueTask mais de uma vez
  • Nunca use .Result ou .GetAwaiter().GetResult() antes da conclusão
  • Em caso de dúvida, use Task

Span e Memory para Bytes

Use Span<T> e Memory<T> em vez de byte[] para operações de baixo nível.

// FAÇA: Aceite Span para operações síncronas
public static int ParseInt(ReadOnlySpan<char> text)
{
    return int.Parse(text);
}

// FAÇA: Aceite Memory para operações assíncronas
public async Task WriteAsync(ReadOnlyMemory<byte> data)
{
    await _stream.WriteAsync(data);
}

// NÃO FAÇA: Force alocação de array
public static int ParseInt(string text)  // String alocada
{
    return int.Parse(text);
}

Padrões Comuns com Span

// Fatie sem alocação
ReadOnlySpan<char> span = "Hello, World!".AsSpan();
var hello = span[..5];  // Sem alocação

// Alocação na pilha para buffers pequenos
Span<byte> buffer = stackalloc byte[256];

// Use ArrayPool para buffers maiores
var buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
    // Use o buffer...
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

Tipos de Retorno de Coleções

Retorne Coleções Imutáveis de APIs

// FAÇA: Retorne coleção imutável
public IReadOnlyList<Order> GetOrders()
{
    return _orders.ToList();  // O chamador não pode modificar o estado interno
}

// FAÇA: Use coleções congeladas para dados estáticos (.NET 8+)
private static readonly FrozenDictionary<string, Handler> _handlers =
    new Dictionary<string, Handler>
    {
        ["create"] = new CreateHandler(),
        ["update"] = new UpdateHandler(),
    }.ToFrozenDictionary();

// NÃO FAÇA: Retorne coleção mutável
public List<Order> GetOrders()
{
    return _orders;  // O chamador pode modificar!
}

Mutação Interna é Aceitável

public IReadOnlyList<OrderItem> BuildOrderItems(Cart cart)
{
    var items = new List<OrderItem>();  // Mutável internamente

    foreach (var cartItem in cart.Items)
    {
        items.Add(CreateOrderItem(cartItem));
    }

    return items;  // Retorne como IReadOnlyList
}

Diretrizes para Coleções

CenárioTipo de Retorno
Limite de APIIReadOnlyList<T>, IReadOnlyCollection<T>
Dados de consulta estáticaFrozenDictionary<K,V>, FrozenSet<T>
Construção internaList<T>, depois retorne como readonly
Item único ou nenhumT? (nullable)
Zero ou mais, lazyIEnumerable<T>

Referência Rápida

PadrãoBenefício
sealed classDesvirtualização, API clara
readonly record structSem cópias defensivas, semântica de valor
Funções estáticas purasSem vtable, testável, thread-safe
Adiar .ToList()Materialização única
ValueTask para caminhos quentesEvitar alocação de Task
Span<T> para bytesAlocação na pilha, sem cópia
Retorno IReadOnlyList<T>Contrato imutável da API
FrozenDictionaryConsulta mais rápida para dados estáticos

Anti-Padrões

// NÃO FAÇA: Classe sem selar sem motivo
public class OrderService { }  // Sele-a!

// NÃO FAÇA: Struct mutável
public struct Point { public int X; public int Y; }  // Torne readonly

// NÃO FAÇA: Método de instância que poderia ser estático
public int Add(int a, int b) => a + b;  // Torne estático

// NÃO FAÇA: Múltiplas chamadas ToList()
items.Where(...).ToList().OrderBy(...).ToList();  // Um ToList no final

// NÃO FAÇA: Retornar List<T> de uma API pública
public List<Order> GetOrders();  // Retorne IReadOnlyList<T>

// NÃO FAÇA: ValueTask para operações sempre assíncronas
public ValueTask<Order> CreateOrderAsync();  // Apenas use Task

Recursos

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