Decostruzione di tuple e altri tipiDeconstructing tuples and other types

Una tupla è un metodo semplice per recuperare più valori da una chiamata a un metodo.A tuple provides a lightweight way to retrieve multiple values from a method call. Tuttavia dopo aver recuperato la tupla è necessario gestirne i singoli elementi.But once you retrieve the tuple, you have to handle its individual elements. Se eseguita un elemento alla volta, questa operazione può risultare molto laboriosa, come visualizzato nell'esempio seguente.Doing this on an element-by-element basis is cumbersome, as the following example shows. Il metodo QueryCityData restituisce una tupla con 3 elementi e ogni elemento viene assegnato a una variabile in un'operazione separata.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);
   }
}

Il recupero di più valori di campi e proprietà da un oggetto può essere altrettanto complesso: è necessario assegnare un valore di campo o proprietà a una variabile, un membro alla volta.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 partire da C# 7.0 è possibile recuperare più elementi da una tupla o recuperare più valori di campi, proprietà e valori calcolati da un oggetto in una singola operazione di decostruzione.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. Quando si decostruisce una tupla gli elementi corrispondenti vengono assegnati a singole variabili.When you deconstruct a tuple, you assign its elements to individual variables. Quando si decostruisce un oggetto si assegnano valori selezionati a singole variabili.When you deconstruct an object, you assign selected values to individual variables.

Decostruzione di una tuplaDeconstructing a tuple

In C# è incluso il supporto per la decostruzione di tuple, che consente di decomprimere tutti gli elementi di una tupla in un'unica operazione.C# features built-in support for deconstructing tuples, which lets you unpackage all the items in a tuple in a single operation. La sintassi generale per la decostruzione di una tupla è simile alla sintassi per la definizione della tupla: le variabili a cui va assegnato ogni elemento vengono racchiuse tra parentesi sul lato sinistro di un'istruzione di assegnazione.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. Ad esempio l'istruzione seguente assegna gli elementi di una tupla con 4 elementi a quattro variabili distinte:For example, the following statement assigns the elements of a 4-tuple to four separate variables:

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

Esistono tre modi per decostruire una tupla:There are three ways to deconstruct a tuple:

  • È possibile dichiarare in modo esplicito il tipo di ogni campo all'interno di parentesi.You can explicitly declare the type of each field inside parentheses. Nell'esempio seguente viene usato questo approccio per decostruire la tupla con 3 elementi restituita dal metodo 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.
    }
    
  • È possibile usare la parola chiave var in modo che C# deduca il tipo di ogni variabile.You can use the var keyword so that C# infers the type of each variable. Posizionare la parola chiave var all'esterno delle parentesi.You place the var keyword outside of the parentheses. Nell'esempio seguente viene usata l'inferenza del tipo questo approccio per decostruire la tupla con 3 elementi restituita dal metodo 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.
    }
    

    È anche possibile usare la parola chiave var individualmente con una o con tutte le dichiarazioni di variabili all'interno delle parentesi.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.
    }
    

    Questo approccio è eccessivamente complesso e non è consigliato.This is cumbersome and is not recommended.

  • Infine, è possibile decostruire la tupla in variabili che sono già state dichiarate.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.
    }
    

Si noti che non è possibile usare un tipo specifico all'esterno delle parentesi, anche se ogni campo nella tupla presenta lo stesso tipo.Note that you cannot specify a specific type outside the parentheses even if every field in the tuple has the same type. Questa operazione genera l'errore del compilatore CS8136 "Nel form di decostruzione 'var (...)' non è consentito un tipo specifico per 'var'".This generates compiler error CS8136, "Deconstruction 'var (...)' form disallows a specific type for 'var'.".

Si noti anche che è necessario assegnare ogni elemento della tupla a una variabile.Note that you must also assign each element of the tuple to a variable. Se si omette un elemento, il compilatore genera l'errore CS8132: "Non è possibile decostruire una tupla di 'x' elementi in 'y' variabili".If you omit any elements, the compiler generates error CS8132, "Cannot deconstruct a tuple of 'x' elements into 'y' variables."

Si noti che non è possibile combinare le dichiarazioni e le assegnazioni a variabili esistenti sul lato sinistro di una decostruzione.Note that you cannot mix declarations and assignments to existing variables on the left-hand side of a deconstruction. Questa operazione genera l'errore del compilatore CS8184: "Nella parte sinistra di una decostruzione non è possibile combinare dichiarazioni ed espressioni"The compiler generates error CS8184, "a deconstruction cannot mix declarations and expressions on the left-hand-side." quando i membri includono variabili esistenti e appena dichiarate.when the members include newly declared and existing variables.

Decostruzione degli elementi della tupla con variabili discardDeconstructing tuple elements with discards

Spesso quando si decostruisce una tupla si è interessati solo ai valori di alcuni elementi.Often when deconstructing a tuple, you're interested in the values of only some elements. A partire da C# 7.0 è possibile avvalersi del supporto in C# delle variabili discard, variabili di sola scrittura delle quali si è scelto di ignorare i valori.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. Una variabile discard è indicata da un carattere di sottolineatura ("_") in un'assegnazione.A discard is designated by an underscore character ("_") in an assignment. È possibile rimuovere il numero di valori desiderato; tutti sono rappresentati dalla variabile discard singola _.You can discard as many values as you like; all are represented by the single discard, _.

L'esempio seguente illustra l'uso delle tuple con le variabili discard.The following example illustrates the use of tuples with discards. Il metodo QueryCityDataForYears restituisce una tupla con 6 elementi con il nome di una città, l'area della città, un anno, la popolazione della città per tale anno, un secondo anno e la popolazione della città per tale anno.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. L'esempio visualizza la variazione della popolazione tra questi due anni.The example shows the change in population between those two years. Tra i dati resi disponibili dalla tupla non interessa l'area della città, mentre il nome della città e le due date sono già noti in fase di progettazione.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. Di conseguenza interessano soltanto i due valori di popolazione archiviati nella tupla, mentre gli altri valori possono essere gestiti come variabili discard.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

Decostruzione dei tipi definiti dall'utenteDeconstructing user-defined types

I tipi diversi dalla tupla non dispongono del supporto incorporato per le variabili discard.Non-tuple types do not offer built-in support for discards. Tuttavia l'autore di una classe, uno struct o un'interfaccia può consentire la decostruzione di istanze del tipo implementando uno o più metodi 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. Il metodo restituisce un valore void e ogni valore da decostruire è indicato da un parametro out nella firma del metodo.The method returns void, and each value to be deconstructed is indicated by an out parameter in the method signature. Ad esempio il seguente metodo Deconstruct di una classe Person restituisce il nome, il secondo nome e il cognome: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)

È quindi possibile decostruire un'istanza della classe Person denominata p con un'assegnazione simile alla seguente:You can then deconstruct an instance of the Person class named p with an assignment like the following:

var (fName, mName, lName) = p;

L'esempio che segue implementa l'overload del metodo Deconstruct per restituire varie combinazioni di proprietà di un oggetto Person.The following example overloads the Deconstruct method to return various combinations of properties of a Person object. I singoli overload restituiscono:Individual overloads return:

  • Un nome e un cognome.A first and last name.
  • Un nome, un secondo nome e un cognome.A first, last, and middle name.
  • Un nome, un cognome, un nome di città e un nome di stato.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()
   {
       Person 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!

Poiché è possibile eseguire l'overload del metodo Deconstruct in modo da riflettere gruppi di dati estratti comunemente da un oggetto, è importante definire metodi Deconstruct con firme uniche e non ambigue.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. Più metodi Deconstruct che hanno lo stesso numero di parametri out o lo stesso numero e tipo di parametri out in un ordine diverso possono provocare confusione.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.

Il metodo Deconstruct con overload dell'esempio seguente visualizza una possibile causa di confusione.The overloaded Deconstruct method in the following example illustrates one possible source of confusion. Il primo overload restituisce il nome, il secondo nome, il cognome e l'età di un oggetto Person in quest'ordine.The first overload returns the first name, middle name, last name, and age of a Person object, in that order. Il secondo overload restituisce solo le informazioni del nome oltre al reddito annuale, ma i dati relativi a nome, secondo nome e cognome sono in un ordine diverso.The second overload returns name information only along with annual income, but the first, middle, and last name are in a different order. In questo modo è più facile confondere l'ordine degli argomenti quando si esegue la decostruzione di un'istanza di 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;
      age = DateTime.Now.Year - DateOfBirth.Year;

      if (DateTime.Now.DayOfYear - (new DateTime(DateTime.Now.Year, DateOfBirth.Month, DateOfBirth.Day)).DayOfYear < 0)
         age--;
   }

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

Decostruzione di un tipo definito dall'utente con variabili discardDeconstructing a user-defined type with discards

Come per le tuple, è possibile usare le variabili discard per ignorare elementi selezionati restituiti da un metodo Deconstruct.Just as you do with tuples, you can use discards to ignore selected items returned by a Deconstruct method. Ogni variabile discard è definita da una variabile denominata _ e una singola operazione di decostruzione può includere diverse variabili discard.Each discard is defined by a variable named "_", and a single deconstruction operation can include multiple discards.

L'esempio seguente esegue la decostruzione di un oggetto Person in quattro stringhe (nome, cognome, città e stato) ma rimuove il cognome e lo stato.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!       

Decostruzione di un tipo definito dall'utente con un metodo di estensioneDeconstructing a user-defined type with an extension method

Anche un utente che non ha creato una classe, uno struct o un'interfaccia può eseguire la decostruzione di oggetti di questo tipo implementando uno o più Deconstruct metodi di estensione per restituire i valori che risultano di interesse.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.

L'esempio riportato di seguito illustra due metodi di estensione Deconstruct per la classe System.Reflection.PropertyInfo.The following example defines two Deconstruct extension methods for the System.Reflection.PropertyInfo class. Il primo metodo restituisce un set di valori che indicano le caratteristiche della proprietà: il tipo, se è statica o di istanza, se è di sola lettura e se è indicizzata.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. Il secondo indica l'accessibilità della proprietà.The second indicates the property's accessibility. Dato che l'accessibilità delle funzioni di accesso get e set può essere diversa, i valori booleani indicano se la proprietà ha funzioni di accesso get e set separate e in questo caso se tali funzioni presentano la stessa accessibilità.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. Se è presente solo una funzione di accesso o se le funzioni di accesso get e set hanno la stessa accessibilità, la variabile access indica l'accessibilità della proprietà nel suo complesso.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. In caso contrario l'accessibilità delle funzioni di accesso get e set è indicata dalle variabili getAccess e 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

Vedere ancheSee also