You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

获取托管堆上值类型的可随GC移动更新的8字节引用

问题描述

假设托管堆中已有作为类或数组一部分的值类型实例,可通过以下代码获取引用数组某元素的Memory<int>实例:

Memory<int> memory = new(array, 42, 1);

该方案可行,但x86_64平台下Memory<T>结构体占16字节。现需实现**仅用8字节(对应平台IntPtr大小)**达成相同目标,同时满足以下限制:

  • 非托管指针不可行,因GC运行时可能移动数组;
  • 无法为数组特定元素创建GCHandle,也找不到将数组中值类型转为可通过Unsafe.Unbox<>()拆箱的装箱对象的方法;
  • 长期存储固定GCHandle会阻碍GC运行(可能贯穿应用生命周期),不考虑;
  • 尽量避免分配到非托管堆,减少unsafe代码,优先安全C#代码;
  • 不接受将数组元素改为装箱对象的workaround(会失去数组线性特性);
  • ref struct无法满足需求,因最终需存储到堆上。

背景:现有复杂类型内部存储其他复杂类型的集合或值,存在循环引用,当前用类实现,希望尽可能改用结构体以作为类的一部分存储而非引用。示例代码如下:

class Top
{
    public Top(Foo foo, Bar bar)
    {
        Foo = foo;
        Bar = bar;
        foo.Bar = Bar;
        Bar.Foo = Foo;
    }
    public Foo Foo;
    public Bar Bar;
}
class Foo(Bar bar)
{
    public Bar Bar = bar;
}
class Bar(Foo foo)
{
    public Foo Foo = foo;
}
方案结论

目前不存在仅用8字节就能安全实现该目标的方案,核心原因如下:

  1. GC移动的本质限制:托管堆中的数组会被GC移动位置,要稳定引用数组内的元素,必须同时持有数组的根引用和元素的偏移量——这两个信息在x86_64平台下正好需要16字节(IntPtr类型的数组引用 + nint类型的偏移量),和Memory<T>的内部结构完全一致,无法压缩到8字节。
  2. 托管堆无特殊对象支持:托管堆中不存在永不移动且不阻碍GC的对象,任何通过GCHandleType.Pinned固定的对象都会让GC无法压缩对应内存区域,长期持有必然影响GC性能。
  3. 值类型装箱的局限性:数组中的值类型无法单独装箱(装箱操作会生成独立的堆对象,和原数组元素无关联),无法通过Unsafe.Unbox<>()关联回原数组元素。

若必须追求8字节存储,唯一路径是使用非托管堆分配,但这会大幅增加复杂度,需要手动管理内存生命周期,且必须配合大量unsafe代码,不符合优先安全C#代码的需求。

内容的提问来源于stack exchange,提问作者patvax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 22:42:46