Using Span and Memory to read UTF8 from a socket
I have no experience with Span&lt;T&gt; or Memory&lt;T&gt; so the following is just some general considerations on coding etc.



In FakeNetworkStreamRead(...): why have a internal limit of 5 bytes? Why not let the length of the Span&lt;T&gt; argument determine the maximum bytes to read?



Why are you using a Memory&lt;T&gt; as buffer, when reading from the stream?:

Memory&lt;byte&gt; byteMemory = new byte[3];


Why not just a Span, because you only use it as a such and not holding or collecting any data in it:

Span&lt;byte&gt; byteSpan = new byte[3];




IMO you should change the do {} while(...) loop to a while (...) loop, because, if the stream is empty you do a "lot" of work in the first round trip for no reason:

  int read = 0;

  while ((read = FakeNetworkStreamRead(byteSpan)) &gt; 0)
  {
    ...
  }




With the above in mind I've tried to refactor your code to: 

static int FakeNetworkStreamRead(Span&lt;byte&gt; span)
{
  int stop = Math.Min(_position + span.Length, _data.Length);
  int i = 0;

  for (; _position &lt; stop; i++, _position++)
  {
    span[i] = _data[_position];
  }

  return i;
}



static void Main(string[] args)
{
  Span&lt;byte&gt; byteSpan = new byte[3];
  Memory&lt;char&gt; charMemory = new char[1024];

  var charMemoryPos = 0;
  var decoder = Encoding.UTF8.GetDecoder();
  int read = 0;

  while ((read = FakeNetworkStreamRead(byteSpan)) &gt; 0)
  {
    Console.WriteLine($"Read {read} bytes");

    // pass the sliced part of the memory I want to parse, and the part 
    // of the char buffer where I want to write
    decoder.Convert(byteSpan.Slice(0, read),
                    charMemory.Slice(charMemoryPos).Span,
                    false, out int bytesUsed, out int charsUsed, out bool completed);

    // update offsets and counts
    charMemoryPos += charsUsed;
  }

  Console.WriteLine(new string(charMemory.Slice(0, charMemoryPos).Span));
}




In order to flush the decoder state, the do {} while(...) loop is useful anyway:

static void Main(string[] args)
{
  int bufferLength = 3;
  Span&lt;byte&gt; byteSpan = new byte[bufferLength];
  Memory&lt;char&gt; charMemory = new char[1024];

  var charMemoryPos = 0;
  var decoder = Encoding.UTF8.GetDecoder();
  int read = 0;

  do 
  {
    read = FakeNetworkStreamRead(byteSpan);

    decoder.Convert(byteSpan.Slice(0, read),
                    charMemory.Slice(charMemoryPos).Span,
                    read == 0, out int bytesUsed, out int charsUsed, out bool completed);

    Console.WriteLine($"Read {read} bytes -&gt; Used: {bytesUsed} -&gt; Completed: {completed} -&gt; CharUsed: {charsUsed} -&gt; {decoder.FallbackBuffer.Remaining}");

    charMemoryPos += charsUsed;
  } while (read &gt; 0) ;

  Console.WriteLine(new string(charMemory.Slice(0, charMemoryPos).Span);
}


Here the read == 0 argument to decoder.Convert(...) becomes true at the end of the stream, which will flush the state and clears the internal buffer of decoder