Tuples can be used to store a finite sequence of homogeneous or heterogeneous data of fixed sizes and can be used to return multiple values from a method
Like anonymous types, tuples provide a simple way to store a set of values. The main purpose of tuples is to safely return multiple values from a method without resorting to out parameters (something you cannot do with anonymous types).
C# 7’s tuple functionality relies on a set of supporting generic structs named System.ValueTuple<…>. These are not part of .NET Framework 4.6, and are contained in an assembly called System.ValueTuple, available in a NuGet package of the same name. You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.
The following example creates a tuple with three elements:
1 2 3 4 5 6 7 | var person = Tuple.Create(1, "Bob", "Marley"); Console.WriteLine(person.Item1); // returns 1 Console.WriteLine(person.Item2); // returns "Bob" Console.WriteLine(person.Item3); // returns "Marley" |
Tuples are value types, with mutable (read/write) elements:
1 2 3 4 5 6 7 | var bob = (1, "Bob", "Marley"); // Allow compiler to infer the element types var joe = bob; // joe is a *copy* of job joe.Item2 = "Joe"; // Change joe's Item1 from Bob to Joe Console.WriteLine(bob); // (1, "Bob", "Marley") Console.WriteLine(joe); // (1, "Joe", "Marley") |
However, when you initialize a tuple, you can use new language features that give better names to each field.Doing so creates a named tuple. Named tuples still have elements named Item1
, Item2
, Item3
and so on. But they also have synonyms for any of those elements that you have named. You create a named tuple by specifying the names for each element. One way is to specify the names as part of the tuple initialization:
1 2 3 4 | var person = (number:1, name:"Bob", lastname:"Marley"); Console.WriteLine(person.name); // "Bob" |
Unlike with anonymous types, you can specify a tuple type explicitly. Just list each
of the element types in parentheses:
1 2 3 | (string,int) bob = ("Bob", 23); // var is not compulsory with tuples! |
This means that you can usefully return a tuple from a lambda method:
1 2 3 4 5 6 7 8 9 | static (string,int) GetPerson() => ("Bob", 23); static void Main() { (string,int) person = GetPerson(); // Could use 'var' here if we want Console.WriteLine (person.Item1); // Bob Console.WriteLine (person.Item2); // 23 } |
Alternative writing syntax. When we create a Tuple, we can specify the order and types of the fields. If you would rather have a double, byte, char Tuple, change the declaration to Tuple. Tuples play well with generics, so the following types are all legal:
1 2 3 4 5 | Task<(string,int)> Dictionary<(string,int),Uri> IEnumerable<(int ID, string Name)> // See below for naming elements |
1 2 3 4 5 6 7 | Tuple<int, string, bool> person = new Tuple<int, string, bool>(1,"Bob",false); Console.WriteLine(person.Item1); Console.WriteLine(person.Item2); Console.WriteLine(person.Item3); |
Returning Multiple Values
Methods return a single object. Tuples enable you to package multiple values in that single object more easily.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Program { public static (double Area, double Perimeter) circleAreaPerimeter(double r) { double perimeter, area; perimeter = 2 * 3.14 * r; area = 3.14 * Math.Pow(r, 2); //area = 3.14 * r * r; return (area, perimeter); } static void Main(string[] args) { Console.Write("Please write the radius of your circle : "); double r = Convert.ToDouble(Console.ReadLine()); var result = circleAreaPerimeter(r); Console.WriteLine("============================================="); Console.WriteLine("The perimeter of yor circle : {0}", result.Perimeter); Console.WriteLine("The area of yor circle : {0}", result.Perimeter); Console.ReadLine(); } } |
Naming Tuple Elements
You can optionally give meaningful names to elements when creating tuple literals:
1 2 3 4 5 | var tuple = (Name:"Bob", Age:23); Console.WriteLine (tuple.Name); // Bob Console.WriteLine (tuple.Age); // 23 |
You can do the same when specifying tuple types:
1 2 3 4 5 6 7 8 9 | static (string Name, int Age) GetPerson() => ("Bob", 23); static void Main() { var person = GetPerson(); Console.WriteLine (person.Name); // Bob Console.WriteLine (person.Age); // 23 } |
Note that you can still treat the elements as unnamed and refer to them as Item1, Item2, etc. (although Visual Studio hides these fields from IntelliSense).
Tuples are type-compatible with one another if their element types match up (in order). Their element names need not:
1 2 3 4 | (string Name, int Age, char Sex) bob1 = ("Bob", 23, 'M'); (string Age, int Sex, char Name) bob2 = bob1; // No error! |
Our particular example leads to confusing results:
1 2 3 4 5 | Console.WriteLine (bob2.Name); // M Console.WriteLine (bob2.Age); // Bob Console.WriteLine (bob2.Sex); // 23 |
Type erasure
We stated previously that the C# compiler handles anonymous types by building custom classes with named properties for each of the elements. With tuples, C# works differently and leverages a pre-existing family of generic structs:
1 2 3 4 5 6 | public struct ValueTuple<T1> public struct ValueTuple<T1,T2> public struct ValueTuple<T1,T2,T3> ... |
Each of the ValueType<> structs has fields named Item1, Item2, and so on.
Hence, (string,int) is an alias for ValueTuple<string,int>, and this means that named tuple elements have no corresponding property names in the underlying types. Instead, the names exist only in the source code, and in the imagination of the compiler. At runtime, the names mostly disappear, so if you decompile a program that refers to named tuple elements, you’ll see just references to Item1, Item2, etc. Further, when you examine a tuple variable in a debugger after having assigned it to an object.
ValueTuple.Create
You can also create tuples via a factory method on the (nongeneric) ValueTuple type:
1 2 3 4 | ValueTuple<string,int> bob1 = ValueTuple.Create ("Bob", 23); (string,int) bob2 = ValueTuple.Create ("Bob", 23); |
Named elements cannot be created in this way, as element naming relies on compiler magic
Deconstructing Tuples
Tuples implicitly support the deconstruction pattern. So you can easily deconstruct a tuple into individual variables. So,
instead of doing this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var bob = ("Bob", 23); string name = bob.Item1; int age = bob.Item2; var bob = ("Bob", 23); (string name, int age) = bob; // Deconstruct the bob tuple into // separate variables (name and age). Console.WriteLine (name); Console.WriteLine (age); |
The syntax for deconstruction is confusingly similar to the syntax for declaring a tuple with named elements! The following highlights the difference:
1 2 3 4 | (string name, int age) = bob; // Deconstructing a tuple (string name, int age) bob2 = bob; // Declaring a new tuple |
Here’s another example, this time when calling a method, and with type inference (var):
1 2 3 4 5 6 7 8 9 10 | static (string, int, char) GetBob() => ( "Bob", 23, 'M'); static void Main() { var (name, age, sex) = GetBob(); Console.WriteLine (name); // Bob Console.WriteLine (age); // 23 Console.WriteLine (sex); // M } |