对元组的语言支持
基本
一个元组是元素的有序,有限的名单。元组通常在编程中用作与单个实体一起工作的手段,而不是单独使用每个元组的元素,并在关系数据库中表示各个行(即记录)。
在 C#7.0 中,方法可以有多个返回值。在幕后,编译器将使用新的 ValueTuple 结构。
public (int sum, int count) GetTallies()
{
return (1, 2);
}
附注 :为了在 Visual Studio 2017 中工作,你需要获取 System.ValueTuple
包。
如果将元组返回方法结果分配给单个变量,则可以通过方法签名上的已定义名称访问成员:
var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2
元组解构
元组解构将元组分成了它的部分。
例如,调用 GetTallies
并将返回值赋值给两个单独的变量会将元组解构为这两个变量:
(int tallyOne, int tallyTwo) = GetTallies();
var
也有效:
(var s, var c) = GetTallies();
你也可以使用更短的语法,var
以外的 var
:
var (s, c) = GetTallies();
你还可以解构为现有变量:
int s, c;
(s, c) = GetTallies();
交换现在变得更加简单(不需要临时变量):
(b, a) = (a, b);
有趣的是,任何对象都可以通过在类中定义 Deconstruct
方法来解构:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}
var person = new Person { FirstName = "John", LastName = "Smith" };
var (localFirstName, localLastName) = person;
在这种情况下,(localFirstName, localLastName) = person
语法在 person
上调用 Deconstruct
。
甚至可以用扩展方法来定义解构。这相当于上面的内容:
public static class PersonExtensions
{
public static void Deconstruct(this Person person, out string firstName, out string lastName)
{
firstName = person.FirstName;
lastName = person.LastName;
}
}
var (localFirstName, localLastName) = person;
Person
类的另一种方法是将 Name
本身定义为 Tuple
。考虑以下:
class Person
{
public (string First, string Last) Name { get; }
public Person((string FirstName, string LastName) name)
{
Name = name;
}
}
然后你可以像这样实例化一个人(我们可以把一个元组作为参数):
var person = new Person(("Jane", "Smith"));
var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last; // "Smith"
元组初始化
你还可以在代码中任意创建元组:
var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John
Console.WriteLine(name.Item2);
// Outputs Smith
创建元组时,你可以为元组的成员分配特殊项目名称:
var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John
类型推断
使用相同签名(匹配类型和计数)定义的多个元组将被推断为匹配类型。例如:
public (int sum, double average) Measure(List<int> items)
{
var stats = (sum: 0, average: 0d);
stats.sum = items.Sum();
stats.average = items.Average();
return stats;
}
stats
可以返回,因为 stats
变量的声明和方法的返回签名是匹配的。
反射和元组字段名称
成员名称在运行时不存在。即使成员名称不匹配,Reflection 也会考虑具有相同数量和类型的成员的元组。将元组转换为 object
然后转换为具有相同成员类型但名称不同的元组也不会导致异常。
虽然 ValueTuple 类本身不保留成员名称的信息,但可以通过 TupleElementNamesAttribute 中的反射获得信息。此属性不应用于元组本身,而是应用于方法参数,返回值,属性和字段。这使得元组项目名称被保留整个组件,即如果一个方法返回(字符串名称,诠释计数)名称名称和数量将可在另一组装方法的调用者,因为返回值将包含值 TupleElementNameAttribute 标记名字和计数。
与泛型和 async
一起使用
新的元组功能(使用底层的 ValueTuple
类型)完全支持泛型,可以用作泛型类型参数。这使得它可以与 async
/ await
模式一起使用:
public async Task<(string value, int count)> GetValueAsync()
{
string fooBar = await _stackoverflow.GetStringAsync();
int num = await _stackoverflow.GetIntAsync();
return (fooBar, num);
}
与集合一起使用
在一个场景中有一个元组集合可能会变得有益,在这个场景中,你试图根据条件找到匹配的元组以避免代码分支。
例:
private readonly List<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
new Tuple<string, string, string>("test1", "test2", "Value"),
new Tuple<string, string, string>("test1", "test1", "Value2"),
new Tuple<string, string, string>("test2", "test2", "Value3"),
};
public string FindMatchingValue(string firstElement, string secondElement)
{
var result = labels
.Where(w => w.Item1 == firstElement && w.Item2 == secondElement)
.FirstOrDefault();
if (result == null)
throw new ArgumentException("combo not found");
return result.Item3;
}
随着新元组可以成为:
private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = new List<(string firstThingy, string secondThingyLabel, string foundValue)>()
{
("test1", "test2", "Value"),
("test1", "test1", "Value2"),
("test2", "test2", "Value3"),
}
public string FindMatchingValue(string firstElement, string secondElement)
{
var result = labels
.Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement)
.FirstOrDefault();
if (result == null)
throw new ArgumentException("combo not found");
return result.foundValue;
}
虽然上面示例元组的命名非常通用,但相关标签的概念允许更深入地理解代码中引用 item1
,item2
和 item3
的内容。
ValueTuple 和 Tuple 之间的差异
引入 ValueTuple
的主要原因是性能。
输入名称 | ValueTuple |
Tuple |
---|---|---|
类或结构 | struct |
class |
可变性(创建后改变值) | 易变的 | 一成不变 |
命名成员和其他语言支持 | 是 | 不( TBD ) |