(Print this page)

Using ValueTuple in .Net
Published date: Friday, May 22, 2020
On: Moer and Éric Moreau's web site

I was updating a library lately to their latest bits (PostSharp Cache if you are curious) and this new version started to return compilation warnings for code that was compiling for years. But when you really look at it, it totally makes sense. In short, I was using cache on a method returning an integer (a function) but that was also returning one of the arguments ByRef (ref in C#).

Time to find a workaround because I do not want to go back to a previous version, and I want my code to compile.

Available source code

Both VB and C# projects are provided this month.

The solution was created using Visual Studio 2019 but should also work in recent versions.

When you run the code, you will find the result in the Output window as shown in figure 1.

Figure 1: The results of the examples in the Output window

Definition

But before getting to far, what is a tuple? It can be defined as simply as a container of related data that can be used a group, passed to a function, or used a return type.

Wait a minute. Isn’t it also, to some extent, the definition of a structure or a class? Yes. But the tuples are much more light weight. They are also quite limited compared to full classes and/or structures, but they come in very handy when all you want is fields.

What kind of limitation? A Tuple only contains fields. Do not even try to add Properties, Methods, … and other members you would normally add to your classes or structures. That explains why they are so light weight.

Tuple versus ValueTuple

If you look at old articles, you might find some saying that tuples are immutable and read-only. These old articles will also tell that the various fields forming the tuple cannot be named. These limitations were too important for me to start using tuples when it was released a couple of years ago.

These limitations were lifted with the ValueTuple type. ValueTuple specifically were introduced in .Net Framework 4.7. They are now remarkably interesting.

And if you are using an older version of the .Net Framework, everything is not lost. You just have to add a NuGet reference to System.ValueTuple and you are all set (as long as you are targeting at least .Net Framework 4.5). This package is provided by Microsoft. It was ready to use but the timing to insert into the full framework was not good. So instead of waiting, they made it available through NuGet.

Examples of ValueTuple usage

Start by looking at this short snippet of code. Below this code section, you will find some explanation of what examples are trying to show.

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    '1- Declaring an implicit Unnamed tuple
    Dim vt1 = (1, "Eric")
    Trace.WriteLine($"Example 1 (reading from initialization): {vt1.Item1} - {vt1.Item2}")
    vt1.Item2 = "Eric Moreau"
    Trace.WriteLine($"Example 1 (reading after modification): {vt1.Item1} - {vt1.Item2}")
    Trace.WriteLine($"Example 1 (ToString): {vt1.ToString()}")

    '2- Declaring a Named tuple explicitely
    Dim vt2 As (ID As Integer, FirstName As String, LastName As String)
    vt2.ID = 2
    vt2.FirstName = "Eric"
    vt2.Item3 = "Moreau"
    Trace.WriteLine($"Example 2 (a named tuple): {vt2.ID} - {vt2.Item2} {vt2.LastName}")

    '3- inferring fields name
    Dim vt3 = (ID:=3, Description:="Description text")
    Trace.WriteLine($"Example 3 (inferred field names): {vt3.ID} - {vt3.Description}")

    '4- Passing a tuple to a method
    vt1.Item1 = 4
    MethodAcceptingTuple(4, vt1) 'look Ma! passing an unnamed tuple to a named one!

    '5- using a tuple as the return value of a method
    Dim vt5 = MethodReturningTuple()
    Trace.WriteLine($"Example 5 (a tuple returned by a method): {vt5.ID} - {vt5.Code}")

    '6- Assigning different named tuples
    vt5 = vt3
    Trace.WriteLine($"Example 6 (after affecting vt3 to vt5): {vt5.ID} - {vt5.Code}")
End Sub

Private Sub MethodAcceptingTuple(ByVal pFirstArg As Integer, ByVal pSecondArg As (ID As Integer, Code As String))
    Trace.WriteLine($"Example 4 (MethodAcceptingTuple): {pSecondArg.ID} - {pSecondArg.Code}")
End Sub

Private Function MethodReturningTuple() As (ID As Integer, Code As String)
    Dim vtReturn = (5, "Eric")
    Return vtReturn
End Function
private void Button1_Click(object sender, EventArgs e)
{
    //1- Declaring an implicit Unnamed tuple
    var vt1 = (1, "Eric");
    Trace.WriteLine($"Example 1 (reading from initialization): {vt1.Item1} - {vt1.Item2}");
    vt1.Item2 = "Eric Moreau";
    Trace.WriteLine($"Example 1 (reading after modification): {vt1.Item1} - {vt1.Item2}");
    Trace.WriteLine($"Example 1 (ToString): {vt1.ToString()}");

    //2- Declaring a Named tuple explicitely
    (int ID, string FirstName, string LastName) vt2;
    vt2.ID = 2;
    vt2.FirstName = "Eric";
    vt2.Item3 = "Moreau";
    Trace.WriteLine($"Example 2 (a named tuple): {vt2.ID} - {vt2.Item2} {vt2.LastName}");

    //3- inferring fields name
    var vt3 = (ID: 3, Description: "Description text");
    Trace.WriteLine($"Example 3 (inferred field names): {vt3.ID} - {vt3.Description}");

    //4- Passing a tuple to a method
    vt1.Item1 = 4;
    MethodAcceptingTuple(4, vt1); //look Ma! passing an unnamed tuple to a named one!

    //5- using a tuple as the return value of a method
    var vt5 = MethodReturningTuple();
    Trace.WriteLine($"Example 5 (a tuple returned by a method): {vt5.ID} - {vt5.Code}");

    //6- Assigning different named tuples
    vt5 = vt3;
    Trace.WriteLine($"Example 6 (after affecting vt3 to vt5): {vt5.ID} - {vt5.Code}");
}

private void MethodAcceptingTuple(int pFirstArg, (int ID, string Code) pSecondArg)
{
    Trace.WriteLine($"Example 4 (MethodAcceptingTuple): {pSecondArg.ID} - {pSecondArg.Code}");
}

private (int ID, string Code) MethodReturningTuple()
{
    var vtReturn = (5, "Eric");
    return vtReturn;
}

Example #1 is showing the simplest form of a tuple. By default, the fields of your ValueTuple will get the name Item followed by a one-based number indicating its position (i.e. Item1, Item2, Item3…). Between you and I, I really do not like this approach because Item1 is not helpful indicating the content. After having outputted the values to the Output window, I change the value of Item2 proving that ValueTuples are mutable. I also show the usage of the ToString method applied on the ValueTuple which can be useful while debugging.

Example #2 shows how to name the fields in your ValueTuple. I personally prefer to name my own fields. I could have assigned values at the same time I was declaring it, but I preferred to demonstrate another of assigning the values. Even if you are naming the fields in your ValueTuple objects, you can also continue to use Item1, Item2, … if you want (or if your code is already using unnamed fields).

Example #3 shows that the names of the fields can be inferred from the initialization.

Example #4 is showing how you pass a ValueTuple to a method. One thing you should notice here is that an unnamed ValueTuple is passed to a named one which is totally acceptable. This will work if the types of fields are matching (or on which a widening conversion can be applied).

Example #5 is showing a method returning a ValueTuple. Notice here that an unnamed ValueTuple is created (vtReturn) but because the signature is defining names in the return type, these field names are available to the caller (vt5).

Example #6 shows a situation in which it might be confusing. The vt3 variable (that contains fields ID and Description) is assigned to the vt5 variable (that contains compatible fields named ID and Code). If you want to use the fields, which names can you use? Of course, Item1 and Item2 will always work. Same for ID because it is a name found in both ValueTuples at the same position. But when you want to use the second field, will you use the name Description (from vt3) or Code (from vt5)? Or both names are good? The answer is simple, the names of the receiving ValueTuple dictates the names of the fields. In this example, we assign vt3 into vt5. That means that the names of the fields of vt5 are kept. Only the values from vt3 are kept (which is the most important part).

Conclusion

Yet another goodies from the .Net Framework for which you will find a lot of useful usage in your applications. It might offer a very niche usage but believe me, it is extremely useful.


(Print this page)