Search code examples
c#memorytextreader

C# Memory to TextReader


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?


Solution

  • 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!