C# Memory to TextReader

1.2k views Asked by At

What is optimal way to get TextReader instance from a Memory<byte> object?

I could write something like:

using (var stream = new MemoryStream(body.ToArray()))
using (var reader = new StreamReader(stream))
{
}

but maybe there is a better way?

1

There are 1 answers

5
aepot On BEST ANSWER

StreamReader will dispose underlying Stream automatically.

#1 The simpliest way

Memory<byte> memory = GetSomeData();
using TextReader reader = new StreamReader(new MemoryStream(memory.ToArray()));
// some code

But here you're copying whole memory content into another array, it's memory-consuming and gives Garbage Collector more work. It's not recommended especially if array contains large amount of data.

There's another way of doing it without allocation of new array.

#2 The optimal way (recommended to save memory)

Memory<byte> memory = GetSomeData();
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
{
    using TextReader reader = new StreamReader(new MemoryStream(segment.Array, segment.Offset, segment.Count));
    // some code
}

In other words ArraySegment returns the source memory area as array.

Tests

Here's an example to play with it (based on .NET Core 3.1 Console Application).

class Program
{
    static void Main(string[] args)
    {
        string text = "Hello World!";
        byte[] data = Encoding.UTF8.GetBytes(text);
        Memory<byte> memory = data;

        byte[] data1 = memory.ToArray();
        Console.WriteLine("data == data1: {0}", data == data1);
            
        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
        {
            byte[] data2 = segment.Array;
            Console.WriteLine("data == data2: {0}", data == data2);
        }

        Console.WriteLine();

        Console.WriteLine("Test 1");
        Test1(text);

        Console.WriteLine();

        Console.WriteLine("Test 2");
        Test2(text);

        Console.ReadKey();
    }

    private static void Test1(string text)
    {
        Memory<byte> memory = Encoding.UTF8.GetBytes(text);
        byte[] data = memory.ToArray();
        ReadItTwice(memory, data);
    }

    private static void Test2(string text)
    {
        Memory<byte> memory = Encoding.UTF8.GetBytes(text);
        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
        {
            byte[] data = segment.Array;
            ReadItTwice(memory, data);
        }
    }

    private static void ReadItTwice(Memory<byte> memory, byte[] data)
    {
        using MemoryStream ms = new MemoryStream(data);
        using TextReader sr = new StreamReader(ms);
        Console.WriteLine("Before change: {0}", sr.ReadToEnd());
        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment))
            segment.Array[0] = (byte)'_'; // change first symbol
        ms.Position = 0;
        Console.WriteLine("After change: {0}", sr.ReadToEnd());
    }
}

Output

data == data1: False
data == data2: True

Test 1
Before change: Hello World!
After change: Hello World!

Test 2
Before change: Hello World!
After change: _ello World!