Tupel (Visual Basic)

Ab Visual Basic 2017 bietet die Visual Basic-Sprache integrierte Unterstützung für Tupel, was die Erstellung von Tupeln und den Zugriff auf die Elemente von Tupeln erleichtert. Ein Tupel ist eine einfache Datenstruktur, die über eine bestimmte Anzahl und Sequenz von Werten verfügt. Beim Instanziieren des Tupels werden Anzahl und Datentyp jedes Werts (oder Elements) definiert. Ein 2-Tupel (oder Paar) hat zum Beispiel zwei Elemente. Das erste ist vielleicht ein Boolean-Wert und das zweite ein String. Da Tupel die unkomplizierte Speicherung mehrerer Werte in einem einzelnen Objekt ermöglichen, werden sie häufig verwendet, um auf einfache Weise mehrere Werte aus einer Methode zurückzugeben.

Wichtig

Für die Tupelunterstützung ist der Typ ValueTuple erforderlich. Falls .NET Framework 4.7 nicht installiert ist, muss das im NuGet-Katalog verfügbare NuGet-Paket System.ValueTuple hinzugefügt werden. Ohne dieses Paket tritt möglicherweise ein Kompilierungsfehler auf, in dem darauf hingewiesen wird, dass der vordefinierte Typ „ValueTuple(Of,,,)“ nicht definiert oder importiert wurde.

Instanziieren und Verwenden eines Tupels

Ein Tupel wird instanziiert, indem seine durch Kommas getrennten Werte in eine Klammer eingeschlossen werden. Jeder dieser Werte wird dann zu einem Feld des Tupels. Der folgende Code definiert z. B. ein Tripel (oder 3-Tupel) mit einem Date-, einem String- und einem Boolean-Wert (in dieser Reihenfolge).

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

Standardmäßig besteht der Name jedes Felds in einem Tupel aus der Zeichenfolge Item, gefolgt von der auf 1 basierenden Position des Felds im Tupel. Bei diesem 3-Tupel ist das Date-Feld Item1, das String-Feld ist Item2, und das Boolean-Feld ist Item3. Im folgenden Beispiel werden die Werte der Felder des Tupels angezeigt, das in der vorherigen Codezeile instanziiert wurde:

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

Die Felder eines Visual Basic-Tupels sind les- und schreibbar, und die Werte eines Tupels können nach dem Instanziieren geändert werden. Im folgenden Beispiel werden zwei der drei Felder des im vorherigen Beispiel erstellten Tupels geändert, und das Ergebnis wird angezeigt:

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

Instanziieren und Verwenden eines benannten Tupels

Anstatt Standardnamen für die Felder eines Tupels zu verwenden, können Sie auch ein benanntes Tupel instanziieren, indem Sie den Elementen des Tupels benutzerdefinierte Namen zuweisen. Auf die Felder des Tupels kann dann über die zugewiesenen Namen oder über die Standardnamen zugegriffen werden. Im folgenden Beispiel wird das gleiche 3-Tupel wie zuvor instanziiert. Diesmal werden die Felder aber explizit benannt. Das erste Feld erhält den Namen EventDate, das zweite den Namen Name und das dritte den Namen IsHoliday. Anschließend werden die Feldwerte angezeigt, geändert und die Feldwerte erneut angezeigt.

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

Sie können die Tupelnamen auch als Teil der Typdeklaration einer Variablen, eines Felds oder eines Parameters angeben:

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

Eine weitere Möglichkeit ist die Angabe im Rückgabetyp einer Methode.

Dies ist besonders nützlich, wenn Tupel für einen Auflistungsinitialisierer bereitgestellt werden. Die Tupelnamen können als Teil der Typdeklaration der Auflistung bereitgestellt werden:

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

Abgeleitete Tupelelementnamen

Ab Visual Basic 15.3 kann Visual Basic die Namen von Tupelelementen ableiten, sodass sie nicht explizit zugewiesen werden müssen. Abgeleitete Tupelnamen sind nützlich, wenn Sie ein Tupel über eine Gruppe von Variablen initialisieren und der Name des Tupelelements dem Variablennamen entsprechen soll.

Im folgenden Beispiel wird ein stateInfo-Tupel erstellt, das drei explizit benannte Elemente enthält: state, stateName und capital. Beachten Sie, dass die Tupelinitialisierungsanweisung aufgrund der Benennung der Elemente den benannten Elementen einfach die Werte der identisch benannten Variablen zuweist.

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

Da Elemente und Variablen den gleichen Namen haben, kann der Visual Basic-Compiler die Namen der Felder ableiten, wie im folgenden Beispiel gezeigt:

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

Um abgeleitete Namen von Tupelelementen zu aktivieren, müssen Sie in Ihrer Visual Basic-Projektdatei (VBPROJ-Datei) die zu verwendende Version des Visual Basic-Compilers definieren:

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

Die Versionsnummer kann eine beliebige Version des Visual Basic-Compilers ab der Version 15.3 sein. Anstatt eine bestimmte Compilerversion hart zu codieren, können Sie auch „Latest“ als Wert von LangVersion angeben, um für die Kompilierung die neueste Version des auf Ihrem System installierten Visual Basic-Compilers zu verwenden.

Weitere Informationen finden Sie unter Select the Visual Basic language version (Auswählen der Visual Basic-Sprachversion).

Manchmal kann der Visual Basic-Compiler den Namen eines Tupelelements nicht vom Kandidatennamen ableiten. In diesen Fällen kann nur über den Standardnamen (Item1, Item2 usw.) auf das Tupelfeld verwiesen werden. Dies ist in folgenden Szenarien der Fall:

  • Der Kandidatenname entspricht dem Namen eines Tupelelements (beispielsweise Item3, Rest oder ToString).

  • Der Kandidatenname wird im Tupel dupliziert.

Im Falle eines nicht erfolgreichen Feldnamenrückschlusses generiert Visual Basic keinen Compilerfehler, und es wird auch keine Ausnahme zur Laufzeit ausgelöst. Stattdessen muss auf Tupelfelder über den jeweils vordefinierten Namen wie Item1 und Item2 verwiesen werden.

Tupel im Vergleich zu Strukturen

Ein Visual Basic-Tupel ist ein Werttyp, bei dem es sich um eine Instanz eines der generischen System.ValueTuple-Typen handelt. Das im vorherigen Beispiel definierte Tupel holiday ist beispielsweise eine Instanz der Struktur ValueTuple<T1,T2,T3>. Es ist als einfacher Container für Daten konzipiert. Da das Tupel dazu dient, die einfache Erstellung eines Objekts mit mehreren Datenelementen zu ermöglichen, fehlen ihm einige der Features, die ggf. bei einer benutzerdefinierten Struktur zur Verfügung stehen. Dazu gehören:

  • Benutzerdefinierte Member. Für ein Tupel können keine eigenen Eigenschaften, Methoden oder Ereignisse definiert werden.

  • Validierung. Die den Feldern zugewiesenen Daten können nicht überprüft werden.

  • Unveränderlichkeit. Visual Basic-Tupel sind änderbar. Im Gegensatz dazu können Sie bei einer benutzerdefinierten Struktur steuern, ob eine Instanz änderbar oder unveränderlich sein soll.

Wenn Sie benutzerdefinierte Member, Eigenschafts- und Feldvalidierung oder Unveränderlichkeit benötigen, sollten Sie die Structure-Anweisung von Visual Basic verwenden, um einen benutzerdefinierten Werttyp zu definieren.

Ein Visual Basic-Tupel erbt die Member des zugehörigen ValueTuple-Typs. Hierzu zählen neben den zugehörigen Feldern die folgenden Methoden:

Methode BESCHREIBUNG
CompareTo Vergleicht das aktuelle Tupel mit einem anderen Tupel mit der gleichen Anzahl von Elementen.
Equals Überprüft, ob das aktuelle Tupel einem anderen Tupel oder Objekt entspricht.
GetHashCode Berechnet den Hashcode für die aktuelle Instanz.
ToString Gibt die Zeichenfolgendarstellung dieses Tupels im Format (Item1, Item2...) zurück, wobei Item1 und Item2 die Werte der Felder des Tupels darstellen.

Darüber hinaus werden von den ValueTuple-Typen Schnittstellen vom Typ IStructuralComparable und IStructuralEquatable implementiert, mit denen Sie benutzerdefinierte Vergleiche definieren können.

Zuweisung und Tupel

Visual Basic unterstützt die Zuweisung zwischen Tupeltypen, die über die gleiche Anzahl von Feldern verfügen. Die Feldtypen können konvertiert werden, wenn einer der folgenden Punkte zutrifft:

  • Quell- und Zielfeld sind vom gleichen Typ.

  • Eine erweiternde (oder implizite) Konvertierung des Quelltyps in den Zieltyp wurde definiert.

  • Option Strict ist On, und eine einschränkende (oder explizite) Konvertierung des Quelltyps in den Zieltyp wurde definiert. Bei dieser Konvertierung kann eine Ausnahme auftreten, wenn der Quellwert außerhalb des Bereichs des Zieltyps liegt.

Andere Konvertierungen gelten nicht für Zuordnungen. Sehen wir uns die Arten von Zuweisungen an, die zwischen Tupeltypen zulässig sind.

Berücksichtigen Sie diese Variablen, die in den folgenden Beispielen verwendet werden:

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

Bei den ersten beiden Variablen (unnamed und anonymous) wurden keine semantischen Namen für die Felder bereitgestellt. Die Felder haben die Standardnamen Item1 und Item2. Die letzten beiden Variablen (named und differentName) verfügen über semantische Feldnamen. Beachten Sie, dass diese zwei Tupel unterschiedliche Namen für die Felder besitzen.

Alle vier dieser Tupel haben die gleiche Anzahl von Feldern (was als Arität bezeichnet wird), und die Typen dieser Felder sind identisch. Daher funktionieren alle Zuweisungen:

' 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

Beachten Sie, dass die Namen der Tupel nicht zugewiesen sind. Die Werte der Felder werden nach der Reihenfolge der Felder im Tupel zugewiesen.

Beachten Sie außerdem, dass das Tupel named dem Tupel conversion zugewiesen werden kann, obwohl das erste Feld von named den Typ Integer und das erste Feld von conversion den Typ Long hat. Diese Zuweisung ist erfolgreich, da es sich bei der Konvertierung von Integer in Long um eine erweiternde Konvertierung handelt.

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

Tupel mit unterschiedlicher Anzahl von Feldern sind nicht zuweisbar:

' 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

Tupel als Methodenrückgabewert

Eine Methode kann nur einen einzelnen Wert zurückgeben. Oftmals sollen bei einem Methodenaufruf jedoch mehrere Werte zurückgegeben werden. Es gibt mehrere Möglichkeiten, diese Einschränkung zu umgehen:

  • Sie können eine benutzerdefinierte Klasse oder Struktur erstellen, deren Eigenschaften oder Felder Werte darstellen, die von der Methode zurückgegeben werden. Diese Lösung ist umständlich, da hierzu ein benutzerdefinierter Typ erstellt werden muss, dessen einziger Zweck darin besteht, Werte aus einem Methodenaufruf abzurufen.

  • Sie können einen einzelnen Wert von der Methode zurückgeben und die restlichen Werte zurückgeben, indem Sie sie durch Verweis auf die Methode übergeben. Hierzu muss zusätzlich eine Variable instanziiert werden, und es besteht die Gefahr, dass der Wert der Variablen, die Sie durch Verweis übergeben, versehentlich überschrieben wird.

  • Sie können ein Tupel als einfache Lösung zum Abrufen mehrerer Rückgabewerte verwenden.

Ein Beispiel: Die Methoden vom Typ TryParse in .NET geben einen Boolean-Wert zurück, der angibt, ob der Analysevorgang erfolgreich war. Das Ergebnis des Analysevorgangs wird in einer Variablen zurückgegeben, die durch Verweis an die Methode übergeben wird. Normalerweise sieht ein Aufruf einer Analysemethode wie Int32.TryParse wie folgt aus:

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

Wenn Sie den Aufruf der Methode Int32.TryParse mit Ihrer eigenen Methode umschließen, kann aus dem Analysevorgang ein Tupel zurückgegeben werden. Im folgenden Beispiel ruft NumericLibrary.ParseInteger die Methode Int32.TryParse auf und gibt ein benanntes Tupel mit zwei Elementen zurück:

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

Anschließend können Sie die Methode mit Code wie dem folgenden aufrufen:

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

Visual Basic-Tupel und Tupel in .NET Framework

Ein Visual Basic-Tupel ist eine Instanz eines der generischen System.ValueTuple-Typen, die in .NET Framework 4.7 eingeführt wurden. .NET Framework enthält auch eine Reihe generischer System.Tuple-Klassen. Diese Klassen unterscheiden sich jedoch in einigen Punkten von Visual Basic-Tupeln und den generischen System.ValueTuple-Typen:

  • Die Elemente der Klassen vom Typ Tuple sind Eigenschaften mit den Namen Item1, Item2 usw. In Visual Basic-Tupeln und den ValueTuple-Typen sind Tupelelemente Felder.

  • Den Elementen einer Instanz von Tuple oder ValueTuple können keine aussagekräftigen Namen zugewiesen werden. In Visual Basic können Sie Namen zuweisen, die Aufschluss über die Bedeutung der Felder geben.

  • Die Eigenschaften einer Instanz von Tuple sind schreibgeschützt. Die Tupel sind also unveränderlich. In Visual Basic-Tupeln und den ValueTuple-Typen sind Tupelfelder les- und schreibbar. Die Tupel sind also änderbar.

  • Die generischen Tuple-Typen sind Verweistypen. Bei der Verwendung dieser Tuple-Typen werden Objekte zugeordnet. Auf dem langsamsten Pfad kann das einen messbaren Einfluss auf die Leistung Ihrer Anwendungen haben. Visual Basic-Tupel und die ValueTuple-Typen sind Werttypen.

Erweiterungsmethoden in der Klasse TupleExtensions erleichtern die Konvertierung zwischen Visual Basic-Tupeln und .NET-Objekten vom Typ Tuple. Die Methode ToTuple konvertiert ein Visual Basic-Tupel in ein .NET-Objekt vom Typ Tuple, und die Methode ToValueTuple konvertiert ein .NET-Objekt vom Typ Tuple in ein Visual Basic-Tupel.

Im folgenden Beispiel wird ein Tupel erstellt, in ein .NET-Objekt vom Typ Tuple konvertiert und wieder in ein Visual Basic-Tupel konvertiert. Dieses Tupel wird dann mit dem ursprünglichen Tupel verglichen, um zu überprüfen, ob sie gleich sind.

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

Siehe auch