Deconstruir tuplas y otros tipos

Tip

Este artículo forma parte de la sección Aspectos básicos de los desarrolladores que ya conocen al menos un lenguaje de programación y están aprendiendo C#. Comience con la descripción general de la coincidencia de patrones si no está familiarizado con los patrones.

Una deconstrucción asigna las partes individuales de un valor (sus componentes ) a varias variables en una sola operación. Los componentes de una tupla son sus elementos, expuestos por posición. Otro tipo puede exponer componentes definiendo el método Deconstruct. Los registros posicionales, que declaran sus propiedades como parámetros similares al constructor, obtienen automáticamente un método Deconstruct.

Deconstruir tuplas

Supongamos que un método devuelve una tupla con datos de ciudad. Puede leer cada componente de uno en uno:

var cityData = QueryCityData("New York City");
var city = cityData.City;
var population = cityData.Population;
var area = cityData.Area;

Una desestructuración asigna esos componentes en un paso:

(string city, int population, double area) = QueryCityData("New York City");

También puede permitir que C# infiera los tipos de variable:

var (city, population, area) = QueryCityData("New York City");

Una deconstrucción puede mezclar variables existentes, variables recién declaradas y descartes en una asignación:

(city, var population, _) = QueryCityData("New York City");

Elija la forma que facilita la lectura del código. Un único var antes de los paréntesis suele ser la forma inferida más clara. También puede mezclar tipos explícitos y var entre paréntesis, pero esa forma suele ser más difícil de examinar. Si solo necesita algunos valores, use guiones bajos en lugar de omitir posiciones.

Omitir valores innecesarios con discards

Cada valor generado debe alinearse con una posición en el lado izquierdo de la instrucción de asignación. Cuando no necesite una o varias posiciones, use _ como marcador de descarte:

var (_, _, population1960, _, population2010) = QueryPopulationDataForYears(
    "New York City", 1960, 2010);

Aquí, la tupla devuelve el nombre de la ciudad, dos años y dos valores de población. La deconstrucción mantiene solo los valores de población porque el cálculo solo usa esos componentes.

Deconstruir tipos definidos por el usuario

Una clase, estructura o interfaz puede admitir la deconstrucción declarando un Deconstruct método. Cada componente se convierte en un out parámetro, que permite al método asignar un valor de nuevo a la variable del llamador sin devolverlo. Dado que todos los componentes se devuelven a través de un parámetro out, el propio método devuelve void:

public void Deconstruct(out string firstName, out string middleName, out string lastName)
{
    firstName = FirstName;
    middleName = MiddleName;
    lastName = LastName;
}

A continuación, puede deconstruir una instancia directamente:

var (firstName, middleName, lastName) = passenger;

Un tipo puede proporcionar varias Deconstruct sobrecargas con una aridad diferente (el número de out parámetros que declara el método), por lo que quien llama al método puede elegir cuántos componentes recuperar:

public void Deconstruct(out string firstName, out string lastName)
{
    firstName = FirstName;
    lastName = LastName;
}

public void Deconstruct(out string firstName, out string middleName, out string lastName)
{
    firstName = FirstName;
    middleName = MiddleName;
    lastName = LastName;
}

public void Deconstruct(out string firstName, out string lastName, out string city, out string state)
{
    firstName = FirstName;
    lastName = LastName;
    city = City;
    state = State;
}

Dos sobrecargas con el mismo número de out parámetros son ambiguas. El compilador notifica un error para la llamada ambigua, por lo que distinga las sobrecargas por aridad, no solo por tipos de parámetros.

Los descartes también funcionan con deconstrucción definida por el usuario. Para obtener más información sobre los elementos descartados en general, consulte Elementos descartados y el patrón de descarte:

var (firstName, _, city, _) = passenger;

Desglosar registros

Un registro posicional record o record struct declara sus propiedades como parámetros en la propia declaración de tipo, de forma similar a un constructor. El compilador genera un Deconstruct método automáticamente, con out parámetros que coinciden con esos parámetros posicionales:

var (city, highTempC, lowTempC) = forecast;

Solo los parámetros posicionales participan en esa deconstrucción generada. Las propiedades adicionales que declara en otro lugar del registro no se agregan automáticamente.

Desestructurar tipos que no controlas

Si no puede modificar un tipo, todavía puede admitir la deconstrucción escribiendo un método de extensión —un método estático que agrega un método Deconstruct a un tipo que no posee, como si fuera miembro de ese tipo—. Después de agregar el método, cualquier valor Uri puede usar la sintaxis de deconstrucción:

static class UriExtensions
{
    public static void Deconstruct(this Uri uri, out string scheme, out string host, out int port)
    {
        scheme = uri.Scheme;
        host = uri.Host;
        port = uri.Port;
    }
}

La misma regla de ambigüedad se aplica aquí: dos métodos de extensión Deconstruct con la misma aridad son ambiguos. La ambigüedad también puede surgir entre un método de instancia Deconstruct y un método de extensión de la misma aridad. En cualquier caso, el compilador notifica un error para la llamada ambigua.

deconstrucción integrada en tipos de sistema

Algunos tipos de sistema ya definen un método Deconstruct, mediante el mismo mecanismo que usarías para sus propios tipos. Por ejemplo, System.Collections.Generic.KeyValuePair<TKey,TValue> admite la deconstrucción, lo que hace que la iteración del diccionario sea concisa:

foreach (var (repo, commitCount) in repoCommitCounts)
{
    Console.WriteLine($"{repo} had {commitCount:N0} commits in this snapshot.");
}

Deconstrucción y coincidencia de patrones

Un Deconstruct método también habilita patrones posicionales para ese tipo. Un patrón posicional prueba y deconstruye un valor en un paso, utilizando la misma sintaxis entre paréntesis que una deconstrucción: person is ("Alice", 30) coincide con un Person cuyos componentes deconstruidos son iguales a esos valores. Esto es diferente de un patrón de propiedad, que comprueba las propiedades con nombre directamente, como person is { Name: "Alice", Age: 30 }. Los patrones de propiedad suelen ser más claros para las formas de objeto porque los nombres de miembro explican la prueba. Los patrones posicionales son más sólidos cuando el orden ya lleva el significado, como con tuplas u otros valores ordenados pequeños.

Consulte también