C# 的未来:元组及匿名结构体

阅读数:1167 2015 年 5 月 7 日

话题:.NETC#语言 & 开发

随着 C# 6 接近完成,C# 7 的开发计划也开始提到了日程上。虽然目前为止,还没有任何可确定的内容,但 C# 团队已经开始按照“兴趣及预计可行性”将各种提议进行分类。在这个系列文章中,我们将对某些提议进行分析,首先从对元组的语言支持开始。

使用元组的目标是以一种轻量级的方式从一个函数中返回多个值。对元组的良好支持能够消除对 out 参数的使用,这种参数通常被认为是一种笨重的方案。此外,out 参数无法兼容 async/await,因此在许多场景中 out 参数将变得毫无作用。

元组类存在什么问题?

从.NET Framework 4 开始,就加入了 Tuple(元组)这个类。但是多数开发者都认为这个类只在非常有限的场景中才能够体现出实用性。首先,元组是一个类,这意味着在使用时必须为它分配内存,而这一点会增加内存的压力,并使垃圾回收器的执行周期变得更加频繁。如果要让元组与 out 参数在性能方面进行竞争,需要将其实现为一个结构体。

第二个问题与 API 的设计有关,如果你看到了某个返回类型 Tuple<int, int>,那么从类型本身你无法了解任何信息。如此一来,在使用这个函数的过程中你至少需要检查文档两次,一次是在编写函数时,另一次则是在代码审查时。如果返回类型能够表现为类似于 Tuple<int count, int sum> 这样,那么它的实用性将会大大增加。

匿名结构体

考虑一下以下代码:

public (int sum, int count) Tally(IEnumerable<int> values) { ... }

var t = new (int sum, int count) { sum = 0, count = 0 };

在这条提议中,以上每一行代码都将定义一个新的匿名值类型,其中包括 sum 和 count 属性。需要注意的是,和匿名类不同,匿名结构体要求你明确地列举出属性的名称与类型。

使用结构体的一个好处在于它们会自动定义 Equals 和 GetHashCode 方法。不过也有人会表示默认的实现方式不够高效,编译器应当替换它的实现。

分解元组

关于元组的提议中有一个重要的部分,即应当能够通过一行代码对元组进行分解。考虑一下下面的代码块:

var t = Tally(myValues);

var sum = t.Sum;

var count = t.Count;

使用分解功能,这段代码就能够进行简化:

(var sum, var count) = Tally(myValues);

目前还没有决定是否能够在不定义新变量的提前下对元组进行分组。或者换句话说,能否省略“var”的使用,而代之以某个已经存在的本地变量。

返回元组

关于如何从某个函数中返回元组类型这一点,有两条提议正在考虑中。第一条提议非常容易理解:

return (a, b);

而第二条提议的方式将完全不使用 return 语句。考虑一下这个示例:

public (int sum, int count) Tally(IEnumerable<int> values)

{

    sum = 0; count = 0;

foreach (var value in values) { sum += value; count++; }

}

隐式创建的本地及返回变量并不是一种新的概念,Visual Basic 最初的设计就是这样的,只是在 VB 7 中引入了返回语句之后,这种方法才逐渐变得不流行了。这种写法也类似于使用 out 参数的方式。不过,对于许多开发者来说,在函数中看不到返回语句总是让他们感觉有些不安。

其它问题

对元组的支持是一个比看上去还要复杂的主题。虽然本文目前讨论的主要是日常的使用方式,但还有许多细节需要从编译器的作者以及高级使用场景的角度进行处理。

元组是否应该作为可变类型?从性能及便利性的角度考虑,这种选择具有一定实用性,但这有可能让代码变得更容易出错,尤其是在处理多线程的情况下。

元组是否应该跨多个程度集进行统一化?匿名类型并不是统一的,但元组与匿名类型不同,它们可以作为 API 的一部分向外部暴露。

元组能否被转型为其它元组?表面上看,如果两个元组具有相同的类型结构,但具有不同的属性名称。或者具有相同的属性名称,但属性的类型更宽泛,那么这种转换应该是可以接受的。

如果你将某个具有两个值的元组作为参数传递给某个接受两个参数的函数,那么是否应该将元组进行自动分解(摊平)呢?反过来,你能够“反分解”某个参数对,将其合并为某个元组参数吗?

查看英文原文:C# Futures: Tuples and Anonymous Structs