在Tuples之前,我曾经创建过一个class
及其变量,然后从此类创建对象,并使该对象成为某些函数的返回类型。
现在用的元组,我可以做同样的在C#7.0,我们可以为元组的属性分配可以理解的名称(在此之前它item1
,item2
等..)
所以现在我想知道,什么时候应该使用它,什么时候应该在c#7.0中创建一个类?
在Tuples之前,我曾经创建过一个class
及其变量,然后从此类创建对象,并使该对象成为某些函数的返回类型。
现在用的元组,我可以做同样的在C#7.0,我们可以为元组的属性分配可以理解的名称(在此之前它item1
,item2
等..)
所以现在我想知道,什么时候应该使用它,什么时候应该在c#7.0中创建一个类?
Answers:
由于此答案在这里引起了某些人的困惑,因此我应该澄清-根据问题-这里所有对“元组”的引用都指的ValueTuple
是C#7的类型和新的元组语法糖功能,而绝不是旧的System.Tuple
参考类型。
所以现在我想知道,什么时候应该使用元组,什么时候应该在c#7.0中创建类?
只有您才能真正回答该问题,因为它实际上取决于您的代码。
但是,在指导您进行选择时,可以遵循以下准则和规则:
大多数时候,这应该不是问题。但是,如果您绕过大型结构的元组,则可能会影响性能。不过,可以使用Ref locals / returns来解决这些性能问题。
此外,由于它们是值,因此远程修改副本不会更改原始副本。这是一件好事,但可以吸引一些人。
赋予元素的名称由编译器使用,并且(在大多数情况下)在运行时不可用。这意味着不能使用反射来发现其名称。它们不能动态访问,也不能在剃刀视图中使用。
这也是API的重要考虑因素。从方法返回的元组是关于编译后名称可发现性的规则的例外。编译器向方法添加属性,以保存有关元组名称的信息。这意味着您可以安全地从一个程序集中的公共方法返回一个元组,并在另一个程序集中访问其名称。
元组比类型更容易编写,因为它们不那么冗长,并且声明可以“内联”(即在使用时声明)。例如,在声明返回多个值的方法时,此方法效果很好。
但是,由于它们是在使用时声明的,因此,如果您有MethodA
要进行调用的调用MethodB
,MethodC
并且每个调用都返回一个元组,则需要在每个阶段重新定义该元组。没有(还)创建一个元组的别名,并重新使用它跨多个方法的方法。
在任何可能考虑使用元组的情况下:只需问自己一个问题:“元组会在这里简化代码吗?”。如果答案为“是”,则使用一个。最终,这是使用元组还是自定义类的主要考虑因素。
ValueTuple
是值类型。Tuple
是引用类型。如此简单,Tuple
不会被引用复制。您应该以粗体字提及它,因为它现在令人困惑
使用课程
如果对象是在整个应用程序中广泛使用的实体,并且还存储在某种持久性存储中,例如关系数据库(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
变量,但我认为元组更具表达能力,并提高了可读性。
Coordinates
为,struct
而不是class
。
ToString
以提供可读的输出(顺便说一下,元组的默认ToString在这里已经可以正常工作),也可以重载Equals
/GetHashCode
进行相等性比较。
通常,当您的对象将在其他地方使用时,或者如果它表示您领域中的真实对象或概念,您都想拥有一个类。您可能要创建一个类来表示汽车或汽车商店,而不是元组。
另一方面,有时您只想从一个方法返回几个对象。也许它们并不代表任何特殊之处,只是您需要使用该特定方法将它们一起返回。有时,即使它们确实代表了您的域中的一个概念(例如,您正在返回(Car, Store)
,也可能表示为一个Sale
对象),您实际上并不会在任何地方使用它们-您只是在移动数据。在这些情况下,使用元组是可以的。
现在,具体说说C#,您还应该了解一件事。C#7的元组类型实际上是ValueTuple
,这是一个结构。与引用类型的类不同,结构是值类型。您可以在msdn上阅读有关此内容的更多信息。最重要的是,要知道它们可能涉及很多复制,所以要小心。
我认为这将成为一个经常被问到的问题。对于何时使用新值元组和类,目前没有“最佳实践”。
但是,值得一读的是在先前的讨论中,有关元组和类的先前版本的讨论。
在我看来,值元组只能使用最少,最多只能使用三个值。我认为这在“无需类就返回某些值”和“令人费解的值”之间取得了很好的平衡。如果要返回的值超过三个,则创建一个类。
我也永远不会使用元组从消费者必须使用的面向公众的API中返回。同样,只需使用一个类。
这是我使用的一些实际代码:
public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()
我想返回更复杂的数据时,就创建了一个类。
我想首先提到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
是值类型。使用aclass
和struct
apply之间的所有优缺点。ValueTuple
可以显式使用(带有或不带有语法糖),从而使其System.Tuple
在保留命名成员的同时具有通用性。ValueTuple
支持解构。鉴于其必须是语法糖,我会说选择的较强ValueTuple
论点与选择a的论点相同struct
。这对于大多数生活在堆栈中的小型,不可变的类型将是理想的(因此,您无需进行大量装箱和拆箱)。
与ValueTuple
完整的结构相比,考虑语法糖,我建议ValueTuple
默认使用,除非您需要显式的布局或需要向其添加方法。
我还想说句法糖不一定能提高可读性。主要原因是您没有命名类型,而类型的名称为代码提供了含义。除此之外,您可以将文档添加到struct
或class
声明中,以便于理解。
总而言之,ValueTuple
真正发光的情况是从一个方法返回多个值。在这种情况下,无需创建新out
参数。和的文档ValueTuple
使用能活的方法的文档。如果发现需要使用其他ValueTuple
方法(例如,定义扩展方法),则建议考虑创建一个命名类型。
当您想将多个值(可以是不同的类型)组合到一个对象中而不创建自定义类时,元组是一个不错的选择。在这种情况下,Tuple将是一个快速而完美的选择。