1
\$\begingroup\$

I am communicating with a machine that sends/received structured binary data to my netcore service. The machine supplier's new library uses Memory in their library's API.

I have written the following code for Ser/Des which works, but seems verbose. I know I can wrap some of this in methods/helpers, but it feels cumbersome compared to the "old" Streams & BinaryWriter/BinaryReader approach.

Is there a better way to approach this? A better part of the framework or technique I should be using?

Deserialising from Memory:

public void Read(Memory<byte> data)
    {
      if (data.Length < StaticSize) // StaticSize = 56. Defined by machine's struct.
        throw new ArgumentOutOfRangeException($"Span length too small. Minimum: {StaticSize}, Received: {data.Length}.");

      var s = data.Span;
      MemoryMarshal.TryRead<bool>(s.Slice(0, 1), out _forward);
      MemoryMarshal.TryRead<bool>(s.Slice(1, 2), out _reverse);
      SomeString = Encoding.UTF8.GetString(s.Slice(2, 22)).Replace("\0", string.Empty);
      Dynamics = new TcDynamics(data.Slice(24)); // "TcDynamics" object de-serialized in same manner as this.
    }

Serialising to Memory:

 public void Write(Memory<byte> data)
    {
      if (data.Length < StaticSize) // StaticSize = 56. Defined by machine's struct.
        throw new ArgumentOutOfRangeException($"Span length too small. Minimum: {StaticSize}, Received: {data.Length}.");

      var s = data.Span;
      MemoryMarshal.TryWrite<bool>(s.Slice(0, 1), ref _forward);
      MemoryMarshal.TryWrite<bool>(s.Slice(1, 2), ref _reverse);
      Encoding.UTF8.GetBytes(_someString).CopyTo(s.Slice(2,22)); // Strings are fixed length (21 + termination).
      Dynamics.Write(data.Slice(24)); // "TcDynamics" object serialized in same manner as this.
    } 
\$\endgroup\$
  • \$\begingroup\$ Why do you think it is cumbersome? Have you considered SequenceReader and PipeWriter 1, 2 \$\endgroup\$ – Peter Csala Jul 2 at 9:47
  • 1
    \$\begingroup\$ @PeterCsala, when using a binary reader, I could just call something like: myDouble= reader.ReadDouble(); or writer.Write(myDouble); writer.Write(myString, lengthInBytes); I know I can create that myself using Facade pattern to hide away most of the above, but thought there would be a better way somewhere else. I will have a look at your suggestions, thank you. \$\endgroup\$ – Mark Lazz Jul 2 at 14:27
  • \$\begingroup\$ Yes, I agree that those abstractions make the usage more convenient. Memory, Span and ReadOnlySpanwere designed to work mainly with byteand char types. \$\endgroup\$ – Peter Csala Jul 3 at 8:05
  • \$\begingroup\$ If the data structure in data is the same, couldn't you then load a MemoryStream with the data, and then use a `BinaryReader-/Writer as you did before? \$\endgroup\$ – Henrik Hansen Jul 5 at 10:59
  • \$\begingroup\$ @HenrikHansen, I couldn't figure out how to do this without copying data into the Memory Stream. That feels like I'm losing one of the efficiency benefits of Memory<T>. I.e. I'd be creating memory in the stream to copy the memory<> into. Then the read operation would be copying to variable. The last copy is unavoidable, but the interim one I was hoping to avoid with these new classes. have I misunderstood the MemoryStream WriteAsync docs? \$\endgroup\$ – Mark Lazz Jul 5 at 14:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.