元组类型(C# 参考)Tuple types (C# reference)

元组功能在 C# 7.0 及更高版本中可用,它提供了简洁的语法,用于将多个数据元素分组成一个轻型数据结构。Available in C# 7.0 and later, the tuples feature provides concise syntax to group multiple data elements in a lightweight data structure. 下面的示例演示了如何声明元组变量、对它进行初始化并访问其数据成员:The following example shows how you can declare a tuple variable, initialize it, and access its data members:

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

如前面的示例所示,若要定义元组类型,需要指定其所有数据成员的类型,或者,可以指定字段名称As the preceding example shows, to define a tuple type, you specify types of all its data members and, optionally, the field names. 虽然不能在元组类型中定义方法,但可以使用 .NET 提供的方法,如下面的示例所示:You cannot define methods in a tuple type, but you can use the methods provided by .NET, as the following example shows:

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

从 C# 7.3 开始,元组类型支持相等运算符 ==!=Beginning with C# 7.3, tuple types support equality operators == and !=. 有关详细信息,请参阅元组相等部分。For more information, see the Tuple equality section.

元组类型是值类型;元组元素是公共字段。Tuple types are value types; tuple elements are public fields. 这使得元组为可变的值类型。That makes tuples mutable value types.

备注

元组功能需要 System.ValueTuple 类型和相关的泛型类型(例如 System.ValueTuple<T1,T2>),这些类型在 .NET Core 和 .NET Framework 4.7 及更高版本中可用。The tuples feature requires the System.ValueTuple type and related generic types (for example, System.ValueTuple<T1,T2>), which are available in .NET Core and .NET Framework 4.7 and later. 若要在面向 .NET Framework 4.6.2 或更早版本的项目中使用元组,请将 NuGet 包 System.ValueTuple 添加到项目。To use tuples in a project that targets .NET Framework 4.6.2 or earlier, add the NuGet package System.ValueTuple to the project.

可以使用任意数量的元素定义元组:You can define tuples with an arbitrary large number of elements:

var t = 
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26);  // output: 26

元组的用例Use cases of tuples

元组最常见的用例之一是作为方法返回类型。One of the most common use cases of tuples is as a method return type. 也就是说,你可以将方法结果分组为元组返回类型,而不是定义 out 方法参数,如以下示例所示:That is, instead of defining out method parameters, you can group method results in a tuple return type, as the following example shows:

var xs = new[] { 4, 7, 9 };
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9

var ys = new[] { -9, 0, 67, 100 };
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100

(int min, int max) FindMinMax(int[] input)
{
    if (input is null || input.Length == 0)
    {
        throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
    }

    var min = int.MaxValue;
    var max = int.MinValue;
    foreach (var i in input)
    {
        if (i < min)
        {
            min = i;
        }
        if (i > max)
        {
            max = i;
        }
    }
    return (min, max);
}

如前面的示例所示,可以直接使用返回的元组实例,或者可以在单独的变量中析构它。As the preceding example shows, you can work with the returned tuple instance directly or deconstruct it in separate variables.

还可以使用元组类型,而不是匿名类型;例如,在 LINQ 查询中。You can also use tuple types instead of anonymous types; for example, in LINQ queries. 有关详细信息,请参阅在匿名类型和元组类型之间选择For more information, see Choosing between anonymous and tuple types.

通常,你会使用元组对相关的数据元素进行松散分组。Typically, you use tuples to group loosely related data elements. 这通常在专用和内部实用工具方法中有用。That is usually useful within private and internal utility methods. 对于公共 API 用例,请考虑定义结构类型。In the case of public API, consider defining a class or a structure type.

元组字段名称Tuple field names

可以在元组初始化表达式中或元组类型的定义中显式指定元组字段的名称,如下面的示例所示:You can explicitly specify the names of tuple fields either in a tuple initialization expression or in the definition of a tuple type, as the following example shows:

var t = (Sum: 4.5, Count: 3);
Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");

(double Sum, int Count) d = (4.5, 3);
Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");

从 C# 7.1 开始,如果未指定字段名称,则可以根据元组初始化表达式中相应变量的名称推断出此名称,如下面的示例所示:Beginning with C# 7.1, if you don't specify a field name, it may be inferred from the name of the corresponding variable in a tuple initialization expression, as the following example shows:

var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");

这称为元组投影初始值设定项。That's known as tuple projection initializers. 在以下情况下,变量名称不会被投影到元组字段名称中:The name of a variable isn't projected onto a tuple field name in the following cases:

  • 候选名称是元组类型的成员名称,例如 Item3ToStringRestThe candidate name is a member name of a tuple type, for example, Item3, ToString, or Rest.
  • 候选名称是另一元组的显式或隐式字段名称的重复项。The candidate name is a duplicate of another tuple field name, either explicit or implicit.

在这些情况下,你可以显式指定字段的名称,或按字段的默认名称访问字段。In those cases you either explicitly specify the name of a field or access a field by its default name.

元组字段的默认名称为 Item1Item2Item3 等。The default names of tuple fields are Item1, Item2, Item3 and so on. 始终可以使用字段的默认名称,即使字段名称是显式指定的或推断出的,如下面的示例所示:You can always use the default name of a field, even when a field name is specified explicitly or inferred, as the following example shows:

var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.

元组赋值元组相等比较不会考虑字段名称。Tuple assignment and tuple equality comparisons don't take field names into account.

在编译时,编译器会将非默认字段名称替换为相应的默认名称。At compile time, the compiler replaces non-default field names with the corresponding default names. 因此,显式指定或推断的字段名称在运行时不可用。As a result, explicitly specified or inferred field names aren't available at run time.

元组赋值和析构Tuple assignment and deconstruction

C# 支持满足以下两个条件的元组类型之间的赋值:C# supports assignment between tuple types that satisfy both of the following conditions:

  • 两个元组类型有相同数量的元素both tuple types have the same number of elements
  • 对于每个元组位置,右侧元组元素的类型与左侧相应的元组元素的类型相同或可以隐式转换为左侧相应的元组元素的类型for each tuple position, the type of the right-hand tuple element is the same as or implicitly convertible to the type of the corresponding left-hand tuple element

元组元素是按照元组元素的顺序赋值的。Tuple element values are assigned following the order of tuple elements. 元组字段的名称会被忽略且不会被赋值,如下面的示例所示:The names of tuple fields are ignored and not assigned, as the following example shows:

(int, double) t1 = (17, 3.14);
(double First, double Second) t2 = (0.0, 1.0);
t2 = t1;
Console.WriteLine($"{nameof(t2)}: {t2.First} and {t2.Second}");
// Output:
// t2: 17 and 3.14

(double A, double B) t3 = (2.0, 3.0);
t3 = t2;
Console.WriteLine($"{nameof(t3)}: {t3.A} and {t3.B}");
// Output:
// t3: 17 and 3.14

还可以使用赋值运算符 = 在单独的变量中析构元组实例。You can also use the assignment operator = to deconstruct a tuple instance in separate variables. 为此,可以使用以下方式之一进行操作:You can do that in one of the following ways:

  • 在括号内显式声明每个变量的类型:Explicitly declare the type of each variable inside parentheses:

    var t = ("post office", 3.6);
    (string destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • 在括号外使用 var 关键字来声明隐式类型化变量,并让编译器推断其类型:Use the var keyword outside the parentheses to declare implicitly typed variables and let the compiler infer their types:

    var t = ("post office", 3.6);
    var (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • 使用现有变量:Use existing variables:

    var destination = string.Empty;
    var distance = 0.0;
    
    var t = ("post office", 3.6);
    (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    

有关析构元组和其他类型的详细信息,请参阅析构元组和其他类型For more information about deconstruction of tuples and other types, see Deconstructing tuples and other types.

元组相等Tuple equality

从 C# 7.3 开始,元组类型支持 ==!= 运算符。Beginning with C# 7.3, tuple types support the == and != operators. 这些运算符按照元组元素的顺序将左侧操作数的成员与相应的右侧操作数的成员进行比较。These operators compare members of the left-hand operand with the corresponding members of the right-hand operand following the order of tuple elements.

(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right);  // output: True
Console.WriteLine(left != right);  // output: False

var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2);  // output: True
Console.WriteLine(t1 != t2);  // output: False

如前面的示例所示,==!= 操作不会考虑元组字段名称。As the preceding example shows, the == and != operations don't take into account tuple field names.

同时满足以下两个条件时,两个元组可比较:Two tuples are comparable when both of the following conditions are satisfied:

  • 两个元组具有相同数量的元素。Both tuples have the same number of elements. 例如,如果 t1t2 具有不同数目的元素,t1 != t2 则不会进行编译。For example, t1 != t2 doesn't compile if t1 and t2 have different numbers of elements.
  • 对于每个元组位置,可以使用 ==!= 运算符对左右侧元组操作数中的相应元素进行比较。For each tuple position, the corresponding elements from the left-hand and right-hand tuple operands are comparable with the == and != operators. 例如,(1, (2, 3)) == ((1, 2), 3) 不会进行编译,因为 1 不可与 (1, 2) 比较。For example, (1, (2, 3)) == ((1, 2), 3) doesn't compile because 1 is not comparable with (1, 2).

==!= 运算符将以短路方式对元组进行比较。The == and != operators compare tuples in short-circuiting way. 也就是说,一旦遇见一对不相等的元素或达到元组的末尾,操作将立即停止。That is, an operation stops as soon as it meets a pair of non equal elements or reaches the ends of tuples. 但是,在进行任何比较之前,将对所有元组元素进行计算,如以下示例所示:However, before any comparison, all tuple elements are evaluated, as the following example shows:

Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));

int Display(int s)
{
    Console.WriteLine(s);
    return s;
}
// Output:
// 1
// 2
// 3
// 4
// False

元组作为 out 参数Tuples as out parameters

通常,你会将具有 out 参数的方法重构为返回元组的方法。Typically, you refactor a method that has out parameters into a method that returns a tuple. 但是,在某些情况下,out 参数可以是元组类型。However, there are cases in which an out parameter can be of a tuple type. 下面的示例演示了如何将元组作为 out 参数使用:The following example shows how to work with tuples as out parameters:

var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))
{
    Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}
// Output:
// Found limits: min is 10, max is 20

元组与 System.TupleTuples vs System.Tuple

System.ValueTuple 类型支持的 C# 元组不同于 System.Tuple 类型表示的元组。C# tuples, which are backed by System.ValueTuple types, are different from tuples that are represented by System.Tuple types. 主要区别如下:The main differences are as follows:

  • ValueTuple 类型是值类型ValueTuple types are value types. Tuple 类型是引用类型Tuple types are reference types.
  • ValueTuple 类型是可变的。ValueTuple types are mutable. Tuple 类型是不可变的。Tuple types are immutable.
  • ValueTuple 类型的数据成员是字段。Data members of ValueTuple types are fields. Tuple 类型的数据成员是属性。Data members of Tuple types are properties.

C# 语言规范C# language specification

有关详细信息,请参阅以下功能建议说明:For more information, see the following feature proposal notes:

请参阅See also