我反编译了一些C#7库,并看到了ValueTuple泛型。是什么ValueTuples,为什么不Tuple呢?
ValueTuple
ValueTuples
Tuple
是什么ValueTuples,为什么不Tuple呢?
A ValueTuple是反映元组的结构,与原始System.Tuple类相同。
System.Tuple
Tuple和之间的主要区别ValueTuple是:
System.ValueTuple
class
struct
在C#7之前,使用元组还不是很方便。它们的字段名称是Item1,Item2等,并且该语言没有像大多数其他语言(Python,Scala)那样为它们提供语法糖。
Item1
Item2
当.NET语言设计团队决定合并元组并在语言级别向其添加语法糖时,一个重要因素就是性能。随着ValueTuple是值类型,你可以使用它们,因为(作为一个实现细节)时,为了避免GC压力,他们会在栈上分配。
另外,struct运行时会获得自动(浅)等式语义,而运行时class不会。尽管设计团队确保为元组提供了更加优化的相等性,但因此为其实现了自定义相等性。
这是以下设计注释中Tuples的一段:
Tuples
结构或类: 如前所述,我建议创建元组类型structs而不是 classes,这样就不会与它们相关联的分配罚款。它们应尽可能轻巧。 可以说,structs最终可能会导致成本更高,因为分配复制了更大的价值。因此,如果分配给它们的权限远远大于创建的权限,那么structs这将是一个错误的选择。 但是,在其动机中,元组是短暂的。当部分比整体重要时,您将使用它们。因此,常见的模式是构造,返回并立即对其进行解构。在这种情况下,结构显然是更可取的。 结构还具有许多其他好处,这在下面将变得显而易见。
如前所述,我建议创建元组类型structs而不是 classes,这样就不会与它们相关联的分配罚款。它们应尽可能轻巧。
structs
classes
可以说,structs最终可能会导致成本更高,因为分配复制了更大的价值。因此,如果分配给它们的权限远远大于创建的权限,那么structs这将是一个错误的选择。
但是,在其动机中,元组是短暂的。当部分比整体重要时,您将使用它们。因此,常见的模式是构造,返回并立即对其进行解构。在这种情况下,结构显然是更可取的。
结构还具有许多其他好处,这在下面将变得显而易见。
您可以很容易地看到,使用它System.Tuple很快就会变得模棱两可。例如,假设我们有一种计算a的总和和计数的方法List<Int>:
List<Int>
public Tuple<int, int> DoStuff(IEnumerable<int> values) { var sum = 0; var count = 0; foreach (var value in values) { sum += value; count++; } return new Tuple(sum, count); }
在接收端,我们最终得到:
Tuple<int, int> result = DoStuff(Enumerable.Range(0, 10)); // What is Item1 and what is Item2? // Which one is the sum and which is the count? Console.WriteLine(result.Item1); Console.WriteLine(result.Item2);
您可以将值元组解构为命名参数的方法是该功能的强大功能:
public (int sum, int count) DoStuff(IEnumerable<int> values) { var res = (sum: 0, count: 0); foreach (var value in values) { res.sum += value; res.count++; } return res; }
在接收端:
var result = DoStuff(Enumerable.Range(0, 10)); Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");
要么:
var (sum, count) = DoStuff(Enumerable.Range(0, 10)); Console.WriteLine($"Sum: {sum}, Count: {count}");
如果我们看一下先前示例的内容,ValueTuple当我们要求其解构时,我们可以确切地看到编译器的解释方式:
[return: TupleElementNames(new string[] { "sum", "count" })] public ValueTuple<int, int> DoStuff(IEnumerable<int> values) { ValueTuple<int, int> result; result..ctor(0, 0); foreach (int current in values) { result.Item1 += current; result.Item2++; } return result; } public void Foo() { ValueTuple<int, int> expr_0E = this.DoStuff(Enumerable.Range(0, 10)); int item = expr_0E.Item1; int arg_1A_0 = expr_0E.Item2; }
在内部,编译后的代码使用Item1和Item2,但是由于我们使用的是分解的元组,因此所有这些都从我们这里抽象出来。具有命名实参的元组用注释TupleElementNamesAttribute。如果我们使用单个新鲜变量而不是分解,则会得到:
TupleElementNamesAttribute
public void Foo() { ValueTuple<int, int> valueTuple = this.DoStuff(Enumerable.Range(0, 10)); Console.WriteLine(string.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2)); }
请注意,编译器仍然必须作出一些魔术发生(通过属性),当我们调试我们的应用程序,因为这将是奇怪地看到Item1,Item2。