获取结构体上的 Span<byte> 而不复制该结构体

Row*_*ith 8 c# system.io.pipelines

我一直在尝试作为System.IO.PipelinesSpan<T>的一部分。ReadOnlySequence<T>

我目前正在尝试在不使用代码且不复制该内容的情况下获得Span<T>over astructunsafestruct

我的结构很简单:

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct Packet
    {
        public byte TestByte;
    }
Run Code Online (Sandbox Code Playgroud)

方法 1 - 有效 - 但感觉“不安全”

    //
    // Method 1 - uses Unsafe to get a span over the struct
    //
    var packet = new Packet();
    unsafe
    {
        var packetSpan = new Span<byte>(&packet, Marshal.SizeOf(packet));

        packetSpan[0] = 0xFF; // Set the test byte
        Debug.Assert(packet.TestByte == 0xFF, "Error, packetSpan did not update packet.");
            // ^^^ Succeeds
        packet.TestByte = 0xEE;
        Debug.Assert(packetSpan[0] == 0xEE, "Error, packet did not update packetSpan.");
            // ^^^ Succeeds
    }
Run Code Online (Sandbox Code Playgroud)

方法 2 - 由于需要副本,因此无法按预期工作

    //
    // Method 2
    //
    // This doesn't work as intended because the original packet is actually
    // coppied to packet2Array because it's a value type
    //
    // Coppies the packet to an Array of Packets
    // Gets a Span<Packet> of the Array of Packets
    // Casts the Span<Packet> as a Span<byte>
    //
    var packet2 = new Packet();

    // create an array and store a copy of packet2 in it
    Packet[] packet2Array = new Packet[1];
    packet2Array[0] = packet2;

    // Get a Span<Packet> of the packet2Array
    Span<Packet> packet2SpanPacket = MemoryExtensions.AsSpan<Packet>(packet2Array);

    // Cast the Span<Packet> as a Span<byte>
    Span<byte> packet2Span = MemoryMarshal.Cast<Packet, byte>(packet2SpanPacket);

    packet2Span[0] = 0xFF; // Set the test byte
    Debug.Assert(packet2.TestByte == 0xFF, "Error, packet2Span did not update packet2");
        // ^^^ fails because packet2 was coppied into the array, and thus packet2 has not changed.
    Debug.Assert(packet2Array[0].TestByte == 0xFF, "Error, packet2Span did not update packet2Array[i]");
        // ^^^ succeeds

    packet2.TestByte = 0xEE;
    Debug.Assert(packet2Span[0] == 0xEE, "Error, packet2 did not update in packet2Span");
        // ^^^ fails because packet2Span is covering packet2Array which has a copy of packet2 
    packet2Array[0].TestByte = 0xEE;
    Debug.Assert(packet2Span[0] == 0xEE, "Error, packet2 did not update in packet2Span");
        // ^^^ succeeds
Run Code Online (Sandbox Code Playgroud)

进一步的研究表明

Span<T>可以从 a 隐式转换byte[],例如,我可以这样做

Span<byte> packetSpan = new Packet().ToByteArray();
Run Code Online (Sandbox Code Playgroud)

但我目前拥有的 ToByteArray() 的任何实现仍在制作 Packet 结构的副本。

我不能做一些类似的事情:

Span<byte> packetSpan = (byte[])packet;
    // ^^ Won't compile
Run Code Online (Sandbox Code Playgroud)

V0l*_*dek 4

如果没有 ,则无法Span<byte>在任意结构上获取 a unsafe,因为这样的跨度将允许您以任何方式更改结构的任何位,可能会违反类型的不变量 - 这本质上是不安全的操作。

好吧,但是呢ReadOnlySpan<byte>?请注意,您必须将 放在StructLayoutAttribute结构上才能使代码合理。这应该是一个提示。想象一下尝试编写一种更简单的方法,该方法byte[]为任意任意值返回 a T where T : struct。你必须找出struct第一个的大小,不是吗?struct那么,如何在 C# 中找到 a 的大小呢?您可以使用sizeof运算符,它需要unsafe上下文并且需要结构是非托管类型;或者你也可以Marshall.SizeOf,这是不稳定的,仅适用于具有顺序或显式字节布局的结构。没有安全、通用的方法,因此你不能这样做。

Span<T>ReadOnlySpan<T>设计并不是为了访问结构字节,而是为了跨越数组片段,这些片段具有已知的大小并保证是连续的。

如果您确信自己知道自己在做什么,那么您就可以在特定unsafe环境中执行此操作 - 这就是它的用途。但请注意,unsafe由于上述原因,您的解决方案不能推广到任意结构。

如果您打算将结构用作 IO 操作的缓冲区,您可能需要研究固定大小缓冲区。它们还需要unsafe上下文,但您可以将不安全性封装在结构中并将 a 返回Span<byte>到该固定缓冲区。基本上任何处理内存中对象字节结构的东西都需要unsafe在 .NET 中,因为内存管理就是这个“安全”所指的东西。