如何从只读结构获取ReadOnlySpan <byte>?

时间:2019-11-24 17:27:47

标签: c# .net-core

以下代码使编译器在属性getter的第一行中引发错误CS1605(“因为它是只读的,所以无法将'var'作为ref或out参数传递”)。

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MyStruct
{
    public readonly int Field1;
    public readonly int Field2;

    public MyStruct(int field1, int field2) => (Field1, Field2) = (field1, field2);

    public ReadOnlySpan<byte> Span
    {
        get
        {
            // This code only works when MyStruct is not read only
            ReadOnlySpan<MyStruct> temp = MemoryMarshal.CreateReadOnlySpan(ref this, 1);
            return MemoryMarshal.Cast<MyStruct, byte>(temp);
        }
    }
}

readonly行中删除public readonly struct MyStruct可以使代码正常工作,但是出于性能原因,我真的希望结构为readonly。与必须始终以ref传递结构相比,它使代码更加简洁。

是否可以从只读结构中获取ReadOnlySpan<byte>

编辑: ... ...而不创建结构的隐式或显式副本?

4 个答案:

答案 0 :(得分:5)

看起来像这样:

// The following code will work from C# 7.3 and up, no unsafe keyword required
Span<MyStruct> span = stackalloc MyStruct[1];
span[0] = new MyStruct(3, 4);
var bytes = MemoryMarshal.Cast<MyStruct, byte>(span);

如果我们想将其公开为属性,可以尝试以下操作:

// Will not work at runtime
public ReadOnlySpan<byte> Span
{
    get
    {
        unsafe
        {
            fixed (MyStruct* ptr = &this)
            {
                return new ReadOnlySpan<byte>(ptr, sizeof(MyStruct)); // If on the heap, we're doomed as returning will unpin the memory.
            }
        }
    }
}

并将结构标记为readonly ref struct,这将再次保护我们曾经在堆上使用的结构。这会编译,但不会在运行时得到AccessViolationException时运行。我将做更多的挖掘工作,看是否可行,从逻辑上讲应该是安全的,但今天可能是不可能的。

另一种折衷解决方案是将其保留为readonly struct(而不是ref struct)并添加此静态方法:

public static unsafe ReadOnlySpan<byte> GetSpan(ref MyStruct myStruct)
{
    return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref myStruct), sizeof(MyStruct));
}

然后从调用代码开始:

var myStruct = new MyStruct(1, 2);
var span = MyStruct.GetSpan(ref myStruct);

我们可以通过将其移到ref扩展方法(C#7.2功能)中来改进此方法的使用:

class Program
{
    static void Main()
    {
        var myStruct = new MyStruct(1, 2);
        var span = myStruct.GetSpan();
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MyStruct
{
    public readonly int Field1;
    public readonly int Field2;

    public MyStruct(int field1, int field2) => (Field1, Field2) = (field1, field2);
}

public static class MyStructExtensions
{
    public static unsafe ReadOnlySpan<byte> GetSpan(ref this MyStruct myStruct)
    {
        return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref myStruct), sizeof(MyStruct));
    }
}

答案 1 :(得分:0)

下面的代码不安全。

请参阅Stuart评论:

  

这样做非常不安全,因为您不知道结构在哪里   生活(堆栈还是堆),如果您将其作为引用结构,则可以   保证它只是堆栈,并且无需假设即可安全地进行操作   您已经被一些不安全的代码预先锁定了。

MemoryMarshal.CreateReadOnlySpan方法has a warning too

现在是代码

您可以分两步实现。

  • 将结构放入span<T>中,其中T是结构的类型。
  • span<T>投射到span<byte>

    var s = new MutableStruct();
    var span = MemoryMarshal.CreateReadOnlySpan(ref s, 1);
    var byteSpan = MemoryMarshal.Cast<MutableStruct, byte>(span);
    
    Console.WriteLine(string.Join("", byteSpan.ToArray().Select(v => $"{v:X2}"))); // 00000000
    
    s.SetX(42);
    
    Console.WriteLine(string.Join("", byteSpan.ToArray().Select(v => $"{v:X2}"))); // 2A0000000
    
    Console.ReadKey();
    
    // here the used struct
    struct MutableStruct
    {
        private int _x;
        public int X => _x;
        public void SetX(int value) => _x = value;
    }
    

您可以here对其进行测试

答案 2 :(得分:0)

如果查看MemoryMarshal.CreateReadOnlySpan的源代码,您会发现它只是通过传递 reference 和<而创建了 ReadOnlySpan 的新实例。 em> length 参数。

简单的解决方案是通过传递数组自己创建ReadOnlySpan实例:

ReadOnlySpan<MyStruct> temp = new ReadOnlySpan<MyStruct>(new MyStruct[] { this });

这将使用不同的ctor,但是实现方式相似,您将获得相同的结果。

编辑:

或使用不安全的方法:

public ReadOnlySpan<byte> Span
{
    get
    {
        unsafe
        {

            return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref this), 1);
        }
    }
}

答案 3 :(得分:0)

仅在此处添加通用解决方案,基于之前的答案:

public class StructExtensions
{
    public static unsafe ReadOnlySpan<byte> AsSpan<T>(ref T myStruct) where T : struct
    {
        return MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref myStruct, 1));
    }
}

你确实像这样使用它:

var p = StructExtensions.AsSpan<MyStruct>(ref this /* or whatever your variable is */);