Tuples (Visual Basic)

Depuis Visual Basic 2017, le langage Visual Basic intègre la prise en charge des tuples, facilitant ainsi la création de tuples et l’accès aux éléments qui les constituent. Un tuple est une structure de données légère qui possède un nombre et une séquence spécifiques de valeurs. Quand vous instanciez un tuple, vous définissez le nombre et le type de données de chaque valeur (ou élément). Par exemple, le tuple appelé « couple » a 2 éléments. Le premier peut être une valeur Boolean, tandis que le second est un type String. Les tuples facilitent le stockage de plusieurs valeurs dans un seul objet. Ils sont donc souvent utilisés comme un moyen léger de retourner plusieurs valeurs d’une méthode.

Important

La prise en charge des tuples nécessite le type ValueTuple. Si le .NET Framework 4.7 n’est pas installé, vous devez ajouter le package NuGet System.ValueTuple qui est disponible dans la galerie NuGet. Sans ce package, vous pouvez obtenir une erreur de compilation similaire à la suivante : « Le type prédéfini 'ValueTuple(Of,,,)' n’est pas défini ou importé ».

Instanciation et utilisation d’un tuple

Pour instancier un tuple, placez ses valeurs délimitées par des virgules entre parenthèses. Chacune de ces valeurs devient alors un champ du tuple. Par exemple, le code suivant définit un tuple à 3 éléments (triplet) avec Date comme première valeur, String comme deuxième valeur et Boolean comme troisième valeur.

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

Par défaut, le nom de chaque champ dans un tuple est composé de la chaîne Item et de la position du champ dans le tuple (base 1). Pour ce tuple à 3 éléments, le champ Date porte le nom Item1, le champ String le nom Item2 et le champ Boolean le nom Item3. L’exemple suivant affiche les valeurs des champs du tuple instancié dans la ligne de code précédente.

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

Les champs d’un tuple en Visual Basic sont accessibles en lecture-écriture. Après avoir instancié un tuple, vous pouvez modifier ses valeurs. L’exemple suivant modifie deux des trois champs du tuple créé dans l’exemple précédent et affiche le résultat.

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

Instanciation et utilisation d’un tuple nommé

Au lieu d’utiliser les noms par défaut pour les champs d’un tuple, vous pouvez instancier un tuple nommé en affectant vos propres noms aux éléments du tuple. Vous pouvez ensuite accéder aux champs du tuple en spécifiant le nom qui leur a été affecté ou leur nom par défaut. L’exemple suivant instancie le même tuple à 3 éléments que précédemment, mais en nommant explicitement le premier champ EventDate, le deuxième Name et le troisième IsHoliday. Il affiche ensuite les valeurs des champs, les modifie et affiche à nouveau les valeurs des champs.

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

Vous pouvez également spécifier les noms de tuples dans le cadre de la déclaration de type d’une variable, d’un champ ou d’un paramètre :

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

ou dans le type de retour d’une méthode.

Cela est particulièrement utile quand vous fournissez des tuples à un initialiseur de collection. Les noms de tuples peuvent être fournis dans le cadre de la déclaration de type de la collection :

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

Noms des éléments de tuple inférés

À compter de Visual Basic 15.3, Visual Basic peut déduire les noms des éléments de tuple. Vous n’êtes pas obligé de les affecter explicitement. La déduction des noms est utile quand vous initialisez un tuple à partir d’un ensemble de variables et que vous souhaitez que le nom de l’élément de tuple soit le même que le nom de la variable.

L’exemple suivant crée un tuple stateInfo contenant trois éléments explicitement nommés : state, stateName et capital. Notez que, en nommant les éléments, l’instruction d’initialisation de tuple affecte simplement aux éléments nommés les valeurs des variables portant le même nom.

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

Les éléments et les variables portant le même nom, le compilateur Visual Basic peut déduire les noms des champs, comme le montre l’exemple suivant.

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

Pour activer la déduction des noms d’éléments de tuple, vous devez définir la version du compilateur Visual Basic à utiliser dans votre fichier projet Visual Basic (*.vbproj) :

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

Vous pouvez utiliser n’importe quelle version du compilateur Visual Basic à compter de la version 15.3. Au lieu de coder en dur une version spécifique du compilateur, vous pouvez également spécifier « Latest » comme valeur de LangVersion pour compiler votre code avec la version la plus récente du compilateur Visual Basic installée sur votre système.

Pour plus d’informations, consultez Définir la version du langage Visual Basic.

Dans certains cas, le compilateur Visual Basic ne peut pas déduire le nom des éléments de tuple à partir du nom candidat. Les champs du tuple doivent alors être référencés avec leur nom par défaut (Item1, Item2, etc.). Ces cas sont les suivants :

  • Le nom candidat est identique au nom d’un membre de tuple, par exemple Item3, Rest ou ToString.

  • Le nom candidat est dupliqué dans le tuple.

En cas d’échec de la déduction du nom des champs, Visual Basic ne génère pas d’erreur au niveau du compilateur et aucune exception n’est levée au moment de l’exécution. Vous devez donc référencer les champs du tuple par leurs noms prédéfinis, comme Item1 et Item2.

Tuples et structures

Un tuple Visual Basic est un type valeur qui est une instance de l’un des types génériques System.ValueTuple. Par exemple, le tuple holiday défini dans l’exemple précédent est une instance de la structure ValueTuple<T1,T2,T3>. Il est conçu pour être un conteneur de données léger. Étant donné que le tuple vise à faciliter la création d’un objet avec plusieurs éléments de données, il lui manque certaines des fonctionnalités qu’une structure personnalisée pourrait avoir. Il s’agit notamment des paramètres suivants :

  • Membres personnalisés. Vous ne pouvez pas définir vos propres propriétés, méthodes ou événements pour un tuple.

  • Validation. Vous ne pouvez pas valider les données affectées aux champs.

  • Immuabilité. Les tuples Visual Basic sont mutables. En revanche, une structure personnalisée vous permet de contrôler si une instance est mutable ou immuable.

Si les membres personnalisés, la validation des propriétés et des champs ou encore l’immuabilité sont importants pour vous, utilisez l’instruction Visual Basic Structure pour définir un type valeur personnalisé.

Un tuple Visual Basic hérite des membres de son type ValueTuple. Il s’agit non seulement des champs, mais aussi des méthodes suivantes :

Méthode Description
CompareTo Compare le tuple actuel à un autre tuple avec le même nombre d’éléments.
Égal à Détermine si le tuple actuel est égal à un autre tuple ou objet.
GetHashCode Calcule le code de hachage pour l’instance actuelle.
ToString Retourne la représentation sous forme de chaîne de ce tuple, qui prend la forme (Item1, Item2...), où Item1 et Item2 représentent les valeurs des champs du tuple.

De plus, les types ValueTuple implémentent les interfaces IStructuralComparable et IStructuralEquatable qui vous permettent de définir des comparateurs personnalisés.

Affectation et tuples

Visual Basic prend en charge l’affectation entre les types de tuples qui ont le même nombre de champs. Les types de champ peuvent être convertis si l’une des conditions suivantes est vraie :

  • Les champs source et cible sont du même type.

  • Une conversion étendue (ou implicite) du type source en type cible est définie.

  • Option Strict est On, et une conversion restrictive (ou explicite) du type source en type cible est définie. Cette conversion peut lever une exception si la valeur source se trouve en dehors de la plage du type cible.

Les autres conversions ne sont pas prises en compte pour les affectations. Examinons les types d’affectation qui sont autorisés entre les types tuple.

Prenez en compte les variables utilisées dans les exemples suivants :

' 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")

Les deux premières variables, unnamed et anonymous, n’ont pas de noms sémantiques fournis pour les champs. Les champs portent les noms par défaut Item1 et Item2. Les deux dernières variables, named et differentName, ont des noms de champs sémantiques. Notez que ces deux tuples ont des noms différents pour les champs.

Ces quatre tuples ont le même nombre de champs (ce que l’on appelle « arité ») et les types de ces champs sont identiques. Par conséquent, toutes ces affectations fonctionnent :

' 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

Notez que les noms des tuples ne sont pas affectés. Les valeurs des champs sont affectées suivant l’ordre des champs dans le tuple.

Enfin, notez que nous pouvons affecter le tuple named au tuple conversion, même si le premier champ de named est un Integer et le premier champ de conversion est un Long. Cette affectation réussit, car la conversion d’un Integer en Long est une conversion étendue.

' 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)

Les tuples n’ayant pas le même nombre de champs ne sont pas attribuables :

' 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

Tuples comme valeurs de retour de méthode

Une méthode ne peut retourner qu’une seule valeur. Pourtant, il est courant d’avoir besoin d’un appel de méthode capable de retourner plusieurs valeurs. Il existe plusieurs façons de contourner cette limitation :

  • Vous pouvez créer une classe ou une structure personnalisée dont les propriétés ou les champs représentent les valeurs retournées par la méthode. Cette solution est lourde et nécessite que vous définissiez un type personnalisé dont le seul but est de récupérer des valeurs à partir d’un appel de méthode.

  • Vous pouvez retourner une seule valeur de la méthode et retourner les valeurs restantes en les passant par référence à la méthode. Cela implique une surcharge de travail liée à l’instanciation d’une variable, sans compter que vous courez le risque de remplacer par inadvertance la valeur de la variable passée par référence.

  • Un tuple constitue une solution légère pour récupérer plusieurs valeurs de retour.

Par exemple, les méthodes TryParse dans .NET retournent une valeur Boolean qui indique si l’opération d’analyse a réussi. Le résultat de l’opération d’analyse est retourné dans une variable passée par référence à la méthode. Normalement, un appel à une méthode d’analyse comme Integer.TryParse ressemble à ce qui suit :

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

Nous pouvons renvoyer un tuple de l’opération d’analyse si nous encapsulons l’appel à la méthode Integer.TryParse dans notre propre méthode. Dans l’exemple suivant, NumericLibrary.ParseInteger appelle la méthode Integer.TryParse et renvoie un tuple nommé avec deux éléments.

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

Vous pouvez ensuite appeler la méthode avec du code semblable à celui-ci :

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

Tuples Visual Basic et tuples dans le .NET Framework

Un tuple Visual Basic est une instance de l’un des types génériques System.ValueTuple qui ont été introduits dans le .NET Framework 4.7. Le .NET Framework inclut également un ensemble de classes System.Tuple génériques. Cependant, ces classes diffèrent des tuples Visual Basic et des types génériques System.ValueTuple à bien des égards :

  • Les éléments des classes Tuple sont des propriétés nommées Item1, Item2, etc. Dans les tuples Visual Basic et les types ValueTuple, les éléments de tuple sont des champs.

  • Vous ne pouvez pas affecter de noms significatifs aux éléments d’une instance Tuple ou d’une instance ValueTuple. Visual Basic vous permet d’affecter des noms qui communiquent la signification des champs.

  • Les propriétés d’une instance Tuple sont en lecture seule ; les tuples sont immuables. Dans les tuples Visual Basic et les types ValueTuple, les champs de tuple sont accessibles en lecture-écriture ; les tuples sont mutables.

  • Les types Tuple génériques sont des types référence. Utiliser ces types Tuple signifie allouer des objets. Sur des chemins réactifs, cela peut avoir un impact mesurable sur les performances de votre application. Les tuples Visual Basic et les types ValueTuple sont des types valeur.

Les méthodes d’extension de la classe TupleExtensions facilitent la conversion entre les tuples Visual Basic et les objets .NET Tuple. La méthode ToTuple convertit un tuple Visual Basic en objet .NET Tuple, tandis que la méthode ToValueTuple convertit un objet .NET Tuple en tuple Visual Basic.

L’exemple suivant crée un tuple, le convertit en objet .NET Tuple, puis le reconvertit en tuple Visual Basic. L’exemple compare ensuite ce tuple au tuple d’origine pour vérifier qu’ils sont égaux.

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

Voir aussi