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

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关键字,这个稍微注意下就行了。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页

打赏作者

江湖评谈

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值