将 stackalloc'ed Span<T> 和 by-ref 结构作为参数传递

Passing both stackalloc'ed Span<T> and by-ref struct as arguments

我正在使用 SequenceReader<T> 将内容从 ReadOnlySequence<T> 复制到不同的 Span<T>。但是,当我尝试将复制逻辑封装到一个单独的函数中并希望使用在堆栈上分配的 Span<T> 来调用它时,出现编译器错误。

var sequence = new ReadOnlySequence<byte>(new byte[20]); // Source data to copy

Span<byte> heap = new byte[10]; // Target location 1
Span<byte> stack = stackalloc byte[10]; // Target location 2

var reader = new SequenceReader<byte>(sequence);

TryReadInto(ref reader, heap); // This is fine
TryReadInto(ref reader, stack); // Gives compile time error CS8350

由于 SequenceReader<T> 上的实例方法 TryCopyTo 不会推进 reader,我创建了一个更高级别的函数来处理这个问题:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool TryReadInto(ref SequenceReader<byte> reader, Span<byte> destination)
{
    if (reader.TryCopyTo(destination)) {
        reader.Advance(destination.Length);
        return true;
    }

    return false;
}

只要目标是堆分配的,一切都可以正常工作。但是,当使用 stackalloc 分配目标时,编译器会报错 CS8350:这种参数组合是不允许的,因为它可能会在声明范围 之外公开参数引用的变量。

当我没有将 reader 作为参考传递时,编译器错误消失了,但需要将 reader 作为参考传递,因为 SequenceReader<T> is a mutable struct 并且位置在函数内提前。

我收到编译器错误。参数 reader 可以在较早的堆栈帧中分配,并且作为类似 ref 的结构将能够将 Span<T> 作为字段。方法体可以使第二个(堆栈分配的)参数 destination 逃脱它自己的堆栈帧,因此同时使用 ref 参数和堆栈分配的 Span<T> 的函数调用是非法的在 C# 中。

但是,我如何封装我的逻辑呢? SequenceReader<T> 是一个封闭类型,所以我不能只添加一个实例方法。当我只是手动内联函数调用时,我没有收到编译器错误。我猜编译器认为 reader 分配在与堆栈分配的 Span<byte> 相同的堆栈帧中。按值传递 reader 不是一个选项,因为 SequenceReader<T> is a mutable struct.

您已经看到编译器试图阻止的问题。

如果我将您所描述的问题翻译成 C#,代码可能如下所示:

// This is a struct lives on stack
ref struct SomeStructOnStack
{
    public Span<byte> Something;
}

// This method saves "stack" to the "obj"
static void SomeMethod(ref SomeStructOnStack obj, Span<byte> stack)
{
    obj = new SomeStructOnStack
    {
        Something = stack
    };
}

// This method accepts byref "obj" from its caller
// In such case, "stack" will escape its scope
// This is what CS8350 is for
void Test(ref SomeStructOnStack obj)
{
    Span<byte> stack = stackalloc byte[20];
    SomeMethod(ref obj, stack);
}

ref structstack 位于同一堆栈帧中时,我同意你的看法,我们不应该看到编译器错误。但目前不支持。

要解决此问题,您可以像这样创建 Span<byte> 的副本:

Span<byte> stack = stackalloc byte[20];

unsafe
{
    fixed (byte* pStack = stack)
    {
        var copy = new Span<byte>(pStack, stack.Length);
        TryReadInto(ref reader, copy);
    }
}