我反编译了一些C#7库并看到了ValueTuple
个泛型。什么是ValueTuples
,为什么不是Tuple
呢?
答案 0 :(得分:163)
什么是
ValueTuples
,为什么不是Tuple
呢?
ValueTuple
是反映元组的结构,与原始System.Tuple
类相同。
Tuple
和ValueTuple
之间的主要区别是:
System.ValueTuple
是值类型(struct),而System.Tuple
是引用类型(class
)。在讨论分配和GC压力时,这很有意义。System.ValueTuple
不仅是struct
,而且是 mutable ,在使用它们时必须要小心。想想当一个班级将System.ValueTuple
作为一个字段时会发生什么。System.ValueTuple
通过字段而非属性公开其项目。直到C#7,使用元组不是很方便。他们的字段名称是Item1
,Item2
等,并且语言没有为大多数其他语言提供语法糖(Python,Scala)。
当.NET语言设计团队决定合并元组并在语言级别向他们添加语法糖时,一个重要因素是性能。如果ValueTuple
是值类型,则可以在使用时避免GC压力,因为(作为实现细节)它们将在堆栈中分配。
此外,struct
由运行时获取自动(浅)相等语义,而class
则不然。虽然设计团队确保元组的优化更加平等,但是为它实现了自定义相等。
结构或类:
如上所述,我建议制作元组类型
structs
而不是classes
,因此不会将分配惩罚与它们相关联。他们 应该尽可能轻巧。可以说,
但是,在他们的动机中,元组是短暂的。你会用的 当零件比整体更重要时。所以很常见 模式将是构建,返回并立即解构 他们。在这种情况下,结构显然是可取的。structs
最终会变得更加昂贵,因为任务 复制更大的价值。因此,如果他们的分配比他们多得多 创建后,structs
将是一个糟糕的选择。结构也有许多其他好处,这将成为 以下是显而易见的。
您可以很容易地看到使用System.Tuple
变得模糊不清。例如,假设我们有一个计算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
进行注释。如果我们使用单个新变量而不是分解,我们得到:
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
会很奇怪。
答案 1 :(得分:18)
Tuple
和ValueTuple
之间的区别在于Tuple
是引用类型,ValueTuple
是值类型。后者是可取的,因为对C#7语言的更改使得元组被更频繁地使用,但是为每个元组在堆上分配新对象是一个性能问题,特别是当它不必要时。
然而,在C#7中,我们的想法是你永远不会 显式使用任何一种类型,因为为元组使用添加了语法糖。例如,在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#pre-7中经常使用,因为它们既麻烦又冗长,而且只用于仅为单个工作实例构建数据类/结构的情况会比它更麻烦价值。但是在C#7中,元组现在具有语言级支持,因此使用它们更清晰,更有用。
答案 2 :(得分:8)
我查看了Tuple
和ValueTuple
的来源。不同之处在于Tuple
是class
而ValueTuple
是实现struct
的{{1}}。
这意味着如果IEquatable
不是同一个实例,Tuple == Tuple
将返回false
,但如果ValueTuple == ValueTuple
属于同一类型且true
将返回Equals
true
}}为它们包含的每个值返回
private boolean isDateValid ()
{
boolean flag = false;
if ((_day>31) || (_day12) || (_month9999) || (_yearJANUARY)
flag = true;
break;
case 2:
if ((_year % 400 == 0) || ((_year % 4 == 0) && (_year % 100 != 0)))
{
if (_day>FEBRUARY_LEAP)
flag = true;
}
else if (_day>FEBRUARY)
flag = true;
break;
case 3:
if (_day>MARCH)
flag = true;
break;
case 4:
if (_day>APRIL)
flag = true;
break;
case 5:
if (_day>MAY)
flag = true;
break;
case 6:
if (_day>JUNE)
flag = true;
break;
case 7:
if (_day>JULY)
flag = true;
break;
case 8:
if (_day>AUGUST)
flag = true;
break;
case 9:
if (_day>SEPTEMBER)
flag = true;
break;
case 10:
if (_day>OCTOBER)
flag = true;
break;
case 11:
if (_day>NOVEMBER)
flag = true;
break;
case 12:
if (_day>DECEMBER)
flag = true;
break;
default: return flag;
}
return flag;
}
。
答案 3 :(得分:5)
其他答案忘了提到重点。我不打算改写,而是引用instrumentation options of gcc
中的XML文档:
ValueTuple类型(从arity 0到8)构成了基础的运行时实现 C#中的元组和F#中的struct元组。
除了通过语言语法创建之外,它们最容易通过
System.ValueTuple
工厂方法。
System.Tuple
类型与(int, string) idAndName = (1, "John");
类型不同:
通过引入此类型和C#7.0编译器,您可以轻松编写
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
从方法中返回两个值:
System.Tuple
与(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
相反,您可以更新其成员(Mutable),因为它们是公共读写字段,可以给出有意义的名称:
Right Left Center Default
------- ------ ---------- -------
12 12 12 12
123 123 123 123
1 1 1 1
Table: Demonstration of simple table syntax.
答案 4 :(得分:5)
除了上面的注释之外,ValueTuple的一个不幸的问题是,作为一个值类型,命名参数在编译为IL时会被擦除,因此它们在运行时不可用于序列化。
即。当您通过例如序列化时,您的甜蜜命名参数仍然会以“Item1”,“Item2”等结尾。 Json.NET。
答案 5 :(得分:2)
后期联接可对这两个事实进行快速说明:
人们会认为,改变规模内的价值元组很简单:
foreach (var x in listOfValueTuples) { x.Foo = 103; } // wont even compile because x is a value (struct) not a variable
var d = listOfValueTuples[0].Foo;
有人可能会这样解决:
// initially *.Foo = 10 for all items
listOfValueTuples.Select(x => x.Foo = 103);
var d = listOfValueTuples[0].Foo; // 'd' should be 103 right? wrong! it is '10'
这种古怪行为的原因是值元组完全基于值(结构),因此.Select(...)调用适用于克隆结构,而不适用于原始结构。要解决此问题,我们必须诉诸:
// initially *.Foo = 10 for all items
listOfValueTuples = listOfValueTuples
.Select(x => {
x.Foo = 103;
return x;
})
.ToList();
var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed
当然,也可以尝试直接方法:
for (var i = 0; i < listOfValueTuples.Length; i++) {
listOfValueTuples[i].Foo = 103; //this works just fine
// another alternative approach:
//
// var x = listOfValueTuples[i];
// x.Foo = 103;
// listOfValueTuples[i] = x; //<-- vital for this alternative approach to work if you omit this changes wont be saved to the original list
}
var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed
希望这可以帮助某人在列表托管的价值元组中挣扎。