17. Jun 2019
Lesedauer 5 Min.
Mehr zu Tupeln
Werte mit Tupeln zusammenfassen, Teil 2
Die mit Visual Basic 15 eingeführten Tupel bündeln zusammengehörende Daten.

In der zurückliegenden Ausgabe der dotnetpro haben Sie die ersten Möglichkeiten des Programmierens mit Tupeln unter Visual Basic kennengelernt [1]. Wichtig: Sie benötigen dafür .NET ab Version 4.7 sowie Visual Studio 2017 oder 2019. Wichtigste Einschränkung: Tupel nehmen maximal acht Werte auf. Weiter geht’s jetzt mit der Option, Tupel in Listen zusammenzufassen.Mehrere Tupel mit gleichem Aufbau fassen Sie optional in Listen zusammen. Wie das geht, zeigt die Funktion GetAdressesList in Listing 1, die als Ergebnis eine Liste von Adress-Tupeln mit unbenannten Elementen zurückgibt. Dazu wird zunächst eine Liste angelegt, die dem Rückgabewert entspricht. Dieser Liste mit Namen tList werden dann drei Adress-Tupel hinzugefügt, um dann die Liste abschließend mit Return an das aufrufende Programm zu übergeben. Die folgenden Anweisungen zeigen den Aufruf der Funktion GetAdressesAsList, wobei der Zielvariablen tL der Datentyp durch die Funktion GetAdressesAsList übergeben wird.
Listing 1: GetAdressesAsList
<span class="hljs-keyword">Function</span> GetAdressesAsList() <span class="hljs-keyword">As</span> List(<span class="hljs-keyword">Of</span> Tuple( <br/> <span class="hljs-keyword">Of</span> <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">UInteger</span>,<br/> <span class="hljs-built_in">String</span>)) <br/><br/> <span class="hljs-keyword">Dim</span> tList <span class="hljs-keyword">As</span> <span class="hljs-keyword">New</span> List(<span class="hljs-keyword">Of</span> Tuple(<span class="hljs-keyword">Of</span> <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>,<br/> <span class="hljs-built_in">String</span>, <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">String</span>)) <br/> tList.Add(<span class="hljs-keyword">New</span> Tuple(<span class="hljs-keyword">Of</span> <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <br/> <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">String</span>)(<span class="hljs-string">"Peter"</span>, <span class="hljs-string">"Pan"</span>, <br/> <span class="hljs-string">"Goethestraße"</span>, <span class="hljs-number">234</span>, <span class="hljs-number">20323</span>, <span class="hljs-string">"Hamburg"</span>)) <br/> tList.Add(<span class="hljs-keyword">New</span> Tuple(<span class="hljs-keyword">Of</span> <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>,<br/> <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">String</span>)(<span class="hljs-string">"Maria"</span>, <span class="hljs-string">"Müller"</span>, <br/> <span class="hljs-string">"Schillerweg"</span>, <span class="hljs-number">76</span>, <span class="hljs-number">80545</span>, <span class="hljs-string">"München"</span>)) <br/> tList.Add(<span class="hljs-keyword">New</span> Tuple(<span class="hljs-keyword">Of</span> <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>, <br/> <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">UInteger</span>, <span class="hljs-built_in">String</span>)(<span class="hljs-string">"Klaus"</span>, <span class="hljs-string">"Schmidt"</span>, <br/> <span class="hljs-string">"Glockengasse"</span>, <span class="hljs-number">7</span>, <span class="hljs-number">30567</span>, <span class="hljs-string">"Hannover"</span>)) <br/><br/> <span class="hljs-keyword">Return</span> tList <br/><span class="hljs-keyword">End</span> <span class="hljs-keyword">Function</span>
' Datenrückgabe als änderbare Werteliste
Dim tL = GetAdressesAsList()
Dim n as String = vbCrLf ' Neue Zeile
Dim s as String = <span class="hljs-string">" "</span> ' Leerzeichen
MsgBox(
<span class="hljs-string">"Anzahl Elemente: "</span> & tL.Count & nZ & <span class="hljs-string">"1. Element: "</span> & tL(<span class="hljs-number">0</span>).Item1 & s & tL(<span class="hljs-number">0</span>).Item2 & n & <span class="hljs-string">"2. Element: "</span> & tL(<span class="hljs-number">1</span>).Item1 & s & tL(<span class="hljs-number">1</span>).Item2 & n & <span class="hljs-string">"3. Element: "</span> & tL(<span class="hljs-number">2</span>).Item1 & s & tL(<span class="hljs-number">2</span>).Item2, ...)
Tupellisten nachträglich bearbeiten
Mithilfe der Methode Add der Tupelliste tL können Sie weitere Adresselemente hinzufügen. Alle Vor- und Nachnamen der erweiterten Liste werden per Meldung angezeigt (Bild 1).
Ausgabeder erweiterten Tupel-Adressliste(Bild 1)
Autor
' Liste erweitern
tL.Add(New Tuple(Of String, String, String,
UInteger, UInteger, String)("Hans", "Friedrich",
"Bahnhoftstraße", 104, 60489, "Frankfurt"))
MsgBox(
"Anzahl Elemente: " & tL.Count & n & "1. Element: " & tL(0).Item1 & s & tL(0).Item2 & n & "2. Element: " & tL(1).Item1 & s & tL(1).Item2 & n & "3. Element: " & tL(2).Item1 & s & tL(2).Item2 & n & "4. Element: " & tL(3).Item1 & s & tL(3).Item2, ...)
Mit RemoveAt entfernen Sie ein beliebiges Listenelement über den angegebenen Index – in Bild 2 wurde das Element mit dem Index 1 (Maria Müller) entfernt und das Ergebnis per Dialog angezeigt.

Die gekürzteTupel-Adressliste(Bild 2)
Autor
' Liste um einen Eintrag kürzen
tL.RemoveAt(1)
MsgBox(
"Anzahl Elemente: " & tL.Count & n &
"1. Element: " & tL(0).Item1 & s & tL(0).Item2 & n &
"2. Element: " & tL(1).Item1 & s & tL(1).Item2 & n &
"3. Element: " & tL(2).Item1 & s & tL(2).Item2, ... )
Mit einer For-Each-Anweisung iterieren Sie durch die Elemente einer Tupelliste. Im folgenden Beispiel werden die Tupel nacheinander verarbeitet und per Dialog angezeigt.
Dim Counter As Integer = -1
For Each i As Tuple(Of String, String, String, UInteger,
UInteger, String) In tL
With i
Counter += 1
MsgBox("Element: " & Counter & n &
"Vorname: " & i.Item1 & n &
"Nachname: " & i.Item2 & n &
"Straße: " & i.Item3 & n &
"Hausnummer: " & i.Item4 & n &
"PLZ: " & i.Item5 & n & "Ort: " & i.Item6, ...)
End With
Next
Bleibt anzuführen, dass auch Listen bereits diverse Methoden bieten (zum Beispiel Sort), die hier aber nicht im Detail behandelt werden sollen.
Tupel bearbeiten, klonen und ändern
Tupel lassen sich bearbeiten und können kopiert oder geklont werden (als Wertekopie/-klon). Um das bereits für Maria Müller eingerichtete Tupel mit benannten Elementnamen zu kopieren, legen Sie eine neue Variable an (hier MariaMüller2) und weisen dieser den Wert von MariaMüller zu. Mit Equals ist der unmittelbare Vergleich der Tupel möglich. Wie erwartet, werden beide Inhalte als gleich eingestuft.
Dim MariaMüller2 = MariaMüller
If MariaMüller.Equals(MariaMüller2) Then
MsgBox("Die Tupel sind gleich!", ...)
Wollen Sie Elemente eines Tupels bearbeiten, weisen Sie den jeweiligen Elementnamen neue Werte zu. Im folgenden Beispiel werden zum Tupel MariaMüller2 die Daten zu Postleitzahl und Wohnort geändert. Ein Vergleich zeigt, dass keine Gleichheit mehr mit dem kopierten Tupel MariaMüller besteht.
' Werteänderungen
MariaMüller2.PLZ = 20323
MariaMüller2.Ort = "Hamburg"
If MariaMüller.Equals(MariaMüller2) Then
MsgBox("Die Tupel sind gleich!", ... )
Else
MsgBox("Die Tupel sind ungleich!", ...
End If
Setzen Sie den Wert eines Tupels auf Nothing, so werden lediglich die Werte, nicht aber die Elementnamen und deren Datentypen entfernt. Die nachfolgende MsgBox-Anweisung ist zulässig, da der Wert weiterhin existiert und die Elementanzahl weiterhin gültig ist, nur die Werte sind auf Nothing gesetzt. Dementsprechend liefert der Dialog keinen Inhalt zum abgefragten Nachnamen zurück.
MariaMüller2 = Nothing
MsgBox(MariaMüller2.Nachname)
Eine Wertzuweisung wie MariaMüller2 = t2 funktioniert dementsprechend nicht, da sich die Struktur, die Datentypen und auch die Elementanzahl eines Tupels (hier MariaMüller2) im Nachhinein nicht mehr ändern lassen.

Iterierter Datensatzeiner Tupel-Adressliste(Bild 3)
Autor
Tupel mit unbenannten Funktionsergebnissen
An dieser Stelle soll gezeigt werden, wie Sie Funktionen definieren, die Tupel mit mehreren unbenannten Elementen als Ergebnis zurückliefern. Der Einfachheit halber wird erneut der Aufbau des Adress-Tupels genutzt. Die Funktion GetAdressAsTupel bekommt einen Indexwert übergeben, über den ein Adressdatensatz gewählt wird. Dieser wird als Tupel angelegt und per Return zurückgegeben (Listing 2).Listing 2: GetAdressAsTupel
Function GetAdressAsTupel(<br/> ByVal Number) As (String, String, String, <br/> UInteger, UInteger, String) <br/> ' Rückgabe als Item1, Item2 usw... <br/> Select Case Number <br/> Case 1 <br/> Return ("Peter", "Pan", "Goethestraße", 234,<br/> 20323, "Hamburg") <br/> Case 2 <br/> Return ("Maria", "Müller", "Schillerweg", 76, <br/> 80545, "München") <br/> Case 3 <br/> Return ("Klaus", "Schmidt", "Glockengasse", 7, <br/> 30567, "Hannover") <br/> Case Else <br/> Return Nothing <br/> End Select <br/>End Function
GetAdressAsTupel2 zeigt die entsprechende Definition auszugsweise, jetzt aber mit dem benannten Datentyp ValueTuple und dem Of-Operator (Listing 3).
Listing 3: GetAdressAsTupel2 as ValueTuple
Function GetAdressAsTupel2(ByVal Number) As <br/> ValueTuple(Of String, String, String, UInteger, <br/> UInteger, String) <br/><br/> Select Case Number <br/> Case 1 <br/> Return ("Peter", "Pan", "Goethestraße", 234,<br/> 201323, "Hamburg") <br/> Case 2 <br/> ...<br/> Case Else <br/> Return Nothing <br/> End Select <br/>End Function
An dieser Stelle wird mit GetAdressAsTupel der Adressdatensatz mit dem Index 3 abgefragt und in die Variable MariaMüller2 übernommen. GetAdressAsTupel liefert unbenannte Elemente zurück und in MariaMüller2 sind entsprechend der ursprünglichen Kopie benannte Elementnamen angelegt. Dies erklärt, warum sich hier zur Werteausgabe im Meldungsdialog die benannten Elementnamen nutzen lassen.
MariaMüller2 = GetAdressAsTupel(3)
' dies funktioniert nur, weil die Elementnamen
' für MariaMüller2 weiterhing gültig sind
MsgBox("MariaMüller2 –
Vorname: " & MariaMüller2.Vorname & n &
"Nachname: " & MariaMüller2.Nachname & n &
"Straße: " & MariaMüller2.Straße & n &
"Hausnummer: " & MariaMüller2.Hausnummer & n &
"PLZ: " & MariaMüller2.PLZ & n &
"Ort: " & MariaMüller2.Ort, ...)
Richten Sie mit TestOhneElementNamen eine neue Variable ein und weisen Sie dieser das Ergebnis der Funktion GetAdressAsTupel2 zu, hier mit dem Indexwert 1, können Sie die Elementnamen für den Wertezugriff nicht verwenden, sondern müssen in jedem Fall die indizierten Item-Namen nutzen. Jede Änderung wird unmittelbar mit einer Fehlermeldung quittiert.

Die Prozedur ShowTupelAdress(Bild 4)
Autor
' neues Tupel, die Elementnamen sind nicht zugewiesen
Dim TestOhneElementNamen = GetAdressAsTupel2(1)
'dementsprechend ist eine Item-Angabe erforderlich
MsgBox("MariaMüller2 -
Vorname: " & TestOhneElementNamen.Item1 & n &
"Nachname: " & TestOhneElementNamen.Item2 & n &
"Straße: " & TestOhneElementNamen.Item3 & n &
"Hausnummer: " & TestOhneElementNamen.Item4 & n &
"PLZ: " & TestOhneElementNamen.Item5 & n &
"Ort: " & TestOhneElementNamen.Item6, ... )
Tupel mit benannten Funktionsergebnissen
Selbstverständlich können Sie Funktionen auch so codieren, dass diese Tupel mit benannten Elementnamen zurückliefern, siehe Listing 4.Listing 4: GetAdressNamedTupel
Function GetAdressAsNamedTupel(ByVal Number) As ( <br/> Vorname As String, Nachname As String, <br/> Straße As String, Hausnummer As UInteger, <br/> PLZ As UInteger, Ort As String) <br/><br/> Select Case Number <br/> Case 1 <br/> Return ("Peter", "Pan", "Goethestraße", 234,<br/> 201323, "Hamburg") <br/> Case 2 ...<br/> Case Else <br/> Return Nothing <br/> End Select <br/>End Function
Definieren Sie nun eine neue Variable TestMitElementNamen und weisen Sie dieser das Ergebnis der Funktion GetAdressAsNamedTuple zu, so werden die Elementnamen wie gewünscht wieder in die Zielvariable übernommen. Sie können also wieder gezielt per Name auf Elemente zugreifen und diese im Dialog ausgeben.
Dim TestMitElementNamen = GetAdressAsNamedTupel(3)
MsgBox("Klaus Schmidt -
Vorname: " & TestMitElementNamen.Vorname & n &
"Nachname: " & TestMitElementNamen.Item2 & n &
"Straße: " & TestMitElementNamen.Straße & n &
"Hausnummer: " & TestMitElementNamen.Item4 & n &
"PLZ: " & TestMitElementNamen.PLZ & n &
"Ort: " & TestMitElementNamen.Item6, ...)
Die letztgenannte Wertzuweisung können Sie auch in der folgenden Langfassung vornehmen:
Dim TestAdresseMitElementNamen As (Vorname As String,
Nachname As String, Straße As String,
Hausnummer As UInteger, PLZ As UInteger,
Ort As String) = GetAdressAsNamedTupel(3)
Tupel als Parameter
Bleibt zu zeigen, wie Sie Tupel an Funktionen oder Prozeduren übergeben. Die Funktion ShowTupelAdress übernimmt die Datenstruktur eines Adress-Tupels per Parameter und gibt diese aus.
Sub ShowTupelAdress(ByVal tAdresse As (
String, String, String, UInteger, UInteger, String))
'Typangabe erforderlich
Try
MsgBox(
"Vorname: " & tAdresse.Item1 & n &
"Nachname: " & tAdresse.Item2 & n &
"Straße: " & tAdresse.Item3 & n &
"Hausnummer: " & tAdresse.Item4 & n &
"PLZ: " & tAdresse.Item5 & n &
"Ort: " & tAdresse.Item6, ... )
Catch ex As Exception
MsgBox("Bei der Adressverarbeitung ist ein Fehler
aufgetreten!", ...)
End Try
End Sub
Das an die Prozedur übergebene Adress-Tupel muss diejenige Datenstruktur aufweisen, die auch in der Parameterliste definiert wurde. Dies ist bei der bereits definierten und initialisierten Variablen PeterPan sichergestellt.
ShowTupelAdress(PeterPan)
Geschachtelte Tupel
Eine Sonderfunktion soll abschließend noch vorgestellt werden, das Verschachteln von Tupeln. Zwar lassen sich so die über ein einzelnes Tupel verwaltbaren Werte theoretisch auf 64 Elemente (8 mal 8) heraufsetzen, sinnvoll ist das aufgrund der Lesbarkeit aber nicht. Bei diesem Verfahren werden innerhalb eines Tupels bis zu acht untergeordnete Tupel definiert. Das folgende Beispiel erzeugt mit Tupel.Create nur ein untergeordnetes Tupel. Der Elementzugriff erfolgt elementbezogen:
Dim ntVar = Tuple.Create(
"erstes Wertepaar", Tuple.Create(23, 45))
MsgBox(
"Wert Haupttupel: " & ntVar.Item1 & n &
"Untertupel 1. Wert: " & ntVar.Item2.Item1 & n &
" 2. Wert: " & ntVar.Item2.Item2, ... )
Anhand der Beispiele in dieser und der vorangegangenen Folge von Basic Instinct konnten Sie die kompakte Nutzung von Tupeln, die wichtigsten Einsatzmöglichkeiten, aber auch deren Schwachpunkte nachvollziehen. Prinzipiell machen Sie sich das Leben mit Tupeln einfacher, müssen dann aber auch in Kauf nehmen, dass die damit generierten Anwendungen zumindest .NET 4.7 erfordern. Lebensnotwenig sind Tupel generell nicht. Alles, was Sie mit Tupeln erreichen, realisieren Sie auch weiterhin – mitunter mit mehr Aufwand – auf den altbekannten Wegen.
Fussnoten
- Andreas Maslo, Werte mit Tupeln zusammenfassen, Teil 1, dotnetpro 6/2019, Seite 136 ff., http://www.dotnetpro.de/A1906BasicInstinct