【问题标题】:Returning two values, Tuple vs 'out' vs 'struct'返回两个值,元组 vs 'out' vs 'struct'
【发布时间】:2011-09-16 22:29:50
【问题描述】:

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

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

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

// Using struct:
MyStruct MyFunction(string input)

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

【问题讨论】:

  • 字符串不是值类型。我认为您的意思是“考虑一个返回两个值的函数”。
  • @Eric:你说得对。我的意思是不可变类型。
  • 上课有什么问题?
  • @lukas:没什么,但肯定不是最佳实践。这是一个轻量级值(struct,因为提到了 Eric
  • 我会说只有当你需要返回值来决定是否应该处理返回数据时才使用 out,就像在 TryParse 中一样,否则你应该总是返回一个结构化对象,如果结构化对象应该是值类型还是引用类型取决于您对数据的其他用途

标签: c# struct tuples out value-type


【解决方案1】:

它们各有优缺点。

Out 参数既快速又便宜,但需要您传入一个变量,并依赖于变异。在 LINQ 中正确使用 out 参数几乎是不可能的。

元组产生收集压力1 并且是非自记录的。 “Item1”的描述性不是很好。

如果自定义结构很大,复制可能会很慢,但如果它们很小,则可以自我记录并且效率很高。然而,为琐碎的用途定义一大堆自定义结构也很痛苦。

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

请注意,在编写此答案六年后发布的 C# 7 中的元组是值类型,因此不太可能产生收集压力。


1 每次从堆中分配一个小对象时,都会对垃圾收集器施加“压力”。压力越大,收集越频繁。在某些应用程序中,控制产生的收集压力量很重要,因此在这些应用程序中不必要地分配几百万个元组可能是一件坏事。当然,就像所有关于性能的问题一样,在您了解问题的严重性之前,不要盲目地进行更改。

【讨论】:

  • 返回两个值通常可以替代没有选项类型或 ADT。
  • 根据我使用其他语言的经验,我会说元组通常用于对项目进行快速而肮脏的分组。通常最好创建一个类或结构,因为它允许您命名每个项目。当使用元组时,每个值的含义可能很难确定。但它确实可以节省您花时间创建类/结构,如果该类/结构不会在其他地方使用,这可能是矫枉过正。
  • @Xaqron:如果您发现“数据超时”的想法在您的程序中很常见,那么您可以考虑制作一个泛型类型“TimeLimited”,以便您可以使用您的方法返回 TimeLimited 或 TimeLimited 或其他。然后 TimeLimited 类可以有帮助方法来告诉您“我们还剩下多长时间?”或“它过期了吗?”管他呢。尝试在类型系统中捕获类似这样的有趣语义。
  • 当然,我永远不会将 Tuple 用作公共接口的一部分。但即使对于“私有”代码,我也可以从适当的类型而不是使用元组获得巨大的可读性(尤其是使用自动属性创建私有内部类型是多么容易)。
  • @Marc.2377:元组确实有优点:它们是将两个值合二为一的逻辑原则方式。引用元组的优点是通过引用进行复制,速度很快。值元组具有作为值类型的优点,可以减轻收集压力。有很多方法可以解决这个问题;在 C# 7 中,规范的做法是使用值元组。
【解决方案2】:

除了前面的答案之外,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

但这个新功能真正强大的地方在于能够命名元组。所以我们可以像这样重写上面的代码:

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

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42

还支持解构:

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

【讨论】:

  • 我是否正确地认为这基本上返回了一个结构,其中引用作为引擎盖下的成员?
  • 我们知道它的性能与使用 out 参数相比如何吗?
【解决方案3】:

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

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

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

我个人会避免将tuple 用于外部代码可见的任何内容,因为检索成员的语法很尴尬。

【讨论】:

  • +1 表示语义。当我们可以保留 out 参数 null 时,您的答案更适合 whit 引用类型。有一些可以为空的不可变类型。
  • 实际上,TryParse 中的两个值确实非常相似,远不止将一个作为返回值,另一个作为 ByRef 参数所暗示的那样。在许多方面,返回的逻辑事物将是可为空的类型。在某些情况下 TryParse 模式可以很好地工作,而在某些情况下却很痛苦(可以在“if”语句中使用它很好,但是在许多情况下返回可为空的值或能够指定默认值会更方便)。
  • @supercat 我同意安德鲁的观点,他们不属于一起。尽管它们是相关的,但返回会告诉您是否需要担心价值,而不是需要串联处理的东西。因此,在您处理完返回后,与输出值相关的任何其他处理都不再需要它,这与从字典中返回 KeyValuePair 不同,在字典中键和值之间存在清晰且持续的链接。虽然我同意如果可空类型已经在 .Net 1.1 中,他们可能会使用它们,因为 null 将是标记无值的正确方法
  • @MikeT:我认为非常不幸的是,微软建议结构只能用于表示单个值的事物,而实际上暴露字段结构是传递一组数据的理想媒介用胶带捆绑在一起的自变量。 在返回时,成功指示符和值一起有意义,即使在那之后它们作为单独的变量会更有用。存储在变量中的暴露字段结构的字段本身可用作单独的变量。无论如何...
  • @MikeT:由于框架内支持和不支持协方差的方式,唯一适用于协变接口的try 模式是T TryGetValue(whatever, out bool success);这种方法将允许接口IReadableMap&lt;in TKey, out TValue&gt; : IReadableMap&lt;out TValue&gt;,并让想要将Animal 实例映射到Car 实例的代码接受Dictionary&lt;Cat, ToyotaCar&gt; [使用TryGetValue&lt;TKey&gt;(TKey key, out bool success)。如果将TValue 用作ref 参数,则不可能出现这种差异。
【解决方案4】:

我将采用使用 Out 参数的方法,因为在第二种方法中,您需要创建 Tuple 类的对象,然后为其添加值,与在 out 参数中返回值相比,我认为这是一项昂贵的操作。虽然如果你想在 Tuple Class 中返回多个值(实际上不能通过只返回一个 out 参数来完成),那么我将采用第二种方法。

【讨论】:

  • 我同意out。此外,还有一个 params 关键字我没有提到以保持问题直截了当。
【解决方案5】:

您没有提到另一个选项,即使用自定义类而不是结构。如果数据具有与其相关联的语义,可以通过函数进行操作,或者实例大小足够大(根据经验,> 16 字节),则可能首选自定义类。 不建议在公共 API 中使用“out”,因为它与指针关联并且需要了解引用类型的工作原理。

https://msdn.microsoft.com/en-us/library/ms182131.aspx

元组适合内部使用,但在公共 API 中使用起来很尴尬。 所以,我的投票是在公共 API 的结构和类之间。

【讨论】:

  • 如果一个类型的存在纯粹是为了返回值的聚合,我会说一个简单的暴露字段值类型最适合这些语义。如果类型中除了其字段之外没有任何内容,则不会有任何关于它执行哪种数据验证的问题(显然没有),它是否代表捕获或实时视图(开放字段结构不能起作用作为实时视图)等。不可变类使用起来不太方便,并且只有在实例可以多次传递时才能提供性能优势。
【解决方案6】:

没有“最佳实践”。这是您感到满意的,并且在您的情况下最有效的方法。只要您对此保持一致,您发布的任何解决方案都没有问题。

【讨论】:

  • 当然它们都有效。万一没有技术优势,我还是很想知道专家们最常用的是什么。
猜你喜欢
  • 2011-02-24
  • 2013-06-28
  • 2011-02-20
  • 2016-08-06
  • 1970-01-01
  • 2020-09-03
  • 2010-09-29
  • 2010-12-03
  • 2011-04-02
相关资源
最近更新 更多