C#中ValueTuple的原理详解
作者:lindexi 发布时间:2023-10-26 06:40:34
前言
本文告诉大家一些 ValueTuple 的原理,避免在使用出现和期望不相同的值。ValueTuple 是 C# 7 的语法糖,如果使用的 .net Framework 是 4.7 以前,那么需要使用 Nuget 安装System.ValueTuple
虽然 ValueTuple 的很好用,但是需要知道他有两个地方都是在用的时候需要知道他原理。如果不知道原理,可能就发现代码和预期不相同
json 转换
先创建一个项目,然后安装 Json 解析,使用下面的代码,在运行之前,先猜一下,下面的代码会出现什么
var foo = (name: "lindexi", site: "blog.csdn.net/lindexi_gd");
var str = JsonConvert.SerializeObject(foo);
实际上输出的是 {"Item1":"lindexi","Item2":"blog.csdn.net/lindexi_gd"}
那么刚才的命名在哪?
如果想知道,那么请看 ValueTuple 的原理
原理
先来写一段代码,编译之后对他反编译,看一下他是怎么做的
static void Main(string[] args)
{
var foo = Foo();
var str = JsonConvert.SerializeObject(foo);
Console.WriteLine(str);
}
static (string name, string site) Foo()
{
return (name: "lindexi", site: "blog.csdn.net/lindexi_gd");
}
不需要安装反编译软件,可以使用这个网站拿到反编译
可以看到Foo被编译为 TupleElementNames 特性的两个字符串
[return: TupleElementNames(new string[]
{
"name",
"site"
})]
private static ValueTuple<string, string> Foo()
{
return new ValueTuple<string, string>("lindexi", "blog.csdn.net/lindexi_gd");
}
所以实际上代码是 ValueTuple<string, string> 不是刚才定义的代码,只是通过 TupleElementNames 让编译器知道值,所以是语法糖。
IL 代码是
private hidebysig static valuetype [mscorlib]System.ValueTuple`2<string, string>
Foo() cil managed
{
.param [0]
.custom instance void [mscorlib]System.Runtime.CompilerServices.TupleElementNamesAttribute::.ctor(string[])
= (
01 00 02 00 00 00 04 6e 61 6d 65 04 73 69 74 65 // .......name.site 这里就是 return: TupleElementNames 的命名
00 00 // ..
)
.maxstack 2
.locals init (
[0] valuetype [mscorlib]System.ValueTuple`2<string, string> V_0
)
// [20 9 - 20 10]
IL_0000: nop
// [21 13 - 21 72]
IL_0001: ldstr "lindexi"
IL_0006: ldstr "blog.csdn.net/lindexi_gd"
IL_000b: newobj instance void valuetype [mscorlib]System.ValueTuple`2<string, string>::.ctor(!0/*string*/, !1/*string*/)
IL_0010: stloc.0 // V_0
IL_0011: br.s IL_0013
// [22 9 - 22 10]
IL_0013: ldloc.0 // V_0
IL_0014: ret
}
这个特性只有编译器可以用,不可以在代码使用。
在上面的解释,实际上 IL 不知道存在定义的命名,所以不可以通过这个方法获得值。
动态类型获得值
如果希望使用动态类型获得值,那么下面的代码实际上会运行出现异常
static void Main(string[] args)
{
dynamic foo = Foo();
Console.WriteLine(foo.name);
}
static (string name, string site) Foo()
{
return (name: "lindexi", site: "blog.csdn.net/lindexi_gd");
}
运行出现 RuntimeBinderException 异常,因为没有发现 name 属性
实际上对比下面匿名类,也就是很差不多写法。
dynamic foo = new { name = "lindexi", site = "blog.csdn.net/lindexi_gd" };
Console.WriteLine(foo.name);
运行是可以的,所以在使用动态类型,请不要使用 ValueTuple ,如果需要使用,那么请知道有存在找不到变量异常,而且是在运行才出现异常。
性能提升
如果使用 ValueTuple 编程会有一些优点,性能是其中之一。而且对于异步编程,使用 ValueTuple 可以继续使用 await 的方法。
假如有一个方法需要返回 5 个参数,那么以前的做法有三个方法,第一个方法是使用 out 的方法,第二个方法是使用 Tuple ,第三个方法是定义一个临时的类。
如果使用了 out 的方法,那么这个方法就不可以继续使用异步 await 的方法,因为 await 需要做出状态机,参见我写的await原理。如果使用 Tuple ,或这定义一个临时的类,就会出现性能的问题。
从上面的原理,已经告诉大家,ValueTuple 是值类型,而 Tuple 或定义的一个类不是值类型。编译器的优化是让 ValueTuple 分配在栈,对于普通的类分配在堆空间。如果一个类分配到堆空间,那么就需要使用垃圾回收才可以清理空间。而分配到栈就不需要使用垃圾回收,使用完成就清空栈,效率比堆空间大。
但是使用栈空间需要注意,栈空间是很小的,如果使用了大量栈空间可能会出现堆栈gg。因为考虑到部分刚入门的小伙伴,所以我就需要多说一些,上面说的 ValueTuple 使用了栈空间需要小心栈空间不足,和你存放的值的关系不大,而是和定义的 ValueTuple 数量有关,这个数量是非常大的。但是在递归方法中,本来是刚好空间足够的,在使用了 ValueTuple 可能就不够了。
使用 ValueTuple 可以继续使用异步,而且不需要垃圾回收,性能比Tuple高,所以建议在多返回参数使用 ValueTuple,而不是定义一个类。
其他需要知道的
不要随便定义一个看不懂的值
实际上下面的代码,编译是可以通过
(int x, (int y, (float a, float b))[] c) f1
但是这个值,在看的时候,几乎说不出他的属性
第二个需要知道的,ValueTuple 是值类型,所以他的默认值不是 null 而是 default(xx),在C# 7.2 支持使用关键字,所以不需要去写 defalut(xx,xx)
关于 ValueTuple 变量名的定义也是很难说的,有的小伙伴觉得需要使用 Axx 的方式命名,但是很多小伙伴觉得使用 aaBa 的命名更好,所以暂时对于他的命名使用 aaBa 的方法,大家觉得什么方式好请告诉我
参见: Exploring Tuples as a Library Author
C# 7: Dynamic types and Reflection cannot access Tuple fields by name
来源:https://lindexi.gitee.io/post/C-ValueTuple-原理.html


猜你喜欢
- Android PopWindow 设置背景亮度的实例设置背景 /** * 设置添加屏幕的背景透明度 * @param bgAl
- 使用类的全权名: System.Text.StringBuilder sb = new System.Text.StringBuilder(
- 前言JDK 1.5 之前 synchronized 的性能是比较低的,但在 JDK 1.5 中,官方推出一个重量级功能 Lock,一举改变了
- 本文实例介绍了Android如何画出触摸轨迹的方法,分享给大家供大家参考,具体内容如下效果图:实现代码:package com.androi
- 比如在窗体中显示时间:错误思路一:我在窗体结构函数中写入一个死循环,每隔一秒显示一次当前时间public Form6() &n
- 1.场景线程池使用DiscardOldestPolicy拒绝策略,阻塞队列使用ArrayBlockingQueue,发现在某些情形下对于得到
- 前言Mybatis是web工程开发中非常常用的数据持久化的框架,通过该框架,我们非常容易的进行数据库的增删改查。数据库连接进行事务提交的时候
- 两种android图片裁剪方式,供大家参考,具体内容如下一、相机拍完照之后利用系统自带裁剪工具进行截取public static void
- 序言:使用MyBatis3提供的注解可以逐步取代XML,例如使用@Select注解直接编写SQL完成数据查询,使用@SelectProvid
- 1、ThreadLocal知识体系本文还是不能免俗,在回答这个问题之前需要先和大家介绍一下ThreadLocal的知识,使大家对Thread
- 前言这篇文章介绍如何使用Springboot+Junit+Mockito做单元测试,案例选取撮合交易的一个类来做单元测试。单元测试前先理解需
- 1、注意事项众所周知,对一个文件进行读写操作时,我们需要创建对应的输入流和输出流但需要注意的是,读写操作不能同时进行(边读边写),即不能同时
- 目录概述ClassPoolCtClassCtMthodCtFieldCtConstructorClassPathClassLoader示例创
- 依然使用IE9的捕获参数,做了一个12306的登录功能。参照了网上童鞋们的做法。其他都和前面几篇读取余票、票价一样,不过登录要用到证书的问题
- 一、前言null与NULL不都是表示空值吗?这有什么值得深入讨论的的?首先你在编写Java代码时使用过NULL吗?大概用IDE用习惯了,自动
- 1.雪崩效应 雪崩效应如上图所示,假设我们有3个微服务A,B,C,A调用B,B调用C,如果C挂掉了,由于B是同步调用,不断等待,导致资源耗尽
- 原因每次使用idea新建项目,就会在默认的c盘下的一个maven仓库中下载jar包,可是我自己指定maven仓库不是这个。如何让idea在新
- 什么是数组数组(Array)是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,
- 图1如图1,我们工作过程中,无论平台网站还是企业官网,总少不了新闻展示。如某天产品经理跟我们说,推广人员想要抓取百度新闻中热点要闻版块提高站
- 一、思路1.获取播放组件一般我们使用UI的Raw Image或者Image来显示图片Image:仅支持Sprite类型图片,需要更改图片的格