欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#元组类型ValueTuple用法详解

程序员文章站 2024-02-19 12:48:40
system.tuple类型是在.net 4.0中引入的,但是有两个明显的缺点:(1) tuple 类型是引用类型。(2) 没有构造函数支持。为了解决这些问题,c# 7 引入了新的语言功能以及新的类型...

system.tuple 类型是在.net 4.0中引入的,但是有两个明显的缺点:
(1) tuple 类型是引用类型。
(2) 没有构造函数支持。

为了解决这些问题,c# 7 引入了新的语言功能以及新的类型。

现在,如果您需要从函数中返回两个值的合并结果,或者把两个值合并到一个哈希表中,可以使用system.valuetuple类型并使用一个精短的语法来构造它们:

system.valuetuple 类型在.net framework 4.7中引入。但是您仍然可以在较低的框架版本中使用这个功能,这时候,您必须引用一个特殊的nuget包:system.valuetuple

  • 元组声明的语法与函数参数声明相似:(type1 name1, type2 name2)
  • 元组的构造语法类似于参数构造:(value1, optionalname: value2)
  • 两个元组具有相同的元素类型,但不同的名称是兼容(**):(int a, int b) = (1, 2)
  • 元组值的语义: (1,2).equals((a: 1, b: 2))(1,2).gethashcode() == (1,2).gethashcode() 返回的值均是true
  • 元组不支持==!=。在github上有一个悬而未决的讨论:“支持==和!=元组类型”
  • 元组可以被“解构”,但只能转换成“变量声明”,而不能“out var”或case语句中转换:var (x, y) = (1,2) - ok, (var x, int y) = (1,2) - ok, dictionary.trygetvalue(key, out var (x, y)) - not ok, case var (x, y): break; - not ok。
  • 元组是可变的:(int a, int b) x = (1,2); x.a++;.
  • 元组元素可以通过名称(如果提供的话)或通过通用名称item1item2等来访问。

我们马上就会明白上面几点。

元组名称

缺少用户定义的名称导致system.tuple类型不常用。我们可以将system.tuple用作一个精减方法的实现细节,但如果我们需要传递它,我更喜欢使用具有描述性属性名称的命名类型。新元组功能很好地解决了这个问题:可以为元组元素指定名称,而不像匿名类型,即使在不同的程序集中也可以使用这些名称。

c#编译器为方法签名中使用的每个元组类型指定了一个特殊的标记tupleelementnamesattribute

tupleelementnamesattribute标记非常特殊,不能在用户代码中直接使用。如果您尝试使用它,编译器会报出错误。

这有助于ide和编译器“检查”元素名称,并警告错误地使用它们:

编译器对继承的成员有较强的要求:

常规方法参数可以在重写成员中*更改,重写成员中的元组元素名称应该与基本类型中的元素名称完全匹配。

元素名称推断

c# 7.1 引入了一个额外的增强功能:元素名称推断类似于c#为匿名类型所做的推断。

值语义和可变性

元组是公共字段可变的值类型。这听起来令人担忧,因为我们知道可变值类型被认为是有害的。这是一个邪恶的小例子:

如果运行这个代码,您会得到一个无限循环。list<t>.enumerator是一个可变值类型,但是items是属性。这意味着x.items在每个循环迭代中返回原始迭代器的副本,从而导致无限循环。

但是只有当数据与行为混合在一起时,可变值类型才是危险的:枚举器拥有一个状态(当前元素)并具有行为(通过调用movenext方法来推进迭代器的能力)。这种组合可能会导致问题,因为在副本上调用方法而不是在原始实例上调用方法,从而导致无效操作。下面是一组由于值类型的隐藏副本而导致不明显行为的示例:gist

但可变性问题依然存在:

元组在字典中作为键是非常有用的,并且由于适当的值语义可以存储在哈希表中。但是您不应该在集合的不同操作之间改变一个元组变量的状态。

解构

虽然元组的构造函数对于元组来说非常特殊的,但是解构非常通用,并且可以与任何类型一起使用。

解构使用“鸭子类型(duck-typing)”的方法:如果编译器可以找到一个方法调用deconstruct给定的类型 - 实例方法或扩展方法 - 类型即是可解构的。

元组别名

一旦您开始使用元组,很快就会意识到想在源代码的多个地方“重用”一个元组类型,但这并没有什么问题。首先,虽然c#不支持给定类型的全局别名,不过您可以使用“using”别名指令,它会在一个文件中创建一个别名;其次,您不能将元组指定别名:

github上有一个关于“使用指令中的元组类型”的讨论。所以,如果您发现自己在多个地方使用一个元组类型,你有两个选择:保持复制粘贴或创建一个命名的类型。

命名规则

下面是一个有趣的问题:我们应该遵循什么命名规则来处理元组元素?pascal规则喜欢elementname还是骆峰规则elementname?一方面,元组元素应该遵循公共成员的命名规则(即pascalcase),但另一方面,元组只是包含变量的变量,变量应该遵循骆峰规则。

如果元组被用作参数或方法的返回类型使用pascalcase规则,并且如果在函数中本地创建元组使用camelcase规则,可以考虑使用基于用法和使用的不同命名方案。但我更喜欢总是使用camelcase

总结

我发现元组在日常工作中非常有用。我需要不止一个函数返回值,或者我需要把一对值放入一个哈希表,或者字典的key非常复杂,我需要用另一个“字段”来扩展它。

我甚至使用它们来避免与方法类似的concurrentdictionary.trygetoradd的闭包分配,需要额外的参数。在许多情况下,状态也是一个元组。

该功能是非常有用的,但我还想看到一些增强功能:

  • 全局别名:能够“命名”一个元组并在整个程序集中使用它们。
  • 在模式匹配中解构一个元组:out varcase var语法。
  • 使用运算符==进行相等比较。

到此这篇关于c#元组类型valuetuple用法详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。