雷达智富

首页 > 内容 > 程序笔记 > 正文

程序笔记

.NET9 Pre4 UnsafeAccessor泛型用法

2024-06-19 60

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

来看一个简单的使用UnsafeAccessorAttribute的例子:

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了

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;
}
更新于:6个月前
赞一波!2

文章评论

评论问答