csharp:元组

这是本文档旧的修订版!


C# 元组 (Tuples) 详解

元组(Tuple)是 C# 中一种轻量级的数据结构,用于将多个数据元素(可以是不同类型)组合成一个逻辑整体。

在 C# 7.0 之前,我们主要使用 `System.Tuple`(引用类型);而在 C# 7.0 及更高版本中,引入了基于 `System.ValueTuple` 的底层支持,提供了更高效、语法更简洁的值类型元组。本文主要聚焦于现代 C# 中的 ValueTuple

在很多场景下,我们需要从一个方法返回多个值。在元组普及之前,我们通常使用以下方式:

  • 使用 out 参数(代码冗长,阅读性差)。
  • 创建一个专门的 `class` 或 `struct`(对于临时数据传输来说,定义新类型显得过于笨重)。
  • 使用 `dynamic` 或 `object` 数组(失去了类型安全)。

元组解决了这个问题,它允许你快速定义一个包含多个字段的数据结构,无需显式定义类。

C# 提供了多种创建元组的语法糖。

如果不指定字段名,编译器会默认分配 `Item1`, `Item2`, `Item3` 等名称。

// 定义一个包含字符串和整数的元组
var person = ("John Doe", 30);
 
// 访问成员
Console.WriteLine($"Name: {person.Item1}, Age: {person.Item2}");

为了提高代码可读性,建议为元组的字段命名。

// 方式 A:左侧指定名称
(string Name, int Age) person1 = ("Alice", 28);
Console.WriteLine(person1.Name); // 输出 Alice
 
// 方式 B:右侧指定名称
var person2 = (Name: "Bob", Age: 35);
Console.WriteLine(person2.Age); // 输出 35
 
// 方式 C:混合使用(不推荐,容易混淆)
(string N, int A) person3 = (Name: "Charlie", Age: 40);
// 注意:此时变量名为 N 和 A,右侧的 Name 和 Age 仅作为标签被忽略(但在反射中会保留)
Console.WriteLine(person3.N); 

这是元组最常见的用例。

public class Calculator
{
    // 返回两个整数:商和余数
    public (int Quotient, int Remainder) Divide(int dividend, int divisor)
    {
        int q = dividend / divisor;
        int r = dividend % divisor;
        return (q, r);
    }
}
 
// 调用
var calc = new Calculator();
var result = calc.Divide(10, 3);
Console.WriteLine($"Quotient: {result.Quotient}, Remainder: {result.Remainder}");

解构允许我们将元组中的元素直接拆分赋值给独立的变量。

// 1. 显式类型解构
(int q, int r) = calc.Divide(10, 3);
 
// 2. 推断类型解构 (var)
var (q2, r2) = calc.Divide(10, 3);
 
// 3. 解构到已存在的变量
int x, y;
(x, y) = calc.Divide(10, 3);
 
// 4. 使用弃元 (Discard)
// 如果你只关心余数,不关心商,可以使用下划线 _ 忽略
var (_, remainder) = calc.Divide(10, 3);

从 C# 7.3 开始,元组支持 `==` 和 `!=` 运算符。比较是基于成员的值进行的(按位置比较),而不是引用地址。

var t1 = (A: 1, B: 2);
var t2 = (X: 1, Y: 2);
 
// 即使成员名称不同,只要位置对应的类型和值相同,即为相等
if (t1 == t2) 
{
    Console.WriteLine("Tuples are equal"); // 输出此行
}

这是一个面试中常见的高级话题。

特性 System.Tuple (旧版) System.ValueTuple (新版/推荐)
引入版本 .NET 4.0 C# 7.0 / .NET Framework 4.7+
类型 引用类型 (Class) 值类型 (Struct)
内存分配 堆 (Heap) 栈 (Stack) (通常情况下)
可变性 不可变 (Immutable) 可变 (Mutable)
成员命名 只能是 Item1, Item2… 支持自定义语义名称 (Name, Age…)
性能 较差 (涉及垃圾回收 GC) 较高 (内存开销小)
序列化 标准支持 需要特定配置或转换

最佳实践: 除非你需要向后兼容非常旧的 API,否则始终使用新的语法(即 `ValueTuple`)。

  1. 最多8个元素:虽然元组可以包含很多元素,但如果超过 7 个,第 8 个元素会嵌套另一个元组(`Rest` 属性)。如果你的元组包含这么多数据,建议定义一个 `class` 或 `struct`。
  2. 可变性风险:由于 `ValueTuple` 是可变的结构体,作为字典的 Key 时要小心(虽然它们实现了 `GetHashCode`,但如果修改了内部值,哈希值会变,导致在字典中找不到)。
using System;
 
namespace TupleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. 创建
            var product = (Id: 101, Name: "Laptop", Price: 999.99);
 
            // 2. 修改 (ValueTuple 是可变的)
            product.Price = 899.99;
 
            Console.WriteLine($"Product: {product.Name}, Price: {product.Price}");
 
            // 3. 解构
            var (id, _, price) = product; // 忽略 Name
            Console.WriteLine($"ID: {id}, Price: {price}");
 
            // 4. 交换变量值的经典技巧
            int a = 1, b = 2;
            (a, b) = (b, a); // 不使用临时变量交换 a 和 b
            Console.WriteLine($"a: {a}, b: {b}");
        }
    }
}

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/元组.1764140999.txt.gz
  • 最后更改: 2025/11/26 15:09
  • 张叶安