将结构体转换为其ReadOnlyMemory<byte>表示形式

4

我有以下结构体:

[StructLayout(LayoutKind.Sequential)]
struct Message
{
     int Header;
     int Data;
}

我希望将其通过无需分配内存的方式(使用SendAsync(ReadOnlyMemory<byte>)调用)发送到网络中。

我该如何从给定的结构体中获取Memory<byte>

最终我得到了Span<byte>,但却陷入了困境。

var message = new Message {
  Header = 1, Data = 3
};
var bytes = MemoryMarshal.AsBytes(
    MemoryMarshal.CreateReadOnlySpan(ref message, 1)
);

有没有办法在不进行任何分配的情况下,直接将结构体放入流中?

我使用的是 netcoreapp2.1


Spans 只能存在于堆栈上。异步方法需要其参数至少与任务本身一样长寿。因此,如何确保 span 存活到任务结束?你不能,这就是为什么那些方法使用 Memory<T> 的原因,它可以存活更久。这意味着你仍然需要复制它。 - Etienne de Martel
@EtiennedeMartel 我的 message 已经在 AsyncStateMachine 中被捕获了 - 它不是 ref struct - nothrow
无论如何,标题有误导性。我会更新它。 - nothrow
1个回答

4

Something like:

Span<Message> valSpan = stackalloc Message[1];
valSpan[0] = new Message { Header = 123, Data = 456 };
Span<byte> bytes = MemoryMarshal.Cast<Message, byte>(valSpan); // has length 8

请注意,我在这里使用了 Span<T>。如果需要,您可以使用 Memory<T> 完成大部分相同的操作 - 但是��需要一个支持数组或类似结构的后备存储器,这通常需要一些分配 - 如果不是数组的分配,那么就是自定义 MemoryManager<T> 的分配:

var arr = new Message[1];
arr[0] = new Message { Header = 123, Data = 456 };
Memory<byte> bytes = MemoryMarshal.Cast<Message, byte>(arr); // has length 8

基本上,你已经非常接近了:
MemoryMarshal.CreateReadOnlySpan(ref message, 1)

技巧在于使用MemoryMarshal.Cast<TFrom, byte>(...)来获取字节跨度。


不错,但如果“Message”包含引用或指针字段,有没有办法做相同的事情?比如说一个“byte[] data”。 - joe
@joe,你可以将包含引用的数据转换为字节(其中一些基本上是无意义的 - 即引用/指针),但你不能再次返回 - 基本上,运行时和CLR 真的不希望您创建无效的非零引用 - 这将违反整个假设族。当然,你可以使用不安全的代码来实现它,但是……请不要这样做。 - Marc Gravell
为什么没有意义呢?在很多情况下,我有一个包含“int”和“byte[]”“object”的结构体,我想从中获得内存(内存应该保留引用/指针而不是数据本身)。但在这种情况下,我不能进行ref->data字节操作。Cast不允许我这样做。它不能包含引用或指针。我知道我们不应该退回。但是为什么我们有这个前进的限制呢? - joe
1
@joe 引用只是具有语义意义的指针,但仍然只是随机的数字,在每次运行时或者在GC选择时都会改变,并且无法被有用地解释; 这里是展示如何做到这一点的代码,所以它是可能的 - 只是...没有用: https://gist.github.com/mgravell/0914818c0f1db594a5e75c631128fe77 - Marc Gravell
1
@joe 注意,我确实不得不转换为unsafe代码才能回到一个span;但是你可以不这样做-https://gist.github.com/mgravell/1e65d60e8c21b0de18137fa39688fe25 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接