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 streamVar.CopyTo(memoryStreamVar)
and 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private readonly static byte[] EmptyArray = new byte[0]; 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); } return pool; } private static async Task FillMemoryAsync(this Stream reader, Memory<byte> memory, CancellationToken cancellationToken) { int totalBytesRead = 0; do { int bytesRead = await reader.ReadAsync(memory.Slice(totalBytesRead), cancellationToken); if (bytesRead < 0) throw new EndOfStreamException(); totalBytesRead += bytesRead; } while (totalBytesRead < memory.Length); } |