网页资讯视频图片知道文库贴吧地图采购
进入贴吧全吧搜索

 
 
 
日一二三四五六
       
       
       
       
       
       

签到排名:今日本吧第个签到,

本吧因你更精彩,明天继续来努力!

本吧签到人数:0

一键签到
成为超级会员,使用一键签到
一键签到
本月漏签0次!
0
成为超级会员,赠送8张补签卡
如何使用?
点击日历上漏签日期,即可进行补签。
连续签到:天  累计签到:天
0
超级会员单次开通12个月以上,赠送连续签到卡3张
使用连续签到卡
06月25日漏签0天
c#吧 关注:188,398贴子:822,749
  • 看贴

  • 图片

  • 吧主推荐

  • 视频

  • 游戏

  • 0回复贴,共1页
<<返回c#吧
>0< 加载中...

C# 关键字Record,从IL,汇编,寄存器,CLR等四个方面彻底的了解

  • 只看楼主
  • 收藏

  • 回复
  • 诗_韵抚华裳
  • c#爱好者
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
C# 关键字Record,从IL,汇编,寄存器,CLR等四个方面彻底的了解它原创 tangyanzhi1111 DotNet研究 今天
Record关键字并不是最近新增的,而是之前C#9里面就有的,但是在最近.Net 6 LTS版本到来之际,突然有提了出来。(如果您喜欢技术,请关注以下公众号)

有人说它是一个特殊的结构或者结构,我们来看看Record到底什么?
以下为VS2022+.Net 6.0编译结果:
首先我们新建一个控制台应用程序,可以看到新版的Vs2022里面是没有Main函数入口点的。
tangyanzhi tyz = new tangyanzhi() { name="zhangsan",age=15};Console.WriteLine(tyz);tangyanzhi tyz1 = tyz with { age = 16 };Console.WriteLine(tyz1);Console.ReadLine();
record tangyanzhi{ public string name { get; set; } public int age { get; set; }}
为啥没有Main函数入口点,通过ILDASM查看了下IL代码,在Program.CS类里面发现:
.method private hidebysig static void '<Main>$'(string[] args) cil managed{ .entrypoint // 代码大小 64 (0x40) .maxstack 3 .locals init (class tangyanzhi V_0, class tangyanzhi V_1)}
其实通过上面这段IL代码可以看到,在Vs编译的时候,会自动加上Main函数入口点,因为无论如何一个应用程序必须要有一个入口点,只不过在Vs2022里面可以省略这部分,而没有严格的要求必须带上Main函数。
回到上面,我们继续来看Record, 我们从以下四个个方面来分析,第一IL代码,第二汇编代码,第三寄存器,第四Runtime代码。以便彻底弄懂Record到底是个什么东西
1:IL代码
method private hidebysig static void '<Main>$'(string[] args) cil managed{ .entrypoint // 代码大小 64 (0x40) .maxstack 3 .locals init (class tangyanzhi V_0, class tangyanzhi V_1) IL_0000: newobj instance void tangyanzhi::.ctor() IL_0005: dup IL_0006: ldstr "zhangsan" IL_000b: callvirt instance void tangyanzhi::set_name(string) IL_0010: nop IL_0011: dup IL_0012: ldc.i4.s 15 IL_0014: callvirt instance void tangyanzhi::set_age(int32) IL_0019: nop IL_001a: stloc.0 IL_001b: ldloc.0 IL_001c: call void [System.Console]System.Console::WriteLine(object) IL_0021: nop IL_0022: ldloc.0 IL_0023: callvirt instance class tangyanzhi tangyanzhi::'<Clone>$'() IL_0028: dup IL_0029: ldc.i4.s 16 IL_002b: callvirt instance void tangyanzhi::set_age(int32) IL_0030: nop IL_0031: stloc.1}
从上面代码其实可以看到第一次tangyanzhi tyz=new tangyanzhi()这个对象的时候,它实际上在IL里面调用的是newobj。这个newobj最终会调用malloc分配内存给对象,实际上newobj也是我们常用的熟悉的实例化一个对象所必须的操作步骤(注意了这个地方其实已经证明了Record实际上就是类,就是我们常用的类Class)。
2:汇编代码
我们来看看汇编代码Record编译的样子:
Console.WriteLine(tyz);00007FF8F91D0DBE mov rcx,qword ptr [rbp+48h] 00007FF8F91D0DC2 call CLRStub[MethodDescPrestub]@7ff8f91d0d20 (07FF8F91D0D20h)
注意看这三行代码,实际上就是调用Console.WriteLine输出new tangyanzhi()这个类的实例地址。
00007FF8F91D0DBE mov rcx,qword ptr [rbp+48h]
这句汇编实际上是把它引用的地址赋值给rcx,也就是实例化的对象的tyz的地址赋值给rcx。谁才会有地址?当然引用类型(注意了,所以这个地方其实也是证明了Record其实就是个Class,也就是引用类型)
3:寄存器
00007FF8F91D0DBE mov rcx,qword ptr [rbp+48h]
还是这段代码,rbp寄存器存储的地址+ 16进制的48,所反汇编的引用对象,其实就是Record实例化的对象。这个地方主要是映证上面的汇编代码。
4:Runtime代码
作为.Net最底层的CLR,这个地方也是个难点。实际上在github的\src\coreclr\jit\importer.cpp这个目录下面包含了一些代码
case CEE_CALLVIRT: // cannot do callvirt on valuetypes VerifyOrReturn(!(methodClassFlgs & CORINFO_FLG_VALUECLASS), "callVirt on value class"); VerifyOrReturn(sig->hasThis(), "CallVirt on static method"); break;
注意看前面的IL代码当用Record with 的时候它调用的是CALLVIRT这个IL代码。CEE_CALLVIRT就是CALLVIRT的Def方式,其实这里面啥都没做,唯一的做的是把它With后面的对象的所占的内存,地址空间复制过来。变成自己的,然后修改其中一些变量,比如本例子中tangyanzhi类里面的age被修改为16.
实际上到这里基本上就看到了Record关键字的一个本质了实际上它就是一个Class,你把它当成Class用就行了,其它的至于它自己多一些特性,比如With关键字,这个稍微注意下就行了。


登录百度账号

扫二维码下载贴吧客户端

下载贴吧APP
看高清直播、视频!
  • 贴吧页面意见反馈
  • 违规贴吧举报反馈通道
  • 贴吧违规信息处理公示
  • 0回复贴,共1页
<<返回c#吧
分享到:
©2025 Baidu贴吧协议|隐私政策|吧主制度|意见反馈|网络谣言警示