Un deconstructor es una forma de transformar una tupla o un objeto en diferentes variables, de forma que dejen de trabajar de manera agrupada. Es decir, nos permite controlar con exactitud cómo podemos "mapear" una tupla o las propiedades de un objeto hacia diferentes variables.
Para ilustrarlo pensemos en un método que devuelve una tupla con dos elementos: nombre y apellidos.
public (string nombre, string apellido) ObtenerDatos()
{
return ("Rubén", "Rubio");
}
Hasta este momento, si obtenemos el valor devuelto por el método e intentamos mostrarlo por consola, tendríamos que realizar algo como lo siguiente:
var tuplaDesdeMetodo = ObtenerDatos();
Console.WriteLine($"Usuario: {tuplaDesdeMetodo.nombre} {tuplaDesdeMetodo.apellido}");
Pero no tendríamos forma de manejar nombre
y apellidos
como variables independientes, puesto que es una tupla. Pues precisamente, eso es lo que nos va a permitir la deconstrucción, es decir, que cada elemento pase a una variable independiente.
Para eso tendremos dos opciones:
- Declarar explícitamente el tipo de cada variable.
- Emplear la palabra reservada
var
y realizar una declaración implícita.
Por lo tanto podríamos hacer lo siguiente:
// Con declaración explícita
(string nombreExplicito, string apellidoExplicito) = ObtenerDatos();
Console.WriteLine($"Usuario con declaración explícita: {nombreExplicito} {apellidoExplicito}");
// Con declaración implícita
var (nombreImplicito, apellidoImplicito) = ObtenerDatos();
Console.WriteLine($"Usuario con declaración implícita: {nombreImplicito} {apellidoImplicito}");
Como podemos observar, de cualquier manera que lo hagamos, el uso posterior es como si se tratase de variables independientes, siendo el siguiente el resultado de la ejecución:
Pero, inicializar variables nuevas para almacenar los valores obtenidos no es la única opción; también podremos realizar una asignación a variables previamente declaradas.
El siguiente código es equivalente al ejemplo anterior, con la salvedad de que en esta ocasión el deconstructor realiza una asignación a variables ya existentes:
string nombre = "";
string apellido = "";
(nombre, apellido) = ObtenerDatos();
Console.WriteLine($"Usuario con asignación: {nombre} {apellido}");
Otra posibilidad, disponible con C# 10 o posterior, es la capacidad de mezclar ambas opciones. Es decir, por un lado tendremos valores almacenados mediante la inicialización de variables, mientras que por otro estaremos realizando una asignación en variables preexistentes:
string nombre = "";
(nombre, string apellido) = ObtenerDatos();
Console.WriteLine($"Usuario con asignación y declaración: {nombre} {apellido}");
Deconstrucción en nuestras propias clases
Si queremos ir más allá de las tuplas y dotar de esta capacidad de deconstrucción a nuestras propias clases, también podemos hacerlo. Para eso bastará con añadir un método llamado Deconstruct
que no devuelva nada (void
). Además, tendrá "n" parámetros de tipo out
, que serán los que devuelvan los valores.
Tomemos como ejemplo una clase Alumno
con dos propiedades: Nombre
y Apellidos
. Bastaría con añadir un método Deconstruct
quedando como sigue:
public class Alumno
{
public string Nombre { get; set; }
public string Apellidos { get; set; }
public void Deconstruct(out string nombre, out string apellido)
{
nombre = Nombre;
apellido = Apellidos;
}
}
Posteriormente, si declaramos un objeto de tipo Alumno
, podremos llevar a cabo la deconstrucción, obteniendo en este caso dos variables con el nombre y el apellido que contiene el objeto:
Alumno alumno = new Alumno()
{
Nombre = "Rubén",
Apellidos = "Rubio"
};
var (nombre, apellido) = alumno;
Console.WriteLine($"Alumno deconstruído: {nombre} {apellido}");
Esto es muy útil en ocasiones porque nos permite desestructurar objetos de manera directa, con solo asignarlos, gracias a la deconstrucción.
Deconstrucción de clases ajenas mediante métodos de extensión
Pero ¿qué ocurre en el caso de que nos interese poder deconstruir de esta manera una clase que no sea nuestra, o sea de la que no tengamos acceso a modificar su código?
Por suerte podemos utilizar métodos de extensión, como en otros tantos sitios de la plataforma, para lograr el mismo resultado. En este caso creamos el método extensor Deconstruct
tomando como primer parámetro una clase que nos interesa, de la manera convencional para este tipo de métodos.
Por ejemplo, para deconstruir objetos de la clase DateOnly
y obtener sus miembros de manera independiente, podemos crear el siguiente método de extensión:
public static class Deconstructores
{
public static void Deconstruct(this DateOnly fecha, out int anho, out int mes, out int dia)
{
anho = fecha.Year;
mes = fecha.Month;
dia = fecha.Day;
}
}
Ahora podríamos utilizarlo de manera directa, así:
DateOnly elFuturo = new(2099,5,23);
(int anhoFuturo, int mesFuturo, int diaFuturo) = elFuturo;
Console.WriteLine($@"El año del futuro es {anhoFuturo},
en el mes {mesFuturo},
con el día {diaFuturo}");
que nos devolvería por pantalla la cadena:
El año del futuro es 2099,
en el mes 5,
con el día 23