[英]Obtain a Span<byte> over a struct without making a copy of the struct
我一直在试验Span<T>
作为ReadOnlySequence<T>
和 System.IO.Pipelines 的一部分。
我目前正在尝试在struct
上获取Span<T>
而不使用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
您必须在不安全的环境中执行此操作,因为从这个词的真正含义来看,这是不安全的,因为如果您不够小心,您会撞到自己的脚。 原因如下:
考虑以下代码:
Span<byte> GiveMeSpan()
{
MyLovelyStruct value = new MyLovelyStruct();
unsafe
{
return new Span<byte>(&value, sizeof(MyLovelyStruct));
}
}
该实例MyLovelyStruct
我们在创建GiveMeSpan()
住在方法的调用栈以及您做的是它的地址,给它的Span<byte>
,并返回Span<byte>
。 一旦一个方法返回,它就会弹出它的堆栈帧,因此你的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()
方法正在处理您的MyLovelyStruct
和MyLovelyClass
突然被在存储器中(是的,在存储器GC移动的物体,搬到这里阅读)? 是的,指向MyLovelyStruct
Span<byte>
将不再指向新的MyLovelyStruct
地址,并且您的程序会损坏。
因此,为了使用Span<byte>
或任何其他指针类型安全地包装struct
,您必须确保:
因此,需要使用unsafe
关键字,即使您可以绕过它,也需要您向代码的读者发出警告。
如果没有unsafe
,就无法在任意结构上获取Span<byte>
,因为这样的跨度将允许您以任何方式更改结构的任何位,可能违反类型的不变量 - 这本质上是不安全的操作。
好的,但是ReadOnlySpan<byte>
呢? 请注意,您必须将StructLayoutAttribute
放在您的结构上才能使您的代码合理。 这应该是一个提示。 想象一下尝试编写一个更简单的方法,该方法为任意T where T : struct
返回一个byte[]
, T where T : struct
。 你必须先找出struct
的大小,不是吗? 那么,如何在 C# 中找出struct
的大小? 您可以使用sizeof
运算符,它需要一个unsafe
上下文并且需要结构是非托管类型; 或者你可以Marshall.SizeOf
它是不稳定的并且仅适用于具有顺序或显式字节布局的结构。 没有安全的通用方法,因此您不能这样做。
Span<T>
和ReadOnlySpan<T>
的设计并不是为了访问结构字节,而是为了跨越数组的片段,这些片段具有已知的大小并保证是顺序的。
如果您确信自己知道自己在做什么,那么您可以在unsafe
环境中这样做——这就是它的用途。 但请注意,出于上述原因,您的unsafe
解决方案不能推广到任意结构。
如果您打算将结构用作 IO 操作的缓冲区,则可能需要查看固定大小的缓冲区。 它们还需要一个unsafe
上下文,但您可以将unsafe
封装在您的结构中并将Span<byte>
返回到该固定缓冲区。 基本上任何处理内存中对象字节结构的东西都需要在 .NET 中unsafe
,因为内存管理就是这个“安全”所指的东西。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.