Deconstruir tuplas y otros tiposDeconstructing tuples and other types

Una tupla proporciona una manera ligera de recuperar varios valores de una llamada de método.A tuple provides a lightweight way to retrieve multiple values from a method call. Pero una vez que recupere la tupla, deberá controlar sus elementos individuales.But once you retrieve the tuple, you have to handle its individual elements. Como se muestra en el ejemplo siguiente, es complicado hacerlo elemento a elemento.Doing this on an element-by-element basis is cumbersome, as the following example shows. El método QueryCityData devuelve una tupla de 3, y cada uno de sus elementos se asigna a una variable en una operación independiente.The QueryCityData method returns a 3-tuple, and each of its elements is assigned to a variable in a separate operation.

using System;

public class Example
{
    public static void Main()
    {
        var result = QueryCityData("New York City");

        var city = result.Item1;
        var pop = result.Item2;
        var size = result.Item3;

         // Do something with the data.
    }

    private static (string, int, double) QueryCityData(string name)
    {
        if (name == "New York City")
            return (name, 8175133, 468.48);

        return ("", 0, 0);
    }
}

También puede ser complicado recuperar varios valores de campo y propiedad de un objeto, ya que tendrá que asignar un valor de campo o propiedad a una variable miembro a miembro.Retrieving multiple field and property values from an object can be equally cumbersome: you have to assign a field or property value to a variable on a member-by-member basis.

A partir de C# 7.0, puede recuperar varios elementos de una tupla o recuperar varios valores de campo, de propiedad y calculados de un objeto en una sola operación deconstruct.Starting with C# 7.0, you can retrieve multiple elements from a tuple or retrieve multiple field, property, and computed values from an object in a single deconstruct operation. Cuando se deconstruye una tupla, sus elementos se asignan a variables individuales.When you deconstruct a tuple, you assign its elements to individual variables. Cuando se deconstruye un objeto, los valores seleccionados se asignan a variables individuales.When you deconstruct an object, you assign selected values to individual variables.

Deconstruir una tuplaDeconstructing a tuple

C# incluye compatibilidad integrada para deconstruir tuplas, lo que permite desempaquetar todos los elementos de una tupla en una sola operación.C# features built-in support for deconstructing tuples, which lets you unpackage all the items in a tuple in a single operation. La sintaxis general para deconstruir una tupla es parecida a la sintaxis para definirla, ya que las variables a las que se va a asignar cada elemento se escriben entre paréntesis en el lado izquierdo de una instrucción de asignación.The general syntax for deconstructing a tuple is similar to the syntax for defining one: you enclose the variables to which each element is to be assigned in parentheses in the left side of an assignment statement. Por ejemplo, la instrucción siguiente asigna los elementos de una tupla de 4 a cuatro variables independientes:For example, the following statement assigns the elements of a 4-tuple to four separate variables:

var (name, address, city, zip) = contact.GetAddressInfo();

Hay tres formas de deconstruir una tupla:There are three ways to deconstruct a tuple:

  • Se puede declarar explícitamente el tipo de cada campo entre paréntesis.You can explicitly declare the type of each field inside parentheses. En el ejemplo siguiente se usa este enfoque para deconstruir la tupla de 3 devuelta por el método QueryCityData.The following example uses this approach to deconstruct the 3-tuple returned by the QueryCityData method.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Puede usar la palabra clave var para que C# deduzca el tipo de cada variable.You can use the var keyword so that C# infers the type of each variable. Debe colocar la palabra clave var fuera de los paréntesis.You place the var keyword outside of the parentheses. En el ejemplo siguiente se usa la inferencia de tipos al deconstruir la tupla de 3 devuelta por el método QueryCityData.The following example uses type inference when deconstructing the 3-tuple returned by the QueryCityData method.

    public static void Main()
    {
        var (city, population, area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    

    También se puede usar la palabra clave var individualmente con alguna de las declaraciones de variable, o todas, dentro de los paréntesis.You can also use the var keyword individually with any or all of the variable declarations inside the parentheses.

    public static void Main()
    {
        (string city, var population, var area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    

    Esto es complicado y no se recomienda.This is cumbersome and is not recommended.

  • Por último, puede deconstruir la tupla en variables que ya se hayan declarado.Lastly, you may deconstruct the tuple into variables that have already been declared.

    public static void Main()
    {
        string city = "Raleigh";
        int population = 458880;
        double area = 144.8;
    
        (city, population, area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    

Tenga en cuenta que no se puede especificar un tipo determinado fuera de los paréntesis, aunque todos los campos de la tupla tengan el mismo tipo.Note that you cannot specify a specific type outside the parentheses even if every field in the tuple has the same type. Esto genera el error del compilador CS8136: “El formato de desconstrucción ‘var (...)’ no permite especificar un tipo determinado para ‘var’”.This generates compiler error CS8136, "Deconstruction 'var (...)' form disallows a specific type for 'var'.".

Tenga en cuenta que también debe asignar cada elemento de la tupla a una variable.Note that you must also assign each element of the tuple to a variable. Si se omite algún elemento, el compilador genera el error CS8132: "No se puede deconstruir una tupla de 'x' elementos en 'y' variables".If you omit any elements, the compiler generates error CS8132, "Cannot deconstruct a tuple of 'x' elements into 'y' variables."

Tenga en cuenta que no se pueden mezclar declaraciones y asignaciones en variables existentes en el lado izquierdo de una deconstrucción.Note that you cannot mix declarations and assignments to existing variables on the left-hand side of a deconstruction. El compilador genera el error CS8184: "Una deconstrucción no puede mezclar declaraciones y expresiones en el lado izquierdo",The compiler generates error CS8184, "a deconstruction cannot mix declarations and expressions on the left-hand-side." cuando los miembros incluyen variables existentes recién declaradas.when the members include newly declared and existing variables.

Deconstruir elementos de tupla con descartesDeconstructing tuple elements with discards

A menudo, cuando se deconstruye una tupla, solo interesan los valores de algunos elementos.Often when deconstructing a tuple, you're interested in the values of only some elements. A partir de C# 7.0, puede aprovechar la compatibilidad de C# con los descartes, que son variables de solo escritura cuyos valores se decide omitir.Starting with C# 7.0, you can take advantage of C#'s support for discards, which are write-only variables whose values you've chosen to ignore. Los descartes suelen designarse mediante un carácter de guion bajo ("_") en una asignación.A discard is designated by an underscore character ("_") in an assignment. Puede descartar tantos valores como quiera; todos se representan mediante el descarte único _.You can discard as many values as you like; all are represented by the single discard, _.

En el ejemplo siguiente se muestra el uso de tuplas con descartes.The following example illustrates the use of tuples with discards. El método QueryCityDataForYears devuelve una tupla de 6 con el nombre de una ciudad, su superficie, un año, la población de la ciudad en ese año, un segundo año y la población de la ciudad en ese segundo año.The QueryCityDataForYears method returns a 6-tuple with the name of a city, its area, a year, the city's population for that year, a second year, and the city's population for that second year. En el ejemplo se muestra la evolución de la población entre esos dos años.The example shows the change in population between those two years. De los datos disponibles en la tupla, no nos interesa la superficie de la ciudad, y conocemos el nombre de la ciudad y las dos fechas en tiempo de diseño.Of the data available from the tuple, we're unconcerned with the city area, and we know the city name and the two dates at design-time. Como resultado, solo nos interesan los dos valores de población almacenados en la tupla, y podemos controlar los valores restantes como descartes.As a result, we're only interested in the two population values stored in the tuple, and can handle its remaining values as discards.

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Deconstruir tipos definidos por el usuarioDeconstructing user-defined types

C# no ofrece compatibilidad integrada para deconstruir tipos que no son de tupla.C# does not offer built-in support for deconstructing non-tuple types. A pesar de ello, como autor de una clase, una estructura o una interfaz, puede permitir que las instancias del tipo se deconstruyan mediante la implementación de uno o varios métodos Deconstruct.However, as the author of a class, a struct, or an interface, you can allow instances of the type to be deconstructed by implementing one or more Deconstruct methods. El método no devuelve ningún valor, y cada valor que se va a deconstruir se indica mediante un parámetro out en la firma del método.The method returns void, and each value to be deconstructed is indicated by an out parameter in the method signature. Por ejemplo, el siguiente método Deconstruct de una clase Person devuelve el nombre de pila, el segundo nombre y los apellidos:For example, the following Deconstruct method of a Person class returns the first, middle, and last name:

public void Deconstruct(out string fname, out string mname, out string lname)

Después, puede deconstruir una instancia de la clase Person denominada p con una asignación similar a la siguiente:You can then deconstruct an instance of the Person class named p with an assignment like the following:

var (fName, mName, lName) = p;

En el ejemplo siguiente se sobrecarga el método Deconstruct para devolver varias combinaciones de las propiedades de un objeto Person.The following example overloads the Deconstruct method to return various combinations of properties of a Person object. Las sobrecargas individuales devuelven lo siguiente:Individual overloads return:

  • El nombre de pila y los apellidos.A first and last name.
  • El nombre de pila, los apellidos y el segundo nombre.A first, last, and middle name.
  • El nombre de pila, los apellidos, el nombre de la ciudad y el nombre del estado.A first name, a last name, a city name, and a state name.
using System;

public class Person
{
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    public Person(string fname, string mname, string lname,
                  string cityName, string stateName)
    {
        FirstName = fname;
        MiddleName = mname;
        LastName = lname;
        City = cityName;
        State = stateName;
    }

    // Return the first and last name.
    public void Deconstruct(out string fname, out string lname)
    {
        fname = FirstName;
        lname = LastName;
    }

    public void Deconstruct(out string fname, out string mname, out string lname)
    {
        fname = FirstName;
        mname = MiddleName;
        lname = LastName;
    }

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

public class Example
{
    public static void Main()
    {
        var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

        // Deconstruct the person object.
        var (fName, lName, city, state) = p;
        Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!");
    }
}
// The example displays the following output:
//    Hello John Adams of Boston, MA!

Dado que se puede sobrecargar el método Deconstruct para reflejar los grupos de datos que se suelen extraer de un objeto, debe tener cuidado y definir métodos Deconstruct con firmas que sean distintivas y no resulten ambiguas.Because you can overload the Deconstruct method to reflect groups of data that are commonly extracted from an object, you should be careful to define Deconstruct methods with signatures that are distinctive and unambiguous. Puede resultar confuso usar varios métodos Deconstruct que tengan el mismo número de parámetros out o el mismo número y tipo de parámetros out en un orden diferente.Multiple Deconstruct methods that have the same number of out parameters or the same number and type of out parameters in a different order can cause confusion.

El método Deconstruct sobrecargado del ejemplo siguiente muestra un posible motivo de confusión.The overloaded Deconstruct method in the following example illustrates one possible source of confusion. La primera sobrecarga devuelve el nombre de pila, el segundo nombre, los apellidos y la edad de un objeto Person, en ese orden.The first overload returns the first name, middle name, last name, and age of a Person object, in that order. La segunda sobrecarga devuelve información sobre el nombre acompañada únicamente de los ingresos anuales, pero el nombre de pila, el segundo nombre y los apellidos están en un orden diferente.The second overload returns name information only along with annual income, but the first, middle, and last name are in a different order. Esto hace que se pueda confundir fácilmente el orden de los argumentos cuando se deconstruye una instancia Person.This makes it easy to confuse the order of arguments when deconstructing a Person instance.

using System;

public class Person
{
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public DateTime DateOfBirth { get; set; }
    public Decimal AnnualIncome { get; set; }

    public void Deconstruct(out string fname, out string mname, out string lname, out int age)
    {
        fname = FirstName;
        mname = MiddleName;
        lname = LastName;

        // calculate the person's age
        var today = DateTime.Today;
        age = today.Year - DateOfBirth.Year;
        if (DateOfBirth.Date > today.AddYears(-age))
            age--;
    }

    public void Deconstruct(out string lname, out string fname, out string mname, out decimal income)
    {
        fname = FirstName;
        mname = MiddleName;
        lname = LastName;
        income = AnnualIncome;
    }
}

Deconstruir un tipo definido por el usuario con descartesDeconstructing a user-defined type with discards

Tal como haría con las tuplas, puede usar descartes para omitir los elementos seleccionados que haya devuelto un método Deconstruct.Just as you do with tuples, you can use discards to ignore selected items returned by a Deconstruct method. Cada descarte se define mediante una variable denominada "_". Una operación de deconstrucción única puede incluir varios descartes.Each discard is defined by a variable named "_", and a single deconstruction operation can include multiple discards.

En el siguiente ejemplo se deconstruye un objeto Person en cuatro cadenas (el nombre propio, los apellidos, la ciudad y el estado), pero se descartan los apellidos y el estado.The following example deconstructs a Person object into four strings (the first and last names, the city, and the state) but discards the last name and the state.

// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
//      Hello John of Boston!

Deconstruir un tipo definido por el usuario con un método de extensiónDeconstructing a user-defined type with an extension method

Aunque no haya creado una clase, una estructura o una interfaz por sí mismo, puede igualmente deconstruir objetos de ese tipo. Para ello, implemente uno o varios métodos de extensión Deconstruct que devuelvan los valores que le interesen.If you didn't author a class, struct, or interface, you can still deconstruct objects of that type by implementing one or more Deconstruct extension methods to return the values in which you're interested.

En el ejemplo siguiente se definen dos métodos de extensión Deconstruct para la clase System.Reflection.PropertyInfo.The following example defines two Deconstruct extension methods for the System.Reflection.PropertyInfo class. El primero devuelve un conjunto de valores que indican las características de la propiedad, incluido su tipo, si es estática o de instancia, si es de solo lectura y si está indexada.The first returns a set of values that indicate the characteristics of the property, including its type, whether it's static or instance, whether it's read-only, and whether it's indexed. El segundo indica la accesibilidad de la propiedad.The second indicates the property's accessibility. Dado que la accesibilidad de los descriptores de acceso get y set puede diferir, los valores booleanos indican si la propiedad tiene descriptores de acceso get y set independientes y, si es así, si tienen la misma accesibilidad.Because the accessibility of get and set accessors can differ, Boolean values indicate whether the property has separate get and set accessors and, if it does, whether they have the same accessibility. Si solo hay un descriptor de acceso, o si los descriptores de acceso get y set tienen la misma accesibilidad, la variable access indica la accesibilidad de la propiedad en conjunto.If there is only one accessor or both the get and the set accessor have the same accessibility, the access variable indicates the accessibility of the property as a whole. En caso contrario, la accesibilidad de los descriptores de acceso get y set se indica mediante las variables getAccess y setAccess.Otherwise, the accessibility of the get and set accessors are indicated by the getAccess and setAccess variables.

using System;
using System.Collections.Generic;
using System.Reflection;

public static class ReflectionExtensions
{
    public static void Deconstruct(this PropertyInfo p, out bool isStatic,
                                   out bool isReadOnly, out bool isIndexed,
                                   out Type propertyType)
    {
        var getter = p.GetMethod;

        // Is the property read-only?
        isReadOnly = ! p.CanWrite;

        // Is the property instance or static?
        isStatic = getter.IsStatic;

        // Is the property indexed?
        isIndexed = p.GetIndexParameters().Length > 0;

        // Get the property type.
        propertyType = p.PropertyType;
    }

    public static void Deconstruct(this PropertyInfo p, out bool hasGetAndSet,
                                   out bool sameAccess, out string access,
                                   out string getAccess, out string setAccess)
    {
        hasGetAndSet = sameAccess = false;
        string getAccessTemp = null;
        string setAccessTemp = null;

        MethodInfo getter = null;
        if (p.CanRead)
            getter = p.GetMethod;

        MethodInfo setter = null;
        if (p.CanWrite)
            setter = p.SetMethod;

        if (setter != null && getter != null)
            hasGetAndSet = true;

        if (getter != null)
        {
            if (getter.IsPublic)
                getAccessTemp = "public";
            else if (getter.IsPrivate)
                getAccessTemp = "private";
            else if (getter.IsAssembly)
                getAccessTemp = "internal";
            else if (getter.IsFamily)
                getAccessTemp = "protected";
            else if (getter.IsFamilyOrAssembly)
                getAccessTemp = "protected internal";
        }

        if (setter != null)
        {
            if (setter.IsPublic)
                setAccessTemp = "public";
            else if (setter.IsPrivate)
                setAccessTemp = "private";
            else if (setter.IsAssembly)
                setAccessTemp = "internal";
            else if (setter.IsFamily)
                setAccessTemp = "protected";
            else if (setter.IsFamilyOrAssembly)
                setAccessTemp = "protected internal";
        }

        // Are the accessibility of the getter and setter the same?
        if (setAccessTemp == getAccessTemp)
        {
            sameAccess = true;
            access = getAccessTemp;
            getAccess = setAccess = String.Empty;
        }
        else
        {
            access = null;
            getAccess = getAccessTemp;
            setAccess = setAccessTemp;
        }
    }
}

public class Example
{
    public static void Main()
    {
        Type dateType = typeof(DateTime);
        PropertyInfo prop = dateType.GetProperty("Now");
        var (isStatic, isRO, isIndexed, propType) = prop;
        Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name} property:");
        Console.WriteLine($"   PropertyType: {propType.Name}");
        Console.WriteLine($"   Static:       {isStatic}");
        Console.WriteLine($"   Read-only:    {isRO}");
        Console.WriteLine($"   Indexed:      {isIndexed}");

        Type listType = typeof(List<>);
        prop = listType.GetProperty("Item",
                                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
        var (hasGetAndSet, sameAccess, accessibility, getAccessibility, setAccessibility) = prop;
        Console.Write($"\nAccessibility of the {listType.FullName}.{prop.Name} property: ");

        if (!hasGetAndSet | sameAccess)
        {
            Console.WriteLine(accessibility);
        }
        else
        {
            Console.WriteLine($"\n   The get accessor: {getAccessibility}");
            Console.WriteLine($"   The set accessor: {setAccessibility}");
        }
    }
}
// The example displays the following output:
//       The System.DateTime.Now property:
//          PropertyType: DateTime
//          Static:       True
//          Read-only:    True
//          Indexed:      False
//
//       Accessibility of the System.Collections.Generic.List`1.Item property: public

Vea tambiénSee also