Decostruire tuple e altri tipi

Tip

Questo articolo fa parte della sezione Nozioni fondamentali per gli sviluppatori che già conoscono almeno un linguaggio di programmazione e stanno imparando C#. Iniziate con la panoramica della corrispondenza dei modelli se non conoscete ancora i modelli.

Una decostruzione assegna le singole parti di un valore , i relativi componenti , a più variabili in un'unica operazione. I componenti di una tupla sono i relativi elementi, esposti in base alla posizione. Un altro tipo può esporre i componenti definendo un metodo Deconstruct. I record posizionali, che dichiarano le relative proprietà come parametri simili al costruttore, ottengono automaticamente un metodo Deconstruct.

Decostruire le tuple

Si supponga che un metodo restituisca una tupla con i dati della città. È possibile leggere ogni componente uno alla volta:

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

Una scomposizione assegna tali componenti in un unico passaggio:

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

È anche possibile consentire a C# di dedurre i tipi di variabile:

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

Una decostruzione può combinare variabili esistenti, variabili appena dichiarate e scarti in un'unica assegnazione:

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

Scegliere la forma che semplifica la lettura del codice. Un singolo var prima delle parentesi è spesso la forma implicita più chiara. È anche possibile combinare tipi espliciti e var all'interno delle parentesi, ma tale forma è in genere più difficile da analizzare. Se sono necessari solo alcuni valori, usare i discard invece di omettere posizioni.

Ignorare i valori non necessari usando gli scarti

Ogni valore prodotto deve essere allineato con una posizione sul lato sinistro dell'assegnazione. Quando non sono necessarie una o più posizioni, usare _ come scarto:

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

In questo caso, la tupla restituisce il nome della città, due anni e due valori di popolazione. La decostruzione mantiene solo i valori della popolazione perché il calcolo usa solo tali componenti.

Scomporre i tipi definiti dall'utente

Una classe, uno struct o un'interfaccia può supportare la decostruzione dichiarando un metodo Deconstruct. Ogni componente diventa un out parametro, che consente al metodo di assegnare un valore, tramite esso, alla variabile del chiamante senza restituirlo. Poiché ogni componente viene restituito tramite un out parametro, il metodo stesso restituisce void:

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

È quindi possibile decostruire direttamente un'istanza:

var (firstName, middleName, lastName) = passenger;

Un tipo può fornire più Deconstruct overload con arità diversa, ovvero il numero di out parametri dichiarati dal metodo, in modo che i chiamanti possano scegliere il numero di componenti da recuperare:

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;
}

Due overload con lo stesso numero di parametri out sono ambigui. Il compilatore segnala un errore per la chiamata ambigua, quindi occorre distinguere gli overload in base all'arità, non solo per i tipi di parametro.

Le variabili di scarto funzionano anche con la destrutturazione definita dall'utente. Per altre informazioni sui discards in generale, vedere Discards e il criterio discard:

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

Scomponi i record

Un record posizionale record o record struct dichiara le relative proprietà come parametri nella dichiarazione del tipo stesso, in modo simile a un costruttore. Il compilatore genera automaticamente un Deconstruct metodo con out parametri corrispondenti a tali parametri posizionali:

var (city, highTempC, lowTempC) = forecast;

Solo i parametri posizionali partecipano alla scomposizione generata. Le proprietà aggiuntive dichiarate altrove nel record non vengono aggiunte automaticamente.

Destrutturare i tipi che non possiedi

Se non è possibile modificare un tipo, è comunque possibile supportare la decostruzione scrivendo un metodo di estensione, ovvero un metodo statico che aggiunge Deconstruct a un tipo di cui non si è proprietari, come se fosse un membro di tale tipo. Dopo aver aggiunto il metodo, qualsiasi valore di tipo Uri può usare la sintassi di decostruzione:

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 stessa regola di ambiguità si applica qui: due metodi di estensione Deconstruct con la stessa arità sono ambigui. L'ambiguità può verificarsi anche tra un metodo di istanza Deconstruct e un metodo di estensione della stessa arità. In entrambi i casi, il compilatore segnala un errore per la chiamata ambigua.

Decostruzione integrata sui tipi di sistema

Alcuni tipi di sistema definiscono già un Deconstruct metodo, usando lo stesso meccanismo che si userà per i propri tipi. Ad esempio, System.Collections.Generic.KeyValuePair<TKey,TValue> supporta la decostruzione, che rende concisa l'iterazione del dizionario:

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

Decostruzione e corrispondenza dei pattern

Un metodo Deconstruct abilita anche gli schemi posizionali per quel tipo. Un modello posizionale verifica e decostruisce un valore in un unico passaggio, usando la stessa sintassi racchiusa tra parentesi di una decostruzione: person is ("Alice", 30) corrisponde a un Person i cui componenti decostruiti sono uguali a tali valori. Ciò è diverso da un pattern di proprietà, che verifica direttamente le proprietà denominate, ad esempio person is { Name: "Alice", Age: 30 }. I modelli di proprietà sono in genere più chiari per le strutture degli oggetti perché i nomi dei membri spiegano il test. I modelli posizionali sono più forti quando l'ordine porta già il significato, ad esempio con tuple o altri valori ordinati di piccole dimensioni.

Vedere anche