给定对结构的托管引用,如何获取对偏移量字段的托管引用?

Given a managed reference to a struct, how to obtain a managed reference to a field at offset?

我正在尝试在 C# 中实现固定大小的数组,这些数组由 hacky 类型级数字参数化。参见 GitHub

我想实现一个方法,它将像这样工作:

static ref T Ref(this ref TContainer container, int index)
  where T: unmanaged
  where TContainer: unmanaged
  => ref container[index];

现在的问题是:我知道,TContainer 内部由 N 个 T 类型的字段组成。但它实际上并没有实现索引操作(或任何接口)。

我能做的是:

CheckIndex(index);
fixed (TContainer* self = &container) {
    T* data = (T*)self;
    return ref data[index];
}

这与 unsafe 一起编译,但我有两个问题:

  1. 像这样检索到的托管引用的生命周期是否会被正确跟踪?
  2. 在这种情况下使用 fixed 是否有任何性能成本? (例如 GC 固定、别名问题等)

此外,即使我将参数类型 ref TContainer 替换为 TContainer,上面带有 fixedreturn ref 的示例也会编译,或者,这应该是同样的,只是将其声明为实例成员而不是扩展方法。它甚至可以在示例测试用例中工作,但我不明白为什么会这样,因为在正常情况下,结构实例方法不能 return ref this.field

回答您的问题:

  1. 是的,因为您在固定时将非托管指针重建回托管指针;托管指针在任何时候都不能无效
  2. 不,fixed 使用 very-low 开销机制 - 基本上,fixed 只是将局部变量标记为具有特殊含义;当 GC 运行时,它 已经 遍历 stack-frames,当它这样做时,它会查找这些局部变量并注意它们的值应被视为固定 [=29] =]

但是!听起来您真正追求的概念是 Span<T>(或 Memory<T>)- Span<T> 基本上是使用托管指针的 "range"。

这是一个不使用 unsafe 的版本:

public static ref T Ref<T, TContainer>(this ref TContainer container, int index)
    where TContainer : unmanaged
    where T : unmanaged
{
    if (index < 0 || Unsafe.SizeOf<T>() * (index + 1) > Unsafe.SizeOf<TContainer>())
        throw new ArgumentOutOfRangeException(nameof(index));
    ref T first = ref Unsafe.As<TContainer, T>(ref container);
    return ref Unsafe.Add<T>(ref first, index);
}

请注意,它仍然是 "unsafe"。