4

I have following struct:

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

and I want to send it over the wire, without allocations (using SendAsync(ReadOnlyMemory<byte>)) call.

How can I get the Memory<byte> from given struct?

I ended up in having Span<byte>, and then got stuck.

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

Is there any way how to put the struct directly into stream without any allocations?

I'm on netcoreapp2.1.

nothrow
  • 15,882
  • 9
  • 57
  • 104
  • Spans can only live on the stack. An asynchronous method needs its parameter to live at least as long as the task itself. Therefore, how can you ensure that the span does live to the end of the task? You can't, hence why those method use Memory, which can live longer. Which means you'll need to copy it anyway. – Etienne de Martel Oct 15 '18 at 22:07
  • @EtiennedeMartel my `message` is captured in the AsyncStateMachine anyway - it is not `ref struct`. – nothrow Oct 15 '18 at 22:11
  • Anyway, the title is misleading. I'll update it. – nothrow Oct 15 '18 at 22:11

1 Answers1

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

Note I'm using Span<T> here. You can do mostly the same stuff with Memory<T> if needed - but you need a backing array or similar, which will usually require an allocation - if not of the array, then of a custom 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

Essentially, you are very close here:

MemoryMarshal.CreateReadOnlySpan(ref message, 1)

The trick, though, is to use MemoryMarshal.Cast<TFrom, byte>(...) to get a a span of bytes.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Nice, but is there any way to do the same if `Message` contains a reference or pointer like field? let's say a `byte[] data` – joe Mar 04 '19 at 03:26
  • @joe you can convert ref-containing data to bytes (some of which will be basically meaningless - i.e. the references/pointers), but you can't go back again - basically, the runtime and CLR **really** doesn't want you creating non-zero references that are invalid - it would violate an entire family of assumptions. You of course *can* do it with unsafe code, but .... please don't – Marc Gravell Mar 05 '19 at 12:44
  • ♦ Why is it meaningless? In many case I have a struct with `int` and `byte[]` `object` inside and I want a Memory from it (the memory should keept the reference/pointer instead of the data itself). But I can't do ref->data bytes in this case.[Cast](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal.cast?view=netcore-2.2) does not allow me to do so. It cannot cotain reference or pointers. I understand that we shouldn't go back. But why do we have this go forward limit? – joe Mar 06 '19 at 03:41
  • 1
    @joe references are just pointers with semantic meaning, but... still just random-ish numbers that will change each run *or during runs if the GC chooses*, and cannot be usefully interpreted; here's the code that shows doing exactly this, so it is possible - just... not useful: https://gist.github.com/mgravell/0914818c0f1db594a5e75c631128fe77 – Marc Gravell Mar 06 '19 at 12:07
  • 1
    @joe note I did have to drop to `unsafe` code to get back as a span; you *can* do it without, though - https://gist.github.com/mgravell/1e65d60e8c21b0de18137fa39688fe25 – Marc Gravell Mar 06 '19 at 12:10