C# Language support for Tuples.
// ***************************************************************************
// C# Language support for Tuples
// From: https://riptutorial.com/csharp/example/6329/language-support-for-tuples
// ***************************************************************************
// ----------------------------------------------------------------------------
// Tuple 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);
}
// 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
// ----------------------------------------------------------------------------
// Creating tuples
// ----------------------------------------------------------------------------
// Tuples are created using generic types Tuple<T1>-Tuple<T1,T2,T3,T4,T5,T6,T7,T8>.
// Each of the types represents a tuple containing 1 to 8 elements. Elements can be
// of different types.
//
// Tuple with 4 elements:
var tuple = new Tuple<string, int, bool, MyClass>("foo", 123, true, new MyClass());
// Tuples can also be created using static Tuple.Create methods. In this case,
// the types of the elements are inferred by the C# Compiler.
//
// Tuple with 4 elements (using `Tuple.Create()`):
var tuple = Tuple.Create("foo", 123, true, new MyClass());
// Since C# 7.0, Tuples can be easily created using `ValueTuple`.
var tuple = ("foo", 123, true, new MyClass());
// Elements can be named for easier decomposition (see topic below, "Tuple Deconstruction" for more info.)
(int number, bool flag, MyClass instance) tuple = (123, true, new MyClass());
// ----------------------------------------------------------------------------
// Accessing tuple elements
// ----------------------------------------------------------------------------
// To access tuple elements use Item1-Item8 properties. Only the properties with
// index number less or equal to tuple size are going to be available (i.e. one
// cannot access Item3 property in Tuple<T1,T2>).
var tuple = new Tuple<string, int, bool, MyClass>("foo", 123, true, new MyClass());
var item1 = tuple.Item1; // "foo"
var item2 = tuple.Item2; // 123
var item3 = tuple.Item3; // true
var item4 = tuple.Item4; // new My Class()
// ----------------------------------------------------------------------------
// Return multiple values from a method
// ----------------------------------------------------------------------------
// Tuples can be used to return multiple values from a method without using out
// parameters. In the following example AddMultiply is used to return two values
// (sum, product).
void Write()
{
var result = AddMultiply(25, 28);
Console.WriteLine(result.Item1); // 53
Console.WriteLine(result.Item2); // 700
}
Tuple<int, int> AddMultiply(int a, int b)
{
return new Tuple<int, int>(a + b, a * b);
}
// Now C# 7.0 offers an alternative way to return multiple values from methods
// using value tuples More info about ValueTuple struct.
// ----------------------------------------------------------------------------
// 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 arbitrarily create tuples in code:
var name = ("John", "Smith");
Console.WriteLine(name.Item1); // 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); // 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<int> 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<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
new Tuple<string, string, string>("test1", "test2", "Value"),
new Tuple<string, string, string>("test1", "test1", "Value2"),
new Tuple<string, string, string>("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, https://github.com/dotnet/roslyn/issues/11031) |
// ----------------------------------------------------------------------------
// Comparing and sorting Tuples
// ----------------------------------------------------------------------------
// Tuples can be compared based on their elements.
//
// As an example, an enumerable whose elements are of type Tuple can be sorted
// based on comparisons operators defined on a specified element:
List<Tuple<int, string>> list = new List<Tuple<int, string>>();
list.Add(new Tuple<int, string>(2, "foo"));
list.Add(new Tuple<int, string>(1, "bar"));
list.Add(new Tuple<int, string>(3, "qux"));
// sort based on the string element
list.Sort((a, b) => a.Item2.CompareTo(b.Item2));
foreach (var element in list) {
Console.WriteLine(element);
}
// Output:
//
// (1, bar)
// (2, foo)
// (3, qux)
// Or to reverse the sort use:
list.Sort((a, b) => b.Item2.CompareTo(a.Item2));