何时使用:Tuple与Class C#7.0


76

在Tuples之前,我曾经创建过一个class及其变量,然后从此类创建对象,并使该对象成为某些函数的返回类型。

现在用的元组,我可以做同样的在C#7.0,我们可以为元组的属性分配可以理解的名称(在此之前它item1item2等..)

所以现在我想知道,什么时候应该使用它,什么时候应该在c#7.0中创建一个类?


2
注意,元组元素名称主要是设计时属性,在已编译的IL中不存在。因此,如果您对此有所顾虑,那么您还有另一个选择上课的理由。

3
@OrkhanAlikhanov虽然那将是一个正确的重复目标,但我确实同意OP的观点,即C#7现在的时代已经改变,因此2012年和2010年的答案现在仅部分适用。

2
@poke这不是一个有效的重复的目标,这个问题是关于System.Tuple,这一个是关于新System.ValueTuple它有特殊的语法糖在C#7
Theraot

@Theraot您确实意识到我在批评关闭此问题的建议,对吗?我知道这个问题是关于什么的,这就是为什么我建议保持公开状态。

@Theraot哦,我在解释这个问题时犯了同样的错误,因为它指向System.Tuple,因为它说的是“ Tuple”。如果您确信它实际上是在指其他内容,则应对其进行编辑以使其更加清晰。我假设您是对的,则恢复了关闭状态。
科迪·格雷

Answers:


56

由于此答案在这里引起了某些人的困惑,因此我应该澄清-根据问题-这里所有对“元组”的引用都指的ValueTuple是C#7的类型和新的元组语法糖功能,而绝不是旧的System.Tuple参考类型。

所以现在我想知道,什么时候应该使用元组,什么时候应该在c#7.0中创建类?

只有您才能真正回答该问题,因为它实际上取决于您的代码。

但是,在指导您进行选择时,可以遵循以下准则和规则:

元组是值,因此按值而不是通过引用复制。

大多数时候,这应该不是问题。但是,如果您绕过大型结构的元组,则可能会影响性能。不过,可以使用Ref locals / returns来解决这些性能问题。

此外,由于它们是值,因此远程修改副本不会更改原始副本。这是一件好事,但可以吸引一些人。

元组元素名称不保留

赋予元素的名称由编译器使用,并且(在大多数情况下)在运行时不可用。这意味着不能使用反射来发现其名称。它们不能动态访问,也不能在剃刀视图中使用。

这也是API的重要考虑因素。从方法返回的元组是关于编译后名称可发现性的规则的例外。编译器向方法添加属性,以保存有关元组名称的信息。这意味着您可以安全地从一个程序集中的公共方法返回一个元组,并在另一个程序集中访问其名称。

元组很轻

元组比类型更容易编写,因为它们不那么冗长,并且声明可以“内联”(即在使用时声明)。例如,在声明返回多个值的方法时,此方法效果很好。

但是,由于它们是在使用时声明的,因此,如果您有MethodA要进行调用的调用MethodBMethodC并且每个调用都返回一个元组,则需要在每个阶段重新定义该元组。没有()创建一个元组的别名,并重新使用它跨多个方法的方法。

只是使用常识

在任何可能考虑使用元组的情况下:只需问自己一个问题:“元组会在这里简化代码吗?”。如果答案为“是”,则使用一个。最终,这是使用元组还是自定义类的主要考虑因素。


1
ValueTuple是值类型。Tuple是引用类型。如此简单,Tuple不会被引用复制。您应该以粗体字提及它,因为它现在令人困惑
Szer

2
@Szer,问题是关于C#7的元组的,所以我不明白为什么会有任何混乱。但我已经更新了问题以澄清这一点。
David Arno

@DavidArno当我第一次开始使用C#7时,我不清楚新的Tuple语法是一种新的类型还是仅仅是语言支持。
Gusdor

我很惊讶在Razor视图中不能使用元组元素名称。Razor文件不是转换为C#然后进行编译吗?如果是这样,他们应该从元数据/ IL中看到元组元素名称,就像任何客户端编译一样。
朱利安·库夫勒

1
@JulienCouvreur,据我所知,这是剃须刀工具的局限性。但是,根据github.com/aspnet/Razor/issues/1046的外观,此问题现已修复,或者将很快发布修复程序。
David Arno

25

一般来说,命名类在系统设计中具有重要意义。它们也更冗长。例如,您可能有一个名为的类MediaFileOpener。对设计很重要,我们知道此类的作用-我们正在处理媒体文件!

如果没有设计意义,并且只需要一个轻量级的数据传输对象(DTO)即可移动信息,则使用匿名类型和元组。

通常,如果您的班级需要一些文档来描述其用途,或者如果提供了某些行为,请使用完整的班级。如果您只需要临时存储或某种分组,请使用元组。考虑一种情况,您想从异步方法返回多个值。元组旨在解决该问题。


12

使用课程

如果对象是在整个应用程序中广泛使用的实体,并且还存储在某种持久性存储中,例如关系数据库(SQL Server,MySQL,SQLite),NoSQL数据库或缓存(Redis,Azure DocumentDB),甚至简单文本文件或CSV文件。

是的,任何持久性都应该有自己的类。

使用元组

如果您的对象寿命短,对您的应用程序没有特殊意义。例如,如果您需要快速返回一对坐标,则最好具有以下内容:

(double Latitude, double Longitude) getCoordinates()
{
    return (144.93525, -98.356346);
}

比定义一个单独的类

class Coordinates
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

new这样的简单操作,元组将节省您不必在堆上分配内存的时间。

我发现元组有用的另一个时间是对某些操作数执行多个数学运算时

(double Result1, double Result2, double Result3) performCalculations(int operand1, int operand 2)

在这种情况下,定义一个类是没有意义的。无论进行什么计算,结果都不属于一个类。因此,替代方法是使用out变量,但我认为元组更具表达能力,并提高了可读性。


2
我将定义Coordinates为,struct而不是class
Orkhan Alikhanov

6
坐标将是因为它是一个足够常见的事情,你可能需要它没有使用一个元组一个很好的例子,所有的时间。另外,您可以重载ToString以提供可读的输出(顺便说一下,元组的默认ToString在这里已经可以正常工作),也可以重载Equals/GetHashCode进行相等性比较。

恕我直言,这似乎是一个很固执的回应,含糊不清的oop设计准则。我认为oop与这无关(匿名类可以放入您的两个类别中)。另外,添加数据库级别的持久性与这些注意事项无关。
丹尼尔·杜波夫斯基

3

通常,当您的对象将在其他地方使用时,或者如果它表示您领域中的真实对象或概念,您都想拥有一个类。您可能要创建一个类来表示汽车或汽车商店,而不是元组。

另一方面,有时您只想从一个方法返回几个对象。也许它们并不代表任何特殊之处,只是您需要使用该特定方法将它们一起返回。有时,即使它们确实代表了您的域中的一个概念(例如,您正在返回(Car, Store),也可能表示为一个Sale对象),您实际上并不会在任何地方使用它们-您只是在移动数据。在这些情况下,使用元组是可以的。

现在,具体说说C#,您还应该了解一件事。C#7的元组类型实际上是ValueTuple,这是一个结构。与引用类型的类不同,结构是值类型。您可以在msdn上阅读有关此内容的更多信息。最重要的是,要知道它们可能涉及很多复制,所以要小心。


3

我认为这将成为一个经常被问到的问题。对于何时使用新值元组和类,目前没有“最佳实践”。

但是,值得一读的是在先前的讨论中,有关元组和类的先前版本的讨论

在我看来,值元组只能使用最少,最多只能使用三个值。我认为这在“无需类就返回某些值”和“令人费解的值”之间取得了很好的平衡。如果要返回的值超过三个,则创建一个类。

我也永远不会使用元组从消费者必须使用的面向公众的API中返回。同样,只需使用一个类。

这是我使用的一些实际代码:

public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()

我想返回更复杂的数据时,就创建了一个类。


3

我想首先提到C#已经支持匿名类型。哪些是引用类型。因此,您已经有了创建命名类的合适选择。

命名类的一个优点是将更易于重用(例如,如果您在多个地方需要相同的类型)和文档。由于匿名类型是匿名的,因此只有在可以使用var的情况下,才能获取键入其类型的变量,这限制了匿名类型有用的上下文(例如,不能将其用作字段类型,返回类型或参数类型) 。

当然,您可以使用来克服匿名类型的一些限制System.Tuple。这也是一种引用类型,您可以显式使用它。缺点是缺少成员的自定义名称。


C#7元组(ValueTuple)可以被视为类似于匿名类型。第一个区别是它们是值类型。这意味着,只要它们在本地范围内或在堆栈中移动,这些元组就将具有性能优势(由于其限制,这是匿名类型的常见用法)。

第二个区别是,新语法允许元组出现在比匿名类型更多的位置,您知道,您有语法糖来定义返回类型ValueTuple(在使用匿名类型时必须返回object)。

第三个区别是ValueTuple支持开箱即用的解构。引用C#7.0的新增功能

消费元组的另一种方法是解构它们。解构声明是一种语法,用于将元组(或其他值)拆分为多个部分,并将这些部分分别分配给新变量:

(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"found {first} {last}.");

您还可以通过添加Deconstruct方法来处理自定义类型。


对于摘要:

  • ValueTuple 在C#7.0中具有语法糖,应考虑其可读性。
  • ValueTuple是值类型。使用aclassstructapply之间的所有优缺点。
  • ValueTuple可以显式使用(带有或不带有语法糖),从而使其System.Tuple在保留命名成员的同时具有通用性。
  • ValueTuple 支持解构。

鉴于其必须是语法糖,我会说选择的较强ValueTuple论点与选择a的论点相同struct。这对于大多数生活在堆栈中的小型,不可变的类型将是理想的(因此,您无需进行大量装箱和拆箱)。

ValueTuple完整的结构相比,考虑语法糖,我建议ValueTuple默认使用,除非您需要显式的布局或需要向其添加方法。

我还想说句法糖不一定能提高可读性。主要原因是您没有命名类型,而类型的名称为代码提供了含义。除此之外,您可以将文档添加到structclass声明中,以便于理解。

总而言之,ValueTuple真正发光的情况是从一个方法返回多个值。在这种情况下,无需创建新out参数。和的文档ValueTuple使用能活的方法的文档。如果发现需要使用其他ValueTuple方法(例如,定义扩展方法),则建议考虑创建一个命名类型。


2

元组用于表示多个值,例如方法打算返回多个值时。C#7中的元组支持使用System.ValueTuple<...>实例来表示该组值。这些值的名称仅在使用它们且未强制使用的上下文中有效。

类旨在表示具有多个属性的单个值。


1

当您想将多个值(可以是不同的类型)组合到一个对象中而不创建自定义类时,元组是一个不错的选择。在这种情况下,Tuple将是一个快速而完美的选择。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.