前言

前几天在技术群里看到有同学在讨论关于dynamic是否会存在装箱拆箱的问题,我当时第一想法是"会"。至于为啥会有很多人有这种疑问,主要是因为觉得dynamic可能是因为有点特殊,因为它被称为动态类型,可能是因为这里的动态对大家造成的误解,认为这里的动态可以推断出具体的类型,所以可以避免装箱拆箱。但是事实并不是这样,今天就一起就这个问题虽然讨论一下。

装箱拆箱

首先咱们先来看下何为装箱拆箱,这个可以在微软官方文档中Boxing and Unboxing文档中看到答案,咱们就简单的摘要一下相关的描述

装箱是将值类型转换为类型对象或此值类型实现的任何接口类型的过程。当公共语言运行时 (CLR) 将值类型装箱时,它会将值包装在 System.Object 实例中并将其存储在托管堆上。拆箱从对象中提取值类型。拳击是隐含的;拆箱是明确的。装箱和拆箱的概念是 C# 类型系统统一视图的基础,其中任何类型的值都可以视为对象。

翻译起来会比较抽象,理解起来就是利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来。也就是值类型和引用类型相互转换的一做桥梁,但是问题也很明显那就是实例会存在在堆栈之前相互copy的问题,会存在一定的性能问题,所以这也一直是一个诟病。

虽然说是这样但是也没必要一直扣死角,毕竟很多时候程序还没有纠结到这种程度,因为任何语言存在的各种方法中或者操作中都会有一定这种问题,所以本质不是语言存在各种问题,而是在什么场景如何使用的问题。比如避免出现装箱和拆箱的办法也就是入概念所说的,那就是避免值类型和和引用类型之间相互转换,但是很多时候还是避免不了的,所以也不必纠结。

探究本质

上面讲解了关于装箱拆箱的概念,接下来咱们就来定义一段代码看看效果,为了方便对比咱们直接对比着看一下

  1. dynamic num = 123;
  2. dynamic str = "a string";

想要看清本质还是要反编译一下生成的结果看一下的,这里我们可以借助ILSpydnSpy来看下,首先看一下反编译回来的效果

  1. private static void <Main>$(string[] args)
  2. {
  3. object num = 123;
  4. object str = "a string";
  5. Console.ReadKey();
  6. }

因为我是使用的是.net6的顶级声明方式所以会生成<Main>$方法。不过从反编译的结果就可以看出来dynamic的本质是object,如果还有点怀疑的话可以直接查看生成的IL代码,还是使用ILSpy工具

  1. .method private hidebysig static
  2. void '<Main>$' (
  3. string[] args
  4. ) cil managed
  5. {
  6. // Method begins at RVA 0x2094
  7. // Header size: 12
  8. // Code size: 30 (0x1e)
  9. .maxstack 1
  10. .entrypoint
  11. .locals init (
  12. // 这里可以看出声明的num和str变量都是object类型的
  13. [0] object num,
  14. [1] object str
  15. )
  16. // object obj = 123;
  17. IL_0000: ldc.i4.s 123
  18. // 这里的box说明存在装箱操作
  19. IL_0002: box [System.Runtime]System.Int32
  20. IL_0007: stloc.0
  21. // object obj2 = "a string";
  22. IL_0008: ldstr "a string"
  23. IL_000d: stloc.1
  24. // Console.ReadKey();
  25. IL_000e: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey()
  26. IL_0013: pop
  27. // (no C# code)
  28. IL_0014: nop
  29. IL_0015: nop
  30. IL_0016: nop
  31. IL_0017: nop
  32. IL_0018: nop
  33. IL_0019: nop
  34. IL_001a: nop
  35. IL_001b: nop
  36. // }
  37. IL_001c: nop
  38. IL_001d: ret
  39. } // 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上运行的动态语言,并为静态类型语言添加动态特征。

匿名类型

总会有人拿dynamicvar进行比较,但是本质上来说,这两者描述的不是一个层面的东西。var叫隐式类型,本质是一种语法糖,也就是说在编译的时候就可以确定类型的具体类型,也就是说var本质是提供了一种更简单的编程体验,不会影响变量本身的行为。这也就解释了为啥同一个var变量多次赋值不能赋不同类型的值,比如以下操作编译器会直接报错

  1. var num = 123;
  2. num = "123"; //报错

如果你是用的集成开发环境的话其实很容易发现,把鼠标放到var类型上就会显示变量对应的真实类型。或者可以直接通过ILSpy看看反编译结果,比如声明了var num = 123编译完成之后就是

  1. private static void <Main>$(string[] args)
  2. {
  3. int num = 123;
  4. Console.ReadKey();
  5. }

请注意这里并不是object而是转换成了具体的类型因为123就是int类型的,严谨一点看一下IL代码

  1. .maxstack 1
  2. .entrypoint
  3. //声明的int32
  4. .locals init (
  5. [0] int32 num
  6. )
  7. // int num = 123;
  8. IL_0000: ldc.i4.s 123
  9. IL_0002: stloc.0

相信这里就可以看出来了dynamicvar确实也不是一个层面的东西。var是隐式类型是语法糖为了简化编程体验用的,dynamic则是动态语言运行时技术,编译时转换成object类型,因为在c#上一切都是object,然后再运行时进行具体的操作。

总结

本篇文章主要是在技术群里看到有同学在讨论关于dynamic是否会装箱引发的思考,相对来说讲解的比较基础也比较简单。想对一个东西理解的更透彻,就要一步一步的了解它到底是什么,这样的话就可以更好的理解和思考。也印证了那句话,你不会用或者用是因为你对它不够了解,当你对它有足够理解的时候,操作起来也就会游刃有余。

欢迎扫码关注我的公众号

由C# dynamic是否装箱引发的思考的更多相关文章

  1. class_copyIvarList方法获取实例变量问题引发的思考

    在runtime.h中,你可以通过其中的一个方法来获取实例变量,那就是class_copyIvarList方法,具体的实现如下: - (NSArray *)ivarArray:(Class)cls { ...

  2. Spring之LoadTimeWeaver——一个需求引发的思考---转

    原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...

  3. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  4. 解决一道leetcode算法题的曲折过程及引发的思考

    写在前面 本题实际解题过程是 从 40秒 --> 24秒 -->1.5秒 --> 715ms --> 320ms --> 48ms --> 36ms --> ...

  5. 【思考】由安装zabbix至排障php一系列引发的思考

    [思考]由安装zabbix至排障php一系列引发的思考 linux的知识点林立众多,很有可能你在排查一个故障的时候就得用到另一门技术的知识: 由于linux本身的应用依赖的库和其它环境环环相扣,但又没 ...

  6. 由<a href = "#" > 引发的思考

    原文:由<a href = "#" > 引发的思考 前阵子在一个移动项目中,通过 <a href = "#" >  的方式 绑定clic ...

  7. 曲演杂坛--一条DELETE引发的思考

    原文:曲演杂坛--一条DELETE引发的思考 场景介绍: 我们有一张表,专门用来生成自增ID供业务使用,表结构如下: CREATE TABLE TB001 ( ID ,) PRIMARY KEY, D ...

  8. 由一个emoji引发的思考

    由一个emoji引发的思考 从毕业以来,基本就一直在做移动端,但是一直就关于移动端的开发,各种适配问题的解决,在日常搬砖中处理了就过了,也没有把东西都沉淀下来,觉得甚是寒颜.现就一个小bug,让我们来 ...

  9. 一次composer错误使用引发的思考

    一次composer错误使用引发的思考 这个思考源自于一个事故.让我对版本依赖重新思考了一下. 事故现象 一个线上的管理后台,一个使用laravel搭建的管理后台,之前在线上跑的好好的,今天comop ...

随机推荐

  1. SVN报错之“Error: Please execute the 'Cleanup' command. ”

    问题 Error: Please execute the 'Cleanup' command. 需要清理下,注意SVN拉数据的时候别打开其中的问题 解决方案

  2. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    静态多态.动态多态 静态多态:程序在编译阶段就可以确定调用哪个函数.这种情况叫做静态多态.比如重载,编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数.动态多态:在运行期间才可以确定最终调用的 ...

  3. simulink仿真过程

    Simulink求解器 Simulink仿真过程 Simulink 模型的执行分几个阶段进行.首先进行的是初始化阶段,在此阶段,Simulink 将库块合并到模型中来,确定传送宽度.数据类型和采样时间 ...

  4. 第一天·浏览器内核及Web标准

    一·浏览器及浏览器内核 1.常见的浏览器 (1)IE浏览器 IE是微软公司旗下浏览器,是目国内用户量最多的浏览器.IE诞生于1994年,当时微软为了对抗市场份额占据将近百分之九十的网景Netscape ...

  5. 【uniapp 开发】UniPush

    App.vue export default { onLaunch: function() { // #ifdef APP-PLUS const _self = this; const _handle ...

  6. mysql的下载和安装详细教程(windows)

    Windows下安装MySQL详细教程 1.安装包下载    2.安装教程 (1)配置环境变量 (2)生成data文件 (3)安装MySQL (4)启动服务 (5)登录MySQL (6)查询用户密码 ...

  7. numpy教程01---ndarray的创建

    欢迎关注公众号[Python开发实战], 获取更多内容! 工具-numpy numpy是使用Python进行数据科学的基础库.numpy以一个强大的N维数组对象为中心,它还包含有用的线性代数,傅里叶变 ...

  8. cannot find module providing package github.com/× working directory is not part of a module

    今天在搭建fabric的过程中遇到一个问题,记录一下 root@zitao /home/hong/Desktop/gowork/src/github.com/hyperledger/fabric re ...

  9. Python数据展示 - 生成表格图片

    前言 前一篇文章介绍了推送信息到企业微信群里,其中一个项目推送的信息是使用Python自动生成的表格,本文来讲讲如何用Python生成表格图片. 选一个合适库 Python最大的优点就是第三方库丰富, ...

  10. Java学习day14

    可变参数用作方法的形参,方法参数的个数就可变 格式:修饰符 返回值类型 方法名(数据类型...变量名){ } 方法内的形参只能有一个,这里的变量是一个数组 public static <T> ...