Что происходит, когда я присваиваю значение » ref «аргументу» out » в C#?

#c# #memory #parameters #unsafe #stackalloc

Вопрос:

Я пишу класс, который извлекает двоичные данные и поддерживает общее преобразование их в примитивные типы. Предполагается, что это должно быть максимально эффективно. Вот как это выглядит прямо сейчас:

 public abstract class MemorySource {
    public abstract Span<byte> ReadBytes(ulong address, int count);

    public unsafe bool TryRead<T>(ulong address, out T result) where T : unmanaged {
        Span<byte> buffer = ReadBytes(address, sizeof(T));
        result = defau<
        // If the above line is commented, `result = ref <...>` won't compile, showing CS0177.
        if (!buffer.IsEmpty) {
            result = ref Unsafe.As<byte, T>(ref buffer.GetPinnableReference());
            return true;
        } else
            return false;
    }
}
 

Так как я работаю с большим объемом памяти, и мой код будет выполнять множество небольших операций чтения. Я хочу свести к минимуму количество раз, когда память копируется.

ReadBytes Реализация либо а) создаст промежуток между частью уже существующего массива в куче, либо б) stackalloc создаст буфер и заполнит его данными из удаленного источника (в зависимости от данных, с которыми я буду работать). Дело в том, что он сам по себе ничего не будет выделять в куче.

Я хочу, чтобы мой TryRead<T> метод возвращал типизированную ссылку на память span, а не копировал эту память в новое значение, и я хочу знать, возможно ли это. Я заметил, что не могу присвоить ref значение out аргументу без его инициализации, но я могу это сделать после, что имеет мало смысла, если предположить, что я назначаю ссылку.

Я предполагаю, что я спрашиваю, что на самом деле происходит в этом коде? Возвращаю ли я ссылку на существующее значение или это значение копируется в новое, выделенное стеком? И как будет отличаться поведение с распределенными по стеку и распределенными по куче промежутками? Будет ли GC знать, чтобы обновить ссылку типа T при перемещении данных в случае использования выделенного для кучи промежутка?

Ответ №1:

Примитивные типы в любом случае являются типами значений, поэтому вам не следует беспокоиться о выделении в куче при их чтении.

Вы не можете использовать stackalloc этот код, потому что вы не можете (или не должны пытаться) возвращать указатель на него, так как он будет уничтожен в конце функции.

Код, который у вас есть до сих пор, опасен, потому что вы возвращаете доступную ссылку, которая на самом деле не закреплена.

Причина, по которой у вас возникли проблемы с ref параметром, заключается в том, что else вы его вообще не назначаете. Вы должны переместить result = defau< линию в else ответвление.


В любом случае, вам гораздо лучше использовать MemoryMarshal все это, обратите внимание, что для этого не требуется небезопасный код

 public bool TryRead<T>(ulong address, out T result) where T : unmanaged
{
    ReadOnlySpan<byte> buffer = ReadBytes(address, sizeof(T));
    if (!buffer.IsEmpty)
    {
        result = MemoryMarshal.Read<T>(buffer);
        return true;
    }
    result = defau<
    return false;
}
 

Комментарии:

1. Хм, да, я только что попытался отладить этот код с помощью фиктивного чтения, и , хотя промежуток был правильно инициализирован ReadBytes , все содержимое промежутка 0xFF было за его пределами (я полагаю, что это то, что раньше было стеком метода), так что, похоже, то, о чем я думаю, невозможно… Думаю, мне тоже придется сделать TryRead абстракцию, чтобы наследующие классы отвечали за минимизацию операций копирования. Спасибо!

2. Предположительно вы используете Span то, что было выделено в стеке. Как уже упоминалось, вы не можете этого сделать, потому что он будет стерт. Это должно быть из byte[] массива

3. Я закончил тем, что абстрагировался void* , чтобы мне больше не приходилось беспокоиться о продолжительности жизни выделенных стеком промежутков.

4. void* также опасно, что вы позволяете указателю выходить из-под контроля сбора мусора. Span есть ли причина, по которой он остается под контролем ГК. Вы могли бы использовать Memory , может быть, с вами было бы проще иметь дело

5. Да, идея заключается в том, что небезопасный код действительно используется только внутри этого класса, и рабочий код может работать с безопасными типами, такими как Span и Memory . Я думаю, что эта новая реализация работает лучше всего: gist.github.com/TheLeftExit/68be0989ffbc77e43fe9c8a36c1b99ed