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: http://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>


 


 

Comments (2)

  1. Ilian says:

    Hi Calvin,

    What do you think about Remote Distributing Fox Applications using iSCSI?

    Just idea…

    Best.