Search code examples
c#memoryparametersunsafestackalloc

What happens when I assign a `ref` value to an `out` argument in C#?


I am writing an class that retrieves binary data and supports generically converting it into primitive types. It's supposed to be as efficient as possible. Here is what it looks like right now:

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 = default;
        // 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;
    }
}

Since I'm working with large amounts of memory, and my code is going to be performing a lot of small reading operations. I want to minimize the amount of times the memory is copied around.

The ReadBytes implementation will either a) create a span across a part an already existing array on the heap, or b) stackalloc a buffer and fill it with data from a remote source (depending on the data I'll be working with). The point is, it will not be allocating anything on the heap by itself.

I want my TryRead<T> method to return a typed reference to the span's memory, rather than copy that memory into a new value, and I want to know if that's possible. I've noticed that I can't assign a ref value to an out argument without initializing it, but I can after, which makes little sense if we assume that I'm assigning a reference.

I guess what I'm asking is, what's really going on in this code? Am I returning a reference to an existing value, or is that value being copied into a new stack-allocated one? And how would the behavior be different with stack-allocated and heap-allocated spans? Would GC know to update the reference of type T when moving the data, in case of a heap-allocated span being used?


Solution

  • Primitive types are value types anyway, so you shouldn't worry about allocating on the heap when reading them.

    You cannot use stackalloc for this code, because you can't (or shouldn't try to) return a pointer to it, as it will be destroyed at the end of the function.

    The code you have so far is dangerous, because you are returning a pinnable reference which is not actually pinned.

    The reason you are having problems with the ref parameter is because in the else you are not assigning it at all. You should move the result = default; line into the else branch.


    Either way, you are far better off using MemoryMarshal for all of this, note that this does not require unsafe code

    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 = default;
        return false;
    }