c# - 在结构上获取 Span<byte> 而不复制结构

标签 c# system.io.pipelines

我一直在试验 Span<T>作为 ReadOnlySequence<T> 的一部分和 System.IO.Pipelines。

我目前正在尝试获取 Span<T>超过 struct不使用 unsafe代码,而不复制该 struct .

我的结构很简单:

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

方法 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
    }

方法 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

进一步的研究表明
Span<T>可以从 byte[] 隐式转换,例如,我可以做
Span<byte> packetSpan = new Packet().ToByteArray();

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

我不能做一些类似的事情:
Span<byte> packetSpan = (byte[])packet;
    // ^^ Won't compile

最佳答案

您必须在 unsafe context 中执行此操作因为它是 不安全 根据这个词的真正含义,因为如果你不够小心,你会射中自己的脚。原因如下:

考虑以下代码:

Span<byte> GiveMeSpan() 
{
    MyLovelyStruct value = new MyLovelyStruct();
    unsafe 
    {
        return new Span<byte>(&value, sizeof(MyLovelyStruct));
    }
}
MyLovelyStruct的实例我们在 GiveMeSpan() 中创建的住在方法的call stack而你所做的就是获取它的地址,把它交给 Span<byte> ,并返回 Span<byte> .一旦一个方法返回,它就会弹出它的 stack frame ,因此您的MyLovelyStruct的内存生活在将成为免费的,并且可能会被调用者调用的下一个方法回收并破坏它。

但这还不是全部,如果您的 MyLovelyStruct 怎么办?生活在这样的对象字段中:
class MyLovelyClass 
{
    private MyLovelyStruct value;

    public void Foo() 
    {
        unsafe 
        {
            var span = new Span(&value, sizeof(MyLovelyStruct));
            Process(span);
        }
    }
}

// Declaration 
Process(Span<byte> span);

还有一个 GC发生在 Process()方法正在处理您的 MyLovelyStructMyLovelyClass突然在内存中移动(是的,GC 移动内存中的对象,read here)?是的,您的 Span<byte>指向 MyLovelyStruct将不再指向新的 MyLovelyStruct地址并且您的程序已损坏。

所以为了安全地包装一个struct使用 Span<byte>或任何其他指针类型,您必须确保:
  • 该实例位于固定的内存位置(例如在堆栈或非托管内存中,例如由 Marshal.AllocHGlobal 分配的内存块)
  • 在您完成指针
  • 之前,不会声明实例内存。

    所以unsafe关键字是必需的,即使您可以绕过它,您也有责任向代码的读者发出警告。

    关于c# - 在结构上获取 Span<byte> 而不复制结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59596364/

    相关文章:

    c# - 从 PipeReader 读取超时

    c# - 如何使用 System.IO.Pipelines 包创建响应 TCP 监听器?

    c# - 在不计算重复项的情况下计算字符串中子字符串的出现次数

    c# - 脚本任务 C# 中的命令超时

    c# - 取消选中 Checkbox 不会触发 OnCheckedChanged

    c# - 如何使用 PipeReader 读取 JSON 平面文件?

    c# - 应用程序中的等待事件

    c# - 这是其中一个循环引用吗?

    c# - TCP 服务器的 IDuplexPipe 的示例实现