Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / IO / StreamWriter.cs
blob4950d28082c5fb0393a9005acbb1dfd783d1b159
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9 using System.Text;
10 using System.Threading;
11 using System.Threading.Tasks;
13 namespace System.IO
15 // This class implements a TextWriter for writing characters to a Stream.
16 // This is designed for character output in a particular Encoding,
17 // whereas the Stream class is designed for byte input and output.
18 public class StreamWriter : TextWriter
20 // For UTF-8, the values of 1K for the default buffer size and 4K for the
21 // file stream buffer size are reasonable & give very reasonable
22 // performance for in terms of construction time for the StreamWriter and
23 // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
24 // which means we take advantage of adaptive buffering code.
25 // The performance using UnicodeEncoding is acceptable.
26 private const int DefaultBufferSize = 1024; // char[]
27 private const int DefaultFileStreamBufferSize = 4096;
28 private const int MinBufferSize = 128;
30 // Bit bucket - Null has no backing store. Non closable.
31 public static new readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, leaveOpen: true);
33 private readonly Stream _stream;
34 private readonly Encoding _encoding;
35 private readonly Encoder _encoder;
36 private readonly byte[] _byteBuffer;
37 private readonly char[] _charBuffer;
38 private int _charPos;
39 private int _charLen;
40 private bool _autoFlush;
41 private bool _haveWrittenPreamble;
42 private readonly bool _closable;
43 private bool _disposed;
45 // We don't guarantee thread safety on StreamWriter, but we should at
46 // least prevent users from trying to write anything while an Async
47 // write from the same thread is in progress.
48 private Task _asyncWriteTask = Task.CompletedTask;
50 private void CheckAsyncTaskInProgress()
52 // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety.
53 // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
54 if (!_asyncWriteTask.IsCompleted)
56 ThrowAsyncIOInProgress();
60 [DoesNotReturn]
61 private static void ThrowAsyncIOInProgress() =>
62 throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
64 // The high level goal is to be tolerant of encoding errors when we read and very strict
65 // when we write. Hence, default StreamWriter encoding will throw on encoding error.
66 // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
67 // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
68 // internal StreamWriter's state to be irrecoverable as it would have buffered the
69 // illegal chars and any subsequent call to Flush() would hit the encoding error again.
70 // Even Close() will hit the exception as it would try to flush the unwritten data.
71 // Maybe we can add a DiscardBufferedData() method to get out of such situation (like
72 // StreamReader though for different reason). Either way, the buffered data will be lost!
73 private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
75 public StreamWriter(Stream stream)
76 : this(stream, UTF8NoBOM, DefaultBufferSize, false)
80 public StreamWriter(Stream stream, Encoding encoding)
81 : this(stream, encoding, DefaultBufferSize, false)
85 // Creates a new StreamWriter for the given stream. The
86 // character encoding is set by encoding and the buffer size,
87 // in number of 16-bit characters, is set by bufferSize.
89 public StreamWriter(Stream stream, Encoding encoding, int bufferSize)
90 : this(stream, encoding, bufferSize, false)
94 public StreamWriter(Stream stream, Encoding? encoding = null, int bufferSize = -1, bool leaveOpen = false)
95 : base(null) // Ask for CurrentCulture all the time
97 if (stream == null)
99 throw new ArgumentNullException(nameof(stream));
101 if (encoding == null)
103 encoding = UTF8NoBOM;
105 if (!stream.CanWrite)
107 throw new ArgumentException(SR.Argument_StreamNotWritable);
109 if (bufferSize == -1)
111 bufferSize = DefaultBufferSize;
113 else if (bufferSize <= 0)
115 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
118 _stream = stream;
119 _encoding = encoding;
120 _encoder = _encoding.GetEncoder();
121 if (bufferSize < MinBufferSize)
123 bufferSize = MinBufferSize;
126 _charBuffer = new char[bufferSize];
127 _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)];
128 _charLen = bufferSize;
129 // If we're appending to a Stream that already has data, don't write
130 // the preamble.
131 if (_stream.CanSeek && _stream.Position > 0)
133 _haveWrittenPreamble = true;
136 _closable = !leaveOpen;
139 public StreamWriter(string path)
140 : this(path, false, UTF8NoBOM, DefaultBufferSize)
144 public StreamWriter(string path, bool append)
145 : this(path, append, UTF8NoBOM, DefaultBufferSize)
149 public StreamWriter(string path, bool append, Encoding encoding)
150 : this(path, append, encoding, DefaultBufferSize)
154 public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) :
155 this(ValidateArgsAndOpenPath(path, append, encoding, bufferSize), encoding, bufferSize, leaveOpen: false)
159 private static Stream ValidateArgsAndOpenPath(string path, bool append, Encoding encoding, int bufferSize)
161 if (path == null)
162 throw new ArgumentNullException(nameof(path));
163 if (encoding == null)
164 throw new ArgumentNullException(nameof(encoding));
165 if (path.Length == 0)
166 throw new ArgumentException(SR.Argument_EmptyPath);
167 if (bufferSize <= 0)
168 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
170 return new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
173 public override void Close()
175 Dispose(true);
176 GC.SuppressFinalize(this);
179 protected override void Dispose(bool disposing)
183 // We need to flush any buffered data if we are being closed/disposed.
184 // Also, we never close the handles for stdout & friends. So we can safely
185 // write any buffered data to those streams even during finalization, which
186 // is generally the right thing to do.
187 if (!_disposed && disposing)
189 // Note: flush on the underlying stream can throw (ex., low disk space)
190 CheckAsyncTaskInProgress();
191 Flush(flushStream: true, flushEncoder: true);
194 finally
196 CloseStreamFromDispose(disposing);
200 private void CloseStreamFromDispose(bool disposing)
202 // Dispose of our resources if this StreamWriter is closable.
203 if (_closable && !_disposed)
207 // Attempt to close the stream even if there was an IO error from Flushing.
208 // Note that Stream.Close() can potentially throw here (may or may not be
209 // due to the same Flush error). In this case, we still need to ensure
210 // cleaning up internal resources, hence the finally block.
211 if (disposing)
213 _stream.Close();
216 finally
218 _disposed = true;
219 _charLen = 0;
220 base.Dispose(disposing);
225 public override ValueTask DisposeAsync() =>
226 GetType() != typeof(StreamWriter) ?
227 base.DisposeAsync() :
228 DisposeAsyncCore();
230 private async ValueTask DisposeAsyncCore()
232 // Same logic as in Dispose(), but with async flushing.
233 Debug.Assert(GetType() == typeof(StreamWriter));
236 if (!_disposed)
238 await FlushAsync().ConfigureAwait(false);
241 finally
243 CloseStreamFromDispose(disposing: true);
245 GC.SuppressFinalize(this);
248 public override void Flush()
250 CheckAsyncTaskInProgress();
252 Flush(true, true);
255 private void Flush(bool flushStream, bool flushEncoder)
257 // flushEncoder should be true at the end of the file and if
258 // the user explicitly calls Flush (though not if AutoFlush is true).
259 // This is required to flush any dangling characters from our UTF-7
260 // and UTF-8 encoders.
261 ThrowIfDisposed();
263 // Perf boost for Flush on non-dirty writers.
264 if (_charPos == 0 && !flushStream && !flushEncoder)
266 return;
269 if (!_haveWrittenPreamble)
271 _haveWrittenPreamble = true;
272 ReadOnlySpan<byte> preamble = _encoding.Preamble;
273 if (preamble.Length > 0)
275 _stream.Write(preamble);
279 int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder);
280 _charPos = 0;
281 if (count > 0)
283 _stream.Write(_byteBuffer, 0, count);
285 // By definition, calling Flush should flush the stream, but this is
286 // only necessary if we passed in true for flushStream. The Web
287 // Services guys have some perf tests where flushing needlessly hurts.
288 if (flushStream)
290 _stream.Flush();
294 public virtual bool AutoFlush
296 get { return _autoFlush; }
300 CheckAsyncTaskInProgress();
302 _autoFlush = value;
303 if (value)
305 Flush(true, false);
310 public virtual Stream BaseStream => _stream;
312 public override Encoding Encoding => _encoding;
314 public override void Write(char value)
316 CheckAsyncTaskInProgress();
318 if (_charPos == _charLen)
320 Flush(false, false);
323 _charBuffer[_charPos] = value;
324 _charPos++;
325 if (_autoFlush)
327 Flush(true, false);
331 [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
332 public override void Write(char[]? buffer)
334 WriteSpan(buffer, appendNewLine: false);
337 [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
338 public override void Write(char[] buffer, int index, int count)
340 if (buffer == null)
342 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
344 if (index < 0)
346 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
348 if (count < 0)
350 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
352 if (buffer.Length - index < count)
354 throw new ArgumentException(SR.Argument_InvalidOffLen);
357 WriteSpan(buffer.AsSpan(index, count), appendNewLine: false);
360 [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
361 public override void Write(ReadOnlySpan<char> buffer)
363 if (GetType() == typeof(StreamWriter))
365 WriteSpan(buffer, appendNewLine: false);
367 else
369 // If a derived class may have overridden existing Write behavior,
370 // we need to make sure we use it.
371 base.Write(buffer);
375 [MethodImpl(MethodImplOptions.AggressiveInlining)]
376 private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine)
378 CheckAsyncTaskInProgress();
380 if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
381 buffer.Length <= _charLen - _charPos)
383 // For very short buffers and when we don't need to worry about running out of space
384 // in the char buffer, just copy the chars individually.
385 for (int i = 0; i < buffer.Length; i++)
387 _charBuffer[_charPos++] = buffer[i];
390 else
392 // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
393 // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
394 // resulting in significant overhead (even when the if branch above is taken rather than this
395 // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also
396 // make local copies of instance state to protect against potential concurrent misuse.
398 ThrowIfDisposed();
399 char[] charBuffer = _charBuffer;
401 fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
402 fixed (char* dstPtr = &charBuffer[0])
404 char* srcPtr = bufferPtr;
405 int count = buffer.Length;
406 int dstPos = _charPos; // use a local copy of _charPos for safety
407 while (count > 0)
409 if (dstPos == charBuffer.Length)
411 Flush(false, false);
412 dstPos = 0;
415 int n = Math.Min(charBuffer.Length - dstPos, count);
416 int bytesToCopy = n * sizeof(char);
418 Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
420 _charPos += n;
421 dstPos += n;
422 srcPtr += n;
423 count -= n;
428 if (appendNewLine)
430 char[] coreNewLine = CoreNewLine;
431 for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
433 if (_charPos == _charLen)
435 Flush(false, false);
438 _charBuffer[_charPos] = coreNewLine[i];
439 _charPos++;
443 if (_autoFlush)
445 Flush(true, false);
449 [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
450 public override void Write(string? value)
452 WriteSpan(value, appendNewLine: false);
455 [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
456 public override void WriteLine(string? value)
458 CheckAsyncTaskInProgress();
459 WriteSpan(value, appendNewLine: true);
462 [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
463 public override void WriteLine(ReadOnlySpan<char> value)
465 if (GetType() == typeof(StreamWriter))
467 CheckAsyncTaskInProgress();
468 WriteSpan(value, appendNewLine: true);
470 else
472 // If a derived class may have overridden existing WriteLine behavior,
473 // we need to make sure we use it.
474 base.WriteLine(value);
478 private void WriteFormatHelper(string format, ParamsArray args, bool appendNewLine)
480 StringBuilder sb =
481 StringBuilderCache.Acquire((format?.Length ?? 0) + args.Length * 8)
482 .AppendFormatHelper(null, format!, args); // AppendFormatHelper will appropriately throw ArgumentNullException for a null format
484 StringBuilder.ChunkEnumerator chunks = sb.GetChunks();
486 bool more = chunks.MoveNext();
487 while (more)
489 ReadOnlySpan<char> current = chunks.Current.Span;
490 more = chunks.MoveNext();
492 // If final chunk, include the newline if needed
493 WriteSpan(current, appendNewLine: more ? false : appendNewLine);
496 StringBuilderCache.Release(sb);
499 public override void Write(string format, object? arg0)
501 if (GetType() == typeof(StreamWriter))
503 WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: false);
505 else
507 base.Write(format, arg0);
511 public override void Write(string format, object? arg0, object? arg1)
513 if (GetType() == typeof(StreamWriter))
515 WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: false);
517 else
519 base.Write(format, arg0, arg1);
523 public override void Write(string format, object? arg0, object? arg1, object? arg2)
525 if (GetType() == typeof(StreamWriter))
527 WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: false);
529 else
531 base.Write(format, arg0, arg1, arg2);
535 public override void Write(string format, params object?[] arg)
537 if (GetType() == typeof(StreamWriter))
539 if (arg == null)
541 throw new ArgumentNullException((format == null) ? nameof(format) : nameof(arg)); // same as base logic
543 WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: false);
545 else
547 base.Write(format, arg);
551 public override void WriteLine(string format, object? arg0)
553 if (GetType() == typeof(StreamWriter))
555 WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: true);
557 else
559 base.WriteLine(format, arg0);
563 public override void WriteLine(string format, object? arg0, object? arg1)
565 if (GetType() == typeof(StreamWriter))
567 WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: true);
569 else
571 base.WriteLine(format, arg0, arg1);
575 public override void WriteLine(string format, object? arg0, object? arg1, object? arg2)
577 if (GetType() == typeof(StreamWriter))
579 WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: true);
581 else
583 base.WriteLine(format, arg0, arg1, arg2);
587 public override void WriteLine(string format, params object?[] arg)
589 if (GetType() == typeof(StreamWriter))
591 if (arg == null)
593 throw new ArgumentNullException(nameof(arg));
595 WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: true);
597 else
599 base.WriteLine(format, arg);
603 public override Task WriteAsync(char value)
605 // If we have been inherited into a subclass, the following implementation could be incorrect
606 // since it does not call through to Write() which a subclass might have overridden.
607 // To be safe we will only use this implementation in cases where we know it is safe to do so,
608 // and delegate to our base class (which will call into Write) when we are not sure.
609 if (GetType() != typeof(StreamWriter))
611 return base.WriteAsync(value);
614 ThrowIfDisposed();
615 CheckAsyncTaskInProgress();
617 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
618 _asyncWriteTask = task;
620 return task;
623 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
624 // to ensure performant access inside the state machine that corresponds this async method.
625 // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
626 private static async Task WriteAsyncInternal(StreamWriter _this, char value,
627 char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
628 bool autoFlush, bool appendNewLine)
630 if (charPos == charLen)
632 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
633 Debug.Assert(_this._charPos == 0);
634 charPos = 0;
637 charBuffer[charPos] = value;
638 charPos++;
640 if (appendNewLine)
642 for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
644 if (charPos == charLen)
646 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
647 Debug.Assert(_this._charPos == 0);
648 charPos = 0;
651 charBuffer[charPos] = coreNewLine[i];
652 charPos++;
656 if (autoFlush)
658 await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
659 Debug.Assert(_this._charPos == 0);
660 charPos = 0;
663 _this._charPos = charPos;
666 public override Task WriteAsync(string? value)
668 // If we have been inherited into a subclass, the following implementation could be incorrect
669 // since it does not call through to Write() which a subclass might have overridden.
670 // To be safe we will only use this implementation in cases where we know it is safe to do so,
671 // and delegate to our base class (which will call into Write) when we are not sure.
672 if (GetType() != typeof(StreamWriter))
674 return base.WriteAsync(value);
677 if (value != null)
679 ThrowIfDisposed();
680 CheckAsyncTaskInProgress();
682 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
683 _asyncWriteTask = task;
685 return task;
687 else
689 return Task.CompletedTask;
693 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
694 // to ensure performant access inside the state machine that corresponds this async method.
695 // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
696 private static async Task WriteAsyncInternal(StreamWriter _this, string value,
697 char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
698 bool autoFlush, bool appendNewLine)
700 Debug.Assert(value != null);
702 int count = value.Length;
703 int index = 0;
705 while (count > 0)
707 if (charPos == charLen)
709 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
710 Debug.Assert(_this._charPos == 0);
711 charPos = 0;
714 int n = charLen - charPos;
715 if (n > count)
717 n = count;
720 Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
722 value.CopyTo(index, charBuffer, charPos, n);
724 charPos += n;
725 index += n;
726 count -= n;
729 if (appendNewLine)
731 for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
733 if (charPos == charLen)
735 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
736 Debug.Assert(_this._charPos == 0);
737 charPos = 0;
740 charBuffer[charPos] = coreNewLine[i];
741 charPos++;
745 if (autoFlush)
747 await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
748 Debug.Assert(_this._charPos == 0);
749 charPos = 0;
752 _this._charPos = charPos;
755 public override Task WriteAsync(char[] buffer, int index, int count)
757 if (buffer == null)
759 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
761 if (index < 0)
763 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
765 if (count < 0)
767 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
769 if (buffer.Length - index < count)
771 throw new ArgumentException(SR.Argument_InvalidOffLen);
774 // If we have been inherited into a subclass, the following implementation could be incorrect
775 // since it does not call through to Write() which a subclass might have overridden.
776 // To be safe we will only use this implementation in cases where we know it is safe to do so,
777 // and delegate to our base class (which will call into Write) when we are not sure.
778 if (GetType() != typeof(StreamWriter))
780 return base.WriteAsync(buffer, index, count);
783 ThrowIfDisposed();
784 CheckAsyncTaskInProgress();
786 Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
787 _asyncWriteTask = task;
789 return task;
792 public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
794 if (GetType() != typeof(StreamWriter))
796 // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
797 return base.WriteAsync(buffer, cancellationToken);
800 ThrowIfDisposed();
801 CheckAsyncTaskInProgress();
803 if (cancellationToken.IsCancellationRequested)
805 return Task.FromCanceled(cancellationToken);
808 Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
809 _asyncWriteTask = task;
810 return task;
813 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
814 // to ensure performant access inside the state machine that corresponds this async method.
815 // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
816 private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
817 char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
818 bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
820 int copied = 0;
821 while (copied < source.Length)
823 if (charPos == charLen)
825 await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
826 Debug.Assert(_this._charPos == 0);
827 charPos = 0;
830 int n = Math.Min(charLen - charPos, source.Length - copied);
831 Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
833 source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
834 charPos += n;
835 copied += n;
838 if (appendNewLine)
840 for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
842 if (charPos == charLen)
844 await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
845 Debug.Assert(_this._charPos == 0);
846 charPos = 0;
849 charBuffer[charPos] = coreNewLine[i];
850 charPos++;
854 if (autoFlush)
856 await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
857 Debug.Assert(_this._charPos == 0);
858 charPos = 0;
861 _this._charPos = charPos;
864 public override Task WriteLineAsync()
866 // If we have been inherited into a subclass, the following implementation could be incorrect
867 // since it does not call through to Write() which a subclass might have overridden.
868 // To be safe we will only use this implementation in cases where we know it is safe to do so,
869 // and delegate to our base class (which will call into Write) when we are not sure.
870 if (GetType() != typeof(StreamWriter))
872 return base.WriteLineAsync();
875 ThrowIfDisposed();
876 CheckAsyncTaskInProgress();
878 Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
879 _asyncWriteTask = task;
881 return task;
885 public override Task WriteLineAsync(char value)
887 // If we have been inherited into a subclass, the following implementation could be incorrect
888 // since it does not call through to Write() which a subclass might have overridden.
889 // To be safe we will only use this implementation in cases where we know it is safe to do so,
890 // and delegate to our base class (which will call into Write) when we are not sure.
891 if (GetType() != typeof(StreamWriter))
893 return base.WriteLineAsync(value);
896 ThrowIfDisposed();
897 CheckAsyncTaskInProgress();
899 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
900 _asyncWriteTask = task;
902 return task;
906 public override Task WriteLineAsync(string? value)
908 if (value == null)
910 return WriteLineAsync();
913 // If we have been inherited into a subclass, the following implementation could be incorrect
914 // since it does not call through to Write() which a subclass might have overridden.
915 // To be safe we will only use this implementation in cases where we know it is safe to do so,
916 // and delegate to our base class (which will call into Write) when we are not sure.
917 if (GetType() != typeof(StreamWriter))
919 return base.WriteLineAsync(value);
922 ThrowIfDisposed();
923 CheckAsyncTaskInProgress();
925 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
926 _asyncWriteTask = task;
928 return task;
932 public override Task WriteLineAsync(char[] buffer, int index, int count)
934 if (buffer == null)
936 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
938 if (index < 0)
940 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
942 if (count < 0)
944 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
946 if (buffer.Length - index < count)
948 throw new ArgumentException(SR.Argument_InvalidOffLen);
951 // If we have been inherited into a subclass, the following implementation could be incorrect
952 // since it does not call through to Write() which a subclass might have overridden.
953 // To be safe we will only use this implementation in cases where we know it is safe to do so,
954 // and delegate to our base class (which will call into Write) when we are not sure.
955 if (GetType() != typeof(StreamWriter))
957 return base.WriteLineAsync(buffer, index, count);
960 ThrowIfDisposed();
961 CheckAsyncTaskInProgress();
963 Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
964 _asyncWriteTask = task;
966 return task;
969 public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
971 if (GetType() != typeof(StreamWriter))
973 return base.WriteLineAsync(buffer, cancellationToken);
976 ThrowIfDisposed();
977 CheckAsyncTaskInProgress();
979 if (cancellationToken.IsCancellationRequested)
981 return Task.FromCanceled(cancellationToken);
984 Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
985 _asyncWriteTask = task;
987 return task;
991 public override Task FlushAsync()
993 // If we have been inherited into a subclass, the following implementation could be incorrect
994 // since it does not call through to Flush() which a subclass might have overridden. To be safe
995 // we will only use this implementation in cases where we know it is safe to do so,
996 // and delegate to our base class (which will call into Flush) when we are not sure.
997 if (GetType() != typeof(StreamWriter))
999 return base.FlushAsync();
1002 // flushEncoder should be true at the end of the file and if
1003 // the user explicitly calls Flush (though not if AutoFlush is true).
1004 // This is required to flush any dangling characters from our UTF-7
1005 // and UTF-8 encoders.
1006 ThrowIfDisposed();
1007 CheckAsyncTaskInProgress();
1009 Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
1010 _asyncWriteTask = task;
1012 return task;
1015 private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
1016 char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
1018 if (cancellationToken.IsCancellationRequested)
1020 return Task.FromCanceled(cancellationToken);
1023 // Perf boost for Flush on non-dirty writers.
1024 if (sCharPos == 0 && !flushStream && !flushEncoder)
1026 return Task.CompletedTask;
1029 Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
1030 _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
1032 _charPos = 0;
1033 return flushTask;
1037 // We pass in private instance fields of this MarshalByRefObject-derived type as local params
1038 // to ensure performant access inside the state machine that corresponds this async method.
1039 private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
1040 char[] charBuffer, int charPos, bool haveWrittenPreamble,
1041 Encoding encoding, Encoder encoder, byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
1043 if (!haveWrittenPreamble)
1045 _this._haveWrittenPreamble = true;
1046 byte[] preamble = encoding.GetPreamble();
1047 if (preamble.Length > 0)
1049 await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false);
1053 int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
1054 if (count > 0)
1056 await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false);
1059 // By definition, calling Flush should flush the stream, but this is
1060 // only necessary if we passed in true for flushStream. The Web
1061 // Services guys have some perf tests where flushing needlessly hurts.
1062 if (flushStream)
1064 await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
1068 private void ThrowIfDisposed()
1070 if (_disposed)
1072 ThrowObjectDisposedException();
1075 void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_WriterClosed);
1077 } // class StreamWriter
1078 } // namespace