Voltar para artigos
Linguagens

Records em C#: Imutabilidade, Semântica e o Paralelo com Classes

Publicado em 25 de abr. de 2026
Records em C#: Imutabilidade, Semântica e o Paralelo com Classes

Records em C#: Imutabilidade, Semântica e o Paralelo com Classes

Introdução — Com o C# 9.0, a Microsoft introduziu um novo tipo de referência: o record. Mais do que um açúcar sintático, os records representam uma mudança filosófica na forma como modelamos dados em C#. Este artigo explora o que são records, como funcionam e quando preferí-los às classes tradicionais.


1. O Problema que os Records Resolvem

Antes dos records, criar um tipo que representasse dados imutáveis em C# era verboso e propenso a erros. Considere um simples modelo de Ponto:

// Abordagem tradicional com classe
public class Ponto
{
    public int X { get; init; }
    public int Y { get; init; }

    public Ponto(int x, int y)
    {
        X = x;
        Y = y;
    }

    public override bool Equals(object? obj)
        => obj is Ponto other && X == other.X && Y == other.Y;

    public override int GetHashCode()
        => HashCode.Combine(X, Y);

    public override string ToString()
        => $"Ponto {{ X = {X}, Y = {Y} }}";
}

São mais de 20 linhas para algo conceitualmente simples. Com record, isso vira:

public record Ponto(int X, int Y);

Uma linha. Mesmo comportamento.


2. O que é um Record?

Um record é um tipo de referência (por padrão) cujo propósito principal é encapsular dados. Ele oferece automaticamente:

Funcionalidade Classes (manual) Records (automático)
Equals por valor
GetHashCode por valor
ToString descritivo
Operador == por valor
Expressão with
Deconstrução
Imutabilidade por padrão

3. Sintaxe: Posicional vs. Nominal

3.1 Record Posicional (Positional Record)

public record Pessoa(string Nome, int Idade);

O compilador gera automaticamente:

  • Construtor primário com os parâmetros
  • Propriedades init-only (imutáveis após construção)
  • Deconstrutor Deconstruct
var p = new Pessoa("Alice", 30);
var (nome, idade) = p; // Deconstrução
Console.WriteLine(p);  // Pessoa { Nome = Alice, Idade = 30 }

3.2 Record Nominal (com corpo)

public record Produto
{
    public string Nome { get; init; } = string.Empty;
    public decimal Preco { get; init; }
    public string Categoria { get; init; } = string.Empty;
}

Útil quando você precisa de mais controle sobre inicializadores, validações ou membros adicionais.


4. O Paralelo com Classes: Semelhanças e Diferenças

4.1 O que Records e Classes compartilham

Records são, em essência, classes especializadas. Eles suportam:

public record Animal(string Nome)
{
    // Herança
}

public record Cachorro(string Nome, string Raca) : Animal(Nome)
{
    // Propriedades extras
    public string Latido => $"{Nome} diz: Au au!";

    // Métodos normais
    public void Apresentar() => Console.WriteLine(Latido);
}
  • ✅ Herança (entre records)
  • ✅ Interfaces
  • ✅ Métodos, propriedades e campos
  • ✅ Modificadores de acesso
  • ✅ Genéricos
  • ✅ Atributos

4.2 O que os diferencia fundamentalmente

Semântica de Igualdade

// CLASSE — igualdade por referência
var c1 = new ClassePonto(1, 2);
var c2 = new ClassePonto(1, 2);
Console.WriteLine(c1 == c2); // False ❌ (referências diferentes)

// RECORD — igualdade por valor
var r1 = new Ponto(1, 2);
var r2 = new Ponto(1, 2);
Console.WriteLine(r1 == r2); // True ✅ (valores iguais)

Essa diferença é crucial. Records implementam igualdade estrutural por padrão — dois records são iguais se todos os seus membros forem iguais.

Imutabilidade com with

Records introduzem a expressão with, que cria uma cópia modificada sem alterar o original:

var pessoa = new Pessoa("Alice", 30);
var pessoaAniversario = pessoa with { Idade = 31 };

Console.WriteLine(pessoa);            // Pessoa { Nome = Alice, Idade = 30 }
Console.WriteLine(pessoaAniversario); // Pessoa { Nome = Alice, Idade = 31 }

Com classes, você precisaria implementar esse padrão manualmente (Clone, Copy constructors, etc.).

Mutabilidade

// Classe — mutável por padrão
public class ClasseMutavel
{
    public string Nome { get; set; } = ""; // set = mutável
}

// Record — imutável por padrão
public record RecordImutavel(string Nome); // init-only = imutável

// Record mutável (possível, mas não recomendado)
public record RecordMutavel
{
    public string Nome { get; set; } = ""; // set = mutável (vai contra o propósito)
}

5. Record Struct: O Melhor dos Dois Mundos?

Com C# 10, chegou o record struct:

public record struct Coordenada(double Latitude, double Longitude);
Tipo Referência Valor Igualdade Imutável por padrão
class Referencial
struct Por valor
record (class) Por valor
record struct Por valor ✗ (use readonly)
// Record struct imutável
public readonly record struct Coordenada(double Lat, double Lon);

6. Casos de Uso: Quando Usar Cada Um

✅ Use record quando:

  • Modelar DTOs (Data Transfer Objects)
  • Representar mensagens em sistemas orientados a eventos
  • Implementar o padrão Value Object do DDD
  • Trabalhar com respostas de API
  • Dados que não devem mudar após a criação
// DTO de resposta de API
public record UsuarioResponse(
    Guid Id,
    string Nome,
    string Email,
    DateTime CriadoEm
);

// Value Object no DDD
public record Dinheiro(decimal Valor, string Moeda)
{
    public Dinheiro Somar(Dinheiro outro)
    {
        if (Moeda != outro.Moeda)
            throw new InvalidOperationException("Moedas diferentes");
        return this with { Valor = Valor + outro.Valor };
    }
}

✅ Use class quando:

  • Modelar entidades com identidade própria (ex: entidades do banco)
  • Objeto precisa de estado mutável ao longo do tempo
  • Necessidade de herança polimórfica complexa
  • Integração com ORMs como Entity Framework
  • Gerenciar recursos (IDisposable, streams, conexões)
// Entidade com identidade
public class Pedido
{
    public Guid Id { get; private set; } = Guid.NewGuid();
    public List<ItemPedido> Itens { get; private set; } = new();
    public StatusPedido Status { get; private set; }

    public void AdicionarItem(ItemPedido item) => Itens.Add(item);
    public void Confirmar() => Status = StatusPedido.Confirmado;
}

7. Pattern Matching com Records

Records brilham especialmente com pattern matching, recurso cada vez mais poderoso em C#:

public record Forma;
public record Circulo(double Raio) : Forma;
public record Retangulo(double Largura, double Altura) : Forma;
public record Triangulo(double Base, double Altura) : Forma;

public static double CalcularArea(Forma forma) => forma switch
{
    Circulo(var r)          => Math.PI * r * r,
    Retangulo(var l, var a) => l * a,
    Triangulo(var b, var a) => b * a / 2,
    _                       => throw new ArgumentException("Forma desconhecida")
};

A deconstrução automática dos records torna o pattern matching expressivo e seguro.


8. Tabela Comparativa Final

Característica class record
Tipo Referência Referência (por padrão)
Igualdade padrão Referencial Estrutural (por valor)
Imutabilidade Manual Automática (init)
ToString Manual Automático
Expressão with
Deconstrução Manual Automática
Herança ✓ (entre records)
Ideal para Entidades/Serviços Dados/DTOs/Value Objects

9. Conclusão

Records não vieram para substituir classes — vieram para complementá-las. A escolha entre os dois deve ser guiada pela semântica do problema:

  • Classes modelam coisas com identidade e comportamento → entidades, serviços.
  • Records modelam dados com valor intrínseco → DTOs, value objects, mensagens.

Adotar records onde apropriado resulta em código mais expressivo, seguro e conciso, alinhado com os princípios de imutabilidade que tornam sistemas mais previsíveis e fáceis de testar.


Escrito com base no C# 12 e .NET 8. Records foram introduzidos no C# 9 (.NET 5) e aprimorados nas versões subsequentes.