Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use StreamReader.ReadAsync(Memory<Char>, CancellationToken)

Tags:

c#

.net-core

I'm trying to use StreamReader.ReadAsync(Memory, CancellationToken) overload that accepts a Memory<T>. Here's the sample code: (.NET Core 2.1)

static async Task Main(string[] args)
{
    string code = "Hello, World";
    var memoryStream = Encoding.UTF8.GetBytes(code.ToCharArray(), 0, code.Length);
    using (var stream = new MemoryStream(memoryStream))
    using (var reader = new StreamReader(stream))
    {
        // var text = await reader.ReadLineAsync();
        // Console.WriteLine(text); -> Outputs: Hello, World

        Memory<char> memory = new Memory<char>();
        await reader.ReadAsync(memory);

        Console.WriteLine("Memory<char> length:" + memory.Length);
    }

}

Code tries to read string content through StreamReader and populate Memory<char>. However, code keeps outputting 0 for Memory<char> length.

What's the proper way to use this method to populate Memory<char> ?

My .csproj file content:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <LangVersion>7.2</LangVersion>
  </PropertyGroup>
</Project>
like image 779
Michael Avatar asked Dec 01 '18 16:12

Michael


1 Answers

The working solution is to initialize memory with a buffer and to care the return value of ReadAsync so you know how many characters (not bytes) you did read.

You did run into the issue that Memory is a struct which always allows you to create an instance with no arguments although from an API usage perspective this is not allowed.

using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace ReadMemory
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            string code = "Hello, World";
            var memoryStream = Encoding.UTF8.GetBytes(code.ToCharArray(), 0, code.Length);
            using (var stream = new MemoryStream(memoryStream))
            using (var reader = new StreamReader(stream))
            {

                Memory<char> memory = new Memory<char>(new char[1024]);  // Init with backing buffer. Otherwise you are trying to read 0 bytes into a zero sized buffer. 
                int charsRead = await reader.ReadAsync(memory);

                Console.WriteLine($"Memory<char> len: {memory.Length}, bytes read: {charsRead}");
            }

        }
    }
}

To answer you second question. Yes you can use the ReadAsync(char[] ...) instead of memory. They are equivalent since under the hood it is passed further as System.Memory anyway which is then when it comes to the actual reading converted to a Span anyway. See

public virtual System.Threading.Tasks.Task<int> ReadAsync(char[] buffer, int index, int count)
{
    return this.ReadAsyncInternal(new Memory<char>(buffer, index, count), default(System.Threading.CancellationToken)).AsTask();
}
like image 135
Alois Kraus Avatar answered Oct 13 '22 19:10

Alois Kraus