Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Introducción a los registros (record) y los objetos inmutables de C# 9

Imagen ornamental, un tocadisco con un disco (record), por Lee Campbel, CC0Una de las grandes novedades que se presentaron con .NET 5 y C# 9, fueron los registros.

Antes de eso podíamos trabajar con dos tipos de estructuras para almacenar información: class y struct.

Nota: existen más tipos de estructuras en C#: delegados, interfaces, etc... pero su objetivo no es almacenar información, que es lo que nos importa a efectos de este artículo y es en lo que nos vamos a centrar.

Con C# 9 o posterior disponemos de un tercer elemento para almacenar información: record, también conocido como registro.

¿Y cuál es la diferencia con los dos anteriores que seguramente ya conoces?

Con las clases y las estructuras tenemos el problema de que pueden ser alterados. Los objetos de tipo clase son tipos por referencia, mientras que las estructuras son tipos por valor, que lo más que se podían acercar a un objeto inmutable era declarándolas como readonly.

Los objetos de tipo record, son objetos por referencia que vienen a solucionar el problema existente a la hora de generar objetos inmutables, esto es, objetos que no pueden variar. Por otro lado están "a caballo" entre clases y estructuras, puesto que tienen características de los dos.

Las similitudes con ambos elementos, los vemos inmediatamente al realizar una comparación entre dos registros.

  1. Podremos emplear el operador de igualdad ==, puesto que al ser tipos por referencia nos va a indicar si se tratan de objetos con la misma referencia o no.
  2. Al igual que con las estructuras, el método Equals nos va a decir si son iguales o no, en función de los valores que tiene.

Pero veamos de forma más clara su funcionamiento mediante un ejemplo simple.

En primer lugar vamos a crear un nuevo tipo Persona, pero en lugar de emplear un clase (con class), emplearemos record:

public record Persona {
    public string Nombre { get; set; }
    public string Apellidos { get; set; }

    public Persona (string nombre, string apellidos) {
        Nombre = nombre;
        Apellidos = apellidos;
    }
}

Una vez tenemos nuestro registro, vamos crear varios objetos de este tipo:

  • Dos de ellos serán copias uno del otro.
  • Un tercer objeto nuevo, pero con los mismos valores.
  • Un último objeto con diferentes valores.

A continuación vamos a realizar comparaciones entre ellos para verificar:

  1. Objetos copiados directamente, al igual que las clases, mantienen la misma referencia.
  2. Objetos con los mismos valores, se reportan como iguales.
var persona1 = new Persona ("Rubén", "Rubio");
var persona2 = persona1;
var persona3 = new Persona ("Rubén", "Rubio");
var persona4 = new Persona ("Rubén", "R.");

Console.WriteLine ($"Referencia: persona1 = persona2 {ReferenceEquals(persona1,persona2)}");
Console.WriteLine ($"Valor: persona1 = persona2 {persona1.Equals(persona2)}");
Console.WriteLine ($"Referencia: persona1 = persona3 {ReferenceEquals(persona1,persona3)}");
Console.WriteLine ($"Valor: persona1 = persona3 {persona1.Equals(persona3)}");
Console.WriteLine ($"Referencia: persona1 = persona4 {ReferenceEquals(persona1,persona4)}");
Console.WriteLine ($"Valor: persona1 = persona4 {persona1.Equals(persona4)}");

Y el resultado de ejecutar el código.

Comparación de registros: al comparar por valor, si tienen los mismos miembros se identifican como iguales

Declaración mediante registros posicionales

Al principio decíamos que vienen a solucionar la definición de tipos inmutables, pero... con lo que hemos visto hasta ahora es posible modificar su contenido, por lo que no son inmutables 🤔

La declaración mediante registros posicionales nos va a permitir, por un lado simplificar el cuerpo del registro y, por otro, crear un registro realmente inmutable, siendo el propio compilador el que genere por nosotros toda la fontanería de constructor, deconstructores y propiedades.

Veamos cómo reescribiríamos el registro Persona para hacerlo inmutable:

public record Persona (string Nombre, string Apellidos);

El ejemplo anterior sería equivalente a este otro código:

public record Persona {
    public string Nombre { get; init; }
    public string Apellidos { get; init; }

    public Persona (string nombre, string apellidos) {
        Nombre = nombre;
        Apellidos = apellidos;
    }

    public void Deconstruct (out string nombre, out string apellidos) {
        nombre = Nombre;
        apellidos = Apellidos;
    }
}

Si lo observamos detenidamente, las propiedades no tienen un set, sino que son accesibles únicamente en la inicialización, puesto que son propiedades inicializadoras. De esta forma, ya no podremos alterar el registro siendo realmente inmutable.

Además, es considerable la reducción de código, puesto que hemos reducido toda la declaración a una única línea de código.

Pero, ¿y si necesitásemos constructores adicionales?

Para añadir más constructores a nuestro registro, bastará con añadirlos entre llaves a continuación de la declaración y siempre llamando al constructor base mediante el empleo de this.

public record Persona (string Nombre, string Apellidos) {
    public Persona (string Nombre) : this (Nombre, "") { }
};

Los dos constructores mostrados por Intellisense

Instanciación mediante expresiones con with

Por último veremos cómo podemos instanciar registros con ayuda de expresiones con with, que no es más que una forma de generar un registro a partir de otro, previamente existente, al que indicaremos que alguna de sus propiedades debe tener un valor diferente.

En primer lugar, declararemos un registro a partir del cual copiar:

var persona1 = new Persona ("Rubén", "Rubio");

A continuación, realizaremos la copia mediante igualdad, pero añadiremos la partícula with seguida de la declaración, entre llaves, de los valores que deben ser modificados:

var persona2 = persona1 with { Nombre = "Fernando" };

De esta forma tendremos una copia de persona1 en persona2 pero variando la propiedad Nombre.

var persona1 = new Persona ("Rubén", "Rubio");
var persona2 = persona1 with { Nombre = "Fernando" };
Console.WriteLine ($"Persona 1: {persona1}");
Console.WriteLine ($"Persona 2: {persona2}");

En la pantalla se ven los dos registros en los que solo cambia el nombre

Rubén Rubio Rubén lleva muchos años trabajando como desarrollador de software con diversas tecnologías y certificado por Microsoft desde el año 2003 en desarrollo con .NET. Ha trabajado como analista, desarrollador y responsable de TI en empresas de diferentes sectores. Actualmente trabaja como consultor y desarrollador independiente, ofreciendo sus servicios a empresas e instituciones, siendo también autor y docente en campusMVP. Ver todos los posts de Rubén Rubio
Archivado en: Lenguajes y plataformas

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.