返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >C#元组类型ValueTuple用法详解
  • 825
分享到

C#元组类型ValueTuple用法详解

2024-04-02 19:04:59 825人浏览 八月长安
摘要

System.Tuple 类型是在.net 4.0中引入的,但是有两个明显的缺点:(1) Tuple 类型是引用类型。(2) 没有构造函数支持。 为了解决这些问题,C# 7

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

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

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

    // 构建元组实例
    var tpl = (1, 2);
                
    // 在字典中使用元组
    var d = new Dictionary<(int x, int y), (byte a, short b)>();
     
    // 不同名称的元组是兼容的
    d.Add(tpl, (a: 3, b: 4));
     
    // 元组值的语义
    if (d.TryGetValue((1, 2), out var r))
    {
        // 解构元组忽略第一个元素
        var (_, b) = r;
                    
        // 使用命名语法和定义名称
        Console.WriteLine($"a: {r.a}, b: {r.Item2}");
    }

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标记非常特殊,不能在用户代码中直接使用。如果您尝试使用它,编译器会报出错误。

    public (int a, int b) Foo1((int c, int d) a) => a;
 
    [return: TupleElementNames(new[] { "a", "b" })]
    public ValueTuple<int, int> Foo(
        [TupleElementNames(new[] { "c", "d" })] ValueTuple<int, int> a)
    {
        return a;
    }

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

    // 正确: 元组声明可以跳过元素名称
    (int x, int y) tpl = (1, 2);
     
    // 警告: 由于目标类型“(int x, int y)”指定了其他名称或未指定名称,因此元组元素名称“a”被忽略。
    tpl = (a:1, b:2);
     
    // 正确 :元组解构忽略元素名称
    var (a, b) = tpl;
     
    // x: 2, y: 1. 元组名被忽略
    var (y, x) = tpl;

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

    public abstract class Base
    {
        public abstract (int a, int b) Foo();
        public abstract (int, int) Bar();
    }
     
    public class Derived : Base
    {
        // 错误:替代继承成员“Base.Foo()”时无法更改元组元素名称
        public override (int c, int d) Foo() => (1, 2);
        // 错误:替代继承成员“Base.Bar()”时无法更改元组元素名称
        public override (int a, int b) Bar() => (1, 2);
    }

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

元素名称推断

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

    public void NameInference(int x, int y)
    {
        // (int x, int y)
        var tpl = (x, y);
     
        var a = new {X = x, Y = y};
     
        // (int X, int Y)
        var tpl2 = (a.X, a.Y);
    }

值语义和可变性

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

    var x = new { Items = new List<int> { 1, 2, 3 }.GetEnumerator() };
    while (x.Items.MoveNext())
    {
        Console.WriteLine(x.Items.Current);
    }

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

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

但可变性问题依然存在:

    var tpl = (x: 1, y: 2);
    var hs = new HashSet<(int x, int y)>();
    hs.Add(tpl);
     
    tpl.x++;
    Console.WriteLine(hs.Contains(tpl)); // false

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

解构

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

    public static class VersionDeconstrucion
    {
        public static void Deconstruct(this Version v, out int major, out int minor, out int build, out int revision)
        {
            major = v.Major;
            minor = v.Minor;
            build = v.Build;
            revision = v.Revision;
        }
    }
     
    
    var version = Version.Parse("1.2.3.4");
    var (major, minor, build, _) = version;
     
    // Prints: 1.2.3
    Console.WriteLine($"{major}.{minor}.{build}");

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

元组别名

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

//您不能这样做:编译错误
using Point = (int x, int y);
 
// 但是您可以这样做
using SetOfPoints = System.Collections.Generic.HashSet<(int x, int y)>;

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

命名规则

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

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

总结

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

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

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

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

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

--结束END--

本文标题: C#元组类型ValueTuple用法详解

本文链接: https://lsjlt.com/news/140039.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C#元组类型ValueTuple用法详解
    System.Tuple 类型是在.NET 4.0中引入的,但是有两个明显的缺点:(1) Tuple 类型是引用类型。(2) 没有构造函数支持。 为了解决这些问题,C# 7...
    99+
    2024-04-02
  • C#中元组类型ValueTuple怎么用
    这篇文章将为大家详细讲解有关C#中元组类型ValueTuple怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。System.Tuple 类型是在.NET 4.0中引入的,但是有两个明显的缺点...
    99+
    2023-06-29
  • C#实现数组元素的数据类型转换方法详解
    目录一、场景假设二、解决方案三、问题延伸四、数组类的静态转换方法五、刨根问底一、场景假设 假设有一串字符串如下所示,字符串中的数字之间已用英文状态下的逗号隔开。要求用此字符串中的数字...
    99+
    2024-04-02
  • 浅析C++元组tuple类型
    目录介绍tuple的定义及初始化tuple的使用成员访问获取tuple信息拼接tuple交换tupletuple解包tuple比较tuple遍历tuple开发时的应用介绍 元组tup...
    99+
    2024-04-02
  • C#基元类型、值类型、引用类型是什么
    这篇文章主要讲解了“C#基元类型、值类型、引用类型是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#基元类型、值类型、引用类型是什么”吧!首先了解下什么是基元类型,基元类型是编译器直接...
    99+
    2023-06-17
  • C++详细讲解引用类型
    目录一、C++中的引用类型二、如何使用C++中的引用类型一、C++中的引用类型 在以严蔚敏老师的《数据结构》为代表的诸多数据结构的书中,都出现了C++引用这一用法,所以在学习数据结构...
    99+
    2024-04-02
  • C++类型转换详解
    目录内置类型的转换自定义类型转换explicit 关键字提醒提问:编译器在什么时候使用Stone(double)?转换函数概念介绍自动引用类型转换缺陷总结C++对于内置类型有...
    99+
    2024-04-02
  • Python基本数据类型中元组的用法
    本篇文章为大家展示了Python基本数据类型中元组的用法,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1.元组的概念python中的元组是有序元素组成的集合,与列表的区别在于,元组是不可变的,一旦定...
    99+
    2023-06-02
  • C++ stringstream类用法详解
    本文主要介绍 C++ 中 stringstream 类的常见用法。 1 概述 <sstream> 定义了三个类:istringstream、ostringstream 和...
    99+
    2024-04-02
  • C#DirectoryInfo类用法详解
    DirectoryInfo类是System.IO命名空间的一部分。它用于创建,删除和移动目录。它提供了执行与目录和子目录相关的操作的方法。这是一个密封的类,所以不能继承它。 Dire...
    99+
    2024-04-02
  • C++之友元:友元函数和友元类详解
    一、友元介绍我们知道,类的成员函数可以访问同类的其他成员函数,包括公有、私有和保护成员。而类的外部函数只能访问类的公有成员。友元是一种允许非类成员函数访问类的非公有成员的一种机制。可...
    99+
    2022-11-15
    友元函数 友元类
  • C++ 的类型转换详解
    目录一、C++ 类型转换1.静态类型转换1.语法格式2.转化规则 2.重解释类型转换1.语法格式2.转化规则 3.常类型转换1.语法格式2.语法规则 3.const 常变量(补充) ...
    99+
    2024-04-02
  • 如何解析C++的C++数组类型
    如何解析C++的C++数组类型,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。C++数组的类型实际上是指数组元素的取值类型,对于同一个数组,其所有元素的数据类型都是相同的,数组名...
    99+
    2023-06-17
  • C#中的引用类型以及特殊引用类型详解
    基本 哪些属于引用类型 类(object,string),接口、数组、委托 引用类型分配在哪里 引用类型变量位于线程栈。引用类型实例分配在托管堆上。当引用类型实例的大小小于85000...
    99+
    2022-11-13
    C# 引用类型 特殊引用类型
  • HTML 空元素:定义、类型和用途详解
    HTML空元素类型及用途 在HTML代码中存在一大批没有结束标签的标签,称为空元素。空元素只存在一个开始标签,不存在与之对应的结束标签。空元素通常用于表示诸如换行、分割线、输入文本框之类的内容,通常可以直接使用开始标签而不需要与之对应的结...
    99+
    2024-02-25
    空元素, HTML, 标签, 结束标签, 内容, 简单, 内容, <br>, <hr>, <img>, <input>
  • Python组合数据类型详解
    目录集合元组创建方式列表操作函数操作方法 列表的引用字典查找修改和添加字典的操作函数字典的操作方法 集合 创建集合有两种方式: 第一种: T = {11,111,"11"}...
    99+
    2024-04-02
  • Javapostgresql数组字段类型处理方法详解
    在实际开发中遇到postgresql中定义的数组字段,下面解决两个问题,如何定义数组字段的默认值为空格数组,以及如何再java实体类中直接使用数组对象接受数据或把数据存入数据库。 1...
    99+
    2022-11-13
    Java postgresql Java postgresql数组字段类型
  • C++超详细讲解强制类型转换的用法
    目录static_castdynamic_castconst_castreinterpret_caststatic_cast static_cast<type-id>(e...
    99+
    2024-04-02
  • Python数据类型详解(三)元祖:tuple
    一.基本数据类型   整数:int   字符串:str(注:t等于一个tab键)   布尔值: bool   列表:list   列表用[]   元祖:tuple   元祖用()   字典:dict 注...
    99+
    2022-06-04
    元祖 详解 数据类型
  • C++ 强制类型转换详解
    目录一、C强制转换二、C++强制转换1、static_cast 静态转换(编译时检查)2、const_cast 常量转换3、reinterpret_cast 重新解释转换4、dyna...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作