C# - Oito recursos dos Records
Neste artigo vou apresentar oito recursos dos records na linguagem C#. |
Os records são a terceira maneira de definir tipos de dados em C#; os outros
dois são : class e struct.
Como são um recurso novo, devemos passar algum tempo
experimentando e tentando entender suas possibilidades e funcionalidades.
Neste artigo veremos oito propriedades dos records que você deve conhecer antes de
utilizá-los, para tirar o melhor proveito deste novo tipo de dado.
Os exemplos de código foram feitos no C# 11 usando o recurso Top Level Statement.
1- Records são imutáveis
Por padrão, os records são imutáveis, e, uma vez criado, um record não pode ser modificado. Isso significa que todas as propriedades de um record são somente leitura, o que torna os records imutáveis e seguros para uso em threads.
O trecho de código a seguir ilustra isso:
var
maria = new
Pessoa("Maria",
1); maria.Nome = "Maria Silveira"; //erropublic record Pessoa(string Nome, int Id); |
O código acima não compila e emite uma mensagem de erro quando tentamos alterar a propridade Nome do record Pessoa.
2- Records implementam a igualdade
A outra propriedade dos records é que eles implementam a igualdade pronta para uso.
Em um record, a igualdade é determinada comparando-se o valor das propriedades, em vez de comparar as referências de objeto. Portanto, dois records são iguais se possuem os mesmos valores em suas propriedades.
var
pessoa1 = new
Pessoa("João",
30); var pessoa2 = new Pessoa("João", 30); var pessoa3 = new Pessoa("Maria", 25); // Comparando duas instâncias de Pessoa com valores iguais Console.WriteLine(pessoa1 == pessoa2); // true // Comparando duas instâncias de Pessoa com valores diferentes Console.WriteLine(pessoa1 == pessoa3); // false Console.ReadKey(); public record Pessoa(string Nome, int Idade); |
Nesse exemplo, criamos um record Pessoa com duas propriedades: Nome e Idade.
Em seguida, criamos duas instâncias desse record com valores iguais (pessoa1 e pessoa2) e uma terceira instância com valores diferentes (pessoa3).
Ao compararmos pessoa1 e pessoa2, obtemos true, pois os valores das propriedades desses records são iguais. Já ao compararmos pessoa1 e pessoa3, obtemos false, pois os valores das propriedades são diferentes.
Portanto, podemos concluir que a igualdade em records é determinada pelo valor das suas propriedades, o que torna mais fácil e intuitivo o processo de compará-los.
3- Records podem ser clonados ou atualizados
usando a palavra-chave 'with'
Em um record, podemos clonar ou atualizar suas instâncias usando a palavra-chave
with.
Para clonar um record, usamos a sintaxe with { }, que cria uma nova instância do record com as mesmas propriedades da instância original. Podemos usar isso para criar uma cópia de um record sem modificar o original.
var
pessoa1 = new
Pessoa("João",
30); var pessoa2 = pessoa1 with { };Console.WriteLine(pessoa1 == pessoa2); // trueConsole.ReadKey(); public record Pessoa(string Nome, int Idade); |
No exemplo, criamos um record Pessoa com duas propriedades (Nome e Idade) e uma instância desse record (pessoa1). Em seguida, criamos uma nova instância do record (pessoa2) clonando a instância original usando a sintaxe with { }.
Ao compararmos pessoa1 e pessoa2, obtemos true, pois ambas as instâncias têm os mesmos valores nas suas propriedades.
Além de clonar, podemos usar a palavra-chave with para atualizar as propriedades de um record. Para isso, usamos a mesma sintaxe with { }, mas incluímos as propriedades que queremos atualizar.
var
pessoa1 = new
Pessoa("João",
30); var pessoa2 = pessoa1 with { Nome = "Maria" };Console.WriteLine(pessoa1.Nome); // JoãoConsole.WriteLine(pessoa2.Nome); // MariaConsole.ReadKey(); public record Pessoa(string Nome, int Idade);
|
Aqui, atualizamos a propriedade Nome da instância original de Pessoa (pessoa1) criando uma nova instância (pessoa2) com a propriedade atualizada. Ao executar o código, vemos que a propriedade Nome de pessoa1 permaneceu inalterada, enquanto a de pessoa2 foi atualizada para "Maria".
Assim a palavra-chave with é útil para clonar ou atualizar records, tornando o processo mais simples e legível.
4- Records podem ser classes ou structs
Os records são um tipo de referência, assim como as classes, mas também podem ser definidos como um tipo de valor, como as structs.
Exemplo de record definido como classe:
public record Pessoa(string Nome, int Idade, string Email); |
Neste exemplo, estamos definindo um record chamado Pessoa que contém três propriedades: Nome, Idade e Email. Essas propriedades são inicializadas por meio do construtor de record, que é gerado automaticamente. A seguir, podemos criar uma instância de Pessoa da seguinte maneira:
Pessoa pessoa = new Pessoa("João", 30, "joao@email.com"); |
Exemplo de record definido como struct:
public
record
Ponto(int
X, int
Y) { public Ponto Mover(int dx, int dy) => new Ponto(X + dx, Y + dy); } |
Neste exemplo, estamos definindo um record chamado Ponto que contém duas propriedades: X e Y. Além disso, estamos declarando um método Mover que cria e retorna uma nova instância de Ponto com as coordenadas atualizadas. A seguir, podemos criar uma instância de Ponto fazendo assim:
Ponto ponto = new Ponto(10, 20); |
5- Records podem ter subtipos
Os records podem ter subtipos, pois eles são um tipo de referência e podem herdar de outras classes ou interfaces. Isso significa que é possível criar uma hierarquia de classes de records que compartilham algumas propriedades e comportamentos comuns.
Por exemplo, considere a seguinte hierarquia de classes do tipo record que representam diferentes tipos de veículos:
public
record
Veiculo(string
Marca, string
Modelo); public record Carro(string Marca, string Modelo, int Ano) : Veiculo(Marca, Modelo);public record Moto(string Marca, string Modelo, int Cilindradas) : Veiculo(Marca, Modelo); |
Neste exemplo, a classe base Veiculo define duas propriedades comuns a todos os veículos: Marca e Modelo. Em seguida, as classes Carro e Moto herdam de Veiculo e adicionam propriedades específicas de cada tipo de veículo: Ano para Carro e Cilindradas para Moto.
A seguir, podemos criar instâncias de Carro e Moto da seguinte maneira:
Carro carro =
new
Carro("Chevrolet",
"Cruze",
2020); Moto moto = new Moto("Honda", "CBR 600RR", 600); |
Essa hierarquia de classes de records permite que o código seja mais genérico e reutilizável, já que é possível tratar todos os tipos de veículos como Veiculo, sem precisar se preocupar com as diferenças específicas entre eles.
Além disso, é importante notar que, assim como as classes e interfaces, os records podem implementar interfaces e, portanto, também podem fazer parte de hierarquias de tipos mais complexas.
6- Records podem ser abstract
Os records podem ser declarados como abstract ou não, assim como as classes comuns. No entanto, o uso de records abstratos é limitado, pois eles não podem ser instanciados diretamente.
Um record abstract pode ser útil para fornecer uma base comum para outras classes ou records que herdam suas propriedades, mas não podem ser instanciados por conta própria. Por exemplo:
public
abstract
record
Pessoa(string
Nome, int
Idade); |
Neste exemplo, estamos definindo um record abstrato chamado Pessoa que contém duas propriedades: Nome e Idade. Por ser abstrato, não podemos criar uma instância direta de Pessoa, mas podemos criar subclasses que herdam de Pessoa e adicionam mais propriedades ou comportamentos específicos, como:
public
record
Cliente(string
Nome, int
Idade, string
Email) : Pessoa(Nome, Idade); public record Funcionario(string Nome, int Idade, int Matricula) : Pessoa(Nome, Idade); |
Aqui, estamos criando duas subclasses de Pessoa: Cliente e Funcionario, que herdam as propriedades Nome e Idade de Pessoa, mas adicionam propriedades adicionais específicas de cada tipo de pessoa.
É importante notar que o uso de records abstract é limitado, pois eles não suportam a implementação de métodos ou membros virtuais, o que pode tornar difícil a criação de hierarquias de classes mais complexas com records. Além disso, os records são tipicamente usados para representar dados imutáveis, então não faz muito sentido ter records abstratos com métodos que modificam suas propriedades.
Por isso, em geral, é mais comum usar records como tipos finais e definir classes abstratas para fornecer uma base comum para outras classes que herdam seus membros e comportamentos.
7- Records podem ser sealed
Os records podem ser declarados como sealed ou não. Um record sealed não pode ser herdado por outras classes ou records, enquanto um record não sealed pode ser herdado normalmente.
O uso de records sealed é útil em situações em que se deseja garantir que o record não será modificado ou estendido por outras classes. Por exemplo:
public
sealed
record
Pessoa(string
Nome, int
Idade); |
Neste exemplo, estamos definindo um record sealed chamado Pessoa que contém duas propriedades: Nome e Idade. Por ser sealed, não é possível criar uma subclasse de Pessoa e assim não poderemos criar subtipos.
Já em um exemplo de record não sealed, podemos ter:
public
record
Veiculo(string
Marca, string
Modelo); |
Neste exemplo, estamos definindo um record chamado Veiculo que contém duas propriedades: Marca e Modelo. Como ele não é sealed, outras classes podem herdar de Veiculo e adicionar suas próprias propriedades e comportamentos.
É importante notar que o uso de records sealed deve ser feito com cuidado, pois ele impede a extensibilidade do tipo. Isso pode ser útil em situações em que se deseja garantir a imutabilidade dos dados ou a integridade do comportamento, mas pode ser prejudicial em outros casos em que a extensibilidade é desejada.
Em geral, é recomendável usar records sealed apenas em situações em que se tem certeza de que o record não precisa ser estendido por outras classes e quando se deseja garantir a imutabilidade dos dados. Caso contrário, é melhor usar records não sealed ou classes comuns que permitam a extensibilidade do tipo.
8 - Records podem ser decompostos
Outro recurso importante dos records é que eles podem ser decompostos e a decomposição de um record é uma maneira conveniente de extrair as propriedades de um record em variáveis separadas. Isso pode ser útil em situações em que se deseja trabalhar com cada propriedade individualmente ou passá-las como argumentos para métodos ou construtores.
Para fazer a decomposição de um record em C#, basta declarar as variáveis desejadas entre parênteses e atribuir o record a elas. Por exemplo:
// criar um record
de pessoa var pessoa = new Pessoa("Maria", 30); // fazer a decomposição do record em variáveis separadas var (nome, idade) = pessoa; // usar as variáveis Console.WriteLine($"Nome: {nome}, Idade: {idade}"); Console.ReadKey(); public record Pessoa(string Nome, int Idade); |
Neste exemplo, estamos criando um record de Pessoa com duas propriedades: Nome e Idade. Em seguida, fazemos a decomposição do record em duas variáveis separadas: nome e idade. O resultado é que as propriedades Nome e Idade do record são atribuídas às variáveis correspondentes, permitindo que as usemos separadamente.
Note que a ordem das variáveis na decomposição deve ser a mesma ordem em que as propriedades foram definidas no record.
A decomposição de records pode ser especialmente útil em situações em que se trabalha com coleções de records e se deseja acessar cada propriedade individualmente. Por exemplo:
var
pessoas = new
List<Pessoa> { new Pessoa("Maria", 30), new Pessoa("João", 25), new Pessoa("Ana", 40) }; foreach (var (nome, idade) in pessoas){ Console.WriteLine($"Nome: {nome}, Idade: {idade}"); } Console.ReadKey(); public record Pessoa(string Nome, int Idade); |
Neste exemplo, estamos criando uma lista de pessoas e fazendo a decomposição de cada record em nome e idade dentro de um loop foreach. Isso permite imprimir as informações de cada pessoa separadamente, sem precisar acessar as propriedades do record diretamente.
Para concluir vamos apresentar os principais recursos dos records:
E estamos conversados ...
"Uns confiam em carros e outros em cavalos, mas nós faremos menção do nome do
Senhor nosso Deus."
Salmos 20:7
Referências: