• C#(99):C# 7.0-7.3 新特性Tuple元组 、 ValueTuple 值元组详解


    原文:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/value-tuples

    一、Tuple元组

    Tuple是C# 4.0时出的新特性,.Net Framework 4.0以上版本可用。

    元组是一种数据结构,具有特定数量和元素序列,与数组不同,元祖中的元素可以不同的数据类型。比如设计一个三元组数据结构用于存储学生信息,一共包含三个元素,第一个是名字,第二个是年龄,第三个是身高。

    元组的具体使用如下:

    1、如何创建元组

    默认情况.Net Framework元组仅支持1到7个元组元素,如果有8个元素或者更多,需要使用Tuple的嵌套和Rest属性去实现。另外Tuple类提供创造元组对象的静态方法。

    • 利用构造函数创建元组:
    var testTuple7 = new Tuple<int, int, int, int, int, int, int>(1, 2, 3, 4, 5, 6,7);
    Console.WriteLine($"Item 1: {testTuple7.Item1}, Item 7: {testTuple7.Item7}");
    
    var testTuple10 = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>>
                              (1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int>(8, 9, 10));
    Console.WriteLine($"Item 1: {testTuple10.Item1}, Item 10: {testTuple10.Rest.Item3}");
    • 利用Tuple静态方法构建元组,最多支持八个元素:
    var testTuple7 = System.Tuple.Create<int, int, int, int, int, int, int>(1, 2, 3, 4, 5, 6, 7);
    Console.WriteLine($"Item 1: {testTuple7.Item1}, Item 7: {testTuple7.Item7}");
    
    var testTuple8 = System.Tuple.Create<int, int, int, int, int, int, int, int>
                              (1, 2, 3, 4, 5, 6, 7, 8);
    Console.WriteLine($"Item 1: {testTuple8.Item1}, Item 8: {testTuple8.Rest.Item1}");

    Note:这里构建出来的Tuple类型其实是Tuple<int, int, int, int, int, int, int, Tuple<int>>,因此testTuple8.Rest取到的数据类型是Tuple<int>,因此要想获取准确值需要取Item1属性。

    2、表示一组数据

    如下创建一个元组表示一个学生的三个信息:名字、年龄和身高,而不用单独额外创建一个类。

    var studentInfo = Tuple.Create<string, int, uint>("Bob", 28, 175);
    Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");

    3、从方法返回多个值

    当一个函数需要返回多个值的时候,一般情况下可以使用out参数,这里可以用元组代替out实现返回多个值。

    void Main()
    {
        var studentInfo = GetStudentInfo("Bob");
        Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
    }
    static dynamic GetStudentInfo(string name)
    {
        return new Tuple<string, int, uint>("Bob", 28, 175);
    }

    4、用于单参数方法的多值传递

    当函数参数仅是一个Object类型时,可以使用元组实现传递多个参数值。

    void Main()
    {
        var t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(WriteStudentInfo));
        t.Start(new Tuple<string, int, uint>("Bob", 28, 175));
        while (t.IsAlive)
        {
            System.Threading.Thread.Sleep(50);
        }
    }
    static void WriteStudentInfo(Object student)
    {
        var studentInfo = student as Tuple<string, int, uint>;
        Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
    }

    尽管元组有上述方便使用的方法,但是它也有明显的不足:

    • 访问元素的时候只能通过ItemX去访问,使用前需要明确元素顺序,属性名字没有实际意义,不方便记忆;
    • 最多有八个元素,要想更多只能通过最后一个元素进行嵌套扩展;
    • Tuple是一个引用类型,不像其它的简单类型一样是值类型,它在堆上分配空间,在CPU密集操作时可能有太多的创建和分配工作。

    因此在C# 7.0中引入了一个新的ValueTuple类型,详见下面章节。

    二、ValueTuple值元组

    ValueTuple是C# 7.0的新特性之一,.Net Framework 4.7以上版本可用。

    值元组也是一种数据结构,用于表示特定数量和元素序列,但是是和元组类不一样的,主要区别如下:

    • 值元组是结构,是值类型,不是类,而元组(Tuple)是类,引用类型;
    • 值元组元素是可变的,不是只读的,也就是说可以改变值元组中的元素值;
    • 值元组的数据成员是字段不是属性。

    值元组的具体使用如下:

    1、如何创建值元组

    和元组类一样,.Net Framework值元组也只支持1到7个元组元素,如果有8个元素或者更多,需要使用值元组的嵌套和Rest属性去实现。另外ValueTuple类可以提供创造值元组对象的静态方法。

    • 利用构造函数创建元组:
    var testTuple6 = new ValueTuple<int, int, int, int, int, int>(1, 2, 3, 4, 5, 6);
    Console.WriteLine($"Item 1: {testTuple6.Item1}, Item 6: {testTuple6.Item6}"); 
    
    var testTuple10 = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple <int, int, int>(8, 9, 10));
    Console.WriteLine($"Item 1: {testTuple10.Item1}, Item 10: {testTuple10.Rest.Item3}");
    • 利用Tuple静态方法构建元组,最多支持八个元素:
    var testTuple6 = ValueTuple.Create<int, int, int, int, int, int>(1, 2, 3, 4, 5, 6);
    Console.WriteLine($"Item 1: {testTuple6.Item1}, Item 6: {testTuple6.Item6}"); 
    
    var testTuple8 = ValueTuple.Create<int, int, int, int, int, int, int, int>(1, 2, 3, 4, 5, 6, 7, 8);
    Console.WriteLine($"Item 1: {testTuple8.Item1}, Item 8: {testTuple8.Rest.Item1}");

    注意这里构建出来的Tuple类型其实是Tuple<int, int, int, int, int, int, int, Tuple<int>>,因此testTuple8.Rest取到的数据类型是Tuple<int>,因此要想获取准确值需要取Item1属性。

    优化区别:当构造出超过7个元素以上的值元组后,可以使用接下来的ItemX进行访问嵌套元组中的值,对于上面的例子,要访问第十个元素,既可以通过testTuple10.Rest.Item3访问,也可以通过testTuple10.Item10来访问。

    var testTuple10 = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int, int, int>(8, 9, 10));
    Console.WriteLine($"Item 10: {testTuple10.Rest.Item3}, Item 10: {testTuple10.Item10}");

    2、表示一组数据

    如下创建一个值元组表示一个学生的三个信息:名字、年龄和身高,而不用单独额外创建一个类。

    var studentInfo = ValueTuple.Create<string, int, uint>("Bob", 28, 175);
    Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");

    3、从方法返回多个值

    值元组也可以在函数定义中代替out参数返回多个值。

    static ValueTuple<string, int, uint> GetStudentInfo(string name)
    {
        return new ValueTuple<string, int, uint>("Bob", 28, 175);
    }
    
    static void Main()
    {
        var studentInfo = GetStudentInfo("Bob");
        Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
    }

    优化区别:返回值可以不明显指定ValueTuple,使用新语法(,,)代替,如(string, int, uint):

    static (string, int, uint) GetStudentInfo1(string name)
    {
        return ("Bob", 28, 175);
    }
    
    static void Main()
    {
        var studentInfo = GetStudentInfo1("Bob");
        Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
    }

    调试查看studentInfo的类型就是ValueType三元组。

    优化区别:返回值可以指定元素名字,方便理解记忆赋值和访问:

    static (string name, int age, uint height) GetStudentInfo1(string name)
    {
        return ("Bob", 28, 175);
    }
    
    static void Main()
    {
        var studentInfo = GetStudentInfo1("Bob");
        Console.WriteLine($"Student Information: Name [{studentInfo.name}], Age [{studentInfo.age}], Height [{studentInfo.height}]");
    }

    方便记忆赋值:

    1147484_20170528173418797_1768152173[1]

    方便访问:

    1147484_20170528173429907_1968247262[1]

    4、用于单参数方法的多值传递

    当函数参数仅是一个Object类型时,可以使用值元组实现传递多个值。

    static void WriteStudentInfo(Object student)
    {
        var studentInfo = (ValueTuple<string, int, uint>)student;
        Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
    }
    
    static void Main()
    {
        var t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(WriteStudentInfo));
        t.Start(new ValueTuple<string, int, uint>("Bob", 28, 175));
        while (t.IsAlive)
        {
            System.Threading.Thread.Sleep(50);
        }
    }

    5、解构ValueTuple

    可以通过var (x, y)或者(var x, var y)来解析值元组元素构造局部变量,同时可以使用符号”_”来忽略不需要的元素。

    static (string name, int age, uint height) GetStudentInfo1(string name)
    {
        return ("Bob", 28, 175);
    }
    
    static void Main()
    {
        var (name, age, height) = GetStudentInfo1("Bob");
        Console.WriteLine($"Student Information: Name [{name}], Age [{age}], Height [{height}]");
    
        (var name1, var age1, var height1) = GetStudentInfo1("Bob");
        Console.WriteLine($"Student Information: Name [{name1}], Age [{age1}], Height [{height1}]");
    
        var (_, age2, _) = GetStudentInfo1("Bob");
        Console.WriteLine($"Student Information: Age [{age2}]");
    }

    由上所述,ValueTuple使C#变得更简单易用。较Tuple相比主要好处如下:

    • ValueTuple支持函数返回值新语法”(,,)”,使代码更简单;
    • 能够给元素命名,方便使用和记忆,这里需要注意虽然命名了,但是实际上value tuple没有定义这样名字的属性或者字段,真正的名字仍然是ItemX,所有的元素名字都只是设计和编译时用的,不是运行时用的(因此注意对该类型的序列化和反序列化操作);
    • 可以使用解构方法更方便地使用部分或全部元组的元素;
    • 值元组是值类型,使用起来比引用类型的元组效率高,并且值元组是有比较方法的,可以用于比较是否相等,详见:https://msdn.microsoft.com/en-us/library/system.valuetuple

    三、元组类型

    元组(Tuple)在 .Net 4.0 的时候就有了,但元组也有些缺点,如:

       1)Tuple 会影响代码的可读性,因为它的属性名都是:Item1,Item2.. 。

       2)Tuple 还不够轻量级,因为它是引用类型(Class)。

       备注:上述所指 Tuple 还不够轻量级,是从某种意义上来说的或者是一种假设,即假设分配操作非常的多。

    C# 7 中的值元组(ValueTuple)解决了上述两个缺点:

       1)ValueTuple 支持语义上的字段命名。

       2)ValueTuple 是值类型(Struct)。

    1. 如何创建一个元组?

    var tuple = (1, 2);// 使用语法糖创建元组
    var tuple2 = ValueTuple.Create(1, 2);         // 使用静态方法【Create】创建元组
    var tuple3 = new ValueTuple<int, int>(1, 2);  // 使用 new 运算符创建元组
    
    WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三种方式都是等价的。");

    原理解析:上面三种方式最终都是使用 new 运算符来创建实例。

    2. 如何创建给字段命名的元组?

    // 左边指定字段名称
    (int one, int two) tuple = (1, 2);
    WriteLine($"first:{tuple.one}, second:{tuple.two}");
    
    // 右边指定字段名称
    var tuple2 = (one: 1, two: 2);
    WriteLine($"first:{tuple2.one}, second:{tuple2.two}");
    
    // 左右两边同时指定字段名称
    (int one, int two) tuple3 = (first: 1, second: 2);    /* 此处会有警告:由于目标类型(xx)已指定了其它名称,因为忽略元组名称xxx */
    WriteLine($"first:{tuple3.one}, second:{tuple3.two}");

    注:左右两边同时指定字段名称,会使用左边的字段名称覆盖右边的字段名称(一一对应)。

    原理解析:上述给字段命名的元组在编译后其字段名称还是:Item1, Item2...,即:“命名”只是语义上的命名。

    3. 什么是解构?(不推荐)

    解构顾名思义就是将整体分解成部分。

    4. 解构元组,如下所示:

    var (one, two) = GetTuple();
    WriteLine($"first:{one}, second:{two}");
    
    static (int, int) GetTuple()
    {
        return (1, 2);
    }

    原理解析:解构元组就是将元组中的字段值赋值给声明的局部变量(编译后可查看)。

    备注:在解构时“=”左边能提取变量的数据类型(如上所示),元组中字段类型相同时即可提取具体类型也可以是隐式类型,但元组中字段类型

    不相同时只能提取隐式类型。

    5. 解构可以应用于 .Net 的任意类型,但需要编写 Deconstruct 方法成员(实例或扩展)。

    如下所示:

    public class Student
      {
          public Student(string name, int age)
          {
              Name = name;
              Age = age;
          }
      
          public string Name { get; set; }
      
          public int Age { get; set; }
      
          public void Deconstruct(out string name, out int age)
          {
              name = Name;
              age = Age;
          }
      }

    使用方式如下:

    var(Name, Age) = new Student("Mike", 30);
    WriteLine($"name:{Name}, age:{Age}");

    原理解析:编译后就是由其实例调用 Deconstruct 方法,然后给局部变量赋值。

    Deconstruct 方法签名:

    // 实例签名
    public void Deconstruct(out type variable1, out type variable2...)
      
    // 扩展签名
    public static void Deconstruct(this type instance, out type variable1, out type variable2...)

    总结:

    1. 元组的原理是利用了成员类型的嵌套或者是说成员类型的递归。
    2. 编译器很牛B才能提供如此优美的语法。

    使用 ValueTuple 则需要导入: Install - Package System.ValueTuple

  • 相关阅读:
    exceljs xlsx 前端生成excel 和解析excel (一)
    分片分N次请求记录,包含重试控制,前端拼接总数据数组后导出
    打包文件复制脚本
    复制到剪贴板
    去掉控制台consolelog
    axios 使用 v3
    并发 promiseAll from dalao
    axios 使用v2
    docker-compose安装sonarqube7.9
    centos7 部署minio
  • 原文地址:https://www.cnblogs.com/springsnow/p/13665775.html
Copyright © 2020-2023  润新知