1. O que é imutabilidade?
Bora começar com uma analogia: imagina que você é um contador que todo dia faz um relatório de vendas. Como um profissional de verdade, você não altera o relatório da semana passada, você cria um novo — baseado no antigo, mas com os dados atualizados. É a mesma ideia com objetos imutáveis na programação: depois de criado, esse objeto não muda mais, e qualquer "mudança" significa criar uma nova cópia.
Imutabilidade (immutability) — é a propriedade de um objeto de não mudar depois da inicialização. Todas as propriedades dele ficam "congeladas": se você quiser outro valor — cria um novo objeto.
Pra que serve isso?
- Garante segurança: se ninguém pode mudar seu objeto sem querer, ele não vai estragar seus dados do nada. Isso rola muito em programas multithread, quando várias threads tentam mudar as coisas ao mesmo tempo.
- Facilita o debug: se o objeto não muda, você sabe exatamente o que rolou depois que ele foi criado.
- É prático pra passar dados, principalmente em sistemas distribuídos, onde as cópias podem se perder.
- Permite fazer "snapshots" (snapshots) — o histórico de mudanças fica explícito.
2. Imutabilidade em tipos record
Quando você declara um class normal, as propriedades dele são mutáveis (mutable) por padrão. Exemplo:
public class UserProfile
{
public string Name { get; set; }
public int Age { get; set; }
}
var user = new UserProfile { Name = "Ivan", Age = 25 };
user.Age = 26; // Tudo certo — classe normal: mudando a idade "na hora"
Com record a parada é diferente: eles são pra guardar dados imutáveis.
public record UserProfile(string Name, int Age);
// Criando objeto:
var user = new UserProfile("Ivan", 25);
// Tentando mudar a idade:
user.Age = 26; // Erro de compilação: propriedade só leitura!
As propriedades de um record posicional são só leitura (init-only). Você não pode mudar depois de criar, mas pode usar with-expressão pra criar uma nova cópia com a propriedade alterada.
Classes mutáveis vs records imutáveis
| Classe | Record posicional |
|---|---|
Propriedades padrão |
Imutáveis |
| Como alterar Acessando a propriedade |
Só criando nova cópia |
| Comparação de objetos Por referência (ReferenceEquals) |
Por valor (Equals) |
| Bom pra passar dados Nem sempre |
Sim |
3. with-expressões
Você deve estar pensando — "record é maneiro, mas como faz pra viver se não dá pra mudar eles?" Aí entra a mágica das with-expressões!
with — é uma sintaxe especial que deixa você criar uma nova cópia do record, mudando só as propriedades que quiser.
Tipo: "Pega esse objeto, faz uma cópia, mas aqui muda umas paradas."
Exemplo básico
var user1 = new UserProfile("Anna", 30);
// ... mas a vida segue, e Anna ficou mais velha
var user2 = user1 with { Age = 31 };
// user1 ficou igual, user2 — cópia, mas um ano mais velha
Console.WriteLine(user1); // UserProfile { Name = Anna, Age = 30 }
Console.WriteLine(user2); // UserProfile { Name = Anna, Age = 31 }
Por baixo dos panos
Não é um clone mutante, é um novo objeto, criado com um método especial auto-gerado Clone(), que faz a cópia e coloca os valores novos.
Se with-expressões existissem na vida real, você poderia acordar de manhã não no "corpo cansado de sempre", mas numa cópia sua com o humor ajustado e músculos maiores (mas só se você fosse um record!).
4. Um pouco sobre aninhamento e cópia
Se um record tem outro record dentro — tudo certo:
public record Address(string City, string Street);
public record Student(string Name, int Age, string Email, Address Home);
var a1 = new Address("Moskva", "Tverskaya");
var s1 = new Student("Lena", 21, "lena@mail.ru", a1);
var s2 = s1 with { Home = a1 with { Street = "Arbat" } };
Aqui tudo vai funcionar realmente imutável, porque o Address aninhado — também é record.
5. Últimos detalhes
Record posicional = compactação
Dá pra declarar record de forma “curta” (sintaxe posicional). Assim, todas as propriedades já são init-only.
public record Course(string Name, int Credits);
var c1 = new Course("C#", 5);
var c2 = c1 with { Credits = 6 };
Analogia com propriedades só leitura (init-only)
No record, você pode declarar as propriedades assim:
public record Student
{
public string Name { get; init; }
public int Age { get; init; }
}
Essas propriedades também só podem ser mudadas na inicialização (ou usando with).
6. Prática: demo app
Bora escrever nossa "Escola Online" de exemplo. Imagina que já temos um record pra estudante:
public record Student(string Name, int Age, string Email);
Clássico: alguém errou no email, mas o estudante já criou a conta. Como fazer um "update" no email? Claro, usando with!
var student = new Student("Ekaterina", 19, "kate@school.com");
var updatedStudent = student with { Email = "ekaterina@school.com" };
// Conferindo os objetos:
Console.WriteLine(student); // Student { Name = Ekaterina, Age = 19, Email = kate@school.com }
Console.WriteLine(updatedStudent); // Student { Name = Ekaterina, Age = 19, Email = ekaterina@school.com }
7. Erros comuns e pegadinhas
Agora um pouco de dor — onde a galera mais erra brincando com records imutáveis.
- Primeiro, muita gente acha que with muda o objeto original. Na real, o objeto original fica igualzinho, e um novo é criado com os campos alterados. Às vezes você cai nessa armadilha e perde os valores novos.
- Segundo, lembra: se dentro do record tem objetos aninhados mutáveis (tipo um array ou List), a with-expressão não faz cópia profunda! Sua coleção vai ser a mesma nas duas cópias.
public record Student(string Name, int Age, List<string> Subjects);
var s1 = new Student("Oleg", 22, new List<string> { "Math", "Physics" });
var s2 = s1 with { };
s1.Subjects.Add("C#"); // Opa, agora s2.Subjects também tem "C#"
Por isso, pra um estado realmente imutável, é melhor usar só tipos simples ou coleções que já são imutáveis (ImmutableList<T> e outras de System.Collections.Immutable).
Se quiser garantir imutabilidade de verdade, usa essas coleções ou faz a cópia profunda na mão.
GO TO FULL VERSION