C# 元组

定义

元组是C#中的一种数据结构,它允许你将多个数据元素组合成一个简单的复合值。

分类

特性 System.Tuple System.ValueTuple
类型 引用类型 (class) 值类型 (struct)
可变性 不可变 可变
最大元素数 8(超出需嵌套) 无限制
命名元素 不支持 支持(如 (int Id, string Name)
语法 Tuple.Create(1, "A") (1, "A")
性能 较低(堆分配) 更高(栈分配,减少 GC 压力)
解构支持 不支持 支持(var (a, b) = tuple;

在 C# 中,元组(Tuple)主要分为 两种类型,它们有不同的特性和用途:

1. System.Tuple(引用类型元组)

  • 引入版本:C# 4.0(.NET Framework 4.0)

  • 类型class(引用类型,存储在堆上)

  • 特点

    • 不可变(创建后不能修改)

    • 最多支持 8 个元素Tuple<T1, ..., T8>),超过时需要嵌套

    • 语法较冗长,需要使用 Tuple.Createnew Tuple<>()

cs 复制代码
// 创建 Tuple
var tuple1 = Tuple.Create(1, "Alice"); // Tuple<int, string>
var tuple2 = new Tuple<int, string>(2, "Bob");

// 访问元素(通过 Item1, Item2...)
Console.WriteLine(tuple1.Item1); // 1
Console.WriteLine(tuple1.Item2); // "Alice"

2. System.ValueTuple(值类型元组)推荐使用

  • 引入版本:C# 7.0(.NET Framework 4.7+ / .NET Core / .NET 5+)

  • 类型struct(值类型,通常存储在栈上)

  • 特点

    • 可变(可以修改字段)

    • 支持 任意数量 的元素(无需嵌套)

    • 支持 命名元素(提高可读性)

    • 语法简洁(使用 () 声明)

    • 支持 解构(Deconstruction)

cs 复制代码
// 创建 ValueTuple
var valueTuple1 = (1, "Alice"); // ValueTuple<int, string>
var valueTuple2 = (Id: 2, Name: "Bob"); // 具名元组

// 访问元素(可通过 Item1 或自定义名称)
Console.WriteLine(valueTuple1.Item1); // 1
Console.WriteLine(valueTuple2.Name); // "Bob"(具名访问)

// 修改元组字段(ValueTuple 可变)
valueTuple1.Item1 = 10;

// 解构元组
var (id, name) = valueTuple2;
Console.WriteLine(id); // 2

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

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

3. 如何选择?

  • 推荐 ValueTuple(C# 7.0+)

    • 大多数情况下更高效、更灵活。

    • 适合临时数据组合、方法多返回值、LINQ 查询等。

  • 使用 Tuple(旧代码兼容)

    • 需要与旧版 C#(< 7.0)兼容时。

    • 需要不可变元组时(但通常 readonly struct 更好)。

4、总结

  • System.Tuple:旧版元组,引用类型,不可变,语法冗长。

  • System.ValueTuple:新版元组,值类型,可变,支持命名和解构,性能更好。

  • 优先使用 ValueTuple,除非需要兼容旧代码。

  • 使用 () 声明的是 ValueTuple

补充语法

元组解构

(Tuple Deconstruction)

元组解构是 C# 7.0 引入的一种语法特性,它允许你将元组中的元素分解并赋值给单独的变量。这是一种简洁的方式来访问元组的各个组成部分,而无需通过 Item1Item2 等属性来访问。元组解构大大简化了从复合值中提取数据的代码,使代码更加简洁易读。

cs 复制代码
// 创建一个元组
var person = ("John", "Doe", 30);
// 解构元组到单独变量
var (firstName, lastName, age) = person;
Console.WriteLine($"{firstName} {lastName}, {age}岁");


//忽略某些元素(使用下划线 _)
var (first, _, _) = GetPersonInfo();
Console.WriteLine($"First name: {first}");

例子

object类型和元组转换

在 C# 中,object 类型元组(Tuple) 之间可以相互转换,但需要注意 装箱(Boxing)拆箱(Unboxing)类型匹配 的问题。以下是几种常见的转换方式:

1. 元组 → object(装箱)

元组是值类型(ValueTuple),可以隐式或显式转换为 object(即装箱)。

cs 复制代码
// 定义一个元组
var tuple = (Name: "Alice", Age: 25);

// 隐式装箱为 object
object boxedTuple1 = tuple;

// 显式装箱
object boxedTuple2 = (object)tuple;

Console.WriteLine(boxedTuple1); // 输出: (Alice, 25)
Console.WriteLine(boxedTuple2); // 输出: (Alice, 25)

2. object → 元组(拆箱)

object 转换回元组时,需要进行 显式拆箱 ,并确保类型匹配,否则会抛出 InvalidCastException

cs 复制代码
object boxedTuple = ("Bob", 30);

// 方法1:直接拆箱(需要知道确切类型)
var unboxedTuple1 = (ValueTuple<string, int>)boxedTuple;
Console.WriteLine(unboxedTuple1.Item1); // Bob
Console.WriteLine(unboxedTuple1.Item2); // 30

// 方法2:使用模式匹配(更安全)
if (boxedTuple is ValueTuple<string, int> unboxedTuple2)
{
    Console.WriteLine(unboxedTuple2.Item1); // Bob
    Console.WriteLine(unboxedTuple2.Item2); // 30
}

总结:

1、 优先使用 模式匹配(is 安全拆箱。

2、 如果确定类型,直接拆箱((ValueTuple<...>))。

实现多返回值

普通实现(使用out 或ref)

cs 复制代码
FindMinMax(new[] { 3, 1, 4, 1, 5, 9 },out var min,out var max);
Console.WriteLine($"Min: {min}, Max: {max}");

void FindMinMax(int[] numbers,out int minValue,out int maxValue)
{
    minValue = numbers.Min();
    maxValue = numbers.Max();   
}

使用元组

cs 复制代码
var (min, max) = FindMinMax(new[] { 3, 1, 4, 1, 5, 9 });
Console.WriteLine($"Min: {min}, Max: {max}");

(int min, int max) FindMinMax(int[] numbers)
{
    return (numbers.Min(), numbers.Max());
}

两个变量值交换

普通实现

cs 复制代码
int a = 2;
int b = 4;

int temp = a;
a = b;
b = temp;

使用元组

cs 复制代码
int a = 2;
int b = 4;

(a, b) = (b, a);

这段代码使用了 C# 7.0 引入的元组(ValueTuple)和元组解构(Deconstruction) 来实现 两个变量的值交换,而不需要临时变量。这是一种简洁且高效的交换方式。

执行过程

1、(b, a) 创建一个临时的 ValueTuple<int, int>,其值为 (4, 2)(即 Item1 = b = 4, Item2 = a = 2)。

2、(a, b) = ... 使用 元组解构 ,将临时元组的值按顺序赋给 (a, b)a 被赋值为 Item1(即 4);b 被赋值为 Item2(即 2)。

3、最终结果:a2 变为 4,b4 变为 2。

优势

  • 代码更简洁,可读性更强。

  • 无需额外变量,减少代码量。

  • 适用于任何支持元组的类型(如 string、自定义类型等)。

注意事项

  • 性能:元组交换在底层仍然会创建临时元组,但对现代 C# 编译器优化友好,性能影响极小。

  • 不可变类型 :如果交换的是 不可变类型(如 string,仍然可以这样用,但实际是重新赋值而非修改原对象。

  • 代码可读性:在团队开发中,如果成员不熟悉元组语法,可能需要额外说明。

相关推荐
星尘库2 小时前
excel单元格如果是日期格式,在C#读取的时候会变成45807,怎么处理
开发语言·c#·excel
DoorToZen3 小时前
理解 `.sln` 和 `.csproj`:从项目结构到构建发布的一次梳理
经验分享·笔记·其他·前端框架·c#·.net
姜行运4 小时前
数据结构【二叉搜索树(BST)】
android·数据结构·c++·c#
△曉風殘月〆10 小时前
C#串口通信
嵌入式硬件·c#·串口
奥修的灵魂16 小时前
C#生成二维码和条形码
c#
小浪学编程17 小时前
C#学习7_面向对象:类、方法、修饰符
开发语言·学习·c#
Kookoos21 小时前
从单体到微服务:基于 ABP vNext 模块化设计的演进之路
后端·微服务·云原生·架构·c#·.net
阿蒙Amon1 天前
DevExpress&WinForms-AlertControl-使用教程
c#·devexpress·winforms
吃瓜日常1 天前
ABP项目发布到IIS流程
c#·.netcore
BruceNeter1 天前
c#开发完整的Socks5代理客户端与服务端——客户端(已完结)
网络·c#·socket·代理