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.
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.
1using System;
2
3namespace tuplesample
4{
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 Person person = new Person();
10 person.Name = "Jonathon Pluralsight";
11 person.DateOfBirth = new DateTime(1991, 07, 27);
12 person.Age = CalculateAge(person.DateOfBirth);
13 Console.WriteLine($"{person.Name} was born in {person.DateOfBirth.Year} and is {person.Age} years old");
14 }
15
16 private static int CalculateAge(DateTime dateOfBirth)
17 {
18 DateTime now = DateTime.Now;
19 int age = now.Year - dateOfBirth.Year;
20 if (now.Month < dateOfBirth.Month || now.Month == dateOfBirth.Month && now.Day < dateOfBirth.Day)
21 {
22 age--;
23 }
24 return age;
25 }
26 }
27
28 public class Person
29 {
30 public string Name;
31 public DateTime DateOfBirth;
32 public int Age;
33 }
34}
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.
1using System;
2
3namespace tuplesample
4{
5 class Program
6 {
7 static void Main(string[] args)
8 {
9
10 Tuple<string, DateTime, int> person = Tuple.Create("Jonathon Pluralsight", new DateTime(1991, 07, 27), CalculateAge(new DateTime(1991, 07, 27)));
11 Console.WriteLine($"{person.Item1} was born in {person.Item2.Year} and is {person.Item3} years old");
12 }
13
14 private static int CalculateAge(DateTime dateOfBirth)
15 {
16 DateTime now = DateTime.Now;
17 int age = now.Year - dateOfBirth.Year;
18 if (now.Month < dateOfBirth.Month || now.Month == dateOfBirth.Month && now.Day < dateOfBirth.Day)
19 {
20 age--;
21 }
22 return age;
23 }
24 }
25}
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 particularTuple
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 ofItem
starts again, but is prefixed byRest
, so the eighth item would beRest.Item1
, the ninth item would beRest.Item2
, and so on. The reason for this is because theTuple
classes have limited numbers of implementations; by using thisRest
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 theTuple
. This means that we can't doperson.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:
async
method.out
parameters from a method.Thread.Start
.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:
1private static Tuple<string, DateTime, int> GetPerson()
2{
3 DateTime dateOfBirth = new DateTime(1991, 07, 27);
4 return Tuple.Create("Jonathon Pluralsight", dateOfBirth, CalculateAge(dateOfBirth));
5}
We change the population of person to
1Tuple<string, DateTime, int> person = GetPerson();
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.
1private static (string, DateTime, int) GetPerson()
2{
3 DateTime dateOfBirth = new DateTime(1991, 07, 27);
4 return ("Jonathon Pluralsight", dateOfBirth, CalculateAge(dateOfBirth));
5}
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:
1(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:
1(string name, DateTime dateOfBirth, int age) person = GetPerson();
With this change, we can replace the Item1
, etc., to this:
1Console.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:
1private static (string name, DateTime dateOfBirth, int age) GetPerson()
2{
3 DateTime dateOfBirth = new DateTime(1991, 07, 27);
4 return (name: "Jonathon Pluralsight", dateOfBirth: dateOfBirth, age: CalculateAge(dateOfBirth));
5}
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.