File: src\libraries\System.Private.CoreLib\src\System\IO\StreamWriter.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.IO
{
    // This class implements a TextWriter for writing characters to a Stream.
    // This is designed for character output in a particular Encoding,
    // whereas the Stream class is designed for byte input and output.
    public class StreamWriter : TextWriter
    {
        // For UTF-8, the values of 1K for the default buffer size and 4K for the
        // file stream buffer size are reasonable & give very reasonable
        // performance for in terms of construction time for the StreamWriter and
        // write perf.  Note that for UTF-8, we end up allocating a 4K byte buffer,
        // which means we take advantage of adaptive buffering code.
        // The performance using UnicodeEncoding is acceptable.
        private const int DefaultBufferSize = 1024;   // char[]
        private const int MinBufferSize = 128;
 
        // Bit bucket - Null has no backing store. Non closable.
        public static new readonly StreamWriter Null = new NullStreamWriter();
 
        private readonly Stream _stream;
        private readonly Encoding _encoding;
        private readonly Encoder _encoder;
        private byte[]? _byteBuffer;
        private readonly char[] _charBuffer;
        private int _charPos;
        private int _charLen;
        private bool _autoFlush;
        private bool _haveWrittenPreamble;
        private readonly bool _closable;
        private bool _disposed;
 
        // We don't guarantee thread safety on StreamWriter, but we should at
        // least prevent users from trying to write anything while an Async
        // write from the same thread is in progress.
        private Task _asyncWriteTask = Task.CompletedTask;
 
        private void CheckAsyncTaskInProgress()
        {
            // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety.
            // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
            if (!_asyncWriteTask.IsCompleted)
            {
                ThrowAsyncIOInProgress();
            }
        }
 
        [DoesNotReturn]
        private static void ThrowAsyncIOInProgress() =>
            throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
 
        // The high level goal is to be tolerant of encoding errors when we read and very strict
        // when we write. Hence, default StreamWriter encoding will throw on encoding error.
        // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
        // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
        // internal StreamWriter's state to be irrecoverable as it would have buffered the
        // illegal chars and any subsequent call to Flush() would hit the encoding error again.
        // Even Close() will hit the exception as it would try to flush the unwritten data.
        // Maybe we can add a DiscardBufferedData() method to get out of such situation (like
        // StreamReader though for different reason). Either way, the buffered data will be lost!
        private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
 
        public StreamWriter(Stream stream)
            : this(stream, UTF8NoBOM, DefaultBufferSize, false)
        {
        }
 
        public StreamWriter(Stream stream, Encoding? encoding)
            : this(stream, encoding, DefaultBufferSize, false)
        {
        }
 
        // Creates a new StreamWriter for the given stream.  The
        // character encoding is set by encoding and the buffer size,
        // in number of 16-bit characters, is set by bufferSize.
        //
        public StreamWriter(Stream stream, Encoding? encoding, int bufferSize)
            : this(stream, encoding, bufferSize, false)
        {
        }
 
        public StreamWriter(Stream stream, Encoding? encoding = null, int bufferSize = -1, bool leaveOpen = false)
            : base(null) // Ask for CurrentCulture all the time
        {
            if (stream == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stream);
            }
 
            if (!stream.CanWrite)
            {
                throw new ArgumentException(SR.Argument_StreamNotWritable);
            }
 
            if (bufferSize == -1)
            {
                bufferSize = DefaultBufferSize;
            }
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
 
            _stream = stream;
            _encoding = encoding ?? UTF8NoBOM;
            _encoder = _encoding.GetEncoder();
            if (bufferSize < MinBufferSize)
            {
                bufferSize = MinBufferSize;
            }
 
            _charBuffer = new char[bufferSize];
            _charLen = bufferSize;
            // If we're appending to a Stream that already has data, don't write
            // the preamble.
            if (_stream.CanSeek && _stream.Position > 0)
            {
                _haveWrittenPreamble = true;
            }
 
            _closable = !leaveOpen;
        }
 
        public StreamWriter(string path)
            : this(path, false, UTF8NoBOM, DefaultBufferSize)
        {
        }
 
        public StreamWriter(string path, bool append)
            : this(path, append, UTF8NoBOM, DefaultBufferSize)
        {
        }
 
        public StreamWriter(string path, bool append, Encoding? encoding)
            : this(path, append, encoding, DefaultBufferSize)
        {
        }
 
        public StreamWriter(string path, bool append, Encoding? encoding, int bufferSize) :
            this(ValidateArgsAndOpenPath(path, append, bufferSize), encoding, bufferSize, leaveOpen: false)
        {
        }
 
        public StreamWriter(string path, FileStreamOptions options)
            : this(path, UTF8NoBOM, options)
        {
        }
 
        public StreamWriter(string path, Encoding? encoding, FileStreamOptions options)
            : this(ValidateArgsAndOpenPath(path, options), encoding, DefaultBufferSize)
        {
        }
 
        private StreamWriter()
        {
            Debug.Assert(GetType() == typeof(NullStreamWriter));
            _stream = Stream.Null;
            _encoding = UTF8NoBOM;
            _encoder = null!;
            _charBuffer = [];
        }
 
        private static FileStream ValidateArgsAndOpenPath(string path, FileStreamOptions options)
        {
            ArgumentException.ThrowIfNullOrEmpty(path);
            ArgumentNullException.ThrowIfNull(options);
            if ((options.Access & FileAccess.Write) == 0)
            {
                throw new ArgumentException(SR.Argument_StreamNotWritable, nameof(options));
            }
 
            return new FileStream(path, options);
        }
 
        private static FileStream ValidateArgsAndOpenPath(string path, bool append, int bufferSize)
        {
            ArgumentException.ThrowIfNullOrEmpty(path);
 
            if (bufferSize != -1)
            {
                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
            }
 
            return new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, FileStream.DefaultBufferSize);
        }
 
        public override void Close()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected override void Dispose(bool disposing)
        {
            try
            {
                // We need to flush any buffered data if we are being closed/disposed.
                // Also, we never close the handles for stdout & friends.  So we can safely
                // write any buffered data to those streams even during finalization, which
                // is generally the right thing to do.
                if (!_disposed && disposing)
                {
                    // Note: flush on the underlying stream can throw (ex., low disk space)
                    CheckAsyncTaskInProgress();
                    Flush(flushStream: true, flushEncoder: true);
                }
            }
            finally
            {
                CloseStreamFromDispose(disposing);
            }
        }
 
        private void CloseStreamFromDispose(bool disposing)
        {
            // Dispose of our resources if this StreamWriter is closable.
            if (_closable && !_disposed)
            {
                try
                {
                    // Attempt to close the stream even if there was an IO error from Flushing.
                    // Note that Stream.Close() can potentially throw here (may or may not be
                    // due to the same Flush error). In this case, we still need to ensure
                    // cleaning up internal resources, hence the finally block.
                    if (disposing)
                    {
                        _stream.Close();
                    }
                }
                finally
                {
                    _disposed = true;
                    _charLen = 0;
                    base.Dispose(disposing);
                }
            }
        }
 
        public override ValueTask DisposeAsync() =>
            GetType() != typeof(StreamWriter) ?
                base.DisposeAsync() :
                DisposeAsyncCore();
 
        private async ValueTask DisposeAsyncCore()
        {
            // Same logic as in Dispose(), but with async flushing.
            Debug.Assert(GetType() == typeof(StreamWriter));
            try
            {
                if (!_disposed)
                {
                    await FlushAsync().ConfigureAwait(false);
                }
            }
            finally
            {
                CloseStreamFromDispose(disposing: true);
            }
            GC.SuppressFinalize(this);
        }
 
        public override void Flush()
        {
            CheckAsyncTaskInProgress();
 
            Flush(true, true);
        }
 
        private void Flush(bool flushStream, bool flushEncoder)
        {
            // flushEncoder should be true at the end of the file and if
            // the user explicitly calls Flush (though not if AutoFlush is true).
            // This is required to flush any dangling characters from our UTF-7
            // and UTF-8 encoders.
            ThrowIfDisposed();
 
            // Perf boost for Flush on non-dirty writers.
            if (_charPos == 0 && !flushStream && !flushEncoder)
            {
                return;
            }
 
            if (!_haveWrittenPreamble)
            {
                _haveWrittenPreamble = true;
                ReadOnlySpan<byte> preamble = _encoding.Preamble;
                if (preamble.Length > 0)
                {
                    _stream.Write(preamble);
                }
            }
 
            // For sufficiently small char data being flushed, try to encode to the stack.
            // For anything else, fall back to allocating the byte[] buffer.
            scoped Span<byte> byteBuffer;
            if (_byteBuffer is not null)
            {
                byteBuffer = _byteBuffer;
            }
            else
            {
                int maxBytesForCharPos = _encoding.GetMaxByteCount(_charPos);
                byteBuffer = (uint)maxBytesForCharPos <= 1024 ? // arbitrary threshold
                    stackalloc byte[1024] :
                    (_byteBuffer = new byte[_encoding.GetMaxByteCount(_charBuffer.Length)]);
            }
 
            int count = _encoder.GetBytes(new ReadOnlySpan<char>(_charBuffer, 0, _charPos), byteBuffer, flushEncoder);
            _charPos = 0;
            if (count > 0)
            {
                _stream.Write(byteBuffer.Slice(0, count));
            }
 
            if (flushStream)
            {
                _stream.Flush();
            }
        }
 
        public virtual bool AutoFlush
        {
            get => _autoFlush;
 
            set
            {
                CheckAsyncTaskInProgress();
 
                _autoFlush = value;
                if (value)
                {
                    Flush(true, false);
                }
            }
        }
 
        public virtual Stream BaseStream => _stream;
 
        public override Encoding Encoding => _encoding;
 
        public override void Write(char value)
        {
            CheckAsyncTaskInProgress();
 
            if (_charPos == _charLen)
            {
                Flush(false, false);
            }
 
            _charBuffer[_charPos] = value;
            _charPos++;
            if (_autoFlush)
            {
                Flush(true, false);
            }
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
        public override void Write(char[]? buffer)
        {
            WriteSpan(buffer, appendNewLine: false);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
        public override void Write(char[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (buffer.Length - index < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen);
            }
 
            WriteSpan(buffer.AsSpan(index, count), appendNewLine: false);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
        public override void Write(ReadOnlySpan<char> buffer)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteSpan(buffer, appendNewLine: false);
            }
            else
            {
                // If a derived class may have overridden existing Write behavior,
                // we need to make sure we use it.
                base.Write(buffer);
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine)
        {
            CheckAsyncTaskInProgress();
 
            if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
                buffer.Length <= _charLen - _charPos)
            {
                // For very short buffers and when we don't need to worry about running out of space
                // in the char buffer, just copy the chars individually.
                for (int i = 0; i < buffer.Length; i++)
                {
                    _charBuffer[_charPos++] = buffer[i];
                }
            }
            else
            {
                // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
                // Use unsafe code until https://github.com/dotnet/runtime/issues/8890 is addressed, as spans are
                // resulting in significant overhead (even when the if branch above is taken rather than this
                // else) due to temporaries that need to be cleared.  Given the use of unsafe code, we also
                // make local copies of instance state to protect against potential concurrent misuse.
 
                ThrowIfDisposed();
                char[] charBuffer = _charBuffer;
 
                fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
                fixed (char* dstPtr = &charBuffer[0])
                {
                    char* srcPtr = bufferPtr;
                    int count = buffer.Length;
                    int dstPos = _charPos; // use a local copy of _charPos for safety
                    while (count > 0)
                    {
                        if (dstPos == charBuffer.Length)
                        {
                            Flush(false, false);
                            dstPos = 0;
                        }
 
                        int n = Math.Min(charBuffer.Length - dstPos, count);
                        int bytesToCopy = n * sizeof(char);
 
                        Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
 
                        _charPos += n;
                        dstPos += n;
                        srcPtr += n;
                        count -= n;
                    }
                }
            }
 
            if (appendNewLine)
            {
                char[] coreNewLine = CoreNewLine;
                for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
                {
                    if (_charPos == _charLen)
                    {
                        Flush(false, false);
                    }
 
                    _charBuffer[_charPos] = coreNewLine[i];
                    _charPos++;
                }
            }
 
            if (_autoFlush)
            {
                Flush(true, false);
            }
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
        public override void Write(string? value)
        {
            WriteSpan(value, appendNewLine: false);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
        public override void WriteLine(string? value)
        {
            CheckAsyncTaskInProgress();
            WriteSpan(value, appendNewLine: true);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
        public override void WriteLine(ReadOnlySpan<char> buffer)
        {
            if (GetType() == typeof(StreamWriter))
            {
                CheckAsyncTaskInProgress();
                WriteSpan(buffer, appendNewLine: true);
            }
            else
            {
                // If a derived class may have overridden existing WriteLine behavior,
                // we need to make sure we use it.
                base.WriteLine(buffer);
            }
        }
 
        private void WriteFormatHelper(string format, ReadOnlySpan<object?> args, bool appendNewLine)
        {
            int estimatedLength = checked((format?.Length ?? 0) + args.Length * 8);
            var vsb = (uint)estimatedLength <= 256 ?
                new ValueStringBuilder(stackalloc char[256]) :
                new ValueStringBuilder(estimatedLength);
 
            vsb.AppendFormatHelper(null, format!, args); // AppendFormatHelper will appropriately throw ArgumentNullException for a null format
 
            WriteSpan(vsb.AsSpan(), appendNewLine);
 
            vsb.Dispose();
        }
 
        public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, new ReadOnlySpan<object?>(in arg0), appendNewLine: false);
            }
            else
            {
                base.Write(format, arg0);
            }
        }
 
        public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, [arg0, arg1], appendNewLine: false);
            }
            else
            {
                base.Write(format, arg0, arg1);
            }
        }
 
        public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, [arg0, arg1, arg2], appendNewLine: false);
            }
            else
            {
                base.Write(format, arg0, arg1, arg2);
            }
        }
 
        public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg)
        {
            if (GetType() == typeof(StreamWriter))
            {
                if (arg == null)
                {
                    ArgumentNullException.Throw(format is null ? nameof(format) : nameof(arg)); // same as base logic
                }
                WriteFormatHelper(format, arg, appendNewLine: false);
            }
            else
            {
                base.Write(format, arg);
            }
        }
 
        /// <summary>
        /// Writes a formatted string to the stream, using the same semantics as <see cref="string.Format(string, ReadOnlySpan{object?})"/>.
        /// </summary>
        /// <param name="format">A composite format string.</param>
        /// <param name="arg">An object span that contains zero or more objects to format and write.</param>
        public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, arg, appendNewLine: false);
            }
            else
            {
                base.Write(format, arg);
            }
        }
 
        public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, new ReadOnlySpan<object?>(in arg0), appendNewLine: true);
            }
            else
            {
                base.WriteLine(format, arg0);
            }
        }
 
        public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, [arg0, arg1], appendNewLine: true);
            }
            else
            {
                base.WriteLine(format, arg0, arg1);
            }
        }
 
        public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, [arg0, arg1, arg2], appendNewLine: true);
            }
            else
            {
                base.WriteLine(format, arg0, arg1, arg2);
            }
        }
 
        public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg)
        {
            if (GetType() == typeof(StreamWriter))
            {
                ArgumentNullException.ThrowIfNull(arg);
                WriteFormatHelper(format, arg, appendNewLine: true);
            }
            else
            {
                base.WriteLine(format, arg);
            }
        }
 
        /// <summary>
        /// Writes out a formatted string and a new line to the stream, using the same semantics as <see cref="string.Format(string, ReadOnlySpan{object?})"/>.
        /// </summary>
        /// <param name="format">A composite format string.</param>
        /// <param name="arg">An object span that contains zero or more objects to format and write.</param>
        public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg)
        {
            if (GetType() == typeof(StreamWriter))
            {
                WriteFormatHelper(format, arg, appendNewLine: true);
            }
            else
            {
                base.WriteLine(format, arg);
            }
        }
 
        public override Task WriteAsync(char value)
        {
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteAsync(value);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            Task task = WriteAsyncInternal(value, appendNewLine: false);
            _asyncWriteTask = task;
 
            return task;
        }
 
        private async Task WriteAsyncInternal(char value, bool appendNewLine)
        {
            if (_charPos == _charLen)
            {
                await FlushAsyncInternal(flushStream: false, flushEncoder: false).ConfigureAwait(false);
            }
 
            _charBuffer[_charPos++] = value;
 
            if (appendNewLine)
            {
                for (int i = 0; i < CoreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
                {
                    if (_charPos == _charLen)
                    {
                        await FlushAsyncInternal(flushStream: false, flushEncoder: false).ConfigureAwait(false);
                    }
 
                    _charBuffer[_charPos++] = CoreNewLine[i];
                }
            }
 
            if (_autoFlush)
            {
                await FlushAsyncInternal(flushStream: true, flushEncoder: false).ConfigureAwait(false);
            }
        }
 
        public override Task WriteAsync(string? value)
        {
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteAsync(value);
            }
 
            if (value != null)
            {
                ThrowIfDisposed();
                CheckAsyncTaskInProgress();
 
                Task task = WriteAsyncInternal(value.AsMemory(), appendNewLine: false, default);
                _asyncWriteTask = task;
 
                return task;
            }
            else
            {
                return Task.CompletedTask;
            }
        }
 
        public override Task WriteAsync(char[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
 
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (buffer.Length - index < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen);
            }
 
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteAsync(buffer, index, count);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            Task task = WriteAsyncInternal(new ReadOnlyMemory<char>(buffer, index, count), appendNewLine: false, cancellationToken: default);
            _asyncWriteTask = task;
 
            return task;
        }
 
        public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
        {
            if (GetType() != typeof(StreamWriter))
            {
                // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
                return base.WriteAsync(buffer, cancellationToken);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }
 
            Task task = WriteAsyncInternal(buffer, appendNewLine: false, cancellationToken: cancellationToken);
            _asyncWriteTask = task;
            return task;
        }
 
        private async Task WriteAsyncInternal(ReadOnlyMemory<char> source, bool appendNewLine, CancellationToken cancellationToken)
        {
            int copied = 0;
            while (copied < source.Length)
            {
                if (_charPos == _charLen)
                {
                    await FlushAsyncInternal(flushStream: false, flushEncoder: false, cancellationToken).ConfigureAwait(false);
                }
 
                int n = Math.Min(_charLen - _charPos, source.Length - copied);
                Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress!  This is most likely a race condition in user code.");
 
                source.Span.Slice(copied, n).CopyTo(new Span<char>(_charBuffer, _charPos, n));
                _charPos += n;
                copied += n;
            }
 
            if (appendNewLine)
            {
                for (int i = 0; i < CoreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
                {
                    if (_charPos == _charLen)
                    {
                        await FlushAsyncInternal(flushStream: false, flushEncoder: false, cancellationToken).ConfigureAwait(false);
                    }
 
                    _charBuffer[_charPos++] = CoreNewLine[i];
                }
            }
 
            if (_autoFlush)
            {
                await FlushAsyncInternal(flushStream: true, flushEncoder: false, cancellationToken).ConfigureAwait(false);
            }
        }
 
        public override Task WriteLineAsync()
        {
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteLineAsync();
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            Task task = WriteAsyncInternal(ReadOnlyMemory<char>.Empty, appendNewLine: true, cancellationToken: default);
            _asyncWriteTask = task;
 
            return task;
        }
 
        public override Task WriteLineAsync(char value)
        {
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteLineAsync(value);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            Task task = WriteAsyncInternal(value, appendNewLine: true);
            _asyncWriteTask = task;
 
            return task;
        }
 
        public override Task WriteLineAsync(string? value)
        {
            if (value == null)
            {
                return WriteLineAsync();
            }
 
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteLineAsync(value);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            Task task = WriteAsyncInternal(value.AsMemory(), appendNewLine: true, default);
            _asyncWriteTask = task;
 
            return task;
        }
 
        public override Task WriteLineAsync(char[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
 
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (buffer.Length - index < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen);
            }
 
            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write) when we are not sure.
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteLineAsync(buffer, index, count);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            Task task = WriteAsyncInternal(new ReadOnlyMemory<char>(buffer, index, count), appendNewLine: true, cancellationToken: default);
            _asyncWriteTask = task;
 
            return task;
        }
 
        public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
        {
            if (GetType() != typeof(StreamWriter))
            {
                return base.WriteLineAsync(buffer, cancellationToken);
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
 
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }
 
            Task task = WriteAsyncInternal(buffer, appendNewLine: true, cancellationToken: cancellationToken);
            _asyncWriteTask = task;
 
            return task;
        }
 
        public override Task FlushAsync()
        {
            if (GetType() != typeof(StreamWriter))
            {
                return base.FlushAsync();
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
            return (_asyncWriteTask = FlushAsyncInternal(flushStream: true, flushEncoder: true, CancellationToken.None));
        }
 
        /// <summary>Clears all buffers for this stream asynchronously and causes any buffered data to be written to the underlying device.</summary>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
        /// <returns>A <see cref="Task"/> that represents the asynchronous flush operation.</returns>
        public override Task FlushAsync(CancellationToken cancellationToken)
        {
            if (GetType() != typeof(StreamWriter))
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return Task.FromCanceled(cancellationToken);
                }
 
                return FlushAsync();
            }
 
            ThrowIfDisposed();
            CheckAsyncTaskInProgress();
            return (_asyncWriteTask = FlushAsyncInternal(flushStream: true, flushEncoder: true, cancellationToken));
        }
 
        private Task FlushAsyncInternal(bool flushStream, bool flushEncoder, CancellationToken cancellationToken = default)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }
 
            // Perf boost for Flush on non-dirty writers.
            if (_charPos == 0 && !flushStream && !flushEncoder)
            {
                return Task.CompletedTask;
            }
 
            return Core(flushStream, flushEncoder, cancellationToken);
 
            async Task Core(bool flushStream, bool flushEncoder, CancellationToken cancellationToken)
            {
                if (!_haveWrittenPreamble)
                {
                    _haveWrittenPreamble = true;
                    byte[] preamble = _encoding.GetPreamble();
                    if (preamble.Length > 0)
                    {
                        await _stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false);
                    }
                }
 
                byte[] byteBuffer = _byteBuffer ??= new byte[_encoding.GetMaxByteCount(_charBuffer.Length)];
 
                int count = _encoder.GetBytes(new ReadOnlySpan<char>(_charBuffer, 0, _charPos), byteBuffer, flushEncoder);
                _charPos = 0;
                if (count > 0)
                {
                    await _stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false);
                }
 
                // By definition, calling Flush should flush the stream, but this is
                // only necessary if we passed in true for flushStream.  The Web
                // Services guys have some perf tests where flushing needlessly hurts.
                if (flushStream)
                {
                    await _stream.FlushAsync(cancellationToken).ConfigureAwait(false);
                }
            }
        }
 
        private void ThrowIfDisposed()
        {
            if (_disposed)
            {
                ThrowObjectDisposedException();
            }
 
            void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_WriterClosed);
        }
 
        private sealed class NullStreamWriter : StreamWriter
        {
            public override bool AutoFlush { get => false; set { } }
            [AllowNull]
            public override string NewLine { get => base.NewLine; set { } }
            public override IFormatProvider FormatProvider => CultureInfo.InvariantCulture;
 
            // To avoid all unnecessary overhead in the base, and to ensure StreamWriter's uninitialized state is never touched,
            // override all methods as pure nops.
            public override void Close() { }
            protected override void Dispose(bool disposing) { }
            public override ValueTask DisposeAsync() => default;
            public override void Flush() { }
            public override Task FlushAsync() => Task.CompletedTask;
            public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
            public override void Write(char value) { }
            public override void Write(char[]? buffer) { }
            public override void Write(char[] buffer, int index, int count) { }
            public override void Write(ReadOnlySpan<char> buffer) { }
            public override void Write(bool value) { }
            public override void Write(int value) { }
            public override void Write(uint value) { }
            public override void Write(long value) { }
            public override void Write(ulong value) { }
            public override void Write(float value) { }
            public override void Write(double value) { }
            public override void Write(decimal value) { }
            public override void Write(string? value) { }
            public override void Write(object? value) { }
            public override void Write(StringBuilder? value) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) { }
            public override Task WriteAsync(char value) => Task.CompletedTask;
            public override Task WriteAsync(string? value) => Task.CompletedTask;
            public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override Task WriteAsync(char[] buffer, int index, int count) => Task.CompletedTask;
            public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override void WriteLine() { }
            public override void WriteLine(char value) { }
            public override void WriteLine(char[]? buffer) { }
            public override void WriteLine(char[] buffer, int index, int count) { }
            public override void WriteLine(ReadOnlySpan<char> buffer) { }
            public override void WriteLine(bool value) { }
            public override void WriteLine(int value) { }
            public override void WriteLine(uint value) { }
            public override void WriteLine(long value) { }
            public override void WriteLine(ulong value) { }
            public override void WriteLine(float value) { }
            public override void WriteLine(double value) { }
            public override void WriteLine(decimal value) { }
            public override void WriteLine(string? value) { }
            public override void WriteLine(StringBuilder? value) { }
            public override void WriteLine(object? value) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) { }
            public override Task WriteLineAsync(char value) => Task.CompletedTask;
            public override Task WriteLineAsync(string? value) => Task.CompletedTask;
            public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override Task WriteLineAsync(char[] buffer, int index, int count) => Task.CompletedTask;
            public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override Task WriteLineAsync() => Task.CompletedTask;
        }
    }
}