다음을 통해 공유


VB.NET: Essential Tuples

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.

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

 

Back to top

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.

 

Back to top

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.

 

Back to top

Basic Example

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

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.

Public Function  FindPersonByIdentifierAsPerson(pIdentifier As Integer) As  Person
 Return People.FirstOrDefault(Function(person) person.Identifier = pIdentifier)
End Function

 

Back to top

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.

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

 

Back to top

Anonymous/Iterator/Yield example

An Anonymous function can be an iterator function 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. 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.

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. 

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

 

Back to top

Caveats

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

 

Back to top

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. 

 

Back to top

Other resources

 

Back to top

Source code

https://github.com/karenpayneoregon/VisualBasicNewTuples

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.

 

Back to top

Requires

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

 

Back to top