Introduction

This article provides simple examples for working with Tuples. In the Microsoft documentation for the new tuple the code samples are written towards experienced developer while the novice developer will generally not grasp possibilities for using the new tuples.


Description

Definition of Tuple prior to Visual Studio 2017

A tuple is a data structure that has a specific number and sequence of elements. An example of a tuple is a data structure with three elements (known as a 3-tuple or triple) that is used to store an identifier such as a person's name in the first element, a year in the second element, and the person's income for that year in the third element. The .NET Framework directly supports tuples with one to seven elements. In addition, you can create tuples of eight or more elements by nesting tuple objects in the Rest property of a Tuple(Of T1, T2, T3, T4, T5, T6, T7, TRest) object.

Although tuples had a place in writing code the results sent back from a method returning a tuple were not readily understood as each tuple returns as (where “result” is the tuple) result.ItemN where N is a value such as first name. So if a method using a tuple returned first and last name we would have result.Item1 for first name and result.Item2 for last name.

An alternate would be to create a simple class with two properties, FirstName and LastName, create an instance of the class and return the values so we would have result.FirstName and result.LastName.

Visual Studio 2017/VB.NET 15.4 Tuples

Visual Studio 2017, VB.NET 15.3 changed this by allowing a developer to create named values for tuples returning from a method call. When you instantiate the tuple, you define the number and the data type of each value (or element). For example, a 2-tuple (or pair) has two elements. For example, you have a strong typed list of Person class and want to return only first and last name you can write code as shown below.

Basic example

 

Visual Basic
Edit|Remove
Public Function FindPersonByIdentifierAsTuple(pIdentifier As Integer) As (FirstName As String, LastName As String) 
    Dim personData = People.FirstOrDefault(Function(person) person.Identifier = pIdentifier) 
  
    If personData Is Nothing Then 
        Return ("""") 
    Else 
        Return (personData.FirstName, personData.LastName) 
    End If 
  
End Function
Which would be called as follows
Visual Basic
Edit|Remove
Dim identifier As Integer = 3 
Dim ops = New Operations 
Dim results = ops.FindPersonByIdentifierAsTuple(identifier) 
  
If Not String.IsNullOrWhiteSpace(results.FirstName) Then 
    MessageBox.Show($"{results.FirstName} {results.LastName} for id of {identifier}") 
Else 
    MessageBox.Show($"Failed to locate a person with the id of {identifier}") 
End If
The quark here for some is checking to see if results.FirstName is an empty string which indicates the passed identifier was not located. Some might consider simple returning a class instance as shown below which is perfectly okay to do yet what about if the class Person has many properties and moving parts were one example might be a Entity Framework entity such as Person with Account members? In this case the returning container of the instance Person is heavy rather than light weight as in the tuple example shown above. 
Visual Basic
Edit|Remove
Public Function FindPersonByIdentifierAsPerson(pIdentifier As Integer) As Person 
    Return People.FirstOrDefault(Function(person) person.Identifier = pIdentifier) 
End Function

Anonymous type replacement

What is Anonymous type in VB.NET? An anonymous type is a class that contains one or more named values. Provides a quick and easy way to define simple classes for holding multiple values. Supports both mutable and immutable anonymous types. 
A perfect example for using the new tuple is writing a LINQ or Lambda query which returns an anonymous type, using named tuples a developer can return two or more values from a method as shown below where a character is passed in, a Lambda statement finds the character and returns the character, Occurrences of that character and the code for the character.
Visual Basic
Edit|Remove
Public Function CouldHaveBeenAnonymousResults(pChar As Char) As (Item As Char, Occurrences As Integer, Code As Integer) 
  
    Dim results = 
            ( 
                From T In 
                ( 
                    From c In "T0*A1?0*23aTA3 4T4\+a4 ?407#?A*6T+".ToCharArray() 
                    Group c By c Into Group Select New With 
                    { 
                        .Item = c, 
                        .Occurrences = Group.Count, 
                        .Code = Asc(c) 
                    } 
                ).ToList.OrderBy(Function(x) x.Item) 
            ).FirstOrDefault(Function(x) x.Item = pChar) 
  
  
    Return (results.Item, results.Occurrences, results.Code) 
  
End Function

Anonymous/Iterator/Yield example

An Anonymous function can be an iterator function Jump and can return a tuple. In the following example a DataTable is loaded with mocked data which would represent data returned from reading data from a database table. The task is to find duplicates by name and return the primary key. In GetDuplicatesByIdentifier a Lambda statement goes a GroupBy to get the duplicates where the variable duplicates is IEnumberable(Of 'a') which is an anonymous type with three properties, in this case we will return name and identifier in the iterator function via Yield statement Jump . In the calling method the name and identifier are placed into a second DataGridView to see the results. In a application the task might show this to the user and allow them to delete or not delete the duplicate rows.
Visual Basic
Edit|Remove
Public Class Form1 
    Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown 
        Dim dt = New DataTable With {.TableName = "MyTable"} 
  
        dt.Columns.Add(New DataColumn With {.ColumnName = "Identifier", 
                          .DataType = GetType(Int32), 
                          .AutoIncrement = True, .AutoIncrementSeed = 1, 
                          .ColumnMapping = MappingType.Hidden}) 
  
        dt.Columns.Add(New DataColumn With {.ColumnName = "Name", 
                          .DataType = GetType(String)}) 
  
        dt.Columns.Add(New DataColumn With {.ColumnName = "Status", 
                          .DataType = GetType(Boolean)}) 
  
        dt.Rows.Add(New Object() {Nothing, "Karen", False}) 
        dt.Rows.Add(New Object() {Nothing, "Karen", True}) 
        dt.Rows.Add(New Object() {Nothing, "Bill", True}) 
        dt.Rows.Add(New Object() {Nothing, "Karen", False}) 
        dt.Rows.Add(New Object() {Nothing, "Bill", True}) 
  
        DataGridView1.DataSource = dt 
  
    End Sub 
  
    Private Sub cmdExecute_Click(sender As Object, e As EventArgs) Handles cmdExecute.Click 
  
        DataGridView2.Rows.Clear() 
  
        For Each item In GetDuplicatesByIdendifier() 
            DataGridView2.Rows.Add(item.Identifer, item.Name) 
        Next 
  
    End Sub 
    Public Iterator Function GetDuplicatesByIdendifier() As IEnumerable(Of (Name As String, Identifer As Integer)) 
        Dim dt = CType(DataGridView1.DataSource, DataTable) 
  
        Dim duplicates = dt.AsEnumerable(). 
                GroupBy(Function(r) New With 
                           { 
                           Key .Name = CStr(r("Name")), 
                           Key .Status = r("Status"), 
                           .Identifier = CInt(r("Identifier")) 
                           }). 
                Where(Function(gr) gr.Count() > 1).Select(Function(g) g.Key) 
  
        For Each d In duplicates 
            Yield (d.Name, d.Identifier) 
        Next 
    End Function 
  
End Class
Another example using a iterator method using Entity Framework where a subset of data is needed. In this case the task is to return all countries with an identifier of 4 which is Brazil with the fields CustomerIdentifier (the primary key), CompanyName and ContactName where each field will have a different name then in the Entity model.  
Visual Basic
Edit|Remove
Public Class Form1 
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 
        Dim ops As New Operations 
        For Each customer In ops.CustomersByCountryIdentifier(4) 
            Console.WriteLine($"Id: {customer.Identifer} Name: {customer.Name} Contact: {customer.ContactName}") 
        Next 
    End Sub 
End Class 
Public Class Operations 
    Public Iterator Function CustomersByCountryIdentifier( 
        pCountryIdentifier As Integer) As IEnumerable(Of 
        (Identifer As Integer, Name As String, ContactName As String)) 
  
        Using context As New NorthWindEntities 
            Dim results = context.Customers. 
                Where(Function(cust) cust.CountryIdentfier.HasValue And 
                cust.CountryIdentfier.Value = pCountryIdentifier). 
                Select(Function(cust) New With 
                { 
                    .Id = cust.CustomerIdentifier, 
                    .Company = cust.CompanyName, 
                    .Contact = cust.ContactName 
                }) 
  
            For Each customer In results 
                Yield (customer.Id, customer.Company, customer.Contact) 
            Next 
        End Using 
    End Function 
End Class

Caveats

 
VB.NET tuples do not support discards Jump as in C# 7.


Summary

Using the new style tuple provides additional options for returning values from a function when more than one value needs to be returned and on the caller side the members of the tuple are easily understandable and strong typed. 


Other resources

Microsoft documentation on tuples Jump 

Source code

https://github.com/karenpayneoregon/VisualBasicNewTuples Jump 
In the source code there are three projects, one for demonstrating using a class instance to return data, the second for performing the same operation as the first project using named tuples while the third project demonstrates the alternate to working with anonymous types for dealing with returning information from a method. Note there are several classes that are there to show what could be returned if in the class example would be returned rather than using a light weight solution as with tuples.

Requires

From NuGet package manager in your solution, add System.ValueTuple Jump from the “Browse tab” or from NuGet console PM>Install-Package System.ValueTuple -Version 4.5.0