我反编译了一些C#7库,发现使用了ValueTuple个泛型.ValueTuples是什么,为什么不是Tuple呢?

推荐答案

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

ValueTuple是反映元组的 struct ,与最初的System.Tuple类相同.

TupleValueTuple之间的主要区别是:

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

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

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

此外,struct通过运行时获得自动(浅层)相等语义,而class则没有.尽管设计团队确保元组具有更优化的相等,因此为其实现了自定义相等.

以下是design notes of Tuples篇文章中的一段:

Struct or Class:

如前所述,我建议将元组类型设置为structs,而不是 classes,因此没有与它们相关联的分配惩罚.他们 应该尽可能的轻便.

可以说,structs美元最终可能会更加昂贵,因为

然而,就其动机而言,元组是短暂的.你会用

struct 还有许多其他好处,这将成为

Examples:

你可以很容易地看到,与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}");

Compiler goodies:

如果我们看一下前面示例的封面,我们可以确切地看到,当我们要求编译器解构ValueTuple时,它是如何解释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;
}

在内部,编译的代码利用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));
}

请注意,当我们调试应用程序时,编译器仍然必须(通过属性)使一些神奇的事情发生,因为看到Item1,Item2会很奇怪.

.net相关问答推荐

.NET 7,8 System.Text.Json反序列化多态参数

DI通过对象的接口而不是实际类型来解析服务

[x.x.x,)在Packages.lock.json依赖项中是什么意思?

使用.NET 8时无法识别运行标识符

使用 PostAsJsonAsync C# 时出现错误请求

如何从控制台应用程序中的 Task.WaitAll() 获取返回值?

为 XML 编码文本数据的最佳方法

在 WPF 窗口中获取当前聚焦的元素/控件

找不到 Microsoft.Office.Interop Visual Studio

.NET 世界是否有 Maven 替代方案或端口?

是什么让 Enum.HasFlag 这么慢?

SubscribeOn 和 ObserveOn 有什么区别

.Net 中的 Decimal.One、Decimal.Zero、Decimal.MinusOne 的用途是什么

如何使用 NUnit(或可能使用另一个框架)测试异步方法?

如何将 NuGet 与 Visual C# Express 一起使用?

支持 HTTPS 的 Httplistener

从 Windows 窗体打开 URL

如何判断对象是否已在 C# 中释放

浮动与双重性能

检测到包降级警告(dotnet core,vs 2017)