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
;
10 using System
.Threading
;
11 using System
.Threading
.Tasks
;
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
;
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();
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
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
);
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
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
)
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
);
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()
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);
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.
220 base.Dispose(disposing
);
225 public override ValueTask
DisposeAsync() =>
226 GetType() != typeof(StreamWriter
) ?
227 base.DisposeAsync() :
230 private async ValueTask
DisposeAsyncCore()
232 // Same logic as in Dispose(), but with async flushing.
233 Debug
.Assert(GetType() == typeof(StreamWriter
));
238 await FlushAsync().ConfigureAwait(false);
243 CloseStreamFromDispose(disposing
: true);
245 GC
.SuppressFinalize(this);
248 public override void Flush()
250 CheckAsyncTaskInProgress();
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.
263 // Perf boost for Flush on non-dirty writers.
264 if (_charPos
== 0 && !flushStream
&& !flushEncoder
)
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
);
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.
294 public virtual bool AutoFlush
296 get { return _autoFlush; }
300 CheckAsyncTaskInProgress();
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
)
323 _charBuffer
[_charPos
] = value;
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
)
342 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
346 throw new ArgumentOutOfRangeException(nameof(index
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
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);
369 // If a derived class may have overridden existing Write behavior,
370 // we need to make sure we use it.
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
];
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.
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
409 if (dstPos
== charBuffer
.Length
)
415 int n
= Math
.Min(charBuffer
.Length
- dstPos
, count
);
416 int bytesToCopy
= n
* sizeof(char);
418 Buffer
.MemoryCopy(srcPtr
, dstPtr
+ dstPos
, bytesToCopy
, bytesToCopy
);
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
)
438 _charBuffer
[_charPos
] = coreNewLine
[i
];
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);
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
)
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();
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);
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);
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);
531 base.Write(format
, arg0
, arg1
, arg2
);
535 public override void Write(string format
, params object?[] arg
)
537 if (GetType() == typeof(StreamWriter
))
541 throw new ArgumentNullException((format
== null) ? nameof(format
) : nameof(arg
)); // same as base logic
543 WriteFormatHelper(format
, new ParamsArray(arg
), appendNewLine
: false);
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);
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);
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);
583 base.WriteLine(format
, arg0
, arg1
, arg2
);
587 public override void WriteLine(string format
, params object?[] arg
)
589 if (GetType() == typeof(StreamWriter
))
593 throw new ArgumentNullException(nameof(arg
));
595 WriteFormatHelper(format
, new ParamsArray(arg
), appendNewLine
: true);
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);
615 CheckAsyncTaskInProgress();
617 Task task
= WriteAsyncInternal(this, value, _charBuffer
, _charPos
, _charLen
, CoreNewLine
, _autoFlush
, appendNewLine
: false);
618 _asyncWriteTask
= 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);
637 charBuffer
[charPos
] = value;
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);
651 charBuffer
[charPos
] = coreNewLine
[i
];
658 await _this
.FlushAsyncInternal(true, false, charBuffer
, charPos
).ConfigureAwait(false);
659 Debug
.Assert(_this
._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);
680 CheckAsyncTaskInProgress();
682 Task task
= WriteAsyncInternal(this, value, _charBuffer
, _charPos
, _charLen
, CoreNewLine
, _autoFlush
, appendNewLine
: false);
683 _asyncWriteTask
= task
;
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
;
707 if (charPos
== charLen
)
709 await _this
.FlushAsyncInternal(false, false, charBuffer
, charPos
).ConfigureAwait(false);
710 Debug
.Assert(_this
._charPos
== 0);
714 int n
= charLen
- charPos
;
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
);
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);
740 charBuffer
[charPos
] = coreNewLine
[i
];
747 await _this
.FlushAsyncInternal(true, false, charBuffer
, charPos
).ConfigureAwait(false);
748 Debug
.Assert(_this
._charPos
== 0);
752 _this
._charPos
= charPos
;
755 public override Task
WriteAsync(char[] buffer
, int index
, int count
)
759 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
763 throw new ArgumentOutOfRangeException(nameof(index
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
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
);
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
;
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
);
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
;
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
)
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);
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
));
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);
849 charBuffer
[charPos
] = coreNewLine
[i
];
856 await _this
.FlushAsyncInternal(true, false, charBuffer
, charPos
, cancellationToken
).ConfigureAwait(false);
857 Debug
.Assert(_this
._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();
876 CheckAsyncTaskInProgress();
878 Task task
= WriteAsyncInternal(this, ReadOnlyMemory
<char>.Empty
, _charBuffer
, _charPos
, _charLen
, CoreNewLine
, _autoFlush
, appendNewLine
: true, cancellationToken
: default);
879 _asyncWriteTask
= 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);
897 CheckAsyncTaskInProgress();
899 Task task
= WriteAsyncInternal(this, value, _charBuffer
, _charPos
, _charLen
, CoreNewLine
, _autoFlush
, appendNewLine
: true);
900 _asyncWriteTask
= task
;
906 public override Task
WriteLineAsync(string? value)
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);
923 CheckAsyncTaskInProgress();
925 Task task
= WriteAsyncInternal(this, value, _charBuffer
, _charPos
, _charLen
, CoreNewLine
, _autoFlush
, appendNewLine
: true);
926 _asyncWriteTask
= task
;
932 public override Task
WriteLineAsync(char[] buffer
, int index
, int count
)
936 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
940 throw new ArgumentOutOfRangeException(nameof(index
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
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
);
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
;
969 public override Task
WriteLineAsync(ReadOnlyMemory
<char> buffer
, CancellationToken cancellationToken
= default)
971 if (GetType() != typeof(StreamWriter
))
973 return base.WriteLineAsync(buffer
, cancellationToken
);
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
;
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.
1007 CheckAsyncTaskInProgress();
1009 Task task
= FlushAsyncInternal(true, true, _charBuffer
, _charPos
);
1010 _asyncWriteTask
= 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
);
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
);
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.
1064 await stream
.FlushAsync(cancellationToken
).ConfigureAwait(false);
1068 private void ThrowIfDisposed()
1072 ThrowObjectDisposedException();
1075 void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name
, SR
.ObjectDisposed_WriterClosed
);
1077 } // class StreamWriter