Basically, a tuple (Tuple in C#) is an ordered sequence, immutable, fixed-size and of heterogeneous objects, i.e., each object being of a specific type.
The tuples are not new in programming. They are already used in F#, Python and databases. However, they are new to C#. The tuples were introduced in C# 4.0 with dynamic programming.
While anonymous types have similar functionality in C#, they cannot be used as return of methods, as can the type Tuple.
A tuple is an ordered sequence, immutable, fixed size of heterogeneous objects.
As Tuples don’t have an explicit semantic meaning, your code becomes unreadable.
Creating Tuples
In C#, Tuple
is a static
class that implements the "Factory" Pattern to create instances of Tuples. We can create an instance of a Tuple using the constructor or the static
method "Create
".
The static
method "Create
" that returns an instance of type Tuple
has eight overloads:
Overload
|
Description
|
Create<T1>(T1)
|
Create 1-tuple, or singleton.
|
Create<T1, T2>(T1, T2)
|
Create 2-tuple, or pair.
|
Create<T1, T2, T3>(T1, T2, T3)
|
Create 3-tuple, or triple.
|
Create<T1, T2, T3, T4>(T1, T2, T3, T4)
|
Create 4-tuple, or quadruple.
|
Create<T1, T2, T3, T4, T5>(T1, T2, T3, T4, T5)
|
Create 5-tuple, or quintuple.
|
Create<T1, T2, T3, T4, T5, T6>(T1, T2, T3, T4, T5, T6)
|
Create 6-tuple, or sextuple.
|
Create<T1, T2, T3, T4, T5, T6, T7>(T1, T2, T3, T4, T5, T6, T7)
|
Create 7-tuple, or septuple.
|
Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1, T2, T3, T4, T5, T6, T7, T8)
|
Create 8-tuple, or octuple.
|
Example
Tuple<int, string, DateTime> _cliente =
Tuple.Create(1, "Frederico", new DateTime(1975, 3,24));
Tuples have a limit of 8 items. If you want to create a tuple with more items, we have to create nested Tuples.
The eighth item of the tuple has necessarily to be another Tuple. The example below will generate an exception.
var t8 = new Tuple<int,int,int,int,int,int,int,int>(1, 2, 3, 4, 5, 6, 7, 8);
To create a tuple with 8 items, we must do the following:
var t8 = new Tuple<int,int,int,int,int,int,int,Tuple<int>>
(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8));
var Item8 = t8.Rest.Item1;
To create a tuple with more than 8 items, we do as follows:
var t12 = new Tuple<int,int,int,int,int,int,int,Tuple<int,int,int,int, int>>
(1, 2, 3, 4, 5, 6, 7, new Tuple<int,int,int, int,int>(8,9,10, 11, 12));
<>var Item10 = t12.Rest.Item3;
What Does A Tuple Represent?
Tuples do not have names that may have some significance. The attributes of a tuple are called "Item1
", "Item2
", and so on.
Two Tuples can be equal, but that doesn’t mean they are the same. Its meaning is not explicit, which can make your code less readable. For example, the following two tuples are equal, but represent different things:
(3, 9): Product Code 3 and Quantity 9
(3, 9): 3 and 9 are the codes of clients returned by a query.
As seen above, the fact that a tuple doesn’t carry information about its meaning, its use is generic and the developer decides what it will mean at the time of its creation and use.
So, Why Should We Use Them?
A) Return of Methods
Tuples provide a quick way to group multiple values into a single result, which can be very useful when used as a return of function, without the need to create parameters "ref
" and /
or "out
".
Example
using System;
namespace TuplesConsoleTest
{
class Program
{
static void Main(string[] args)
{
var _cliente1 = RetornaCliente();
Console.WriteLine("O código do usuário1 é: {0}", _cliente1.Item1);
Console.WriteLine("O Nome do usuário1 é: {0}", _cliente1.Item2);
Console.WriteLine("A data de nascimento do usuário1 é: {0}",
_cliente1.Item3.ToString("dd/MM/yyyy"));
Console.Read();
}
static Tuple<int, string, DateTime> RetornaCliente()
{
Tuple<int, string, DateTime> _cliente =
Tuple.Create(1, "Frederico", new DateTime(1975, 3, 24));
return _cliente;
}
}
}
Another example of methods return is when we must return a list of an anonymous type. In this case, we can easily replace this type by tuples.
Example
using System;
using System.Collections.Generic;
using System.Linq;
namespace TuplesConsoleTest
{
class Program
{
static List<Tuple<int, string, string, DateTime>> lista;
static void Main(string[] args)
{
CarregaLista();
var result = SelecionaCLientes("M");
foreach (var r in result)
{
Console.WriteLine("Cliente: {0} \t Nome: {1}", r.Item1, r.Item2);
}
Console.Read();
}
private static void CarregaLista()
{
lista = new List<Tuple<int, string, string, DateTime>>();
lista.Add(new Tuple<int, string, string, DateTime>
(0, "", "", DateTime.MinValue));
lista.Add(new Tuple<int, string, string, DateTime>
(1, "Fred", "M", new DateTime(1975, 3, 24)));
lista.Add(new Tuple<int, string, string, DateTime>
(2, "Rubia", "F", new DateTime(1983, 12, 17)));
lista.Add(new Tuple<int, string, string, DateTime>
(3, "João", "M", new DateTime(2004, 4, 16)));
lista.Add(new Tuple<int, string, string, DateTime>
(4, "Tatá", "F", new DateTime(1999, 7, 14)));
}
private static IEnumerable<Tuple<int, string>> SelecionaCLientes(string sex)
{
var ret = from t in lista
where t.Item3 == sex
select new Tuple<int, string>(t.Item1, t.Item2);
return ret;
}
}
}
B) Composite Key in a Dictionary
Due to the interface IEquatable
defines GetHashCode()
, the implementation of the interface IStructuralEquatable
creates a Hash code combining the members Hash codes, allowing the use of tuples as a composite key for a collection of type Dictionary
.
Example
using System;
using System.Collections.Generic;
namespace TuplesConsoleTest
{
class Program
{
static void Main(string[] args)
{
var lista = ListaClienteConta();
var chave = Tuple.Create(1, 1);
Console.WriteLine("Saldo selecionado é: {0}",
lista[chave].Saldo.ToString());
Console.Read();
}
public static Dictionary<Tuple<int, int>, ClienteConta> ListaClienteConta()
{
Dictionary<Tuple<int, int>, ClienteConta> lista =
new Dictionary<Tuple<int, int>, ClienteConta>();
ClienteConta cc1 = new ClienteConta(){
Codigo_Cliente = 1,
Codigo_Conta = 1,
Saldo = 525.00 };
ClienteConta cc2 = new ClienteConta(){
Codigo_Cliente = 1,
Codigo_Conta = 2,
Saldo = 765.00 };
lista.Add(Tuple.Create(cc1.Codigo_Cliente, cc1.Codigo_Conta), cc1);
lista.Add(Tuple.Create(cc2.Codigo_Cliente, cc2.Codigo_Conta), cc2);
return lista;
}
}
public class ClienteConta
{
public int Codigo_Cliente { get; set; }
public int Codigo_Conta { get; set; }
public double Saldo { get; set; }
}
}
C) Replace Classes or Structs that are Created Just to Carry a Return or to Fill a List
Using the Tuple, we don’t need to create classes or structures to store only temporary values, such as creating a struct or class to add values to a combobox
or listbox
. With the tuples, it will no longer be necessary to create them.
Example
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TuplesTest
{
public partial class Form1 : Form
{
List<Tuple<int, string>> lista = new List<Tuple<int, string>>();
public Form1()
{
InitializeComponent();
lista.Add(Tuple.Create(7, "Tânia"));
lista.Add(Tuple.Create(2, "Rúbia"));
lista.Add(Tuple.Create(4, "Haroldo"));
lista.Add(Tuple.Create(1, "Frederico"));
lista.Add(Tuple.Create(3, "João"));
lista.Add(Tuple.Create(5, "Carlos"));
lista.Add(Tuple.Create(6, "Samanta"));
lista.Add(Tuple.Create(8, "Marcio"));
lista.Add(Tuple.Create(9, "Carla"));
lista.Add(Tuple.Create(10, "Francisco"));
}
private List<Tuple<int, string>> RetornaListaPessoasOrdenadaPorNome()
{
var lstOrdenada = lista.OrderBy(t => t.Item2).Select(t=>t).ToList();
return lstOrdenada;
}
private List<Tuple<int, string>> RetornaListaPessoasOrdenadaPorCodigo()
{
var lstOrdenada = lista.OrderBy(t => t.Item1).Select(t => t).ToList();
return lstOrdenada;
}
private void btnOK_Click(object sender, EventArgs e)
{
List<Tuple<int, string>> listaOrdenada;
if (rbtNome.Checked)
listaOrdenada = RetornaListaPessoasOrdenadaPorNome();
else
listaOrdenada = RetornaListaPessoasOrdenadaPorCodigo();
lstNomes.DataSource = listaOrdenada;
lstNomes.ValueMember = "Item1";
lstNomes.DisplayMember = "Item2";
}
}
}
Comparing and Ordering
The interfaces IStructuralComparable
and IStructuralEquatable
were introduced in .NET 4.0 to assist in supporting Tuples.
A tuple is equal to another if and only if all items are equal, i.e., t1.Item1 ==t2.Item1 and
t1.Item2 == t2.Item2
, and so on.
To sort, a comparison is made on individual items, i.e., the comparison is made in the first Item1
if t1.Item1> t2.Item1
then Tuple t2
is the smallest, if t1.Item1 == t2.Item1
then the comparison is made in item2
and so on.
To use the interfaces IComparable
, IEquatable
, IStructuralComparable
and IStructuralEquatable
, we must make the cast to the desired interface explicitly.
Tuple<int, int> t1 = Tuple.Create(3, 9);
Tuple<int, int> t2 = Tuple.Create(3, 9);
Tuple<int, int> t3 = Tuple.Create(9, 3);
Tuple<int, int> t4 = Tuple.Create(9, 4);
Console.WriteLine("t1 = t2 : {0}", t1.Equals(t2)); Console.WriteLine("t1 = t2 : {0}", t1 == t2); Console.WriteLine("t1 = t3 : {0}", t1.Equals(t3));
Console.WriteLine("t1 < t3 : {0}", ((IComparable)t1).CompareTo(t3) < 0); Console.WriteLine("t3 < t4 : {0}", ((IComparable)t3).CompareTo(t4) < 0);
Conclusion
While the indiscriminated use of Tuples affects the readability of the code, its use at the appropriate time can be very handy for developers, allowing them to return multiple values from a function without the need to create parameters "ref
" and /
or "out
", allowing the creation of composite keys to collections of type Dictionary
and eliminates the need to create struct
s or class
es or just to fill combobox or lists.
History
- 9th May, 2011: Initial post