什么是元组
元组是 C# 提供的简单定义的类型,早期版本为 System.Tuple
,使用该类型可以简化类型的定义。
// 姓名 性别 出生日期
Tuple<string, bool, DateTime> person = new Tuple<string, bool, DateTime>("John", true, new DateTime(1990, 01, 01));
Console.WriteLine($"{person.Item1} is a good {(person.Item2 ? "boy" : "girl")} and {(person.Item2 ? "his" : "her")} birthday is on {person.Item3.ToString("MM.dd")}");
如上,可以组装多个类型成为一个新的类型,可以应用在多返回值或一些数组集合场景下。
// 多返回值:是否执行成功 错误信息
public Tuple<bool, string> Execute(string sql)
{
bool result = false;
string message = null;
try
{
result = SqlHelper.Excute(sql);
}
catch (Exception exc)
{
message = $"错误信息:{exc.Message}\n堆栈信息:{exc.StackTrace}";
}
return new Tuple<bool, string>(result, message);
}
// 特殊类型集合
public void Test()
{
List<Tuple<string, bool, DateTime>> people = new List<Tuple<string, bool, DateTime>>();
people.Add(new Tuple<string, bool, DateTime>("John", true, new DateTime(1990, 01, 01)));
people.Add(new Tuple<string, bool, DateTime>("Jane", false, new DateTime(1992, 02, 02)));
foreach (var item in people)
{
Console.WriteLine($"{item.Item1} is a good {(item.Item2 ? "boy" : "girl")} and {(item.Item2 ? "his" : "her")} birthday is on {item.Item3.ToString("MM.dd")}");
}
}
但是该类型使用时还是有一些缺陷,特别是通过 Item{n}
标记元素,没有具体的含义在使用中就比较容易出错。
另外,其泛型参数只提供了 7 个,如果超过 7 个,我们就要使用第 8 个泛型参数使用 System.Tuple
类型进行嵌套,例如:
// 定义多个参数
Tuple<int, int, int, int, int, int, int, Tuple<int>> nums = new Tuple<int, int, int, int, int, int, int, Tuple<int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int>(8));
所以微软在后来提供了 System.ValueTuple
类型,取代了 System.Tuple
,我们可以通过 NuGet
引用 System.ValueTuple
使用。
如上图所示,其标记的支持 .NET Framework
的最低版本为 .NET Framework 4.5
,但实际上我们给 .NET Framework 4.0
的项目引用依然是可以正常使用的。另外如果想在 .NET Framework 3.5
的项目中使用,可以引用上图中的 ValueTupleBridge
。
ValueTuple 的语法
首先我们使用 NuGet
添加对 System.ValueTuple
的引用,我们可以将以上 Tuple 部分定义的内容进行简单的重写:
// 多返回值:是否执行成功 错误信息
public (bool result, string message) Execute(string sql)
{
bool result = false;
string message = null;
try
{
result = SqlHelper.Excute(sql);
}
catch (Exception exc)
{
message = $"错误信息:{exc.Message}\n堆栈信息:{exc.StackTrace}";
}
return (result, message);
}
// 特殊类型集合
public void Test()
{
List<(string name, bool sex, DateTime birthday)> people = new List<(string name, bool sex, DateTime birthday)>();
people.Add(("John", true, new DateTime(1990, 01, 01)));
people.Add(("Jane", false, new DateTime(1992, 02, 02)));
foreach (var item in people)
{
Console.WriteLine($"{item.name} is a good {(item.sex ? "boy" : "girl")} and {(item.sex ? "his" : "her")} birthday is on {item.birthday.ToString("MM.dd")}");
}
}
// 定义多个参数
(int num1, int num2, int num3, int num4, int num5, int num6, int num7, int num8) nums = (1, 2, 3, 4, 5, 6, 7, 8);
此外我们还可以用多种方式来声明 System.ValueTuple
:
- 将元组赋给单独声明的变量
(string name, bool sex, DateTime birthday) = ("John", true, new DateTime(1992, 02, 02)); Console.WriteLine($"{name} is a good {(sex ? "boy" : "girl")} and {(sex ? "his" : "her")} birthday is on {birthday.ToString("MM.dd")}");
- 将元组赋给预声明的变量
string name; bool sex; DateTime birthday; (name, sex, birthday) = ("John", true, new DateTime(1992, 02, 02)); Console.WriteLine($"{name} is a good {(sex ? "boy" : "girl")} and {(sex ? "his" : "her")} birthday is on {birthday.ToString("MM.dd")}");
- 将元组赋给单独声明和隐式类型的变量
(var name, var sex, var birthday) = ("John", true, new DateTime(1992, 02, 02)); Console.WriteLine($"{name} is a good {(sex ? "boy" : "girl")} and {(sex ? "his" : "her")} birthday is on {birthday.ToString("MM.dd")}");
- 将元组赋给单独声明和隐式类型的变量,但只用一个
var
var (name, sex, birthday) = ("John", true, new DateTime(1992, 02, 02)); Console.WriteLine($"{name} is a good {(sex ? "boy" : "girl")} and {(sex ? "his" : "her")} birthday is on {birthday.ToString("MM.dd")}");
- 声明具名元组,将元组值赋给它,按名称访问元组项
(string name, bool sex, DateTime birthday) person = ("John", true, new DateTime(1992, 02, 02)); Console.WriteLine($"{person.name} is a good {(person.sex ? "boy" : "girl")} and {(person.sex ? "his" : "her")} birthday is on {person.birthday.ToString("MM.dd")}");
- 声明包含具名元组项的元组,将其赋给隐式类型的变量,按名称访问元组项
var person = (name: "John", sex: true, birthday: new DateTime(1992, 02, 02)); Console.WriteLine($"{person.name} is a good {(person.sex ? "boy" : "girl")} and {(person.sex ? "his" : "her")} birthday is on {person.birthday.ToString("MM.dd")}");
- 将元组项未具名的元组赋给隐式类型的变量,通过项编号属性访问单独的元素
var person = ("John", true, new DateTime(1992, 02, 02)); Console.WriteLine($"{person.Item1} is a good {(person.Item2 ? "boy" : "girl")} and {(person.Item2 ? "his" : "her")} birthday is on {person.Item3.ToString("MM.dd")}");
- 将元组项具名的元组赋给隐式类型的变量,但还是通过项编号
var person = (name: "John", sex: true, birthday: new DateTime(1992, 02, 02)); Console.WriteLine($"{person.Item1} is a good {(person.Item2 ? "boy" : "girl")} and {(person.Item2 ? "his" : "her")} birthday is on {person.Item3.ToString("MM.dd")}");
- 赋值时使用下划线丢弃元组的一部分(弃元)
(string name, bool sex, DateTime birthday, _) = ("John", true, new DateTime(1992, 02, 02), "China");
- 通过变量和属性名推断元组项名称(
C# 7.1
新增)string name = "John"; bool sex = true; DateTime birthday = new DateTime(1990, 01, 01); var person = (name, sex, birthday); Console.WriteLine($"{person.name} is a good {(person.sex ? "boy" : "girl")} and {(person.sex ? "his" : "her")} birthday is on {person.birthday.ToString("MM.dd")}");
注意:
- 元组是在对象中封装数据的轻量级方案,元组数据项的类型没有限制,但是他们的类型是由编译器决定,不能在运行时改变。
- 如果需要对封装的数据关联行为(事件、方法),应该创建一个类而不是使用元组。
- 元组
System.ValueTuple
类似结构体,如果将一个元组赋值给另外一个元组,其是值传递而非引用传递。