Sometimes you want 2 returned values: playing around with Tuples

When writing code in various languages, you’ll write functions from which you get a return value.

 

Sometimes you’ll want to get 2 return values. A common way to handle this is to use parameters to pass a variable by reference that will get one of the return values. Alternatively, you could use a Structure, Dictionary or Map or List, and pass that around but those are heavyweight.

 

A lighter way is to use the std::pair class (C++: example Use a Custom Allocator for your STL container ) or the KeyValuePair Generic Structure in VB or C#.

 

See also the Tuple class , new to CLR4, which can hold multiple values. You can even have a Dictionary with the Value being a Tuple.

 

 

        Dim resBoth = GetTwoResults()

        Dim theFirst = resBoth.Key

        Dim theSecond = resBoth.Value

 

 

    Function GetTwoResultsOneByref(ByRef theSecond As String) As String

        ' caller must have already declared theSecond

        theSecond = "two"

        Return "One"

    End Function

    Sub GetTwoResultsTwoByref(ByRef theFirst As String, ByRef theSecond As String)

        ' caller must have already declared both theFirst and theSecond

        theFirst = "One"

        theSecond = "two"

    End Sub

    Function GetTwoResults() As KeyValuePair(Of String, String)

        Dim FirstResult = "One"

        Dim SecondRes = "two"

        Dim dp As New KeyValuePair(Of String, String)(FirstResult, SecondRes)

        Return dp

    End Function

 

More things to play with:

 

    Function Get2Values() As Tuple(Of Integer, String)

        Return New Tuple(Of Integer, String)(11, "#of inches light travels in 1 nanosecond")

    End Function

    Function GetManyManyValues() As Dictionary(Of String, Tuple(Of Integer, Integer))

        Return New Dictionary(Of String, Tuple(Of Integer, Integer)) From

                {

                    {"one", New Tuple(Of Integer, Integer)(1, 2)},

                    {"two", New Tuple(Of Integer, Integer)(1, 2)}

                }

    End Function

I was writing some code using a Dictionary with an Enum as the key, and Tuple as the value.

However, sometimes not every Enum had a dictionary entry, so the code would throw an exception. So I wrote my own wrapper class of a Dictionary.

A sample is below: it uses the processes running on your machine as data, gets the number of threads per process, puts them arbitrarily into buckets. (Note: the “\” operator is integer division with truncation).

 

Then a summary of the buckets is created: count and size. However, not every bucket will have a value, so the dictionary will throw an exception.

A way to get around that is to create your own dictionary, handling the case where the key is absent in the overloaded default item property.

My first attempt is the MyBTDictNonGeneric below. It’s just a non-generic wrapper. Then I tried a generic approach.

 

Start Visual Studio 2010->File-New->Project->VB->WPF Application.

Switch to the MainWindow.xaml.vb file and replace the contents with the sample.

 

A Default Property is needed to create own dictionary which from which values can be returned

 

The “As New” is needed to constrain the generic to types that can have a New with no parameters.

'New' cannot be used on a type parameter that does not have a 'New' constraint

 

 

See also Use DataTemplates and WPF in code to create a general purpose LINQ Query results display

 

<code>

 

Class MainWindow

    Enum Buckets

        wee

        tiny

        small

        medium

        large

        larger

        grand

        super

        extralarge

        biggest

    End Enum

    Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        Me.Width = 1200

        Me.Height = 800

        Dim numBuckets = [Enum].GetNames(GetType(Buckets)).Length

        Dim queryProc = From proc In System.Diagnostics.Process.GetProcesses

                        Let ThrdCnt = proc.Threads.Count

                        Let Bucket = ThrdCnt \ numBuckets

                        Where Bucket < numBuckets

                        Select proc.ProcessName,

                                proc.WorkingSet64,

                                ThrdCnt,

                                BucketName = CType(Bucket, Buckets).ToString

        ' now accum results Count and Size per bucket

        Dim tots = From data In queryProc

                   Group By data.BucketName

                   Into Cnt = Count(), size = Sum(data.WorkingSet64)

        'put results into a dictionary keyed on bucket name

        Dim dict1 = New Dictionary(Of Buckets, Tuple(Of Integer, Long)) ' this won't work if values missing

        Dim dict2 = New MyBTDict(Of Buckets, MyTuple(Of Integer, Long))

        For Each datum In tots

            dict1.Add([Enum].Parse(GetType(Buckets), datum.BucketName), New Tuple(Of Integer, Long)(datum.Cnt, datum.size))

            dict2.Add([Enum].Parse(GetType(Buckets), datum.BucketName), New MyTuple(Of Integer, Long)(datum.Cnt, datum.size))

        Next

        Dim nTotCnt = 0

        Dim nTotSize As Long = 0

        Try ' enumerating all buckets: if there's no dict entry, will throw

            For Each bucket In [Enum].GetNames(GetType(Buckets))

                Dim tup = dict1([Enum].Parse(GetType(Buckets), bucket))

                nTotCnt += tup.Item1

                nTotSize += tup.Item2

            Next

        Catch ex As Exception

            MsgBox("Dict1 doesn't work " + ex.Message)

        End Try

        ' with our version of dict, no problem

        For Each bucket In [Enum].GetNames(GetType(Buckets))

          Dim tup = dict2([Enum].Parse(GetType(Buckets), bucket))

            nTotCnt += tup.Item1

            nTotSize += tup.Item2

        Next

        Dim sp As New StackPanel With {.Orientation = Orientation.Horizontal}

        sp.Children.Add(New Browse(queryProc))

        Dim q2 = From a In dict2

               Select Bucket = a.Key.ToString, a.Value.Item1, a.Value.Item2

        sp.Children.Add(New Browse(q2))

        Me.Content = sp

    End Sub

    Class MyTuple(Of t1 As New, t2 As New) 'need MyTuple, which has a Public New with no params

        Inherits Tuple(Of t1, t2)

        Sub New()

            MyBase.New(New t1, New t2)

        End Sub

        Sub New(ByVal p1 As t1, ByVal p2 As t2)

            MyBase.New(p1, p2)

        End Sub

  End Class

    Private Class MyBTDict(Of key, value As New) ' value must be constructable

        Inherits Dictionary(Of key, value)

        Default Overloads ReadOnly Property item(ByVal bt As key) As value

            Get

                Dim val As value = Nothing

                If Not Me.TryGetValue(bt, val) Then

                    val = New value

                End If

                Return val

            End Get

        End Property

    End Class

    Private Class MyBTDictNonGeneric ' or you could use this non-generic wrapper version

        Private _dict As New Dictionary(Of Buckets, Tuple(Of Integer, Long))

        Sub add(ByVal bt As Buckets, ByVal nCnt As Integer, ByVal nSize As Long)

            _dict.Add(bt, New Tuple(Of Integer, Long)(nCnt, nSize))

        End Sub

        Default ReadOnly Property MyDefault(ByVal bt As Buckets) As Tuple(Of Integer, Long)

            Get

                Dim tp As Tuple(Of Integer, Long) = Nothing

                If Not _dict.TryGetValue(bt, tp) Then

  tp = New Tuple(Of Integer, Long)(0, 0)

                End If

                Return tp

            End Get

        End Property

    End Class

End Class

'The Browse class: https://blogs.msdn.com/calvin\_hsia/archive/2007/11/15/6275106.aspx

Class Browse

    Inherits ListView

    Sub New(ByVal Query As Object, Optional ByVal Parent As Object = Nothing)

        Dim gv As New GridView

        Me.View = gv

        Me.ItemsSource = Query

        If Not Parent Is Nothing Then

            If Parent.GetType.BaseType Is GetType(Window) Then

                CType(Parent, Window).Title = "# items = " + Me.Items.Count.ToString

            End If

        End If

        Me.AddHandler(GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf HandleHeaderClick))

        For Each mem In From mbr In _

                        Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName) _

                            .GetGenericArguments()(0).GetMembers _

                        Where mbr.MemberType = Reflection.MemberTypes.Property

            Dim coltype = CType(mem, Reflection.PropertyInfo).PropertyType.Name

            Select Case coltype

                Case "Int32", "String", "Int64"

                    Dim gvc As New GridViewColumn

                    gvc.Header = mem.Name

                    gv.Columns.Add(gvc)

                    If coltype = "Int32" Then

                        gvc.Width = 80

                        Dim dt As New DataTemplate

                        Dim factSP = New FrameworkElementFactory(GetType(StackPanel))

                        dt.VisualTree = factSP

                        factSP.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal)

                        Dim factTb = New FrameworkElementFactory(GetType(TextBlock))

                        factTb.SetBinding(TextBlock.TextProperty, New Binding(mem.Name))

                        factTb.SetValue(TextBlock.FontWeightProperty, FontWeights.Bold)

                        ' factTb.SetValue(TextBlock.BackgroundProperty, Brushes.SpringGreen)

                        factSP.AppendChild(factTb)

                        'factTb = New FrameworkElementFactory(GetType(Button))

                        'factTb.SetValue(Button.ContentProperty, "hit me")

                        'factSP.AppendChild(factTb)

                        gvc.CellTemplate = dt

                    Else

                        gvc.DisplayMemberBinding = New Binding(mem.Name)

                        gvc.Width = 180

                    End If

            End Select

        Next

        Dim MyListboxStyle As New Style(GetType(ListBoxItem))

        Dim tr = New Trigger

        tr.Property = ListBoxItem.IsSelectedProperty ' when Selected

        tr.Value = True ' is true

     tr.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.Bisque))

        MyListboxStyle.Triggers.Add(tr)

        tr = New Trigger

        tr.Property = ListBoxItem.IsMouseOverProperty ' when mouseover

        tr.Value = True ' is true

        tr.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.Red))

        tr.Setters.Add(New Setter(ListBoxItem.BackgroundProperty, Brushes.Aquamarine)) ' this trigger sets both fore and back properties

        MyListboxStyle.Setters.Add(New Setter(ListBoxItem.ForegroundProperty, Brushes.RoyalBlue)) ' set style for all items

        MyListboxStyle.Triggers.Add(tr)

        Me.ItemContainerStyle = MyListboxStyle

    End Sub

    Dim _Lastdir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

    Dim _LastHeaderClicked As GridViewColumnHeader = Nothing

    Sub HandleHeaderClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If e.OriginalSource.GetType Is GetType(GridViewColumnHeader) Then

            Dim gvh = CType(e.OriginalSource, GridViewColumnHeader)

            Dim dir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

            If Not gvh Is Nothing AndAlso Not gvh.Column Is Nothing Then

                Dim hdr = gvh.Column.Header

                If gvh Is _LastHeaderClicked Then

                    If _Lastdir = ComponentModel.ListSortDirection.Ascending Then

                        dir = ComponentModel.ListSortDirection.Descending

                    End If

                End If

                Sort(hdr, dir)

                _LastHeaderClicked = gvh

                _Lastdir = dir

            End If

        End If

    End Sub

    Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)

        Me.Items.SortDescriptions.Clear()

        Dim sd = New System.ComponentModel.SortDescription(sortby, dir)

        Me.Items.SortDescriptions.Add(sd)

        Me.Items.Refresh()

    End Sub

End Class

</code>