C# 4 - 元组






4.81/5 (91投票s)
元组对开发人员来说非常方便,它允许他们从函数返回多个值,创建字典的复合键,并消除仅用于填充组合框的结构体或类。
引言
基本上,元组(C# 中的 Tuple)是一个有序的、不可变的、固定大小的异构对象序列,也就是说,每个对象都具有特定的类型。
元组在编程中并不新鲜。它们已经在 F#、Python 和数据库中使用。然而,它们对 C# 来说是新的。元组在 C# 4.0 中随动态编程一同引入。
要了解更多信息,请访问:http://msdn.microsoft.com/en-us/library/system.tuple.aspx。
背景
A) “由列的有序列表组成的每一行都是一个记录或元组。记录可能不包含所有列的信息,并且在必要时可以包含 null
值。”
http://pt.wikipedia.org/wiki/Banco_de_dados_relacional#Registros_.28ou_tuples.29
示例
Insert Into Tb_clients values (1,’Frederico’, ‘1975-03-24’)
在此示例中,(1,’Frederico’, ‘1975-03-24’
) 是一个元组。
B) “在数学中,元组是对象的一个固定大小的有序序列”
http://es.wikipedia.org/wiki/Tuple
示例:在方程 2x2 - 4x - 3
中,序列 (2, -4, -3)
是一个元组。
C) “n 元组(也称为 n-tuple)是 n 个元素的有序序列,可以通过递归的有序对来定义。
区分 n 元组的主要属性是
- n 元组可以包含一个对象一次以上。
- 对象必然按给定的顺序表示。”
http://pt.wikipedia.org/wiki/Tuple
.NET 4.0 中的元组
虽然匿名类型在 C# 中具有类似的功能,但它们不能作为方法的返回值,而 Tuple 类型则可以。
KeyValuePair<TKey, TValue>
可以与 tuple<T1, T2>
进行比较,一个显著的区别是 KeyValuePair
是一个 struct
而 Tuple
是一个 class
。
元组是异构对象的有序、不可变、固定大小的序列。
有序序列 |
元组中项的顺序遵循创建时的顺序。 |
不可变 |
所有元组属性都是只读的,也就是说,一旦创建,就不能更改。 |
固定大小 |
大小在创建时确定。如果它创建时包含三个项,则不能添加新项。 |
异构对象 |
每个项都有一个特定于其自身且独立于其他项的类型。 |
缺点
由于元组没有明确的语义含义,您的代码会变得难以阅读。
创建元组
在 C# 中,Tuple
是一个 static
类,它实现了“工厂”模式来创建 Tuple
实例。我们可以使用构造函数或 static
方法“Create
”来创建 Tuple
实例。
返回 Tuple
类型实例的 static
方法“Create
”有八个重载
重载 |
描述 |
创建 1 元组,或单例。 |
|
创建 2 元组,或对。 |
|
创建 3 元组,或三元组。 |
|
创建 4 元组,或四元组。 |
|
创建 5 元组,或五元组。 |
|
创建 6 元组,或六元组。 |
|
Create<T1, T2, T3, T4, T5, T6, T7>(T1, T2, T3, T4, T5, T6, T7) |
创建 7 元组,或七元组。 |
Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1, T2, T3, T4, T5, T6, T7, T8) |
创建 8 元组,或八元组。 |
示例
Tuple<int, string, DateTime> _cliente =
Tuple.Create(1, "Frederico", new DateTime(1975, 3,24));
元组的项数限制为 8。如果要创建包含更多项的元组,则必须创建嵌套元组。
元组的第八个项必须是另一个元组。下面的示例将生成一个异常。
// Error: The eighth element must be a tuple.
var t8 = new Tuple<int,int,int,int,int,int,int,int>(1, 2, 3, 4, 5, 6, 7, 8);
要创建包含 8 个项的元组,我们必须这样做
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;
要创建包含超过 8 个项的元组,我们这样做
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;
元组代表什么?
元组没有可能具有意义的名称。元组的属性称为“Item1
”、“Item2
”等。
两个元组可以相等,但这并不意味着它们是相同的。它的含义不明确,这可能使您的代码可读性降低。例如,以下两个元组是相等的,但代表不同的事物
(3, 9):产品代码 3 和数量 9
(3, 9):3 和 9 是查询返回的客户代码。
如上所述,由于元组不携带关于其含义的信息,因此它的使用是通用的,并且开发人员在创建和使用时决定其含义。
那么,为什么要使用它们?
A) 方法返回值
元组提供了一种将多个值分组到单个结果的快速方法,当用作函数返回值时非常有用,无需创建“ref
”和/或“out
”参数。
示例
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;
}
}
}
方法返回的另一个例子是当我们必须返回一个匿名类型的列表时。在这种情况下,我们可以轻松地用元组替换该类型。
示例
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) 字典中的复合键
由于 IEquatable
接口定义了 GetHashCode()
,IStructuralEquatable
接口的实现会创建一个组合成员哈希码的哈希码,从而允许将元组用作 Dictionary
类型集合的复合键。
示例
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) 替换仅用于携带返回值或填充列表的类或结构体
使用元组,我们无需创建类或结构体来仅存储临时值,例如创建结构体或类来将值添加到 combobox
或 listbox
。使用元组,将不再需要创建它们。
示例
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";
}
}
}
比较和排序
IStructuralComparable
和 IStructuralEquatable
接口已在 .NET 4.0 中引入,以支持元组。
当且仅当所有项都相等时,一个元组才等于另一个元组,即 t1.Item1 ==t2.Item1 and
t1.Item2 == t2.Item2
,依此类推。
为了排序,会根据单个项进行比较,也就是说,首先比较 Item1
,如果 t1.Item1> t2.Item1
,则元组 t2
最小,如果 t1.Item1 == t2.Item1
,则比较 item2
,依此类推。
IComparable
、IEquatable
、IStructuralComparable
和 IStructuralEquatable
接口,我们必须显式地进行类型转换。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)); //true
Console.WriteLine("t1 = t2 : {0}", t1 == t2); //false
Console.WriteLine("t1 = t3 : {0}", t1.Equals(t3)); // false
Console.WriteLine("t1 < t3 : {0}", ((IComparable)t1).CompareTo(t3) < 0); //true
Console.WriteLine("t3 < t4 : {0}", ((IComparable)t3).CompareTo(t4) < 0); //true
结论
虽然对元组的不加区分的使用会影响代码的可读性,但在适当的时候使用它们可以非常方便开发人员,使他们能够从函数返回多个值,而无需创建“ref
”和/或“out
”参数,从而允许创建 Dictionary
类型的集合的复合键,并消除了创建 struct
或 class
以仅用于填充组合框或列表的需要。
历史
- 2011 年 5 月 9 日:初始帖子