Frequently, there is a need to convert a stream into an array of bytes. I came across this problem when uploading files in ASP.NET Core. StackOverflow has certainly a number of answers. In most cases, these answers require copying the array, which is no longer needed. .NET Core 2.0 introduced the concept of Span<T> and in .NET 2.1 we saw the rise of Memory<T>.
Both types enable access to the contiguous regions in memory. Span<T> had one key limitation, it had to be allocated on the stack and consequently could not be used in async operations. Memory<T> is similar but it is allocated on the managed heap and can be used in async operations.
Instead of doing
memoryStreamVar.ToArray() (which under the hood also does a copy), we can be a bit cleverer.
Firstly, we need to allocate an array of bytes equal to the length of the stream.
IFormFile has a property
Length which contains exactly that information.
Streams send data in chunks and have built-in support for the
Memory type. When a new chunk of data arrives, we simply
Slice off from memory to the number of bytes we have already added, we add how many bytes have been added
Memory exposes a method
Slice. All is async, so if a client has a particularly slow connection, we will wait until enough bytes are in the buffer without blocking the thread.
private readonly static byte EmptyArray = new byte;
public static async Task<byte> ToByteArrayAsync(this IFormFile file, CancellationToken cancellationToken = default)
byte pool = new byte[(int)file.Length];
using (Stream reader = file.OpenReadStream())
await reader.FillMemoryAsync(pool.AsMemory(), cancellationToken);
private static async Task FillMemoryAsync(this Stream reader, Memory<byte> memory, CancellationToken cancellationToken)
int totalBytesRead = 0;
int bytesRead = await reader.ReadAsync(memory.Slice(totalBytesRead), cancellationToken);
if (bytesRead < 0)
throw new EndOfStreamException();
totalBytesRead += bytesRead;
} while (totalBytesRead < memory.Length);