Code Focused
Types and Tuples in .NET 4
Visual Basic 10 introduces new generic tuple classes that can help you get more done with less -- if you're careful about it.
- By Bill McCarthy
- 12/01/2009
The Microsoft .NET Framework 4 library includes new generic tuple classes. Tuples are a group of properties that provide a means for you to easily group pieces of data together without having to write your own classes. There are tuples of different sizes, from a tuple with a single typed property, Tuple(Of T1), right up to tuples with eight or more typed values. For example, the abbreviated type definition for the double and triple tuples looks something like this:
Class Tuple(Of T1, T2)
ReadOnly Property Item1 As T1
ReadOnly Property Item2 As T2
...
End Class
Class Tuple(Of T1, T2, T3)
ReadOnly Property Item1 As T1
ReadOnly Property Item2 As T2
ReadOnly Property Item3 As T3
...
End Class
The triple tuple, Tuple(Of T1, T2, T3), allows you to group three different pieces of data of any type: you might store three strings, a string, and integer and a date, and so on. The name tuple is said to derive from the sequence: single, double, triple, quadruple, quintuple, sextuple, septuple, octuple, ... n-tuple. In programming, you'll find tuples in languages such as Python and F#, and now with their inclusion in mscorlib in .NET 4, you'll likely find them in Visual Basic (VB) and C# code in the years ahead.
Because the tuple's data properties are read-only, tuples in .NET are considered immutable. You have to create another tuple if you want to modify a value. To create a new tuple, you can specify the generic parameter types and pass in the data values to the constructor:
Dim person As New Tuple(Of String, String, Date) _
("John", "Citizen", #1/1/1980#)
A simpler syntax is to use the Create factory method on the tuple class and utilize VB's type inference:
Dim person = Tuple.Create("John", "Citizen", _
#1/1/1980#)
The simpler syntax of calling on Tuple.Create relies on type inference. Without type inference you'd have to write the method call using the explicit generic parameters, and in doing so negate any advantage the Shared factory method otherwise provides:
Dim person As Tuple(Of String, String, Date) =
tuple.Create("John", "Citizen", #1/1/1980#)
Note that even with Option Infer Off, you still aren't required to specify the generic parameters for the Create function call.
One problem with tuples is your code becomes less descriptive, because tuples employ non-descript property names such a person.Item1 and person.Item2. Often it will make more sense to quickly add a simple class definition to your project, and in Visual Studio 2010 creating simple classes has become a lot easier too.
Take the example of our person class with data fields of first name, last name and date of birth. In VB10 you can use Auto Properties to reduce the code you write:
Class Person
Property FirstName As String
Property LastName As String
Property DateOfBirth As Date
End Class
You no longer have to declare the backing field for a property or the property getter and setter blocks, just the property name and its type, and then you're good to go.
Auto properties have an important and significant difference in VB compared to C#. In both VB and C#, the compiler generates the backing field and the relevant get and set blocks for the property, but C# generates a name for the backing field that's illegal for you to write yourself in code, whereas VB allows you access to the backing field. Using the FirstName property as an example, in C# the backing field name would be <FirstName>k__BackingField, which is illegal to write due to the angle brackets, whereas VB generates a backing field named _FirstName.
The ability to access the backing field by name in VB is included in the language specification, so you can rest assured it's safe to add code that utilizes the backing field. A prime example of this is when adding a parameterized constructor to your class. In VB this is a straightforward and simple task:
Sub New(ByVal firstName As String, _
ByVal lastName As String, _
ByVal dateOfBirth As Date)
_FirstName = firstName
_lastName = lastName
_dateOfBirth = dateOfBirth
End Sub
To write the equivalent in C# you have to change all the auto properties into standard properties and declare the backing fields yourself, as well as add the code to set and get the backing fields inside the properties.
Also note how in the VB code, you no longer need the line continuation character, _, after commas in the parameter list. This makes formatting your code a lot easier. You can omit the line continuation character after opening parenthesis, commas and operators. For a complete list of where you can omit, see the "Continuing a Statement over Multiple Lines" sub topic in the "Statements in Visual Basic" help topic, which can be found online here.
The Visual Studio IDE also helps you to generate classes, their properties and constructors. If you write code that references a type that doesn't exist, the error correction will give you the option to generate the class:
Dim person As New _
Person(FirstName:="John",
LastName:="Citizen",
DateOfBirth:=#1/1/1980#)
The error correction offers to generate the Person class for you, including a parameterized constructor. The generated code is:
Class Person
Private _lastName As String
Private _dateOfBirth As Date
Private _firstName As String
Sub New(ByVal FirstName As String, _
ByVal LastName As String, _
ByVal DateOfBirth As Date)
_firstName = FirstName
_lastName = LastName
_dateOfBirth = DateOfBirth
End Sub
End Class
If you go back and modify your variable declaration to now use the With {. } syntax to initialize properties, the correction wizard will suggest adding the properties for you. Although you can quickly create a class by using the generate code feature, it's often as quick to write the properties yourself using either a snippet or the new Auto Properties syntax. Where the generate code feature really shines is when you add methods to a class and you don't want to stop and add the method or property yourself; rather, you want to focus on the current code flow. But use it cautiously when adding properties, as there are often other bits of code that need to be modified when you add a property, such as validation and equality.
Neither the generate code feature nor auto properties will help you when it comes to declaring read-only properties. To create an immutable class, you'll still need to write a couple of lines of code yourself in VS 2010.
To make your class the equivalent in features of the tuple classes, you'll also need to add equality, ToString and GetHashCode code based on your backing fields. The ToString method is handy for debugging and testing; the GetHashCode method is critical for sorting, comparison and dictionary-style storage. The Equals method should also be overridden to ensure you do a value equality based on the fields. Likewise, you need to implement IComparable and its CompareTo function to allow for sorting.
The tuple classes already have all this code in place. They even implement two new interfaces, IStructuralComparable and IStructuralEquatable. These interfaces provide the
CompareTo function with an extra IComparer parameter, and the Equals and GetHashCode functions with an IEqualityComparer parameter, allowing for custom sorting and equality tests when cast to the interface.
Writing all that code yourself is considerable work. At a minimum you should consider adding the GetHashCode override to your class. Even that one method is code you need to maintain, and this is when the generate-code features of Visual Studio can work against you. If you use the generate-code feature and it adds a property to your class, you can all too easily continue to code and forget that you need to open up that file and fix your GetHashCode method and other methods to include the new generated fields.
Tuple Benefits
When you weigh all things up, the tuple classes start looking good again despite their nondescript properties of Item1, Item2, Item3, and so on. The tuple classes are fixed: You don't need to maintain them. You also don't need to deal with issue around cross-machine boundaries and distributing type information because tuples are in mscorlib in the Microsoft .NET Framework 4. And there are even nice touches, such as having properties decorated with the TargetedPatchingOptOut attribute, hence allowing the properties to be in-lined across Native Image Generator images.
Without any doubt, using tuples can reduce the amount of code you have to write. When you combine them with multi-statement lambdas in VB10, you can radically reduce the amount of code you have to write. Listing 1 is the VB10 version of the code in my recent article on threading ("Threading and the UI," October 2009). The VB10 version is about half the code of the VB9 version. The use of tuples and lambdas completely removes the need for my PropertyChangedEventContext class. The tuple provides the basic data storage of event handler and synchronization context. The other thing needed is the ability to pass the event handler to the synchronization context's Post method; lambda functions-actually a lambda Sub-provide that means.
Although tuples may look simple and nondescript, they do provide strong typing and important comparison and equality functionality. Tuples are useful across method, class or even machine boundaries. Use them wisely, and they'll save you a lot of time with writing code and maintenance.
About the Author
Bill McCarthy is an independent consultant based in Australia and is one of the foremost .NET language experts specializing in Visual Basic. He has been a Microsoft MVP for VB for the last nine years and sat in on internal development reviews with the Visual Basic team for the last five years where he helped to steer the languageās future direction. These days he writes his thoughts about language direction on his blog at http://msmvps.com/bill.