New Tuple features and deconstruction with Tuples in C# 7
February 7, 2018 1 Comment
Tuples have been available in C# for some time now. C# 7 comes with a handful of new things for tuples though.
Let’s see an example for using tuples in the old way. The following function returns a Tuple with two integers: the first is the total days of a duration and the second is the total hours of a duration. Not the most useful function that the world has ever seen, but it’s good for demo purposes:
private Tuple<int, int> AnalyseDatesOld(DateTime first, DateTime second) { TimeSpan timespan = (second - first).Duration(); return new Tuple<int, int>(Convert.ToInt32(timespan.TotalDays), Convert.ToInt32(timespan.TotalHours)); }
…and then we can call this function and retrieve the individual parts of the tuple as Item1 and Item2:
public void OldWay() { var t = AnalyseDatesOld(DateTime.UtcNow, DateTime.UtcNow.AddDays(10)); Console.WriteLine(t.Item1); Console.WriteLine(t.Item2); }
The code works fine, but it’s not easy to know what Item1 and Item2 refer to.
Before we continue, just as a short aside, we can also build tuples with the static Create function:
return Tuple.Create(Convert.ToInt32(timespan.TotalDays), Convert.ToInt32(timespan.TotalHours));
One addition to tuples is that we can give names to the various items in them. Here’s an updated version of the above function:
private (int TotalDays, int TotalHours) AnalyseDatesNew(DateTime first, DateTime second) { TimeSpan timespan = (second - first).Duration(); return (Convert.ToInt32(timespan.TotalDays), Convert.ToInt32(timespan.TotalHours)); }
Note how we sort of have multiple return types in the signature with two integers within parentheses and both integers have names: TotalDays and TotalHours. Also, the return value will be built in a similar fashion, i.e. two elements within parens, delimited by a comma. Therefore we don’t use the tuple creation techniques we presented above. The reason for that is that the return type of this new function is not exactly Tuple, but it’s rather System.ValueTuple. Also note, that System.ValueTuple is NOT available by default in .NET 4.6.2. If your project is set up with that version of .NET then the compiler will complain about funny syntax with the parens. There are two ways out of this situation:
- Upgrade the target framework of the project (assembly) to at least 4.7 in the project properties window
- Install the System.ValueTuple NuGet package if your target framework must not exceed 4.6.2
Here’s how we can call the ValueTuple function and access its properties:
var t = AnalyseDatesNew(DateTime.UtcNow, DateTime.UtcNow.AddDays(10)); Console.WriteLine(t.TotalDays); Console.WriteLine(t.TotalHours);
We can also override the names of the returned variables:
(int days, int hours) t2 = AnalyseDatesNew(DateTime.UtcNow, DateTime.UtcNow.AddDays(10)); Console.WriteLine(t2.days); Console.WriteLine(t2.hours);
This technique is called deconstruction, when we dissect the elements of an object and assign them to various variables.
We can even forget the variable assignment and still use deconstruction:
(int d, int h) = AnalyseDatesNew(DateTime.UtcNow, DateTime.UtcNow.AddDays(10)); Console.WriteLine(d); Console.WriteLine(h);
So the elements returned by AnalyseDatesNew are directly assigned to the variables “d” and “h” without any other variable assignment in between.
The above is equivalent to the following syntax:
int d2, h2; (d2, h2) = AnalyseDatesNew(DateTime.UtcNow, DateTime.UtcNow.AddDays(10));
We can also declare the deconstructed variables with “var” and the compiler will figure out their exact types:
var (dateDays, dateHours) = AnalyseDatesNew(DateTime.UtcNow, DateTime.UtcNow.AddDays(10)); Console.WriteLine(dateDays); Console.WriteLine(dateHours);
Other bits and pieces with Tuples
We can create ad-hoc objects with inline tuples, i.e. where we declare the property names and values within parens:
var dog = (Name: "Doge", Colour: "Black", Age: 5); Console.WriteLine($"Name: {dog.Name}, Colour: {dog.Colour}, Age: {dog.Age}");
Tuple property names can be inherited by other tuples like in the following example:
var myCat = (Name: "Missy", Colour: "White", Age: 4); var myOtherCat = (myCat.Name, Colour: "Black", myCat.Age);
Note how “myOtherCat” has no property name declaration for the name and age fields. We can still refer to them by Name and Age since those names have been transferred from “myCat”:
Console.WriteLine(myOtherCat.Name); Console.WriteLine(myOtherCat.Colour); Console.WriteLine(myOtherCat.Age);
A similar feature can be observed in the following case, although it’s not perfect:
string[] colours = new[] { "Black", "White", "Blue", "Red" }; var colourTuples = colours.Select(c => (c.ToUpper(), c.ToLower(), c.Length)); foreach (var item in colourTuples) { Console.WriteLine(item.Item1); Console.WriteLine(item.Item2); Console.WriteLine(item.Length); }
So we return a tuple with 3 elements: the capitalised colours, the lowercase colours and their lengths and then we print these properties. Note, how the Length property has been transferred to the colourTuples list of tuples. The ToUpper and ToLower function names are, however, missing and we need to refer to them with Item1 and Item2. So this name transfer feature currently only works with property names, not functions.
View all various C# language feature related posts here.
Pingback: More on deconstruction in C# 7 | Fitness Promotions