Working with Tuples in Csharp
Nov 22, 2019 • 8 Minute Read
Introduction
The word "tuple" in C# refers to a data structure which may consist of multiple parts. This data structure may or may not be a dataset with multiple values. Tuples were first introduced with .NET framework 4.0, and they allow a maximum of 8 elements. More than that will give you a compiler error. Tuples are used when you want to create a data structure that holds objects and their properties, and you do not want to create a separate type.
Generic syntax for a tuple:
Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>
Features of Tuples
Tuples have several advantages. They allow:
- Multiple data types in a single data set
- Creation/manipulation and access to the data set
- Return of multiple values from methods without out
- Storing duplicate elements
- Passing multiple values to a method with a single parameter
Before we had tuples, we had three options to return multiple values from a method. We could use a Class or Struct type, or we could use the out parameters.
Tuples are also ideal for storing object properties up to 8 counts because they allow the items placed in them to be of different types. With, for example, lists or arrays, this task is difficult, as there is no limit to the number of elements they can store, but the type restricts the options. Tuples are used, for example, in one of our company's web applications that handles registration, and we require exactly 8 elements from the newcomer.
Creating Tuples
There are two ways one can utilize the tuple data structure. There is a class-based approach, which utilizes the constructor of the Tuple<T> class, and there is the Create method, also a viable option.
Class-Based Approach
We can utilize the Tuple<T> class to create our tuple with the specific length and type of the elements we would like to store.
Let's take a code-based example.
using System;
namespace tuples
{
class Tupling
{
static void Main(string[] args)
{
Tuple<string> MyStringTuple = new Tuple<string>("Pluralsight");
Tuple<string,int> MyCustomTuple = new Tuple<string,int>("Daniel",28);
Tuple<int, int, int, int, int, int, int, Tuple<int>> MyMaxTuple = new Tuple<int, int, int, int, int, int, int, Tuple<int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int>(8));
Console.WriteLine($"The {nameof(MyStringTuple)} has the following elements: {MyStringTuple}");
Console.WriteLine($"The {nameof(MyCustomTuple)} has the following elements: {MyCustomTuple}");
Console.WriteLine($"The {nameof(MyMaxTuple)} has the following elements: {MyMaxTuple}");
Console.ReadKey(); ;
}
}
}
Let's inspect the output.
The MyStringTuple has the following elements: (Pluralsight)
The MyCustomTuple has the following elements: (Daniel, 28)
The MyMaxTuple has the following elements: (1, 2, 3, 4, 5, 6, 7, 8)
You can see from the initialization that we have three tuples.One is MyStringTuple, another is MyCustomTuple, and finally MyMaxTuple. All are unique and provide all the information you need about tuples. When you have a tuple of 8 items, the last item always must be a tuple.
Create Method
When you use the constructor-based approach, it can make your code harder to read because you have to specify the type of each element for the tuple, and the longer the tuple the more it will flood your code. To ease the pain of developers, the create method was born for the Tuple class. It contains static methods that allow you to create the instance without providing the type of each element.
Our previous example rewritten in this style looks like this.
using System;
namespace tuples
{
class Tupling
{
static void Main(string[] args)
{
var MyStringTuple = Tuple.Create("Pluralsight");
var MyCustomTuple = Tuple.Create("Daniel",28);
var MyMaxTuple = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8));
Console.WriteLine($"The {nameof(MyStringTuple)} has the following elements: {MyStringTuple}");
Console.WriteLine($"The {nameof(MyCustomTuple)} has the following elements: {MyCustomTuple}");
Console.WriteLine($"The {nameof(MyMaxTuple)} has the following elements: {MyMaxTuple}");
Console.ReadKey();
}
}
}
This looks more concise and allows improved readability for the next developer working on the application.
Accessing Items
When we instantiate a new tuple either via the class-based method or the create method, our tuple will have a unique property. It will assign each consecutive element to a property of the instance with the following logic:
Item<elementNumber>
This means that a tuple that has 4 elements will have the following properties:
Tuple.Item1
Tuple.Item2
Tuple.Item3
Tuple.Item4
We then modify our code according to this:
using System;
namespace tuples
{
class Tupling
{
static void Main(string[] args)
{
var MyStringTuple = Tuple.Create("Pluralsight");
var MyCustomTuple = Tuple.Create("Daniel",28);
var MyMaxTuple = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8));
Console.WriteLine($"The first element of our {MyStringTuple} is {MyStringTuple.Item1}");
Console.WriteLine($"The first element of our {MyCustomTuple} is {MyCustomTuple.Item1}, the second is: {MyCustomTuple.Item2}");
Console.WriteLine($"The first element of our {MyMaxTuple} is {MyMaxTuple.Item1}, the last is: {MyMaxTuple.Rest}");
Console.ReadKey();
}
}
}
This gives us the following output:
The first element of our (Pluralsight) is Pluralsight
The first element of our (Daniel, 28) is Daniel, the second is: 28
The first element of our (1, 2, 3, 4, 5, 6, 7, (8)) is 1, the last is: ((8))
Note how the last element of the eight-object tuple can only be referred to as .Rest, and it must always be a tuple.
Nesting Tuples
The limitation of the maximum eight elements can be overcome by nesting tuples into each other. Best practice is to nest a tuple into another at the last element. However, this is not enforced in any way, so you are able to nest a tuple anywhere inside another tuple. When you nest at the end, you can access the nested tuple via the .Rest property; otherwise, you need to use the .Item<value> property to access the tuple.
Let's demonstrate it with a code.
using System;
namespace tuples
{
class Tupling
{
static void Main(string[] args)
{
var MyMaxTupleNested = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21)));
Console.WriteLine($"My original tuple: {MyMaxTupleNested}");
Console.WriteLine($"First element of the outest tuple: {MyMaxTupleNested.Item1}");
Console.WriteLine($"First level of nesting: {MyMaxTupleNested.Rest.Item1}");
Console.WriteLine($"Second level of nesting: {MyMaxTupleNested.Rest.Item1.Rest.Item1}");
Console.ReadKey();
}
}
}
The output should look like this:
My original tuple: (1, 2, 3, 4, 5, 6, 7, (8, 9, 10, 11, 12, 13, 14, (15, 16, 17, 18, 19, 20, 21)))
First element of the outest tuple: 1
First level of nesting: (8, 9, 10, 11, 12, 13, 14, (15, 16, 17, 18, 19, 20, 21))
Second level of nesting: (15, 16, 17, 18, 19, 20, 21)
In my opinion, the method of nesting at the last element provides a clear code and a clear way to troubleshoot. It might not be a one-solution-fits-all kind of approach, however, because in some situations you may better off nesting in the middle.
Conclusion
In this guide, we started out by getting familiar with tuples, a data structure that has been part of C# since .NET 4.0. We then inspected two different ways to create new instances of this class and learned how to access elements of this data structure. We ended with some nesting and additional tips on how to be efficient. I hope this has been informative for you and that you found what you were looking for. Thanks for reading.