File: src\libraries\Common\src\System\IO\ChunkedMemoryStream.cs
Web Access
Project: src\src\libraries\System.Net.WebClient\src\System.Net.WebClient.csproj (System.Net.WebClient)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.IO
{
    /// <summary>Provides an in-memory stream composed of non-contiguous chunks.</summary>
    internal sealed class ChunkedMemoryStream : Stream
    {
        private MemoryChunk? _headChunk;
        private MemoryChunk? _currentChunk;
 
        private const int InitialChunkDefaultSize = 1024;
        private const int MaxChunkSize = 1024 * InitialChunkDefaultSize;
        private int _totalLength;
 
        internal ChunkedMemoryStream() { }
 
        public byte[] ToArray()
        {
            byte[] result = new byte[_totalLength];
            int offset = 0;
            for (MemoryChunk? chunk = _headChunk; chunk != null; chunk = chunk._next)
            {
                Debug.Assert(chunk._next == null || chunk._freeOffset == chunk._buffer.Length);
                Buffer.BlockCopy(chunk._buffer, 0, result, offset, chunk._freeOffset);
                offset += chunk._freeOffset;
            }
            return result;
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            Write(new ReadOnlySpan<byte>(buffer, offset, count));
        }
 
        public override void Write(ReadOnlySpan<byte> buffer)
        {
            while (!buffer.IsEmpty)
            {
                if (_currentChunk != null)
                {
                    int remaining = _currentChunk._buffer.Length - _currentChunk._freeOffset;
                    if (remaining > 0)
                    {
                        int toCopy = Math.Min(remaining, buffer.Length);
                        buffer.Slice(0, toCopy).CopyTo(new Span<byte>(_currentChunk._buffer, _currentChunk._freeOffset, toCopy));
                        buffer = buffer.Slice(toCopy);
                        _totalLength += toCopy;
                        _currentChunk._freeOffset += toCopy;
                        continue;
                    }
                }
 
                AppendChunk(buffer.Length);
            }
        }
 
        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }
 
            Write(buffer, offset, count);
            return Task.CompletedTask;
        }
 
        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return ValueTask.FromCanceled(cancellationToken);
            }
 
            Write(buffer.Span);
            return ValueTask.CompletedTask;
        }
 
        private void AppendChunk(long count)
        {
            int nextChunkLength = _currentChunk != null ? _currentChunk._buffer.Length * 2 : InitialChunkDefaultSize;
            if (count > nextChunkLength)
            {
                nextChunkLength = (int)Math.Min(count, MaxChunkSize);
            }
 
            MemoryChunk newChunk = new MemoryChunk(nextChunkLength);
 
            if (_currentChunk == null)
            {
                Debug.Assert(_headChunk == null);
                _headChunk = _currentChunk = newChunk;
            }
            else
            {
                Debug.Assert(_headChunk != null);
                _currentChunk._next = newChunk;
                _currentChunk = newChunk;
            }
        }
 
        public override bool CanRead => false;
        public override bool CanSeek => false;
        public override bool CanWrite => true;
        public override long Length => _totalLength;
        public override void Flush() { }
        public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
        public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } }
        public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
        public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
        public override void SetLength(long value)
        {
            if (_currentChunk != null) throw new NotSupportedException();
            AppendChunk(value);
        }
 
        private sealed class MemoryChunk
        {
            internal readonly byte[] _buffer;
            internal int _freeOffset;
            internal MemoryChunk? _next;
 
            internal MemoryChunk(int bufferSize) { _buffer = new byte[bufferSize]; }
        }
    }
 
}