.NET9 Pre4 UnsafeAccessor泛型

点击上方蓝字 江湖评谈设为关注/星标

00c77acfef1045b001c671cccddef880.png

前言

.NET9 PreView4 CLR里面添加了对于UnsafeAccessorAttribute特性泛型的支持。而对于UnsafeAccessorAttribute本身的支持则在.NET8里面。本篇看下Pre4里面的这个特性用法以及原理。

用法

来看看一个简单的例子:

internal class Program
{
    public class Class<T>
    {
       private T _field;
       private void M<U>(T t, U u) { Console.WriteLine(t);Console.WriteLine(u); }
    }
    class Accessors<V>
    {
       [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_field")]
        public extern static ref V GetSetPrivateField(Class<V> c);


        [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
        public extern static void CallM<W>(Class<V> c, V v, W w);
     }


    public static void AccessGenericType(Class<int> c)
    {
        ref int f = ref Accessors<int>.GetSetPrivateField(c);
        f = 10;
        Console.WriteLine(f);
        Accessors<int>.CallM<string>(c, 1, "hello");
     }


     static void Main(string[] args)
     {
         Class<int> c1 = new Class<int>() { };         
         AccessGenericType(c1);
         Console.ReadLine();
     }
}

例子取自官方,稍微改了下。Class<T>里面的一个字段T及函数M,通过UnsafeAccessorAttribute进行访问和赋值。这里需要注意的点是,如果通过UnsafeAccessorAttribute访问字段,则UnsafeAccessorAttribute特性声明的方法参数需是类。比如本例的GetSetPrivateField它的参数需要字段所在类。如果是通过UnsafeAccessorAttribute特性访问方法,比如本例的CallM访问M方法,则它的方法(Call)第一个参数是方法(M)所在类(Class<V>),后面是M方法的参数(T t, U u),顺序相同。

(注意以上代码需在.NET9 PreView4里面运行,vs开启preview版本方法:工具-》选项-》环境-》预览功能->使用.NET SDK预览版勾选,下载.NET9 Preivew4安装,重启VS即可),结果打印如下:

10
1
hello

原理

原理其实也比较简单,以GetSetPrivateField为例(也可以看看CLR的GenerateAccessor)。这个函数里面被roslyn compile了一个.cctor

.method public hidebysig static !V&  GetSetPrivateField(class ConsoleApp1.Program/Class`1<!V> c) cil managed
{
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.UnsafeAccessorAttribute::.ctor(valuetype [System.Runtime]System.Runtime.CompilerServices.UnsafeAccessorKind) = ( 01 00 03 00 00 00 01 00 54 0E 04 4E 61 6D 65 06   // ........T..Name.                                                                                                                                                                                     5F 66 69 65 6C 64 )                               // _field
}

JIT导入加载之后,会被识别出它是byref(即C#里的ref),它的加载实际上是.ctor的引用结果放到栈(ldflda)上

IL to import:
IL_0000  02                ldarg.0
IL_0001  7c 01 00 00 0a    ldflda       0xA000001
IL_0006  2a

然后识别,看到此时JIT已经知道它是byref了

*************** In compInitDebuggingInfo() for ConsoleApp1.Program+Accessors`1[int]:GetSetPrivateField(ConsoleApp1.Program+Class`1[int]):byref
*************** In fgFindBasicBlocks() for ConsoleApp1.Program+Accessors`1[int]:GetSetPrivateField(ConsoleApp1.Program+Class`1[int]):byref

下面要做的就是把它变成_filed的引用,如下图:

STMT00000 ( 0x000[E-] ... ??? )
               [000002] ---X-------                         *  RETURN    byref
               [000001] ---X-------                         \--*  FIELD_ADDR byref  ConsoleApp1.Program+Class`1[int]:_field

如此即可通过UnsafeAccessorAttribute特性访问_field字段了。那么JIT实际上是把GetSetPrivateField变形成了如下:

伪代码:
ref GetSetPrivateField()
{
    return c1._field;
}

结尾

finsh here

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值