Yuv*_*kov 184

是什么ValueTuples以及为什么不Tuple呢?

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

Tuple和之间的主要区别ValueTuple是:

  • System.ValueTuple是一个值类型(struct),System.Tuple而是一个引用类型(class).在谈论分配和GC压力时,这很有意义.
  • System.ValueTuple不仅是一个struct,它是一个可变的,并且在使用它们时必须要小心.想想当一个班级System.ValueTuple作为一个领域时会发生什么.
  • System.ValueTuple 通过字段而不是属性公开其项目.

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

当.NET语言设计团队决定合并元组并在语言级别为它们添加语法糖时,一个重要因素是性能.随着ValueTuple是值类型,你可以使用它们,因为(作为一个实现细节)时,为了避免GC压力,他们会在栈上分配.

另外,struct运行时获取自动(浅)相等语义,而class不是.虽然设计团队确保元组的优化更加平等,但是为它实现了自定义相等.

以下是设计说明中Tuples的一段:

结构或类:

如上所述,我建议制作元组类型structs而不是 classes,因此不会将分配惩罚与它们相关联.它们应该尽可能轻.

可以说,structs最终会变得更加昂贵,因为作业会复制更大的价值.因此,如果他们的分配比创建的多得多,那么structs这将是一个糟糕的选择.

然而,在他们的动机中,元组是短暂的.当零件比整体更重要时,你会使用它们.所以常见的模式是构造,返回并立即解构它们.在这种情况下,结构显然是可取的.

结构也有许多其他好处,这些好处将在下面变得明显.

例子:

你可以很容易地发现,使用它System.Tuple会很快变得模棱两可.例如,假设我们有一个计算a的总和和计数的方法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);
}
Run Code Online (Sandbox Code Playgroud)

在接收端,我们最终得到:

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);
Run Code Online (Sandbox Code Playgroud)

您可以将值元组解构为命名参数的方式是该功能的真正强大之处:

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;
}
Run Code Online (Sandbox Code Playgroud)

并在接收端:

var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");
Run Code Online (Sandbox Code Playgroud)

要么:

var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");
Run Code Online (Sandbox Code Playgroud)

编译好东西:

如果我们查看前面示例的封面,我们可以确切地看到编译器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;
}
Run Code Online (Sandbox Code Playgroud)

在内部,编译后的代码利用Item1Item2,而是因为我们有一个元组分解工作,所有这一切都被抽象离我们越来越远.带有命名参数的元组用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));
}
Run Code Online (Sandbox Code Playgroud)

注意,编译器仍然需要做一些魔术发生(通过属性),当我们调试我们的应用程序,因为这将是奇怪地看到Item1,Item2.

  • 一些 nits:_“它们将被分配在堆栈上”_ - 仅适用于局部变量。毫无疑问,您知道这一点,但不幸的是,您的措辞方式可能会延续值类型始终存在于堆栈中的神话。 (3认同)
  • 请注意,您还可以使用更简单(在我看来,更可取)的语法 `var (sum, count) = DoStuff(Enumerable.Range(0, 10));` (2认同)

Abi*_*n47 21

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; 
Run Code Online (Sandbox Code Playgroud)

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

public (string, int) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 
Run Code Online (Sandbox Code Playgroud)

您甚至可以更进一步,并给出值名称:

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

var value = GetValues();
string s = value.S; 
Run Code Online (Sandbox Code Playgroud)

...或完全解构元组:

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

var (S, I) = GetValues();
string s = S;
Run Code Online (Sandbox Code Playgroud)

元组在C#pre-7中并不经常使用,因为它们既麻烦又冗长,而且只用于仅为单个工作实例构建数据类/结构的情况会比它的价值更麻烦.但是在C#7中,元组现在具有语言级支持,因此使用它们更清晰,更有用.


Pet*_*ris 9

我看了看源都TupleValueTuple.不同的是,TupleclassValueTuple是一个struct实现IEquatable.

这意味着,Tuple == Tuple将返回false,如果他们是不一样的实例,但ValueTuple == ValueTuple将返回true如果它们是同一类型和Equals回报true每一个它们所包含的值.

  • @BoltClock 如果您要详细说明,您的评论将具有建设性 (3认同)
  • 此外,值类型不一定在堆栈上.不同之处在于,无论何时存储该变量,语义上都代表值而不是引用,该变量可能是也可能不是堆栈. (3认同)

Zei*_*kki 6

其他答案忘记了要点。我将代替源代码,而是参考源代码中的XML文档:

ValueTuple类型(从0到8)在运行时实现中,它是C#中的元组的基础,而F#中的结构元组。

除了通过语言语法创建之外,还可以通过ValueTuple.Create工厂方法最轻松地创建它们 。该System.ValueTuple类型从不同System.Tuple在于类型:

  • 它们是结构而不是类,
  • 它们是可变的,而不是只读的,并且
  • 它们的成员(例如Item1,Item2等)是字段而不是属性。

通过引入这种类型和C#7.0编译器,您可以轻松编写

(int, string) idAndName = (1, "John");
Run Code Online (Sandbox Code Playgroud)

并从方法中返回两个值:

private (int, string) GetIdAndName()
{
   //.....
   return (id, name);
}
Run Code Online (Sandbox Code Playgroud)

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

(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
Run Code Online (Sandbox Code Playgroud)


小智 6

除了上面的注释外,ValueTuple的不幸之处还在于,作为值类型,命名参数在编译为IL时会被擦除,因此它们在运行时不可用于序列化。

即,当您通过例如Json.NET进行序列化时,您的甜命名参数仍将最终以“ Item1”,“ Item2”等结尾。

  • 所以从技术上讲这是相似而不是不同;) (2认同)