Language support for Tuples C# with Example



Language support for Tuples C# with Example

Basics 
A tuple is an ordered, finite list of elements. Tuples are commonly used in programming as a means to work with 
one single entity collectively instead of individually working with each of the tuple's elements, and to represent 
individual rows (ie. "records") in a relational database. 
In C# 7.0, methods can have multiple return values. Behind the scenes, the compiler will use the new ValueTuple 
struct. 
public (int sum, int count) GetTallies() 
{ 
return (1, 2); 
} 
Side note: for this to work in Visual Studio 2017, you need to get the System.ValueTuple package. 
If a tuple-returning method result is assigned to a single variable you can access the members by their defined 
names on the method signature: 
var result = GetTallies(); 
// > result.sum 
// 1 
// > result.count 
// 2 
Tuple Deconstruction 
Tuple deconstruction separates a tuple into its parts. 
For example, invoking GetTallies and assigning the return value to two separate variables deconstructs the tuple 
into those two variables: 
(int tallyOne, int tallyTwo) = GetTallies(); 
var also works: 
(var s, var c) = GetTallies(); 
You can also use shorter syntax, with var outside of (): 
var (s, c) = GetTallies(); 
You can also deconstruct into existing variables: 
int s, c; 
 

(s, c) = GetTallies(); 
Swapping is now much simpler (no temp variable needed): 
(b, a) = (a, b); 
Interestingly, any object can be deconstructed by defining a Deconstruct method in the class: 
class Person 
{ 
public string FirstName { get; set; } 
public string LastName { get; set; } 
public void Deconstruct(out string firstName, out string lastName) 
{ 
firstName = FirstName; 
lastName = LastName; 
} 
} 
var person = new Person { FirstName = "John", LastName = "Smith" }; 
var (localFirstName, localLastName) = person; 
In this case, the (localFirstName, localLastName) = person syntax is invoking Deconstruct on the person. 
Deconstruction can even be defined in an extension method. This is equivalent to the above: 
public static class PersonExtensions 
{ 
public static void Deconstruct(this Person person, out string firstName, out string lastName) 
{ 
firstName = person.FirstName; 
lastName = person.LastName; 
} 
} 
var (localFirstName, localLastName) = person; 
An alternative approach for the Person class is to define the Name itself as a Tuple. Consider the following: 
class Person 
{ 
public (string First, string Last) Name { get; } 
public Person((string FirstName, string LastName) name) 
{ 
Name = name; 
} 
} 
Then you can instantiate a person like so (where we can take a tuple as an argument): 
var person = new Person(("Jane", "Smith")); 
var firstName = person.Name.First; // "Jane" 
var lastName = person.Name.Last; //   "Smith" 
Tuple Initialization 
 

You can also arbitrarily create tuples in code: 
var name = ("John", "Smith"); 
Console.WriteLine(name.Item1); 
//  Outputs  John 
Console.WriteLine(name.Item2); 
// Outputs Smith 
When creating a tuple, you can assign ad-hoc item names to the members of the tuple: 
var name = (first: "John", middle: "Q", last: "Smith"); 
Console.WriteLine(name.first); 
//  Outputs  John 
Type inference 
Multiple tuples defined with the same signature (matching types and count) will be inferred as matching types. For 
example: 
public (int sum, double average) Measure(List items) 
{ 
var stats = (sum: 0, average: 0d); 
stats.sum = items.Sum(); 
stats.average = items.Average(); 
return stats; 
} 
stats can be returned since the declaration of the stats variable and the method's return signature are a match. 
Reflection and Tuple Field Names 
Member names do not exist at runtime. Reflection will consider tuples with the same number and types of 
members the same even if member names do not match. Converting a tuple to an object and then to a tuple with 
the same member types, but different names, will not cause an exception either. 
While the ValueTuple class itself does not preserve information for member names the information is available 
through reflection in a TupleElementNamesAttribute. This attribute is not applied to the tuple itself but to method 
parameters, return values, properties and fields. This allows tuple item names to be preserved across assemblies 
i.e. if a method returns (string name, int count) the names name and count will be available to callers of the method 
in another assembly because the return value will be marked with TupleElementNameAttribute containing the 
values "name" and "count". 
Use with generics and async 
The new tuple features (using the underlying ValueTuple type) fully support generics and can be used as generic 
type parameter. That makes it possible to use them with the async/await pattern: 
public async Task<(string value, int count)> GetValueAsync() 
{ 
string fooBar = await _stackoverflow.GetStringAsync(); 
int num = await _stackoverflow.GetIntAsync(); 
return (fooBar, num); 
} 
Use with collections 
 

It may become beneficial to have a collection of tuples in (as an example) a scenario where you're attempting to 
find a matching tuple based on conditions to avoid code branching. 
Example: 
private readonly List> labels = new List>() 
{ 
new Tuple("test1", "test2", "Value"), 
new Tuple("test1", "test1", "Value2"), 
new Tuple("test2", "test2", "Value3"), 
}; 
public string FindMatchingValue(string firstElement, string secondElement) 
{ 
var result = labels 
.Where(w => w.Item1 == firstElement && w.Item2 == secondElement) 
.FirstOrDefault(); 
if (result == null) 
throw new ArgumentException("combo not found"); 
return result.Item3; 
} 
With the new tuples can become: 
private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = 
new List<(string firstThingy, string secondThingyLabel, string foundValue)>() 
{ 
("test1", "test2", "Value"), 
("test1", "test1", "Value2"), 
("test2", "test2", "Value3"), 
} 
public string FindMatchingValue(string firstElement, string secondElement) 
{ 
var result = labels 
.Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement) 
.FirstOrDefault(); 
if (result == null) 
throw new ArgumentException("combo not found"); 
return result.foundValue; 
} 
Though the naming on the example tuple above is pretty generic, the idea of relevant labels allows for a deeper 
understanding of what is being attempted in the code over referencing "item1", "item2", and "item3". 
Differences between ValueTuple and Tuple 
The primary reason for introduction of ValueTuple is performance. 
Type name ValueTuple Tuple 
Class or structure struct class 
Mutability (changing values after creation) mutable immutable 
Naming members and other language support yes no (TBD) 
 

References 
Original Tuples language feature proposal on GitHub 
A runnable VS 15 solution for C# 7.0 features 
NuGet Tuple Package 

0 Comment's

Comment Form

Submit Comment