Tuple (Visual Basic)

A partire da Visual Basic 2017, il linguaggio Visual Basic offre il supporto predefinito per le tuple che semplifica la creazione di tuple e l'accesso agli elementi delle tuple. Una tupla è una struttura di dati leggera con un numero specifico e una sequenza specifica di valori. Quando si crea un'istanza della tupla, si definiscono il numero e il tipo di dati di ogni valore (o elemento). Ad esempio, una tupla a 2 elementi (o coppia) ha due elementi. Il primo potrebbe essere un valore Boolean, mentre il secondo è un valore String. Poiché le tuple semplificano l'archiviazione di più valori in un singolo oggetto, vengono spesso usate come modo leggero per restituire più valori da un metodo.

Importante

Il supporto per le tuple richiede il tipo ValueTuple. Se .NET Framework 4.7 non è installato, è necessario aggiungere il pacchetto NuGet System.ValueTuple, disponibile nella raccolta NuGet. Senza questo pacchetto, è possibile che venga visualizzato un errore di compilazione simile al seguente: "Il tipo predefinito 'ValueTuple(Of,,,)' non è definito o importato".

Creazione di istanze e uso di una tupla

Per creare un'istanza di una tupla, racchiuderne i valori delimitati da virgole tra parentesi. Ognuno di questi valori diventa quindi un campo della tupla. Ad esempio, il codice seguente definisce un tripletto (o tupla a 3 elementi) con Date come primo valore, String come secondo e Boolean come terzo.

Dim holiday = (#07/04/2017#, "Independence Day", True)

Per impostazione predefinita, il nome di ogni campo in una tupla è costituito dalla stringa Item insieme alla posizione in base uno del campo nella tupla. Per questa tupla a 3 elementi, il campo Date è Item1, il campo String è Item2 e il campo Boolean è Item3. Nell'esempio seguente vengono visualizzati i valori dei campi della tupla di cui è stata creata un'istanza nella riga di codice precedente

Console.WriteLine($"{holiday.Item1} is {holiday.Item2}" +
                  $"{If(holiday.Item3, ", a national holiday", String.Empty)}")
' Output: 7/4/2017 12:00:00 AM Is Independence Day, a national holiday

I campi di una tupla di Visual Basic sono di lettura/scrittura. Dopo aver creato un'istanza di una tupla, è possibile modificarne i valori. L'esempio seguente modifica due dei tre campi della tupla creata nell'esempio precedente e visualizza il risultato.

holiday.Item1 = #01/01/2018#
holiday.Item2 = "New Year's Day"
Console.WriteLine($"{holiday.Item1} is {holiday.Item2}" +
                  $"{If(holiday.Item3, ", a national holiday", String.Empty)}")
' Output: 1/1/2018 12:00:00 AM Is New Year's Day, a national holiday

Creazione di un'istanza e uso di una tupla denominata

Anziché usare i nomi predefiniti per i campi di una tupla, è possibile creare un'istanza di una tupla denominata assegnando nomi personalizzati agli elementi della tupla. È quindi possibile accedere ai campi della tupla tramite i nomi assegnati o in base ai nomi predefiniti. Nell'esempio seguente viene creata un'istanza della stessa tupla a 3 elementi usata in precedenza, ad eccezione del fatto che il primo campo viene denominato esplicitamente EventDate, il secondo Namee il terzo IsHoliday. I valori dei campi vengono quindi visualizzati, modificati e visualizzati nuovamente.

Dim holiday = (EventDate:=#07/04/2017#, Name:="Independence Day", IsHoliday:=True)
Console.WriteLine($"{holiday.EventDate} Is {holiday.Name}" +
                  $"{If(holiday.IsHoliday, ", a national holiday", String.Empty)}")
holiday.Item1 = #01/01/2018#
holiday.Item2 = "New Year's Day"
Console.WriteLine($"{holiday.Item1} is {holiday.Item2}" +
                  $"{If(holiday.Item3, ", a national holiday", String.Empty)}")
' The example displays the following output:
'   7/4/2017 12:00:00 AM Is Independence Day, a national holiday
'   1/1/2018 12:00:00 AM Is New Year's Day, a national holiday

È anche possibile specificare i nomi delle tuple come parte della dichiarazione di tipo di una variabile, di un campo o di un parametro:

Dim holiday As (EventDate As Date, Name As String, IsHoliday As Boolean) =
    (#07/04/2017#, "Independence Day", True)
Console.WriteLine(holiday.Name)
' Output: Independence Day

Oppure nel tipo restituito di un metodo.

Ciò è particolarmente utile quando si forniscono tuple a un inizializzatore di raccolta. I nomi delle tuple possono essere forniti come parte della dichiarazione di tipo della raccolta:

Dim events As New List(Of (EventDate As Date, Name As String, IsHoliday As Boolean)) From {
    (#07/04/2017#, "Independence Day", True),
    (#04/22/2017#, "Earth Day", False)
}
Console.WriteLine(events(1).IsHoliday)
' Output: False

Nomi di elemento di tupla dedotti

A partire da Visual Basic 15.3, Visual Basic può dedurre i nomi degli elementi della tupla. Non è necessario assegnarli in modo esplicito. I nomi dedotti delle tuple sono utili quando si inizializza una tupla da un set di variabili e si vuole che il nome dell'elemento della tupla corrisponda al nome della variabile.

Nell'esempio seguente viene creata una tupla stateInfo contenente tre elementi denominati in modo esplicito, state, stateName e capital. Si noti che, nella denominazione degli elementi, l'istruzione di inizializzazione della tupla assegna semplicemente agli elementi denominati i valori delle variabili denominate in modo identico.

Const state As String = "MI"
Const stateName As String = "Michigan"
Const capital As String = "Lansing"
Dim stateInfo = (state:=state, stateName:=stateName, capital:=capital)
Console.WriteLine($"{stateInfo.stateName}: 2-letter code: {stateInfo.state}, Capital {stateInfo.capital}")
' The example displays the following output:
'      Michigan: 2-letter code: MI, Capital Lansing

Poiché gli elementi e le variabili hanno lo stesso nome, il compilatore di Visual Basic può dedurre i nomi dei campi, come illustrato nell'esempio seguente.

Const state As String = "MI"
Const stateName As String = "Michigan"
Const capital As String = "Lansing"
Dim stateInfo = (state, stateName, capital)
Console.WriteLine($"{stateInfo.stateName}: 2-letter code: {stateInfo.State}, Capital {stateInfo.capital}")
' The example displays the following output:
'      Michigan: 2-letter code: MI, Capital Lansing

Per abilitare i nomi degli elementi di tupla dedotti, è necessario definire la versione del compilatore di Visual Basic da usare nel file di progetto di Visual Basic (*.vbproj):

<PropertyGroup>
  <LangVersion>15.3</LangVersion>
</PropertyGroup>

Il numero di versione può essere qualsiasi versione del compilatore di Visual Basic a partire dalla versione 15.3. Anziché impostare come hardcoded una versione specifica del compilatore, è anche possibile specificare "Latest" come valore di LangVersion per eseguire la compilazione con la versione più recente del compilatore di Visual Basic installato nel sistema.

Per altre informazioni, vedere Impostazione della versione del linguaggio Visual Basic.

In alcuni casi il compilatore di Visual Basic non può dedurre il nome dell'elemento della tupla dal nome candidato ed è possibile fare riferimento al campo della tupla solo usando il nome predefinito, ad esempio Item1, Item2 e così via. Sono inclusi:

  • Il nome candidato corrisponde al nome di un membro della tupla, ad esempio Item3, Rest o ToString.

  • Il nome candidato viene duplicato nella tupla.

Quando l'inferenza del nome di campo ha esito negativo, Visual Basic non genera un errore del compilatore né viene generata un'eccezione in fase di esecuzione. È invece necessario fare riferimento ai campi della tupla in base ai nomi predefiniti, ad esempio Item1 e Item2.

Confronto tra tuple e strutture

Una tupla di Visual Basic è un tipo valore che è un'istanza di uno dei tipi generici di System.ValueTuple. Ad esempio, la tupla holiday definita nell'esempio precedente è un'istanza della struttura ValueTuple<T1,T2,T3>. È progettata per essere un contenitore leggero per i dati. Poiché la tupla mira a semplificare la creazione di un oggetto con più elementi di dati, non include alcune delle funzionalità che una struttura personalizzata potrebbe avere. tra cui:

  • Membri personalizzati. Non è possibile definire proprietà, metodi o eventi personalizzati per una tupla.

  • Convalida. Non è possibile convalidare i dati assegnati ai campi.

  • Immutabilità. Le tuple di Visual Basic sono modificabili. Al contrario, una struttura personalizzata consente di controllare se un'istanza è modificabile o non modificabile.

Se i membri personalizzati, la convalida delle proprietà e dei campi o l'immutabilità sono importanti, è necessario usare l'istruzione Structure di Visual Basic per definire un tipo di valore personalizzato.

Una tupla di Visual Basic eredita i membri del relativo tipo ValueTuple. Oltre ai rispettivi campi, questi includono i metodi seguenti:

metodo Descrizione
CompareTo Confronta la tupla corrente con un'altra tupla con lo stesso numero di elementi.
Uguale a Determina se la tupla corrente è uguale a un'altra tupla o un altro oggetto.
GetHashCode Calcola il codice hash per l'istanza corrente di .
ToString Restituisce la rappresentazione di stringa di questa tupla, con formato (Item1, Item2...), dove Item1 e Item2 rappresentano i valori dei campi della tupla.

I tipi ValueTuple implementano inoltre le interfacce IStructuralComparable e IStructuralEquatable, che consentono di definire operatori di confronto personalizzati.

Assegnazione e tuple

Visual Basic supporta l'assegnazione tra tipi di tupla con lo stesso numero di campi. I tipi di campo possono essere convertiti se una delle condizioni seguenti è vera:

  • Il campo di origine e di destinazione sono dello stesso tipo.

  • Viene definita una conversione che supporta un maggior numero di dati (o implicita) del tipo di origine nel tipo di destinazione.

  • Option Strict è On e viene definita una conversione che supporta un minor numero di dati (o esplicita) del tipo di origine nel tipo di destinazione. Questa conversione può generare un'eccezione se il valore di origine non è compreso nell'intervallo del tipo di destinazione.

Non sono considerate altre conversioni per le assegnazioni. Esaminiamo i tipi di assegnazioni consentiti tra i tipi di tupla.

Considerare le variabili usate negli esempi seguenti:

' The number and field types of all these tuples are compatible. 
' The only difference Is the field names being used.
Dim unnamed = (42, "The meaning of life")
Dim anonymous = (16, "a perfect square")
Dim named = (Answer:=42, Message:="The meaning of life")
Dim differentNamed = (SecretConstant:=42, Label:="The meaning of life")

Le prime due variabili, unnamed e anonymous, non hanno nomi semantici forniti per i campi. I nomi dei campi sono i valori predefiniti Item1 e Item2. Le ultime due variabili, named e differentName, hanno nomi di campo semantici. Si noti che queste due tuple presentano nomi diversi per i campi.

Tutte e quattro le tuple hanno lo stesso numero di campi (detto "arietà") e i tipi di tali campi sono identici. Pertanto, tutte queste assegnazioni funzionano:

' Assign named to unnamed.
named = unnamed

' Despite the assignment, named still has fields that can be referred to as 'answer' and 'message'.
Console.WriteLine($"{named.Answer}, {named.Message}")
' Output:  42, The meaning of life

' Assign unnamed to anonymous.
anonymous = unnamed
' Because of the assignment, the value of the elements of anonymous changed.
Console.WriteLine($"{anonymous.Item1}, {anonymous.Item2}")
' Output:   42, The meaning of life

' Assign one named tuple to the other.
named = differentNamed
' The field names are Not assigned. 'named' still has 'answer' and 'message' fields.
Console.WriteLine($"{named.Answer}, {named.Message}")
' Output:   42, The meaning of life

Si noti che non sono assegnati i nomi delle tuple. I valori dei campi vengono assegnati seguendo l'ordine dei campi nella tupla.

Si noti infine che è possibile assegnare la tupla named alla tupla conversion, anche se il primo campo di named è Integere il primo campo di conversion è Long. Questa assegnazione ha esito positivo perché la conversione di Integer in Long è una conversione che supporta un maggior numero di dati.

' Assign an (Integer, String) tuple to a (Long, String) tuple (using implicit conversion).
Dim conversion As (Long, String) = named
Console.WriteLine($"{conversion.Item1} ({conversion.Item1.GetType().Name}), " +
                  $"{conversion.Item2} ({conversion.Item2.GetType().Name})")
' Output:      42 (Int64), The meaning of life (String)

Le tuple con numeri diversi di campi non sono assegnabili:

' Does not compile.
' VB30311: Value of type '(Integer, Integer, Integer)' cannot be converted
'          to '(Answer As Integer, Message As String)'
var differentShape = (1, 2, 3)
named = differentShape

Tuple come valori restituiti dal metodo

Un metodo può restituire solo un singolo valore. Spesso, tuttavia, si vuole che una chiamata al metodo restituisca più valori. Esistono diversi modi per ovviare a questa limitazione:

  • È possibile creare una classe o una struttura personalizzata le cui proprietà o campi rappresentano i valori restituiti dal metodo. Questa è una soluzione complessa. Richiede di definire un tipo personalizzato il cui unico scopo è recuperare i valori da una chiamata al metodo.

  • È possibile restituire un singolo valore dal metodo e restituire i valori rimanenti passandoli per riferimento al metodo. Ciò comporta il sovraccarico della creazione di un'istanza di una variabile e rischia inavvertitamente di sovrascrivere il valore della variabile passato per riferimento.

  • È possibile usare una tupla, che offre una soluzione leggera per recuperare più valori restituiti.

Ad esempio, i metodi TryParse in .NET restituiscono un valore Boolean che indica se l'operazione di analisi è riuscita. Il risultato dell'operazione di analisi viene restituito in una variabile passata per riferimento al metodo. Una chiamata a un metodo di analisi, ad esempio Int32.TryParse, ha in genere un aspetto simile al seguente:

Dim numericString As String = "123456"
Dim number As Integer
Dim result = Integer.TryParse(numericString, number)
Console.WriteLine($"{If(result, $"Success: {number:N0}", "Failure")}")
'      Output: Success: 123,456

È possibile restituire una tupla dall'operazione di analisi se si esegue il wrapping della chiamata al metodo Int32.TryParse nel metodo personalizzato. Nell'esempio seguente NumericLibrary.ParseInteger chiama il metodo Int32.TryParse e restituisce una tupla denominata con due elementi.

Imports System.Globalization

Public Module NumericLibrary
    Public Function ParseInteger(value As String) As (Success As Boolean, Number As Integer)
        Dim number As Integer
        Return (Integer.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, number), number)
    End Function
End Module

È quindi possibile chiamare il metodo con codice simile al seguente:

Dim numericString As String = "123,456"
Dim result = ParseInteger(numericString)
Console.WriteLine($"{If(result.Success, $"Success: {result.Number:N0}", "Failure")}")
Console.ReadLine()
'      Output: Success: 123,456

Tuple di Visual Basic e tuple in .NET Framework

Una tupla di Visual Basic è un'istanza di uno dei tipi generici di System.ValueTuple, introdotti in .NET Framework 4.7. .NET Framework include anche un set di classi generiche di System.Tuple. Queste classi, tuttavia, differiscono dalle tuple di Visual Basic e dai tipi generici di System.ValueTuple in diversi modi:

  • Gli elementi delle classi Tuple sono proprietà denominate Item1, Item2 e così via. Nelle tuple di Visual Basic e nei tipi ValueTuple gli elementi della tupla sono campi.

  • Non è possibile assegnare nomi significativi agli elementi di un'istanza di Tuple o di un'istanza di ValueTuple. Visual Basic consente di assegnare nomi che comunicano il significato dei campi.

  • Le proprietà di un'istanza di Tuple sono di sola lettura. Le tuple non sono modificabili. Nelle tuple di Visual Basic e nei tipi ValueTuple i campi della tupla sono di lettura/scrittura. Le tuple sono modificabili.

  • I tipi Tuple generici sono tipi riferimento. L'uso di questi tipi Tuple comporta l'allocazione di oggetti. Nei percorsi critici, ciò può avere un impatto notevole sulle prestazioni dell'applicazione. Le tuple di Visual Basic e i tipi ValueTuple sono tipi valore.

I metodi di estensione nella classe TupleExtensions semplificano la conversione tra le tuple di Visual Basic e gli oggetti Tuple di .NET. Il metodo ToTuple converte una tupla di Visual Basic in un oggetto Tuple di .NET e il metodo ToValueTuple converte un oggetto Tuple di .NET in una tupla di Visual Basic.

L'esempio seguente crea una tupla, la converte in un oggetto Tuple di .NET e la converte nuovamente in una tupla di Visual Basic. L'esempio confronta quindi questa tupla con quella originale per assicurarsi che siano uguali.

Dim cityInfo = (name:="New York", area:=468.5, population:=8_550_405)
Console.WriteLine($"{cityInfo}, type {cityInfo.GetType().Name}")

' Convert the Visual Basic tuple to a .NET tuple.
Dim cityInfoT = TupleExtensions.ToTuple(cityInfo)
Console.WriteLine($"{cityInfoT}, type {cityInfoT.GetType().Name}")

' Convert the .NET tuple back to a Visual Basic tuple and ensure they are the same.
Dim cityInfo2 = TupleExtensions.ToValueTuple(cityInfoT)
Console.WriteLine($"{cityInfo2}, type {cityInfo2.GetType().Name}")
Console.WriteLine($"{NameOf(cityInfo)} = {NameOf(cityInfo2)}: {cityInfo.Equals(cityInfo2)}")

' The example displays the following output:
'       (New York, 468.5, 8550405), type ValueTuple`3
'       (New York, 468.5, 8550405), type Tuple`3
'       (New York, 468.5, 8550405), type ValueTuple`3
'       cityInfo = cityInfo2 :  True

Vedi anche