How to create a `MemoryMarshal.CreateReadOnlySpan` method that uses `in` instead of `ref` parameter?

Viewed 48

MemoryMarshal.CreateReadOnlySpan has this signature:

public static ReadOnlySpan<T> CreateReadOnlySpan<T> (ref T reference, int length);

Notice that it requires a ref parameter. In other words you can't use it to do this:

[StructLayout(LayoutKind.Sequential, Size = 512)]
struct LargeStruct
{}

class Program
{
    static readonly LargeStruct Blob = default;
    
    static byte GetSomethingFrom(in LargeStruct blob)
    {
        ReadOnlySpan<byte> span = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in blob, 1));
        //                                                                               ^^^^^^^ Argument is 'in' while parameter is declared as 'ref'
        return span[5];
    }

    static void Main()
    {
        Console.WriteLine(GetSomethingFrom(in Blob));
    }
}

Of course I cannot take a ref of Blob because it's a readonly field.

So I'm left with doing something like this:

[StructLayout(LayoutKind.Sequential, Size = 512)]
struct LargeStruct
{}

class Program
{
    static readonly LargeStruct Blob = default;

    static unsafe byte GetSomethingFrom(in LargeStruct blob)
    {
        fixed(LargeStruct* pointer = &blob)
        {
            ReadOnlySpan<byte> span = MemoryMarshal.AsBytes(new ReadOnlySpan<LargeStruct>(pointer, 1));
            return span[5];
        }
    }

    static void Main()
    {
        Console.WriteLine(GetSomethingFrom(in Blob));
    }
}

I'm wondering if it is possible to encapsulate this behavior into a method having the following signature:

public static ReadOnlySpan<T> CreateReadOnlySpan<T> (in T reference, int length);

My attempt

Here is my attempt so far:

using System;
using System.Runtime.CompilerServices;

public static class MemoryMarshal2
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static unsafe ReadOnlySpan<T> CreateReadOnlySpan<T>(in T value, int length)
        where T : unmanaged
    {
        fixed(T* pointer = &value)
        {
            return new ReadOnlySpan<T>(pointer, length);
        }
    }
}

However, I'm not sure this usage of the fixed keyword is appropriate. The documentation says

Pointers to movable managed variables are useful only in a fixed context.

...and yet here I am "leaking" that pointer out of the fixed context (through the returned ReadOnlySpan). And I think the Blob variable in my examples above could be moved by GC?

1 Answers

A (simpler?) solution:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

public static class MemoryMarshal2
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ReadOnlySpan<T> CreateReadOnlySpan<T>(in T value, int length) =>
        MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in value), length);
}

Doesn't require unsafe, and does pretty much exactly what MemoryMarshal.CreateReadOnlySpan does.

Related