Programação Record: Simplifique Seus Tipos de Dados em C#

Introdução aos Records em C#

A partir do C# 9, os records foram introduzidos como um novo tipo de dados, oferecendo uma maneira concisa e imutável de definir tipos que carregam dados. Eles são particularmente úteis para cenários onde a imutabilidade e a comparação de valores são cruciais, como transferência de dados (DTOs), modelos de domínio simples e imutabilidade em programação funcional.

Antes dos records, structs e classes eram as principais opções para criar tipos de dados. No entanto, esses tipos exigiam boilerplate considerável para implementar funcionalidades como comparação de valores, impressão amigável e imutabilidade. Os records simplificam drasticamente esse processo, gerando automaticamente esse código repetitivo para você.

Por que Usar Records?

Os records oferecem várias vantagens em relação às classes e structs tradicionais:

  • Imutabilidade: Por padrão, os records são imutáveis, o que significa que, uma vez criados, seus valores não podem ser alterados. Isso ajuda a evitar efeitos colaterais indesejados e torna o código mais fácil de raciocinar.
  • Comparação de Valores: Os records implementam automaticamente a comparação de valores, em vez da comparação de referência (como em classes). Isso significa que dois records são considerados iguais se seus valores forem iguais, mesmo que sejam instâncias diferentes.
  • Impressão Amigável: Os records fornecem uma implementação padrão de ToString() que exibe os valores de suas propriedades de forma clara e concisa.
  • Sintaxe Concisa: A sintaxe de declaração de records é mais concisa e legível do que a de classes tradicionais, especialmente quando combinada com recursos como propriedades inicializáveis.
  • Desconstrução: Records suportam desconstrução, permitindo extrair facilmente os valores de suas propriedades em variáveis separadas.

Criando Records

Existem duas maneiras principais de declarar records em C#:

  1. Records Posicionais: Definem propriedades diretamente na declaração do tipo, usando parâmetros posicionais.
  2. Records de Propriedade: Semelhantes às classes, definem propriedades explicitamente usando a sintaxe de propriedade tradicional.

Records Posicionais

Os records posicionais são a forma mais concisa de declarar records. As propriedades são definidas diretamente nos parênteses após o nome do record.


public record Person(string FirstName, string LastName, int Age);
// Uso:
Person person = new Person("João", "Silva", 30);
Console.WriteLine(person); // Saída: Person { FirstName = João, LastName = Silva, Age = 30 }

O compilador gera automaticamente as propriedades, o construtor, a implementação de Equals(), GetHashCode() e ToString() com base nos parâmetros posicionais.

Records de Propriedade

Os records de propriedade são mais semelhantes às classes tradicionais, com propriedades definidas explicitamente.


public record Product
{
public string Name { get; init; }
public decimal Price { get; init; }
}
// Uso:
Product product = new Product { Name = "Camiseta", Price = 29.99m };
Console.WriteLine(product); // Saída: Product { Name = Camiseta, Price = 29.99 }

Note o uso de init em vez de set. Isso garante que as propriedades só possam ser inicializadas durante a criação do record, mantendo a imutabilidade após a criação.

Imutabilidade e Operador with

A imutabilidade é uma característica central dos records. Embora os records posicionais sejam inerentemente imutáveis, os records de propriedade exigem o uso de init para garantir que as propriedades sejam somente leitura após a inicialização.

Para criar novas instâncias de records com valores ligeiramente diferentes, você pode usar o operador with. Ele cria uma cópia do record existente e permite modificar seletivamente algumas propriedades.


Person person = new Person("João", "Silva", 30);
Person olderPerson = person with { Age = 31 };
Console.WriteLine(person); // Saída: Person { FirstName = João, LastName = Silva, Age = 30 }
Console.WriteLine(olderPerson); // Saída: Person { FirstName = João, LastName = Silva, Age = 31 }

O operador with retorna uma nova instância do record, deixando o record original inalterado.

Comparação de Valores

Uma das principais vantagens dos records é a comparação de valores. Dois records são considerados iguais se todos os seus valores de propriedade forem iguais. Isso simplifica a comparação de objetos e evita a necessidade de implementar manualmente Equals() e GetHashCode().


Person person1 = new Person("João", "Silva", 30);
Person person2 = new Person("João", "Silva", 30);
Person person3 = new Person("Maria", "Souza", 25);
Console.WriteLine(person1 == person2); // Saída: True
Console.WriteLine(person1 == person3); // Saída: False

A comparação de valores funciona tanto para records posicionais quanto para records de propriedade.

Desconstrução

Records suportam desconstrução, o que permite extrair facilmente os valores de suas propriedades em variáveis separadas.


Person person = new Person("João", "Silva", 30);
var (firstName, lastName, age) = person;
Console.WriteLine(firstName); // Saída: João
Console.WriteLine(lastName); // Saída: Silva
Console.WriteLine(age); // Saída: 30

A desconstrução é especialmente útil ao trabalhar com dados complexos e você precisa acessar propriedades específicas com frequência.

Herança em Records

Records podem herdar de outros records e classes, e classes podem herdar de records. No entanto, a herança com records possui algumas peculiaridades:

  • Um record pode herdar de outro record ou de uma classe base.
  • Uma classe pode herdar de um record base.


public record Animal(string Name);
public record Dog(string Name, string Breed) : Animal(Name);
Dog dog = new Dog("Rex", "Labrador");
Console.WriteLine(dog); // Saída: Dog { Name = Rex, Breed = Labrador }

Ao usar herança com records posicionais, o construtor da classe derivada deve chamar o construtor da classe base.

Quando Usar Records?

Records são ideais para os seguintes cenários:

  • Transferência de Dados (DTOs): Representar dados que são transferidos entre diferentes partes de um sistema.
  • Modelos de Domínio Simples: Representar objetos de domínio simples com dados e comportamento mínimos.
  • Tipos de Dados Imutáveis: Garantir que os valores dos objetos não possam ser alterados após a criação.
  • Programação Funcional: Facilitar o uso de padrões de programação funcional que dependem da imutabilidade.
  • Estruturas de Dados: Implementar estruturas de dados imutáveis, como listas e árvores imutáveis.

Comparação com Structs

Tanto records quanto structs podem ser usados para representar tipos de dados. No entanto, existem algumas diferenças importantes:

  • Records são tipos de referência, enquanto structs são tipos de valor. Isso significa que records são alocados no heap, enquanto structs são alocados na stack.
  • Records suportam herança, enquanto structs não.
  • Records implementam comparação de valores por padrão, enquanto structs implementam comparação de membros por padrão (a menos que explicitamente implementado de outra forma).

Em geral, use structs para tipos de dados pequenos e simples que não precisam de herança e onde o desempenho é crucial. Use records para tipos de dados mais complexos que precisam de imutabilidade, comparação de valores e herança.

Conclusão

Os records são uma adição poderosa ao C# que simplifica a criação de tipos de dados imutáveis e concisos. Eles oferecem muitas vantagens em relação às classes e structs tradicionais, incluindo imutabilidade, comparação de valores, impressão amigável e sintaxe concisa. Ao entender os benefícios e as limitações dos records, você pode usá-los para melhorar a clareza, a segurança e a manutenção do seu código C#.

Perguntas Frequentes (FAQs)

O que acontece se eu tentar modificar uma propriedade de um record posicional após a criação?

Você receberá um erro de compilação. Records posicionais são inerentemente imutáveis, e suas propriedades são efetivamente somente leitura após a criação da instância.

Posso usar records com Entity Framework Core?

Sim, você pode usar records com Entity Framework Core. No entanto, você precisará configurar corretamente as chaves primárias e as propriedades de navegação.

Qual a diferença entre record struct e record class?

record struct define um record como um tipo de valor (alocado na stack), enquanto record class define um record como um tipo de referência (alocado no heap). A escolha entre os dois depende dos seus requisitos de desempenho e da necessidade de herança (record struct não suporta herança).

Como posso personalizar a implementação de ToString() de um record?

Você pode substituir o método ToString() em seu record para fornecer uma representação personalizada da string do objeto.

Records afetam o desempenho do meu aplicativo?

Em geral, os records não devem ter um impacto significativo no desempenho. A comparação de valores e a imutabilidade podem, em alguns casos, melhorar o desempenho, pois permitem otimizações pelo compilador. No entanto, a alocação de memória para tipos de referência (record class) ainda pode ter um custo. Avalie o desempenho em cenários críticos.

Posso usar records em versões mais antigas do C#?

Não, records foram introduzidos no C# 9. Você precisará usar o C# 9 ou posterior para usar records.

Deixe um comentário