我反编译了一些C#7库,并看到了正在使用的ValueTuple泛型。什么是ValueTuples,为什么不使用Tuple

  • https://docs.microsoft.com/en-gb/dotnet/api/system.tuple
  • https://docs.microsoft.com/en-gb/dotnet/api/system.valuetuple
  • 最佳答案



    ValueTuple 是反射(reflect)元组的结构,与原始System.Tuple类相同。
    TupleValueTuple之间的主要区别是:

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

  • 在C#7之前,使用元组还不是很方便。它们的字段名称是Item1Item2等,并且该语言没有像大多数其他语言(Python,Scala)那样为它们提供语法糖。

    当.NET语言设计团队决定合并元组并在语言级别向其添加语法糖时,一个重要因素就是性能。将ValueTuple用作值类型,您可以避免在使用它们时引起GC压力,因为(作为实现的详细信息)它们将在堆栈中分配。

    此外,struct在运行时会获得自动(浅)相等语义,而class不会。尽管设计团队确保为元组提供了更加优化的相等性,但因此为其实现了自定义相等性。

    这是design notes of Tuples 的一段:



    例子:

    您可以轻松地看到,使用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时,我们可以确切地看到编译器如何解释Item1:
    [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;
    }
    

    在内部,编译后的代码使用Item2TupleElementNamesAttribute,但是由于我们使用的是分解的元组,因此所有这些内容都从我们这里抽象了出来。具有命名参数的元组将使用 Item1 进行注释。如果我们使用单个新鲜变量而不是分解,则会得到:
    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));
    }
    

    请注意,在调试应用程序时,编译器仍然必须通过属性使某些魔术发生,因为看到Item2和ojit_code会很奇怪。

    关于c# - System.ValueTuple和System.Tuple有什么区别?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41084411/

    10-11 01:53