关于C# dynamic装箱问题
作者:yi念之间 发布时间:2021-09-13 19:22:40
前言
前几天在技术群里看到有同学在讨论关于dynamic是否会存在装箱拆箱的问题,我当时第一想法是"会"。至于为啥会有很多人有这种疑问,主要是因为觉得dynamic
可能是因为有点特殊,因为它被称为动态类型
,可能是因为这里的动态对大家造成的误解,认为这里的动态可以推断出具体的类型,所以可以避免装箱拆箱。但是事实并不是这样,今天就一起就这个问题虽然讨论一下。
装箱拆箱
首先咱们先来看下何为装箱拆箱,这个可以在微软官方文档中Boxing and Unboxing文档中看到答案,咱们就简单的摘要一下相关的描述
装箱是将值类型转换为类型对象或此值类型实现的任何接口类型的过程。当公共语言运行时 (CLR) 将值类型装箱时,它会将值包装在 System.Object 实例中并将其存储在托管堆上。拆箱从对象中提取值类型。拳击是隐含的;拆箱是明确的。装箱和拆箱的概念是 C# 类型系统统一视图的基础,其中任何类型的值都可以视为对象。
翻译起来会比较抽象,理解起来就是利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来。也就是值类型和引用类型相互转换的一做桥梁,但是问题也很明显那就是实例会存在在堆栈之前相互copy的问题,会存在一定的性能问题,所以这也一直是一个诟病。
虽然说是这样但是也没必要一直扣死角,毕竟很多时候程序还没有纠结到这种程度,因为任何语言存在的各种方法中或者操作中都会有一定这种问题,所以本质不是语言存在各种问题,而是在什么场景如何使用的问题。比如避免出现装箱和拆箱的办法也就是入概念所说的,那就是避免值类型和和引用类型之间相互转换,但是很多时候还是避免不了的,所以也不必纠结。
探究本质
上面讲解了关于装箱拆箱
的概念,接下来咱们就来定义一段代码看看效果,为了方便对比咱们直接对比着看一下
dynamic num = 123;
dynamic str = "a string";
想要看清本质还是要反编译一下生成的结果看一下的,这里我们可以借助ILSpy
或dnSpy
来看下,首先看一下反编译回来的效果
private static void <Main>$(string[] args)
{
object num = 123;
object str = "a string";
Console.ReadKey();
}
因为我是使用的是.net6的顶级声明方式所以会生成<Main>$
方法。不过从反编译的结果就可以看出来dynamic的本质是object
,如果还有点怀疑的话可以直接查看生成的IL
代码,还是使用ILSpy
工具
.method private hidebysig static
void '<Main>$' (
string[] args
) cil managed
{
// Method begins at RVA 0x2094
// Header size: 12
// Code size: 30 (0x1e)
.maxstack 1
.entrypoint
.locals init (
// 这里可以看出声明的num和str变量都是object类型的
[0] object num,
[1] object str
)
// object obj = 123;
IL_0000: ldc.i4.s 123
// 这里的box说明存在装箱操作
IL_0002: box [System.Runtime]System.Int32
IL_0007: stloc.0
// object obj2 = "a string";
IL_0008: ldstr "a string"
IL_000d: stloc.1
// Console.ReadKey();
IL_000e: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey()
IL_0013: pop
// (no C# code)
IL_0014: nop
IL_0015: nop
IL_0016: nop
IL_0017: nop
IL_0018: nop
IL_0019: nop
IL_001a: nop
IL_001b: nop
// }
IL_001c: nop
IL_001d: ret
} // end of method Program::'<Main>$'
通过这里可以看出dynamic的本质确实是object
,既然是object那就可以证实确实是存在装箱操作。这个其实在微软官方文档Using type dynamic上有说明,大致描述是这样的
dynamic类型是一种静态类型,但类型为dynamic的对象会跳过静态类型检查。大多数情况下,该对象就像具有类型object一样。 在编译时,将假定类型化为dynamic的元素支持任何操作。因此,不必考虑对象是从 COM API、从动态语言(例如 IronPython)、从 HTML 文档对象模型 (DOM)、从反射还是从程序中的其他位置获取自己的值。但是,如果代码无效,则在运行时会捕获到错误。
从这里可以看出dynamic表现出来的就是object,只是dynamic会跳过静态类型检查,所以编译的时候不会报错,有错误的话会在运行的时候报错,也就是我们说的是在运行时确定具体操作。这涉及到动态语言运行时,动态语言运行时(DLR)是一种运行时环境,可以将一组动态语言服务添加到公共语言运行时(CLR)。使用DLR可以轻松开发在.NET上运行的动态语言,并为静态类型语言添加动态特征。
匿名类型
总会有人拿dynamic
和var
进行比较,但是本质上来说,这两者描述的不是一个层面的东西。var
叫隐式类型,本质是一种语法糖,也就是说在编译的时候就可以确定类型的具体类型,也就是说var本质是提供了一种更简单的编程体验,不会影响变量本身的行为。
这也就解释了为啥同一个var
变量多次赋值不能赋不同类型的值,比如以下操作编译器会直接报错
var num = 123;
num = "123"; //报错
如果你是用的集成开发环境的话其实很容易发现,把鼠标放到var
类型上就会显示变量对应的真实类型。或者可以直接通过ILSpy
看看反编译结果,比如声明了var num = 123
编译完成之后就是
private static void <Main>$(string[] args)
{
int num = 123;
Console.ReadKey();
}
请注意这里并不是object
而是转换成了具体的类型因为123
就是int类型的,严谨一点看一下IL
代码
.maxstack 1
.entrypoint
//声明的int32
.locals init (
[0] int32 num
)
// int num = 123;
IL_0000: ldc.i4.s 123
IL_0002: stloc.0
相信这里就可以看出来了dynamic
和var
确实也不是一个层面的东西。var是隐式类型是语法糖为了简化编程体验用的,dynamic则是动态语言运行时技术,编译时转换成object类型,因为在c#上一切都是object,然后再运行时进行具体的操作。
总结
本篇文章主要是在技术群里看到有同学在讨论关于dynamic是否会装箱引发的思考,相对来说讲解的比较基础也比较简单。想对一个东西理解的更透彻,就要一步一步的了解它到底是什么,这样的话就可以更好的理解和思考。也印证了那句话,你不会用或者用是因为你对它不够了解,当你对它有足够理解的时候,操作起来也就会游刃有余。
来源:https://www.cnblogs.com/wucy/p/16281540.html


猜你喜欢
- 背景:写一个用户登录拦截,在网上找了一圈没找到好用的,于是自己试验了一下,总结出来,分享给大家。1.自定义登录 * LoginInterce
- 今天上班中午吃饱之后、逛博客溜达看到一道题:数组反转 晚上回家洗完澡没事情做,就自己练习一把。public static cla
- 本文实例为大家分享了Android自定义输入法软键盘的具体代码,供大家参考,具体内容如下1 功能描述触屏设备主界面中有一个文本编辑框,底部区
- C# 的析构以及垃圾回收实例分析看书时,自己写的例子代码,了解到几个知识点,记载下来。同时发现自己手写代码的能力比较弱,还是得多写一下。us
- Java的最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问。 每一个用synchronized关键字声明的方法
- 本文实例讲述了Java基于分治算法实现的棋盘覆盖问题。分享给大家供大家参考,具体如下:在一个2^k * 2^k个方格组成的棋盘中,有一个方格
- 本文实例讲述了应用Java泛型和反射导出CSV文件的方法。分享给大家供大家参考。具体如下:项目中有需求要把数据导出为CSV文件,因为不同的类
- 问题之前一直使用Mybatis,最近尝试使用Mybatis-Plus,却在updateById登录成功后更新最近登录时间出现了问题,一般业务
- 详解Android使用@hide的API的方法今天早上想修改MediaPlaybackService.Java(/packages/apps
- 泛型方法是使用类型参数声明的方法,如下所示:static void Swap<T>(ref T lhs, ref T rhs){
- Looper是整个跨线程通信的管理者 // 内部持有的变量如下: ThreadLocal
- 上篇文章已经对Synchronized关键字做了初步的介绍,从字节码层面介绍了Synchronized关键字,最终字节码层面就是monito
- 在日常生活中,我们使用maven下载需要的jar包,但是很多的时候由于中央仓库没有,所以我们没有办法下载到需要的jar包,手动去下载上,然后
- 1.概述Spring Boot Admin是一个Web应用程序,用于管理和监视Spring Boot应用程序。每个应用程序都被视为客户端,并
- 前言:最近终于用上了高性能的测试机(54C96G * 3),相较之前的单机性能提升了三倍,数量提升了三倍,更关键的宽带提单机升了30倍不止,
- 最近一段时间在研究OAuth2的使用,想整个单点登录,从网上找了很多demo都没有实施成功,也许是因为压根就不懂OAuth2的原理导致。有那
- 在Android TV上一般选中某个View, 都会有焦点突出放大的效果, 但是当在RecyclerView中(ListView或GridV
- 验证码及它的作用验证码为全自动区分计算机和人类的图灵测试的缩写,是一种区分用户是计算机的公共全自动程序,这个问题可以由计算机生成并评判,但是
- 对于随机数,大家都知道,计算机不 可能产生完全随机的数字,所谓的随机数发生器都是通过一定的算法对事先选定的随机种子做复杂的运算,用产生的结果
- log4j MDC实现日志追踪MDC 中包含的可以被同一线程中执行的代码所访问内容。当前线程的子线程会继承其父线程中的 MDC 的内容。记录