System.ValueTuple和System.Tuple有什么区别?

我反编译了一些C#7库,并使用了ValueTuplegenerics。 什么是ValueTuples ,为什么不是Tuple呢?

什么是ValuedTuples ,为什么不是Tuple呢?

ValueTuple是一个反映元组的结构,与原始的System.Tuple类相同。

TupleValueTuple的主要区别是:

  • System.ValueTuple是一个值types(结构),而System.Tuple是一个引用types( class )。 谈到分配和GC压力时,这是有意义的。
  • System.ValueTuple不仅是一个struct ,它是一个可变struct ,而且在使用它的时候一定要小心。 想想当一个类持有一个System.ValueTuple作为一个字段时会发生什么。
  • System.ValueTuple通过字段而不是属性暴露它的项目。

直到C#7,使用元组不是很方便。 他们的字段名是Item1Item2等,并且语言没有为大多数其他语言(Python,Scala)提供语法糖。

当.NET语言devise团队决定合并元组并在语言层面添加语法糖时,一个重要的因素就是性能。 由于ValueTuple是一个值types,因此在使用它时可以避免GC压力,因为(作为实现细节)它们将被分配到堆栈中。

此外,一个struct得到运行时的自动(浅层)相等语义,而一个class没有。 尽pipedevise团队确信会有更加优化的元组相等性,因此实现了自定义的相等性。

这里是Tuplesdevise笔记的一段:

结构或类:

如前所述,我build议使用元组typesstructs而不是classes ,这样就不会有分配惩罚。 他们应该尽可能轻。

可以说, structs可能最终成本更高,因为分配复制了更大的价值。 因此,如果他们被分配了比创build更多的东西,那么structs将是一个不好的select。

尽pipe如此,他们的动机是元组短暂的。 当部件比整体更重要时,你会使用它们。 所以常见的模式是构build,返​​回并立即解构它们。 在这种情况下,结构显然是可取的。

结构还有许多其他的好处,在下面将会变得很明显。

例子:

你可以很容易地看到,使用System.Tuple很快变得模糊不清。 例如,假设我们有一个计算List<Int>的总和和计数的方法:

 public Tuple<int, int> DoStuff(IEnumerable<int> ints) { 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); 

您可以将值元组解构成命名参数的方式是该function的真正威力:

 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}"); 

编译器的好东西:

如果我们看一下前面例子的封面,我们可以看到编译器在parsingValueTuple时如何解释:

 [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; } 

在内部,编译后的代码使用Item1Item2 ,但是由于我们使用分解的元组,所有这些都从我们这里抽象出来。 具有命名参数的元组用TupleElementNamesAttribute进行注释。 如果我们使用一个新的variables而不是分解,我们得到:

 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)); } 

请注意,当我们debugging我们的应用程序时,编译器仍然需要做一些魔术(通过属性),因为看到Item1Item2会很奇怪。

TupleValueTuple的区别在于Tuple是一个引用types, ValueTuple是一个值types。 后者是可取的,因为对C#7中语言的改变使用更频繁的元组,但是在堆上为每个元组分配一个新对象是一个性能问题,特别是在没有必要时。

但是,在C#7中,这个想法是,您不必明确使用任何一种types,因为添加了用于元组使用的语法糖。 例如,在C#6中,如果您想使用元组来返回值,则必须执行以下操作:

 public Tuple<string, int> GetValues() { // ... return new Tuple(stringVal, intVal); } var value = GetValues(); string s = value.Item1; 

但是,在C#7中,您可以使用这个:

 public (string, int) GetValues() { // ... return (stringVal, intVal); } var value = GetValues(); string s = value.Item1; 

你甚至可以更进一步,给出值的名字:

 public (string S, int I) GetValues() { // ... return (stringVal, intVal); } var value = GetValues(); string s = value.S; 

…或者完全解构元组:

 public (string S, int I) GetValues() { // ... return (stringVal, intVal); } var (S, I) = GetValues(); string s = S; 

元组在C#之前不经常使用,因为它们很繁琐而且冗长,只有在仅为单个工作实例构build数据类/结构的情况下,才真正用到元组。 但是在C#7中,元组现在拥有语言级别的支持,所以使用它们更清洁,更实用。

我看了TupleValueTuple的来源。 区别在于Tuple是一个classValueTuple是一个实现IEquatablestruct

这意味着如果它们不是相同的实例,则Tuple == Tuple将返回false ,但是如果ValueTuple == ValueTuple的types相同,则它们将返回true ,并且Equals对它们包含的每个值返回true

其他答案忘了提及重要的一点。而不是重新编写,我会参考源代码中的XML文档:

ValueTupletypes(从0到8)构成了C#中的元组和F#中的结构元组的基础的运行时实现。

除了通过语言语法创build ,他们最容易通过ValueTuple.Create工厂方法创build。 System.ValueTupletypes与System.Tupletypes的不同之处在于:

  • 他们是结构而不是阶级,
  • 他们是可变的而不是只读的
  • 他们的成员(比如Item1,Item2等)是字段而不是属性。

随着这种types和C#7.0编译器的引入,您可以轻松编写

 (int, string) idAndName = (1, "John"); 

并从一个方法中返回两个值:

 private (int, string) GetIdAndName() { //..... return (id, name); } 

System.Tuple相反,您可以更新其成员(可变),因为它们是公有读写字段,可以赋予有意义的名称:

 (int id, string name) idAndName = (1, "John"); idAndName.name = "New Name";