65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 4 - 元组

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (91投票s)

2011年5月9日

CPOL

5分钟阅读

viewsIcon

289992

元组对开发人员来说非常方便,它允许他们从函数返回多个值,创建字典的复合键,并消除仅用于填充组合框的结构体或类。

引言

基本上,元组(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 是一个 structTuple 是一个 class

元组是异构对象的有序、不可变、固定大小的序列。

有序序列

元组中项的顺序遵循创建时的顺序。

不可变

所有元组属性都是只读的,也就是说,一旦创建,就不能更改。

固定大小

大小在创建时确定。如果它创建时包含三个项,则不能添加新项。

异构对象

每个项都有一个特定于其自身且独立于其他项的类型。

缺点

由于元组没有明确的语义含义,您的代码会变得难以阅读。

创建元组

在 C# 中,Tuple 是一个 static 类,它实现了“工厂”模式来创建 Tuple 实例。我们可以使用构造函数或 static 方法“Create”来创建 Tuple 实例。

返回 Tuple 类型实例的 static 方法“Create”有八个重载

重载

描述

Create<T1>(T1)

创建 1 元组,或单例。

Create<T1, T2>(T1, T2)

创建 2 元组,或对。

Create<T1, T2, T3>(T1, T2, T3)

创建 3 元组,或三元组。

Create<T1, T2, T3, T4>(T1, T2, T3, T4)

创建 4 元组,或四元组。

Create<T1, T2, T3, T4, T5>(T1, T2, T3, T4, T5)

创建 5 元组,或五元组。

Create<T1, T2, T3, T4, T5, T6>(T1, T2, T3, T4, T5, T6)

创建 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";
        }
    }
} 

form.JPG

比较和排序

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 ,依此类推。

要使用 IComparableIEquatableIStructuralComparable 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 类型的集合的复合键,并消除了创建 structclass 以仅用于填充组合框或列表的需要。

历史

  • 2011 年 5 月 9 日:初始帖子
© . All rights reserved.