返回两个值,Tuple vs'out'vs'truct'

Xaq*_*ron 76 c# struct tuples out value-type

考虑一个返回两个值的函数.我们可以写:

// Using out:
string MyFunction(string input, out int count)

// Using Tuple class:
Tuple<string, int> MyFunction(string input)

// Using struct:
MyStruct MyFunction(string input)
Run Code Online (Sandbox Code Playgroud)

哪一个是最佳实践,为什么?

Eri*_*ert 86

他们各有利弊.

Out参数快速且便宜,但要求您传入变量,并依赖于变异.使用LINQ正确使用out参数几乎是不可能的.

元组会产生收集压力并且是非自我记录的."Item1"不是很具描述性.

如果自定义结构很大,则复制速度很慢,但是它们是自我文档的,如果它们很小则很有效.然而,为琐碎的用途定义一大堆自定义结构也很痛苦.

在所有其他条件相同的情况下,我倾向于自定义结构解决方案.更好的方法是创建一个只返回一个值的函数.为什么你首先返回两个值?

更新:请注意,编写本文六年后发布的C#7中的元组是值类型,因此不太可能产生收集压力.

  • @Xaqron:如果你发现程序中常见"带超时数据"的想法,那么你可以考虑制作一个泛型类型"TimeLimited <T>",这样你就可以让你的方法返回一个TimeLimited <string>或TimeLimited <Uri>或其他什么.然后TimeLimited <T>类可以有帮助方法告诉你"我们还剩多久了?" 或"它过期了吗?" 管他呢.尝试在类型系统中捕获这样的有趣语义. (21认同)
  • 当然,我绝不会将Tuple用作公共接口的一部分.但即使对于"私有"代码,我也从适当的类型获得了巨大的可读性,而不是使用元组(特别是使用自动属性创建私有内部类型是多么容易). (3认同)
  • @Marc.2377:元组当然有优点:它们是将两个值合二为一的 *logical* 和 *principled* 方式。引用元组的优点是通过引用复制,速度很快。值元组具有作为值类型的优点,可以减轻收集压力。有很多方法可以解决这个问题;在 C# 7 中,规范的做法是使用值元组。 (3认同)
  • 返回两个值通常可以替代没有选项类型或ADT. (2认同)
  • 根据我使用其他语言的经验,我会说元组通常用于对项目进行快速和肮脏的分组。创建一个类或结构通常更好,因为它允许您为每个项目命名。使用元组时,很难确定每个值的含义。但它确实可以让您免于花时间创建类/结构,如果所述类/结构不会在其他地方使用,这可能会显得过分。 (2认同)
  • 收款压力是什么意思? (2认同)

And*_*per 19

我认为答案取决于函数正在做什么的语义,以及两个值之间的关系.

例如,TryParse方法接受一个out参数来接受解析后的值,并返回一个bool指示解析是否成功的方法.这两个值并不真正属于一起,因此,从语义上讲,它更有意义,并且代码的意图更容易阅读,以使用out参数.

但是,如果你的函数返回屏幕上某个对象的X/Y坐标,那么这两个值在语义上属于一起,最好使用a struct.

我个人避免使用tuple任何外部代码可见的东西,因为检索成员的笨拙语法.

  • 实际上,TryParse中的两个值非常相似,远远超过将一个值作为返回值而另一个作为ByRef参数所暗示的值.在许多方面,返回的合乎逻辑的东西是可以为空的类型.在某些情况下,TryParse模式运行良好,有些情况下很麻烦(可以在"if"语句中使用它很好,但在很多情况下返回可以为空的值或者能够指定默认值会更方便). (3认同)

dim*_*cas 16

除了之前的答案之外,C#7还带来了值类型元组,与之不同的System.Tuple是它是一种引用类型,并且还提供了改进的语义.

您仍然可以将它们保留为未命名并使用以下.Item*语法:

(string, string, int) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.Item1; //John
person.Item2; //Doe
person.Item3;   //42
Run Code Online (Sandbox Code Playgroud)

但是这个新功能的真正强大之处在于能够拥有命名元组.所以我们可以像这样重写上面的内容:

(string FirstName, string LastName, int Age) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42
Run Code Online (Sandbox Code Playgroud)

还支持解构:

(string firstName, string lastName, int age) = getPerson()

  • 我是否认为这基本上返回了一个结构,其中引用作为幕后成员? (2认同)
  • 我们知道与使用参数相比它的性能如何吗? (2认同)