I decompiled some C# 7 libraries and saw ValueTuple
generics being used. What are ValueTuples
and why not Tuple
instead?
我反编译了一些c# 7库,并看到使用了ValueTuple泛型。什么是ValueTuples,为什么不改为Tuple ?
- https://docs.microsoft.com/en-gb/dotnet/api/system.tuple
- https://docs.microsoft.com/en-gb/dotnet/api/system.tuple
- https://docs.microsoft.com/en-gb/dotnet/api/system.valuetuple
- https://docs.microsoft.com/en-gb/dotnet/api/system.valuetuple
6 个解决方案
#1
117
What are
ValuedTuples
and why notTuple
instead?什么是有价值的元组,为什么不改为元组呢?
A ValueTuple
is a struct which reflects a tuple, same as the original System.Tuple
class.
ValueTuple是反映元组的结构,与原始系统相同。Tuple类。
The main difference between Tuple
and ValueTuple
are:
Tuple和ValueTuple的主要区别是:
-
System.ValueTuple
is a value type (struct), whileSystem.Tuple
is a reference type (class
). This is meaningful when talking about allocations and GC pressure. - 系统。ValueTuple是一个值类型(struct),而System。Tuple是一个引用类型(类)。当讨论分配和GC压力时,这是有意义的。
-
System.ValueTuple
isn't only astruct
, it's a mutable one, and one has to be careful when using them as such. Think what happens when a class holds aSystem.ValueTuple
as a field. - 系统。ValueTuple不仅是一个结构体,它还是一个可变的结构体,因此在使用它们时必须小心。想想当一个类持有一个系统时会发生什么。ValueTuple作为一个领域。
-
System.ValueTuple
exposes its items via fields instead of properties. - 系统。ValueTuple通过字段而不是属性公开它的项。
Until C# 7, using tuples wasn't very convenient. Their field names are Item1
, Item2
, etc, and the language hadn't supplied syntax sugar for them like most other languages do (Python, Scala).
在c# 7之前,使用元组不是很方便。它们的字段名是Item1、Item2等,并且语言没有像大多数其他语言那样为它们提供语法糖(Python、Scala)。
When the .NET language design team decided to incorporate tuples and add syntax sugar to them at the language level an important factor was performance. With ValueTuple
being a value type, you can avoid GC pressure when using them because (as an implementation detail) they'll be allocated on the stack.
当. net语言设计团队决定在语言级别加入元组并添加语法糖时,一个重要的因素是性能。使用ValueTuple作为值类型,您可以避免使用它们时的GC压力,因为(作为实现细节)它们将被分配到堆栈上。
Additionally, a struct
gets automatic (shallow) equality semantics by the runtime, where a class
doesn't. Although the design team made sure there will be an even more optimized equality for tuples, hence implemented a custom equality for it.
此外,结构体在运行时获得自动(浅层)相等语义,而类则没有。尽管设计团队确保了对元组有更优化的等式,因此实现了自定义等式。
Here is a paragraph from the design notes of Tuples
:
以下是元组设计说明中的一段:
Struct or Class:
As mentioned, I propose to make tuple types
structs
rather thanclasses
, so that no allocation penalty is associated with them. They should be as lightweight as possible.如前所述,我建议将元组类型设置为struct而不是类,这样就不会与它们关联分配代价。它们应该尽可能的轻。
Arguably,
structs
can end up being more costly, because assignment copies a bigger value. So if they are assigned a lot more than they are created, thenstructs
would be a bad choice.可以说,结构体最终可能更昂贵,因为赋值复制的值更大。因此,如果分配给它们的数量远远超过创建的数量,那么结构体将是一个糟糕的选择。
In their very motivation, though, tuples are ephemeral. You would use them when the parts are more important than the whole. So the common pattern would be to construct, return and immediately deconstruct them. In this situation structs are clearly preferable.
然而,在他们的动机中,元组是短暂的。当部分比整体更重要时,你可以使用它们。因此,共同的模式是构造、返回并立即解构它们。在这种情况下,结构显然更可取。
Structs also have a number of other benefits, which will become obvious in the following.
struct也有许多其他的好处,这在下面将变得很明显。
Examples:
You can easily see that working with System.Tuple
becomes ambiguous very quickly. For example, say we have a method which calculates a sum and a count of a List<Int>
:
您可以很容易地看到与系统一起工作。Tuple很快变得模棱两可。例如,我们有一个方法,它计算列表
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);
}
On the receiving end, we end up with:
在接收端,我们得到:
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);
The way you can deconstruct value tuples into named arguments is the real power of the feature:
将值元组分解为命名参数的方法是这个特性的真正力量:
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;
}
And on the receiving end:
在接收端:
var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");
Or:
或者:
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");
Compiler goodies:
If we look under the cover of our previous example, we can see exactly how the compiler is interpreting ValueTuple
when we ask it to deconstruct:
如果我们看一下前面例子的封面,我们可以看到编译器是如何解释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;
}
Internally, the compiled code utilizes Item1
and Item2
, but all of this is abstracted away from us since we work with a decomposed tuple. A tuple with named arguments gets annotated with the TupleElementNamesAttribute
. If we use a single fresh variable instead of decomposing, we get:
在内部,编译后的代码使用Item1和Item2,但是由于使用分解的元组,所有这些都被抽象出来了。带有命名参数的tuple会使用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));
}
Note that the compiler still has to make some magic happen (via the attribute) when we debug our application, as it would be odd to see Item1
, Item2
.
注意,当我们调试应用程序时,编译器仍然必须使一些神奇的事情发生(通过属性),因为看到Item1, Item2是很奇怪的。
#2
14
The difference between Tuple
and ValueTuple
is that Tuple
is a reference type and ValueTuple
is a value type. The latter is desirable because changes to the language in C# 7 have tuples being used much more frequently, but allocating a new object on the heap for every tuple is a performance concern, particularly when it's unnecessary.
Tuple和ValueTuple的区别在于Tuple是引用类型,ValueTuple是值类型。后者是可取的,因为c# 7中对语言的更改使用元组要频繁得多,但是为每个元组在堆上分配一个新对象是一个性能问题,尤其是在不必要的情况下。
However, in C# 7, the idea is that you never have to explicitly use either type because of the syntax sugar being added for tuple use. For example, in C# 6, if you wanted to use a tuple to return a value, you would have to do the following:
然而,在c# 7中,您不必显式地使用这两种类型,因为为元组使用添加了语法糖。例如,在c# 6中,如果您想使用tuple返回值,您必须执行以下操作:
public Tuple<string, int> GetValues()
{
// ...
return new Tuple(stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
However, in C# 7, you can use this:
然而,在c# 7中,您可以使用以下内容:
public (string, int) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
You can even go a step further and give the values names:
您甚至可以更进一步,给出值的名称:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.S;
... Or deconstruct the tuple entirely:
…或者完全解构元组:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var (S, I) = GetValues();
string s = S;
Tuples weren't often used in C# pre-7 because they were cumbersome and verbose, and only really used in cases where building a data class/struct for just a single instance of work would be more trouble than it was worth. But in C# 7, tuples have language-level support now, so using them is much cleaner and more useful.
元组在c# pre-7中不经常使用,因为它们笨重而冗长,而且只在仅为单个工作实例构建数据类/结构的情况下才真正使用。但是在c# 7中,元组现在有语言级的支持,所以使用它们要干净得多,也更有用。
#3
8
I looked at the source for both Tuple
and ValueTuple
. The difference is that Tuple
is a class
and ValueTuple
is a struct
that implements IEquatable
.
我查看了Tuple和ValueTuple的源代码。区别在于,Tuple是一个类,ValueTuple是实现IEquatable的结构。
That means that Tuple == Tuple
will return false
if they are not the same instance, but ValueTuple == ValueTuple
will return true
if they are of the same type and Equals
returns true
for each of the values they contain.
这意味着,如果它们不是相同的实例,Tuple = Tuple将返回false,但是如果它们是相同的类型,那么ValueTuple == = ValueTuple将返回true,并且对于它们包含的每个值,Equals返回true。
#4
4
Other answers forgot to mention important points.Instead of rephrasing, I'm gonna reference the XML documentation from source code:
其他的答案忘了提到重要的要点。我将引用源代码中的XML文档,而不是重述:
The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#.
ValueTuple类型(从浓度0到8)包含运行时实现,它是c#中的元组和f#中的结构元组的基础。
Aside from created via language syntax, they are most easily created via the ValueTuple.Create
factory methods. The System.ValueTuple
types differ from the System.Tuple
types in that:
除了通过语言语法创建之外,它们最容易通过ValueTuple创建。创建工厂方法。这个系统。ValueTuple类型与系统不同。Tuple类型:
- they are structs rather than classes,
- 它们是结构体而不是类,
- they are mutable rather than readonly, and
- 它们是可变的,而不是只读的
- their members (such as Item1, Item2, etc) are fields rather than properties.
- 它们的成员(如Item1、Item2等)是字段而不是属性。
With introduction of this type and C# 7.0 compiler, you can easily write
通过介绍这种类型和c# 7.0编译器,您可以轻松编写
(int, string) idAndName = (1, "John");
And return two values from a method:
从一个方法返回两个值:
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
Contrary to System.Tuple
you can update its members (Mutable) because they are public read-write Fields that can be given meaningful names:
与系统。您可以更新它的成员(可变的),因为它们是公共的读写字段,可以赋予有意义的名称:
(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
#5
2
In addition to the comments above, one unfortunate gotcha of ValueTuple is that, as a value type, the named arguments get erased when compiled to IL, so they're not available for serialisation at runtime.
除了上面的注释之外,ValueTuple的一个不幸的问题是,作为值类型,在编译到IL时已命名的参数将被删除,因此它们在运行时不能进行序列化。
i.e. Your sweet named arguments will still end up as "Item1", "Item2", etc. when serialised via e.g. Json.NET.
例如,当通过Json.NET进行序列化时,您的命名参数仍然会以“Item1”、“Item2”等结尾。
#6
0
This is a pretty good summary of what it is and its limitation
这很好地总结了它是什么以及它的局限性
https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations
https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations
#1
117
What are
ValuedTuples
and why notTuple
instead?什么是有价值的元组,为什么不改为元组呢?
A ValueTuple
is a struct which reflects a tuple, same as the original System.Tuple
class.
ValueTuple是反映元组的结构,与原始系统相同。Tuple类。
The main difference between Tuple
and ValueTuple
are:
Tuple和ValueTuple的主要区别是:
-
System.ValueTuple
is a value type (struct), whileSystem.Tuple
is a reference type (class
). This is meaningful when talking about allocations and GC pressure. - 系统。ValueTuple是一个值类型(struct),而System。Tuple是一个引用类型(类)。当讨论分配和GC压力时,这是有意义的。
-
System.ValueTuple
isn't only astruct
, it's a mutable one, and one has to be careful when using them as such. Think what happens when a class holds aSystem.ValueTuple
as a field. - 系统。ValueTuple不仅是一个结构体,它还是一个可变的结构体,因此在使用它们时必须小心。想想当一个类持有一个系统时会发生什么。ValueTuple作为一个领域。
-
System.ValueTuple
exposes its items via fields instead of properties. - 系统。ValueTuple通过字段而不是属性公开它的项。
Until C# 7, using tuples wasn't very convenient. Their field names are Item1
, Item2
, etc, and the language hadn't supplied syntax sugar for them like most other languages do (Python, Scala).
在c# 7之前,使用元组不是很方便。它们的字段名是Item1、Item2等,并且语言没有像大多数其他语言那样为它们提供语法糖(Python、Scala)。
When the .NET language design team decided to incorporate tuples and add syntax sugar to them at the language level an important factor was performance. With ValueTuple
being a value type, you can avoid GC pressure when using them because (as an implementation detail) they'll be allocated on the stack.
当. net语言设计团队决定在语言级别加入元组并添加语法糖时,一个重要的因素是性能。使用ValueTuple作为值类型,您可以避免使用它们时的GC压力,因为(作为实现细节)它们将被分配到堆栈上。
Additionally, a struct
gets automatic (shallow) equality semantics by the runtime, where a class
doesn't. Although the design team made sure there will be an even more optimized equality for tuples, hence implemented a custom equality for it.
此外,结构体在运行时获得自动(浅层)相等语义,而类则没有。尽管设计团队确保了对元组有更优化的等式,因此实现了自定义等式。
Here is a paragraph from the design notes of Tuples
:
以下是元组设计说明中的一段:
Struct or Class:
As mentioned, I propose to make tuple types
structs
rather thanclasses
, so that no allocation penalty is associated with them. They should be as lightweight as possible.如前所述,我建议将元组类型设置为struct而不是类,这样就不会与它们关联分配代价。它们应该尽可能的轻。
Arguably,
structs
can end up being more costly, because assignment copies a bigger value. So if they are assigned a lot more than they are created, thenstructs
would be a bad choice.可以说,结构体最终可能更昂贵,因为赋值复制的值更大。因此,如果分配给它们的数量远远超过创建的数量,那么结构体将是一个糟糕的选择。
In their very motivation, though, tuples are ephemeral. You would use them when the parts are more important than the whole. So the common pattern would be to construct, return and immediately deconstruct them. In this situation structs are clearly preferable.
然而,在他们的动机中,元组是短暂的。当部分比整体更重要时,你可以使用它们。因此,共同的模式是构造、返回并立即解构它们。在这种情况下,结构显然更可取。
Structs also have a number of other benefits, which will become obvious in the following.
struct也有许多其他的好处,这在下面将变得很明显。
Examples:
You can easily see that working with System.Tuple
becomes ambiguous very quickly. For example, say we have a method which calculates a sum and a count of a List<Int>
:
您可以很容易地看到与系统一起工作。Tuple很快变得模棱两可。例如,我们有一个方法,它计算列表
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);
}
On the receiving end, we end up with:
在接收端,我们得到:
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);
The way you can deconstruct value tuples into named arguments is the real power of the feature:
将值元组分解为命名参数的方法是这个特性的真正力量:
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;
}
And on the receiving end:
在接收端:
var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");
Or:
或者:
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");
Compiler goodies:
If we look under the cover of our previous example, we can see exactly how the compiler is interpreting ValueTuple
when we ask it to deconstruct:
如果我们看一下前面例子的封面,我们可以看到编译器是如何解释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;
}
Internally, the compiled code utilizes Item1
and Item2
, but all of this is abstracted away from us since we work with a decomposed tuple. A tuple with named arguments gets annotated with the TupleElementNamesAttribute
. If we use a single fresh variable instead of decomposing, we get:
在内部,编译后的代码使用Item1和Item2,但是由于使用分解的元组,所有这些都被抽象出来了。带有命名参数的tuple会使用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));
}
Note that the compiler still has to make some magic happen (via the attribute) when we debug our application, as it would be odd to see Item1
, Item2
.
注意,当我们调试应用程序时,编译器仍然必须使一些神奇的事情发生(通过属性),因为看到Item1, Item2是很奇怪的。
#2
14
The difference between Tuple
and ValueTuple
is that Tuple
is a reference type and ValueTuple
is a value type. The latter is desirable because changes to the language in C# 7 have tuples being used much more frequently, but allocating a new object on the heap for every tuple is a performance concern, particularly when it's unnecessary.
Tuple和ValueTuple的区别在于Tuple是引用类型,ValueTuple是值类型。后者是可取的,因为c# 7中对语言的更改使用元组要频繁得多,但是为每个元组在堆上分配一个新对象是一个性能问题,尤其是在不必要的情况下。
However, in C# 7, the idea is that you never have to explicitly use either type because of the syntax sugar being added for tuple use. For example, in C# 6, if you wanted to use a tuple to return a value, you would have to do the following:
然而,在c# 7中,您不必显式地使用这两种类型,因为为元组使用添加了语法糖。例如,在c# 6中,如果您想使用tuple返回值,您必须执行以下操作:
public Tuple<string, int> GetValues()
{
// ...
return new Tuple(stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
However, in C# 7, you can use this:
然而,在c# 7中,您可以使用以下内容:
public (string, int) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
You can even go a step further and give the values names:
您甚至可以更进一步,给出值的名称:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.S;
... Or deconstruct the tuple entirely:
…或者完全解构元组:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var (S, I) = GetValues();
string s = S;
Tuples weren't often used in C# pre-7 because they were cumbersome and verbose, and only really used in cases where building a data class/struct for just a single instance of work would be more trouble than it was worth. But in C# 7, tuples have language-level support now, so using them is much cleaner and more useful.
元组在c# pre-7中不经常使用,因为它们笨重而冗长,而且只在仅为单个工作实例构建数据类/结构的情况下才真正使用。但是在c# 7中,元组现在有语言级的支持,所以使用它们要干净得多,也更有用。
#3
8
I looked at the source for both Tuple
and ValueTuple
. The difference is that Tuple
is a class
and ValueTuple
is a struct
that implements IEquatable
.
我查看了Tuple和ValueTuple的源代码。区别在于,Tuple是一个类,ValueTuple是实现IEquatable的结构。
That means that Tuple == Tuple
will return false
if they are not the same instance, but ValueTuple == ValueTuple
will return true
if they are of the same type and Equals
returns true
for each of the values they contain.
这意味着,如果它们不是相同的实例,Tuple = Tuple将返回false,但是如果它们是相同的类型,那么ValueTuple == = ValueTuple将返回true,并且对于它们包含的每个值,Equals返回true。
#4
4
Other answers forgot to mention important points.Instead of rephrasing, I'm gonna reference the XML documentation from source code:
其他的答案忘了提到重要的要点。我将引用源代码中的XML文档,而不是重述:
The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#.
ValueTuple类型(从浓度0到8)包含运行时实现,它是c#中的元组和f#中的结构元组的基础。
Aside from created via language syntax, they are most easily created via the ValueTuple.Create
factory methods. The System.ValueTuple
types differ from the System.Tuple
types in that:
除了通过语言语法创建之外,它们最容易通过ValueTuple创建。创建工厂方法。这个系统。ValueTuple类型与系统不同。Tuple类型:
- they are structs rather than classes,
- 它们是结构体而不是类,
- they are mutable rather than readonly, and
- 它们是可变的,而不是只读的
- their members (such as Item1, Item2, etc) are fields rather than properties.
- 它们的成员(如Item1、Item2等)是字段而不是属性。
With introduction of this type and C# 7.0 compiler, you can easily write
通过介绍这种类型和c# 7.0编译器,您可以轻松编写
(int, string) idAndName = (1, "John");
And return two values from a method:
从一个方法返回两个值:
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
Contrary to System.Tuple
you can update its members (Mutable) because they are public read-write Fields that can be given meaningful names:
与系统。您可以更新它的成员(可变的),因为它们是公共的读写字段,可以赋予有意义的名称:
(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
#5
2
In addition to the comments above, one unfortunate gotcha of ValueTuple is that, as a value type, the named arguments get erased when compiled to IL, so they're not available for serialisation at runtime.
除了上面的注释之外,ValueTuple的一个不幸的问题是,作为值类型,在编译到IL时已命名的参数将被删除,因此它们在运行时不能进行序列化。
i.e. Your sweet named arguments will still end up as "Item1", "Item2", etc. when serialised via e.g. Json.NET.
例如,当通过Json.NET进行序列化时,您的命名参数仍然会以“Item1”、“Item2”等结尾。
#6
0
This is a pretty good summary of what it is and its limitation
这很好地总结了它是什么以及它的局限性
https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations
https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations