C#能否仅用原生代码将对象引用重解释为nint原生整数?
对象引用转nint可行性验证
免责声明:我知晓此操作属于不安全操作且为不良实践,仅想确认其是否具备可行性。
核心需求为将对象引用转换为nint(即IntPtr),二者内存占用大小与平台指针宽度一致,理论上具备实现基础。目前已通过DynamicMethod实现该效果,对应代码如下:
DynamicMethod dyn = new DynamicMethod("", typeof(nint), new[] { typeof(object) }); ILGenerator il = dyn.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ret); Func<object, nint> objectToIntFunction = (Func<object, nint>)dyn.CreateDelegate(typeof(Func<object, nint>)); object obj = new object(); nint asNativeInt = objectToIntFunction(obj);
需要确认是否可以仅使用C#代码实现该转换,在System.Runtime.CompilerServices.Unsafe中暂未找到适用方法。
此前尝试过显式内存布局结构体的实现方案,代码如下:
[StructLayout(LayoutKind.Explicit)] struct Cast { [FieldOffset(0)] public nint Number; [FieldOffset(0)] public object Object; }; Cast cast = new Cast { Object = obj }; nint number = cast.Number;
运行时触发如下错误:
System.TypeLoadException: 无法从程序集加载类型“Cast”,因为该类型在偏移量为0的位置包含对象字段,该字段未正确对齐,或与非对象字段重叠。
问题解答
显式布局结构体报错原因
.NET运行时会对所有自定义类型做布局校验,禁止GC可追踪的引用类型字段和非引用类型(值类型、指针类型)字段内存重叠。GC扫描内存时需要精准识别每个位置是否为对象引用,如果允许引用和nint重叠,GC可能把普通整数值误判为对象引用做追踪,引发内存损坏,因此运行时会直接拒绝加载这类违规类型,没有绕过空间。
纯C#实现方案
不需要手动写IL生成DynamicMethod,纯C#可以实现同等效果,分两种场景:
- 允许开启unsafe编译选项(最简洁,和DynamicMethod逻辑完全一致)
直接取参数存储位置的引用值即可,本质和IL里直接返回ldarg.0的逻辑完全相同:
static unsafe nint ObjectToNint(object obj) { return *(nint*)&obj; }
调用方法和之前DynamicMethod生成的委托完全一致,拿到的就是当前时刻对象在托管堆上的内存地址。
- 不开启unsafe上下文
可以借助System.Runtime.CompilerServices.Unsafe类实现,不需要写指针代码:
using System.Runtime.CompilerServices; static nint ObjectToNint(object obj) { return Unsafe.ReadUnaligned<nint>(ref Unsafe.As<object, byte>(ref obj)); }
如果需要长期持有地址不失效,必须用GCHandle固定对象,否则GC触发堆压缩时移动对象,之前拿到的地址就会变成野指针:
object obj = new object(); GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned); // 固定后拿到的地址在调用Free前不会变化 nint stablePtr = handle.AddrOfPinnedObject(); // 使用完成后必须释放,否则会造成内存泄漏、GC性能下降 handle.Free();
注意事项
- 未固定对象时拿到的
nint是瞬时值,任何GC发生后都可能失效,直接访问会触发内存访问违规 - 固定对象会影响GC堆内存压缩效率,长期固定大量对象会严重降低程序性能
- 该操作属于明确的不安全实践,仅可用于可行性验证,禁止在生产环境使用
内容的提问来源于stack exchange,提问作者Mr Anderson
相关产品推荐
相关产品推荐

