C-Sharp | Java | Python | Swift | GO | WPF | Ruby | Scala | F# | JavaScript | SQL | PHP | Angular | HTML
Part A: In this example, we create a 3-item tuple using the special constructor syntax.
Part B: We read the Item1, Item2 and Item3 properties and test them in if-statements. We do not modify them.
Types: When we create a Tuple, we specify the order and types of the fields. Try changing the types, and the number of types.
Info: We can have value types (such as int) and reference types (such as string) inside a Tuple.
C# program that uses 3 items in Tuple
using System;
class Program
{
static void Main()
{
// Part A: create three-item tuple.
Tuple<int, string, bool> tuple =
new Tuple<int, string, bool>(1, "cat", true);
// Part B: access tuple properties.
if (tuple.Item1 == 1)
{
Console.WriteLine(tuple.Item1);
}
if (tuple.Item2 == "dog")
{
Console.WriteLine(tuple.Item2);
}
if (tuple.Item3)
{
Console.WriteLine(tuple.Item3);
}
}
}
Output
1
True
Here: In this example, we create a four-item Tuple with two arrays—string and int arrays.
ArrayThen: We initialize those arrays inside the constructor invocation. Next we pass our Tuple variable to another method.
Var: Why does the example use the var keyword? The reason is pure syntactic sugar. Var shortens the lines in the code example.
VarC# program that uses 4-item Tuple
using System;
class Program
{
static void Main()
{
// Create four-item tuple.
// ... Use var implicit type.
var tuple = new Tuple<string, string[], int, int[]>("perl",
new string[] { "java", "c#" },
1,
new int[] { 2, 3 });
// Pass tuple as argument.
M(tuple);
}
static void M(Tuple<string, string[], int, int[]> tuple)
{
// Evaluate the tuple's items.
Console.WriteLine(tuple.Item1);
foreach (string value in tuple.Item2)
{
Console.WriteLine(value);
}
Console.WriteLine(tuple.Item3);
foreach (int value in tuple.Item4)
{
Console.WriteLine(value);
}
}
}
Output
perl
java
c#
1
2
3
Warning: This can lead to programs that have complex and hard-to-remember type names. But the compiler will check them.
C# program that uses 6-item tuple
using System;
class Program
{
static void Main()
{
var sextuple =
new Tuple<int, int, int, string, string, string>(1,
1, 2, "dot", "net", "Codex");
Console.WriteLine(sextuple);
}
}
Output
(1, 1, 2, dot, net, Codex)
Note: The naming of tuples is not important in many programs. But these terms can be useful when describing programs in a concise way.
Names: Beyond septuples, we only have n-tuples. These terms will make you sound really smart.
Quote: A tuple is an ordered list of elements. In mathematics, an n-tuple is a sequence (or ordered list) of "n" elements, where "n" is a non-negative integer.
Tuple: WikipediaNames:
A 2-tuple is called a pair.
A 3-tuple is called a triple.
A 4-tuple is called a quadruple.
A 5-tuple is called a quintuple.
A 6-tuple is called a sextuple.
A 7-tuple is called a septuple.
Larger tuples are called n-tuples.
Result: The Create() method returns a class of type Tuple<string, int, bool>. It has three items.
Program: The code does a series of tests of the Tuple. It tests Item1, Item2 and Item3.
C# program that uses Tuple.Create method
using System;
class Program
{
static void Main()
{
// Use Tuple.Create static method.
var tuple = Tuple.Create("cat", 2, true);
// Test value of string.
string value = tuple.Item1;
if (value == "cat")
{
Console.WriteLine(true);
}
// Test Item2 and Item3.
Console.WriteLine(tuple.Item2 == 10);
Console.WriteLine(!tuple.Item3);
// Write string representation.
Console.WriteLine(tuple);
}
}
Output
True
False
False
(cat, 2, True)
Tip: There is essentially no functional reason to ever call Tuple.Create. It might have more pleasing syntax.
One implementation of Tuple.Create: .NET 4.0
public static Tuple<T1> Create<T1>(T1 item1)
{
return new Tuple<T1>(item1);
}
Constructor: All items must be initialized with the constructor. We cannot change a property (like Item1) after the constructor has run.
Note: The properties Item1, Item2 and further do not have setters. We cannot assign them. A Tuple is immutable once created in memory.
Tip: This limitation can lead to more maintainable code that does not rely on field changes through time.
C# program that causes compile-time error
using System;
class Program
{
static void Main()
{
var tuple = new Tuple<int, string>(200, "Greece");
// This will not work.
tuple.Item1 = 300;
}
}
Output
Property or indexer 'System.Tuple...Item1' cannot be assigned to--it is read-only.
First: This program creates a List and adds 3 new Tuple instances to it. We invoke the Sort method on the List.
Sort ListHere: We use the lambda syntax and pass in 2 arguments (a, b) and return the result of CompareTo on the Item2 string property.
Tip: To sort on the int, change the lambda to return a.Item1.CompareTo(b.Item1). A reverse sort would be b.Item2.CompareTo(a.Item2).
C# program that sorts List of Tuple instances
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Tuple<int, string>> list = new List<Tuple<int, string>>();
list.Add(new Tuple<int, string>(1, "cat"));
list.Add(new Tuple<int, string>(100, "apple"));
list.Add(new Tuple<int, string>(2, "zebra"));
// Use Sort method with Comparison delegate.
// ... Has two parameters; return comparison of Item2 on each.
list.Sort((a, b) => a.Item2.CompareTo(b.Item2));
foreach (var element in list)
{
Console.WriteLine(element);
}
}
}
Output
(100, apple)
(1, cat)
(2, zebra)
Note: This causes an allocation. Using ref and out parameters would be faster for a method that is hot.
ParametersNote 2: A Tuple has advantages. It is a reference and can be reused. Less copying is needed when passed to other methods.
C# program that returns multiple values
using System;
class Program
{
static Tuple<string, int> NameAndId()
{
// This method returns multiple values.
return new Tuple<string, int>("Satya Nadella", 100);
}
static void Main()
{
var result = NameAndId();
string name = result.Item1;
int id = result.Item2;
// Display the multiple values returned.
Console.WriteLine(name);
Console.WriteLine(id);
}
}
Output
Satya Nadella
100
Here: We create a 3-item tuple literal, and display its 3 items with Console.WriteLine.
ValueTupleConsoleC# program that uses tuple literals
using System;
class Program
{
static void Main()
{
// Go to NuGet, then search for and install System.ValueTuple.
// ... This program will then compile correctly.
var values = (10, "bird", "plane");
Console.WriteLine(values);
Console.WriteLine(values.Item1);
Console.WriteLine(values.Item2);
Console.WriteLine(values.Item3);
}
}
Output
(10, bird, plane)
10
bird
plane
C# program that uses ToValueTuple
using System;
class Program
{
static void Main()
{
var tuple = new Tuple<int, string>(-20, "Bolivia");
// Convert tuple to a value tuple, and pass it to a method.
Print(tuple.ToValueTuple());
}
static void Print((int, string) items)
{
// Print value tuple.
Console.WriteLine(items);
}
}
Output
(-20, Bolivia)
Version 1: Here we benchmark Tuple in the 4 performance tests. We only use 2-item objects in this test.
Version 2: Here we use a KeyValuePair struct instead of a Tuple class instance as part of the benchmark.
StructVersion 3: This is newest and most modern of the tests: it uses the ValueTuple literal syntax.
Result: Tuples and ValueTuples tend to perform as well as anything in the tests. Tuples can be used as part of fast code.
C# program that benchmarks tuple types
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
class Program
{
static void Main()
{
Allocation();
Argument();
Return();
Load();
}
static void Allocation()
{
// Time allocating the object.
const int max = 1000000;
var a = new Tuple<string, string>("", "");
var b = new KeyValuePair<string, string>("", "");
var c = ("", "");
var s1 = Stopwatch.StartNew();
// Version 1: allocate Tuple.
for (var i = 0; i < max; i++)
{
var tuple = new Tuple<string, string>("cat", "dog");
}
s1.Stop();
var s2 = Stopwatch.StartNew();
// Version 2: allocate KeyValuePair.
for (var i = 0; i < max; i++)
{
var pair = new KeyValuePair<string, string>("cat", "dog");
}
s2.Stop();
var s3 = Stopwatch.StartNew();
// Version 3: allocate tuple literal.
for (var i = 0; i < max; i++)
{
var pair = ("cat", "dog");
}
s3.Stop();
Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max) + " allocation, Tuple");
Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max) + " allocation, KeyValuePair");
Console.WriteLine(((double)(s3.Elapsed.TotalMilliseconds * 1000000) / max) + " allocation, Tuple literal");
Console.WriteLine();
}
static void Argument()
{
// Time passing the object as an argument to a function.
const int max = 10000000;
var a = new Tuple<string, string>("", "");
var b = new KeyValuePair<string, string>("", "");
var c = ("", "");
X(a);
X(b);
X(c);
var s1 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
X(a);
}
s1.Stop();
var s2 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
X(b);
}
s2.Stop();
var s3 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
X(c);
}
s3.Stop();
Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max) + " argument, Tuple");
Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max) + " argument, KeyValuePair");
Console.WriteLine(((double)(s3.Elapsed.TotalMilliseconds * 1000000) / max) + " argument, Tuple literal");
Console.WriteLine();
}
static void Return()
{
// Time returning the object itself.
const int max = 10000000;
var a = new Tuple<string, string>("", "");
var b = new KeyValuePair<string, string>("", "");
var c = ("", "");
Y(a);
Y(b);
Y(c);
var s1 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
Y(a);
}
s1.Stop();
var s2 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
Y(b);
}
s2.Stop();
var s3 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
Y(c);
}
s3.Stop();
Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max) + " return, Tuple");
Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max) + " return, KeyValuePair");
Console.WriteLine(((double)(s3.Elapsed.TotalMilliseconds * 1000000) / max) + " return, Tuple literal");
Console.WriteLine();
}
static void Load()
{
// Time accessing an element.
const int max = 10000000;
var a = new Tuple<string, string>("cat", "dog");
var b = new KeyValuePair<string, string>("cat", "dog");
var c = ("cat", "dog");
var list1 = new List<Tuple<string, string>>();
list1.Add(a);
Z(list1);
var list2 = new List<KeyValuePair<string, string>>();
list2.Add(b);
Z(list2);
var list3 = new List<(string, string)>();
list3.Add(c);
Z(list3);
var s1 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
Z(list1);
}
s1.Stop();
var s2 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
Z(list2);
}
s2.Stop();
var s3 = Stopwatch.StartNew();
for (var i = 0; i < max; i++)
{
Z(list3);
}
s3.Stop();
Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max) + " load, Tuple");
Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max) + " load, KeyValuePair");
Console.WriteLine(((double)(s3.Elapsed.TotalMilliseconds * 1000000) / max) + " load, Tuple literal");
Console.WriteLine();
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void X(Tuple<string, string> a)
{
// This and following methods are used in the benchmarks.
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void X(KeyValuePair<string, string> a)
{
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void X((string, string) a)
{
}
[MethodImpl(MethodImplOptions.NoInlining)]
static Tuple<string, string> Y(Tuple<string, string> a)
{
return a;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static KeyValuePair<string, string> Y(KeyValuePair<string, string> a)
{
return a;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static (string, string) Y((string, string) a)
{
return a;
}
static char Z(List<Tuple<string, string>> list)
{
return list[0].Item1[0];
}
static char Z(List<KeyValuePair<string, string>> list)
{
return list[0].Key[0];
}
static char Z(List<(string, string)> list)
{
return list[0].Item1[0];
}
}
Output
8.3944 allocation, Tuple
0.4949 allocation, KeyValuePair
0.3457 allocation, Tuple literal (FASTEST)
2.16168 argument, Tuple
2.17551 argument, KeyValuePair
2.17316 argument, Tuple literal
1.84421 return, Tuple (FASTEST)
5.42422 return, KeyValuePair
5.32932 return, Tuple literal
2.44545 load, Tuple
3.27982 load, KeyValuePair
2.56207 load, Tuple literal
Allocation: In this test, one instance of a Tuple, KeyValuePair or tuple literal (ValueTuple) is allocated. The new-keyword is used.
Argument: Here, one instance is passed as an argument to another (not inlined) method.
Return: In this test, one instance is passed as an argument to a method and then returned.
Load: In this method, one instance is loaded from a reference stored in a List collection.
And: For accessing an item from the object, Tuple and tuple literals are the fastest (KeyValuePair is slower here).
Note: Thanks to Alex Vincent for helping improve the complex benchmark of tuple objects.