Returning and Consuming Tuples
Jul 31, 2018 • 8 Minute Read
Returning and Consuming Tuples
In this guide, we are going to look at how to create tuples in C#, how to return them from methods and how to consume them elsewhere in code. We will be looking at both of the older mechanisms for creating tuples before we look at language enhancements that make tuples feel like a more integrated syntax feature.
Tuples were introduced in .NET 4.0 and have recently been upgraded in C# 7 to be even easier to work with.
What Is a Tuple?
In order to get an understanding of what a Tuple is we are going to start with a simple program that creates a Person class that holds the name, date of birth, and age of a person.
using System;
namespace tuplesample
{
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Jonathon Pluralsight";
person.DateOfBirth = new DateTime(1991, 07, 27);
person.Age = CalculateAge(person.DateOfBirth);
Console.WriteLine($"{person.Name} was born in {person.DateOfBirth.Year} and is {person.Age} years old");
}
private static int CalculateAge(DateTime dateOfBirth)
{
DateTime now = DateTime.Now;
int age = now.Year - dateOfBirth.Year;
if (now.Month < dateOfBirth.Month || now.Month == dateOfBirth.Month && now.Day < dateOfBirth.Day)
{
age--;
}
return age;
}
}
public class Person
{
public string Name;
public DateTime DateOfBirth;
public int Age;
}
}
This is all very straightforward but having to create a class just to hold a set of arbitrary items seems wasteful. Let’s rewrite this code to use a Tuple instead.
using System;
namespace tuplesample
{
class Program
{
static void Main(string[] args)
{
Tuple<string, DateTime, int> person = Tuple.Create("Jonathon Pluralsight", new DateTime(1991, 07, 27), CalculateAge(new DateTime(1991, 07, 27)));
Console.WriteLine($"{person.Item1} was born in {person.Item2.Year} and is {person.Item3} years old");
}
private static int CalculateAge(DateTime dateOfBirth)
{
DateTime now = DateTime.Now;
int age = now.Year - dateOfBirth.Year;
if (now.Month < dateOfBirth.Month || now.Month == dateOfBirth.Month && now.Day < dateOfBirth.Day)
{
age--;
}
return age;
}
}
}
The Person class has completely been removed from our code and replaced by the Tuple. Obviously, this introduces differences to the way our code looks; with the Person class, we had meaningful names for the different properties and we could add the properties in whatever order we liked. It looks more cumbersome to have Item1, Item2 and so on.
We can create tuples with a variety of parameters (such as Tuple<int, int> and the parameter numbers (e.g. Item1), that reflect the position in the particular Tuple class that we create. There is a little oddity though, while we can create tuples with an excessively long number of parameters, once we get past Item7, the numbering of Item starts again, but is prefixed by Rest, so the eighth item would be Rest.Item1, the ninth item would be Rest.Item2, and so on. The reason for this is because the Tuple classes have limited numbers of implementations; by using this Rest approach, the designers allowed developers as much flexibility as possible without putting hard limitations in place. For simple types, the values of each Item are fixed. If we want to change them, we must recreate the Tuple. This means that we can't do person.Item1 = "Fred Olson"; for instance.
At this point, you might be wondering why we need a Tuple. Typically, I tend to use tuples in one of three situations:
- When I want to return multiple values from an async method.
- When I want to avoid using multiple out parameters from a method.
- When I want to wrap multiple values together to pass into a method such as Thread.Start.
Returning a Tuple
Returning a Tuple out of a method is straightforward. Let's change our sample code above to create the Tuple in its own method like this:
private static Tuple<string, DateTime, int> GetPerson()
{
DateTime dateOfBirth = new DateTime(1991, 07, 27);
return Tuple.Create("Jonathon Pluralsight", dateOfBirth, CalculateAge(dateOfBirth));
}
We change the population of person to
Tuple<string, DateTime, int> person = GetPerson();
Making the Code Simpler
One of the biggest problems with code like this is that it looks cumbersome and it can cause interruptions when we try to review the code that contains it. When C# 7 was released, a new type of tuple was introduced; the ValueTuple. ValueTuples are a similar set of generic classes to the Tuple classes but the team behind this feature took the opportunity to enhance the language to streamline the process of creating and using this new feature.
If you cannot see ValueTuple anywhere in your references, you need to download the System.ValueTuple 4.3.0 pack from NuGet. If you are using .NET 4.7 or higher or .NET Standard Library 2.0 of higher, you don't have to do anything as this will already be available to you.
Let's look at how we can rewrite the GetPerson method to use the new syntax style.
private static (string, DateTime, int) GetPerson()
{
DateTime dateOfBirth = new DateTime(1991, 07, 27);
return ("Jonathon Pluralsight", dateOfBirth, CalculateAge(dateOfBirth));
}
The first change we see here is the slightly odd-looking return type. The (string, DateTime, int) tells the compiler that this is a ValueTuple. The return statement is simplified to remove the Tuple creation altogether. We adopt a similar approach to consuming this return type:
(string, DateTime, int) person = GetPerson();
That's all of the changes that we need to make to our code to use a ValueTuple. The name of the person is still Item1, the date of birth is still Item2 and the age is still Item3. Fortunately for us, the language designers decided that a useful enhancement here would be the ability to use more meaningful names for these properties. First, we change the call to GetPerson to this:
(string name, DateTime dateOfBirth, int age) person = GetPerson();
With this change, we can replace the Item1, etc., to this:
Console.WriteLine($"{person.name} was born in {person.dateOfBirth.Year} and is {person.age} years old");
At this point, we have only changed the code to use name, dateOfBirth, and age in the method that calls GetPerson. The signature of the GetPerson method has not changed; it is still (string, DateTime, int). We should change the method signature as well. By doing this, we are giving people who consume this code a hint as to what each parameter is for. We're no longer just relying on a position. There's nothing forcing us to have the same parameter names on the calling side, but it's good practice to remove obstacles to understanding the code. The GetPerson method now looks like this:
private static (string name, DateTime dateOfBirth, int age) GetPerson()
{
DateTime dateOfBirth = new DateTime(1991, 07, 27);
return (name: "Jonathon Pluralsight", dateOfBirth: dateOfBirth, age: CalculateAge(dateOfBirth));
}
Conclusion
In this guide, we have seen how to create and consume tuples in a variety of ways. We learned how to create and consume a tuple in a single method. We then learned how to use a tuple as the return type from a method and used that tuple in another method. Finally, we looked at ValueTuples and how to use them to make code more intuitive.