编程语言
首页 > 编程语言> > C#(014):C# 7.0-7.3 新特性Tuple元组 、 ValueTuple 值元组详解

C#(014):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类提供创造元组对象的静态方法。

Note:这里构建出来的Tuple类型其实是Tuple<int, int, int, int, int, int, int,
Tuple>,因此testTuple8.Rest取到的数据类型是Tuple,因此要想获取准确值需要取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}]");
}

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

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

二、ValueTuple值元组

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

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

值元组的具体使用如下:

1、如何创建值元组

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

注意这里构建出来的Tuple类型其实是Tuple<int, int, int, int, int, int, int,
Tuple>,因此testTuple8.Rest取到的数据类型是Tuple,因此要想获取准确值需要取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相比主要好处如下:

三、元组类型

元组(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

标签:WriteLine,Tuple,C#,元组,studentInfo,var,ValueTuple
来源: https://www.cnblogs.com/springsnow/p/16272223.html