Передавая в качестве аргументов как stackallo c 'ed Span <T>, так и структуру by-ref - PullRequest
1 голос
/ 23 апреля 2020

Я использую SequenceReader<T> для копирования содержимого из ReadOnlySequence<T> в другие Span<T>. Однако я получаю ошибку компилятора, когда пытаюсь инкапсулировать копию logi c в отдельную функцию и хочу вызвать ее с 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

Поскольку метод экземпляра TryCopyTo на SequenceReader<T> не продвигает читателя, я создал функцию более высокого уровня, которая позаботится об этом:

[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: эта комбинация аргументов to запрещена, поскольку она может предоставлять переменные, на которые ссылается параметр, за пределами области их объявления .

Когда я не передаю читателю ссылку, ошибка компилятора исчезает, но читателю необходимо передать его как ссылку, так как SequenceReader<T> является изменяемой структурой , и позиция продвигается внутри функции.

Я получаю ошибку компилятора. Параметр reader может быть размещен в более раннем стековом кадре, и в качестве ref-like структуры будет возможно иметь Span<T> в качестве поля. Тело метода может заставить второй (выделенный стеком) параметр destination экранировать свой собственный кадр стека, таким образом, вызов функции как с параметром ref, так и с выделенным стеком Span<T> недопустим в C#.

Тем не менее, как я могу инкапсулировать мои логи c тогда? SequenceReader<T> является закрытым типом, поэтому я не могу просто добавить метод экземпляра. Когда я просто включаю вызов функции вручную, я не получаю ошибок компилятора. Я предполагаю, что компилятор видит, что считыватель расположен в том же кадре стека, что и выделенный стек Span<byte>. Передача считывателя по значению не является опцией, поскольку SequenceReader<T> является изменяемой структурой .

1 Ответ

1 голос
/ 23 апреля 2020

Вы видели проблему, которую компилятор пытается предотвратить.

Если я переведу то, что вы описали в вопросе, в 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 struct находится в том же фрейме стека, где stack, мы не должны видеть ошибку компилятора. Но в настоящее время это не поддерживается.

Чтобы обойти эту проблему, вы можете создать копию 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);
    }
}
...