AsReadOnlySpan -> AsSpan rename to fix build breaks
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / IO / StreamWriter.cs
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.
4
5 using System.Diagnostics;
6 using System.Runtime.InteropServices;
7 using System.Text;
8 using System.Threading;
9 using System.Threading.Tasks;
10
11 namespace System.IO
12 {
13     // This class implements a TextWriter for writing characters to a Stream.
14     // This is designed for character output in a particular Encoding, 
15     // whereas the Stream class is designed for byte input and output.  
16     public class StreamWriter : TextWriter
17     {
18         // For UTF-8, the values of 1K for the default buffer size and 4K for the
19         // file stream buffer size are reasonable & give very reasonable
20         // performance for in terms of construction time for the StreamWriter and
21         // write perf.  Note that for UTF-8, we end up allocating a 4K byte buffer,
22         // which means we take advantage of adaptive buffering code.
23         // The performance using UnicodeEncoding is acceptable.  
24         private const int DefaultBufferSize = 1024;   // char[]
25         private const int DefaultFileStreamBufferSize = 4096;
26         private const int MinBufferSize = 128;
27
28         private const int DontCopyOnWriteLineThreshold = 512;
29
30         // Bit bucket - Null has no backing store. Non closable.
31         public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, true);
32
33         private Stream _stream;
34         private Encoding _encoding;
35         private Encoder _encoder;
36         private byte[] _byteBuffer;
37         private char[] _charBuffer;
38         private int _charPos;
39         private int _charLen;
40         private bool _autoFlush;
41         private bool _haveWrittenPreamble;
42         private bool _closable;
43
44         // We don't guarantee thread safety on StreamWriter, but we should at 
45         // least prevent users from trying to write anything while an Async
46         // write from the same thread is in progress.
47         private volatile Task _asyncWriteTask;
48
49         private void CheckAsyncTaskInProgress()
50         {
51             // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety. 
52             // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
53
54             Task t = _asyncWriteTask;
55
56             if (t != null && !t.IsCompleted)
57                 throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
58         }
59
60         // The high level goal is to be tolerant of encoding errors when we read and very strict 
61         // when we write. Hence, default StreamWriter encoding will throw on encoding error.   
62         // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character 
63         // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the 
64         // internal StreamWriter's state to be irrecoverable as it would have buffered the 
65         // illegal chars and any subsequent call to Flush() would hit the encoding error again. 
66         // Even Close() will hit the exception as it would try to flush the unwritten data. 
67         // Maybe we can add a DiscardBufferedData() method to get out of such situation (like 
68         // StreamReader though for different reason). Either way, the buffered data will be lost!
69         private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
70
71
72         internal StreamWriter() : base(null)
73         { // Ask for CurrentCulture all the time 
74         }
75
76         public StreamWriter(Stream stream)
77             : this(stream, UTF8NoBOM, DefaultBufferSize, false)
78         {
79         }
80
81         public StreamWriter(Stream stream, Encoding encoding)
82             : this(stream, encoding, DefaultBufferSize, false)
83         {
84         }
85
86         // Creates a new StreamWriter for the given stream.  The 
87         // character encoding is set by encoding and the buffer size, 
88         // in number of 16-bit characters, is set by bufferSize.  
89         // 
90         public StreamWriter(Stream stream, Encoding encoding, int bufferSize)
91             : this(stream, encoding, bufferSize, false)
92         {
93         }
94
95         public StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool leaveOpen)
96             : base(null) // Ask for CurrentCulture all the time
97         {
98             if (stream == null || encoding == null)
99             {
100                 throw new ArgumentNullException(stream == null ? nameof(stream) : nameof(encoding));
101             }
102             if (!stream.CanWrite)
103             {
104                 throw new ArgumentException(SR.Argument_StreamNotWritable);
105             }
106             if (bufferSize <= 0)
107             {
108                 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
109             }
110
111             Init(stream, encoding, bufferSize, leaveOpen);
112         }
113
114         public StreamWriter(string path)
115             : this(path, false, UTF8NoBOM, DefaultBufferSize)
116         {
117         }
118
119         public StreamWriter(string path, bool append)
120             : this(path, append, UTF8NoBOM, DefaultBufferSize)
121         {
122         }
123
124         public StreamWriter(string path, bool append, Encoding encoding)
125             : this(path, append, encoding, DefaultBufferSize)
126         {
127         }
128
129         public StreamWriter(string path, bool append, Encoding encoding, int bufferSize)
130         { 
131             if (path == null)
132                 throw new ArgumentNullException(nameof(path));
133             if (encoding == null)
134                 throw new ArgumentNullException(nameof(encoding));
135             if (path.Length == 0)
136                 throw new ArgumentException(SR.Argument_EmptyPath);
137             if (bufferSize <= 0)
138                 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
139
140             Stream stream = new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read,
141                 DefaultFileStreamBufferSize, FileOptions.SequentialScan);
142             Init(stream, encoding, bufferSize, shouldLeaveOpen: false);
143         }
144
145         private void Init(Stream streamArg, Encoding encodingArg, int bufferSize, bool shouldLeaveOpen)
146         {
147             _stream = streamArg;
148             _encoding = encodingArg;
149             _encoder = _encoding.GetEncoder();
150             if (bufferSize < MinBufferSize)
151             {
152                 bufferSize = MinBufferSize;
153             }
154
155             _charBuffer = new char[bufferSize];
156             _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)];
157             _charLen = bufferSize;
158             // If we're appending to a Stream that already has data, don't write
159             // the preamble.
160             if (_stream.CanSeek && _stream.Position > 0)
161             {
162                 _haveWrittenPreamble = true;
163             }
164
165             _closable = !shouldLeaveOpen;
166         }
167
168         public override void Close()
169         {
170             Dispose(true);
171             GC.SuppressFinalize(this);
172         }
173
174         protected override void Dispose(bool disposing)
175         {
176             try
177             {
178                 // We need to flush any buffered data if we are being closed/disposed.
179                 // Also, we never close the handles for stdout & friends.  So we can safely 
180                 // write any buffered data to those streams even during finalization, which 
181                 // is generally the right thing to do.
182                 if (_stream != null)
183                 {
184                     // Note: flush on the underlying stream can throw (ex., low disk space)
185                     if (disposing /* || (LeaveOpen && stream is __ConsoleStream) */)
186                     {
187                         CheckAsyncTaskInProgress();
188
189                         Flush(true, true);
190                     }
191                 }
192             }
193             finally
194             {
195                 // Dispose of our resources if this StreamWriter is closable. 
196                 // Note: Console.Out and other such non closable streamwriters should be left alone 
197                 if (!LeaveOpen && _stream != null)
198                 {
199                     try
200                     {
201                         // Attempt to close the stream even if there was an IO error from Flushing.
202                         // Note that Stream.Close() can potentially throw here (may or may not be
203                         // due to the same Flush error). In this case, we still need to ensure 
204                         // cleaning up internal resources, hence the finally block.  
205                         if (disposing)
206                         {
207                             _stream.Close();
208                         }
209                     }
210                     finally
211                     {
212                         _stream = null;
213                         _byteBuffer = null;
214                         _charBuffer = null;
215                         _encoding = null;
216                         _encoder = null;
217                         _charLen = 0;
218                         base.Dispose(disposing);
219                     }
220                 }
221             }
222         }
223
224         public override void Flush()
225         {
226             CheckAsyncTaskInProgress();
227
228             Flush(true, true);
229         }
230
231         private void Flush(bool flushStream, bool flushEncoder)
232         {
233             // flushEncoder should be true at the end of the file and if
234             // the user explicitly calls Flush (though not if AutoFlush is true).
235             // This is required to flush any dangling characters from our UTF-7 
236             // and UTF-8 encoders.  
237             if (_stream == null)
238             {
239                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
240             }
241
242             // Perf boost for Flush on non-dirty writers.
243             if (_charPos == 0 && !flushStream && !flushEncoder)
244             {
245                 return;
246             }
247
248             if (!_haveWrittenPreamble)
249             {
250                 _haveWrittenPreamble = true;
251                 ReadOnlySpan<byte> preamble = _encoding.Preamble;
252                 if (preamble.Length > 0)
253                 {
254                     _stream.Write(preamble);
255                 }
256             }
257
258             int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder);
259             _charPos = 0;
260             if (count > 0)
261             {
262                 _stream.Write(_byteBuffer, 0, count);
263             }
264             // By definition, calling Flush should flush the stream, but this is
265             // only necessary if we passed in true for flushStream.  The Web
266             // Services guys have some perf tests where flushing needlessly hurts.
267             if (flushStream)
268             {
269                 _stream.Flush();
270             }
271         }
272
273         public virtual bool AutoFlush
274         {
275             get { return _autoFlush; }
276
277             set
278             {
279                 CheckAsyncTaskInProgress();
280
281                 _autoFlush = value;
282                 if (value)
283                 {
284                     Flush(true, false);
285                 }
286             }
287         }
288
289         public virtual Stream BaseStream
290         {
291             get { return _stream; }
292         }
293
294         internal bool LeaveOpen
295         {
296             get { return !_closable; }
297         }
298
299         internal bool HaveWrittenPreamble
300         {
301             set { _haveWrittenPreamble = value; }
302         }
303
304         public override Encoding Encoding
305         {
306             get { return _encoding; }
307         }
308
309         public override void Write(char value)
310         {
311             CheckAsyncTaskInProgress();
312
313             if (_charPos == _charLen)
314             {
315                 Flush(false, false);
316             }
317
318             _charBuffer[_charPos] = value;
319             _charPos++;
320             if (_autoFlush)
321             {
322                 Flush(true, false);
323             }
324         }
325
326         public override void Write(char[] buffer)
327         {
328             if (buffer != null)
329             {
330                 WriteCore(buffer, _autoFlush);
331             }
332         }
333
334         public override void Write(char[] buffer, int index, int count)
335         {
336             if (buffer == null)
337             {
338                 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
339             }
340             if (index < 0)
341             {
342                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
343             }
344             if (count < 0)
345             {
346                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
347             }
348             if (buffer.Length - index < count)
349             {
350                 throw new ArgumentException(SR.Argument_InvalidOffLen);
351             }
352
353             WriteCore(new ReadOnlySpan<char>(buffer, index, count), _autoFlush);
354         }
355
356         public override void Write(ReadOnlySpan<char> buffer)
357         {
358             if (GetType() == typeof(StreamWriter))
359             {
360                 WriteCore(buffer, _autoFlush);
361             }
362             else
363             {
364                 // If a derived class may have overridden existing Write behavior,
365                 // we need to make sure we use it.
366                 base.Write(buffer);
367             }
368         }
369
370         private unsafe void WriteCore(ReadOnlySpan<char> buffer, bool autoFlush)
371         {
372             CheckAsyncTaskInProgress();
373
374             if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
375                 buffer.Length <= _charLen - _charPos)
376             {
377                 // For very short buffers and when we don't need to worry about running out of space
378                 // in the char buffer, just copy the chars individually.
379                 for (int i = 0; i < buffer.Length; i++)
380                 {
381                     _charBuffer[_charPos++] = buffer[i];
382                 }
383             }
384             else
385             {
386                 // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
387                 // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
388                 // resulting in significant overhead (even when the if branch above is taken rather than this
389                 // else) due to temporaries that need to be cleared.  Given the use of unsafe code, we also
390                 // make local copies of instance state to protect against potential concurrent misuse.
391
392                 char[] charBuffer = _charBuffer;
393                 if (charBuffer == null)
394                 {
395                     throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
396                 }
397
398                 fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
399                 fixed (char* dstPtr = &charBuffer[0])
400                 {
401                     char* srcPtr = bufferPtr;
402                     int count = buffer.Length;
403                     int dstPos = _charPos; // use a local copy of _charPos for safety
404                     while (count > 0)
405                     {
406                         if (dstPos == charBuffer.Length)
407                         {
408                             Flush(false, false);
409                             dstPos = 0;
410                         }
411
412                         int n = Math.Min(charBuffer.Length - dstPos, count);
413                         int bytesToCopy = n * sizeof(char);
414
415                         Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
416
417                         _charPos += n;
418                         dstPos += n;
419                         srcPtr += n;
420                         count -= n;
421                     }
422                 }
423             }
424
425             if (autoFlush)
426             {
427                 Flush(true, false);
428             }
429         }
430
431         public override void Write(string value)
432         {
433             if (value != null)
434             {
435                 WriteCore(value.AsSpan(), _autoFlush);
436             }
437         }
438
439         //
440         // Optimize the most commonly used WriteLine overload. This optimization is important for System.Console in particular
441         // because of it will make one WriteLine equal to one call to the OS instead of two in the common case.
442         //
443         public override void WriteLine(string value)
444         {
445             CheckAsyncTaskInProgress();
446             if (value != null)
447             {
448                 WriteCore(value.AsSpan(), autoFlush: false);
449             }
450             WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
451         }
452
453         public override void WriteLine(ReadOnlySpan<char> value)
454         {
455             if (GetType() == typeof(StreamWriter))
456             {
457                 CheckAsyncTaskInProgress();
458                 WriteCore(value, autoFlush: false);
459                 WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
460             }
461             else
462             {
463                 // If a derived class may have overridden existing WriteLine behavior,
464                 // we need to make sure we use it.
465                 base.WriteLine(value);
466             }
467         }
468
469         #region Task based Async APIs
470         public override Task WriteAsync(char value)
471         {
472             // If we have been inherited into a subclass, the following implementation could be incorrect
473             // since it does not call through to Write() which a subclass might have overridden.  
474             // To be safe we will only use this implementation in cases where we know it is safe to do so,
475             // and delegate to our base class (which will call into Write) when we are not sure.
476             if (GetType() != typeof(StreamWriter))
477             {
478                 return base.WriteAsync(value);
479             }
480
481             if (_stream == null)
482             {
483                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
484             }
485
486             CheckAsyncTaskInProgress();
487
488             Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
489             _asyncWriteTask = task;
490
491             return task;
492         }
493
494         // We pass in private instance fields of this MarshalByRefObject-derived type as local params
495         // to ensure performant access inside the state machine that corresponds this async method.
496         // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
497         private static async Task WriteAsyncInternal(StreamWriter _this, char value,
498                                                      char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
499                                                      bool autoFlush, bool appendNewLine)
500         {
501             if (charPos == charLen)
502             {
503                 await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
504                 Debug.Assert(_this._charPos == 0);
505                 charPos = 0;
506             }
507
508             charBuffer[charPos] = value;
509             charPos++;
510
511             if (appendNewLine)
512             {
513                 for (int i = 0; i < coreNewLine.Length; i++)   // Expect 2 iterations, no point calling BlockCopy
514                 {
515                     if (charPos == charLen)
516                     {
517                         await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
518                         Debug.Assert(_this._charPos == 0);
519                         charPos = 0;
520                     }
521
522                     charBuffer[charPos] = coreNewLine[i];
523                     charPos++;
524                 }
525             }
526
527             if (autoFlush)
528             {
529                 await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
530                 Debug.Assert(_this._charPos == 0);
531                 charPos = 0;
532             }
533
534             _this.CharPos_Prop = charPos;
535         }
536
537         public override Task WriteAsync(string value)
538         {
539             // If we have been inherited into a subclass, the following implementation could be incorrect
540             // since it does not call through to Write() which a subclass might have overridden.  
541             // To be safe we will only use this implementation in cases where we know it is safe to do so,
542             // and delegate to our base class (which will call into Write) when we are not sure.
543             if (GetType() != typeof(StreamWriter))
544             {
545                 return base.WriteAsync(value);
546             }
547
548             if (value != null)
549             {
550                 if (_stream == null)
551                 {
552                     throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
553                 }
554
555                 CheckAsyncTaskInProgress();
556
557                 Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
558                 _asyncWriteTask = task;
559
560                 return task;
561             }
562             else
563             {
564                 return Task.CompletedTask;
565             }
566         }
567
568         // We pass in private instance fields of this MarshalByRefObject-derived type as local params
569         // to ensure performant access inside the state machine that corresponds this async method.
570         // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
571         private static async Task WriteAsyncInternal(StreamWriter _this, string value,
572                                                      char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
573                                                      bool autoFlush, bool appendNewLine)
574         {
575             Debug.Assert(value != null);
576
577             int count = value.Length;
578             int index = 0;
579
580             while (count > 0)
581             {
582                 if (charPos == charLen)
583                 {
584                     await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
585                     Debug.Assert(_this._charPos == 0);
586                     charPos = 0;
587                 }
588
589                 int n = charLen - charPos;
590                 if (n > count)
591                 {
592                     n = count;
593                 }
594
595                 Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress!  This is most likely a race condition in user code.");
596
597                 value.CopyTo(index, charBuffer, charPos, n);
598
599                 charPos += n;
600                 index += n;
601                 count -= n;
602             }
603
604             if (appendNewLine)
605             {
606                 for (int i = 0; i < coreNewLine.Length; i++)   // Expect 2 iterations, no point calling BlockCopy
607                 {
608                     if (charPos == charLen)
609                     {
610                         await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
611                         Debug.Assert(_this._charPos == 0);
612                         charPos = 0;
613                     }
614
615                     charBuffer[charPos] = coreNewLine[i];
616                     charPos++;
617                 }
618             }
619
620             if (autoFlush)
621             {
622                 await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
623                 Debug.Assert(_this._charPos == 0);
624                 charPos = 0;
625             }
626
627             _this.CharPos_Prop = charPos;
628         }
629
630         public override Task WriteAsync(char[] buffer, int index, int count)
631         {
632             if (buffer == null)
633             {
634                 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
635             }
636             if (index < 0)
637             {
638                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
639             }
640             if (count < 0)
641             {
642                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
643             }
644             if (buffer.Length - index < count)
645             {
646                 throw new ArgumentException(SR.Argument_InvalidOffLen);
647             }
648
649             // If we have been inherited into a subclass, the following implementation could be incorrect
650             // since it does not call through to Write() which a subclass might have overridden.  
651             // To be safe we will only use this implementation in cases where we know it is safe to do so,
652             // and delegate to our base class (which will call into Write) when we are not sure.
653             if (GetType() != typeof(StreamWriter))
654             {
655                 return base.WriteAsync(buffer, index, count);
656             }
657
658             if (_stream == null)
659             {
660                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
661             }
662
663             CheckAsyncTaskInProgress();
664
665             Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
666             _asyncWriteTask = task;
667
668             return task;
669         }
670
671         public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
672         {
673             if (GetType() != typeof(StreamWriter))
674             {
675                 // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
676                 return base.WriteAsync(buffer, cancellationToken);
677             }
678
679             if (_stream == null)
680             {
681                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
682             }
683
684             CheckAsyncTaskInProgress();
685
686             if (cancellationToken.IsCancellationRequested)
687             {
688                 return Task.FromCanceled(cancellationToken);
689             }
690
691             Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
692             _asyncWriteTask = task;
693             return task;
694         }
695
696         // We pass in private instance fields of this MarshalByRefObject-derived type as local params
697         // to ensure performant access inside the state machine that corresponds this async method.
698         // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
699         private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
700                                                      char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
701                                                      bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
702         {
703             int copied = 0;
704             while (copied < source.Length)
705             {
706                 if (charPos == charLen)
707                 {
708                     await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
709                     Debug.Assert(_this._charPos == 0);
710                     charPos = 0;
711                 }
712
713                 int n = Math.Min(charLen - charPos, source.Length - copied);
714                 Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress!  This is most likely a race condition in user code.");
715
716                 source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
717                 charPos += n;
718                 copied += n;
719             }
720
721             if (appendNewLine)
722             {
723                 for (int i = 0; i < coreNewLine.Length; i++)   // Expect 2 iterations, no point calling BlockCopy
724                 {
725                     if (charPos == charLen)
726                     {
727                         await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
728                         Debug.Assert(_this._charPos == 0);
729                         charPos = 0;
730                     }
731
732                     charBuffer[charPos] = coreNewLine[i];
733                     charPos++;
734                 }
735             }
736
737             if (autoFlush)
738             {
739                 await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
740                 Debug.Assert(_this._charPos == 0);
741                 charPos = 0;
742             }
743
744             _this.CharPos_Prop = charPos;
745         }
746
747         public override Task WriteLineAsync()
748         {
749             // If we have been inherited into a subclass, the following implementation could be incorrect
750             // since it does not call through to Write() which a subclass might have overridden.  
751             // To be safe we will only use this implementation in cases where we know it is safe to do so,
752             // and delegate to our base class (which will call into Write) when we are not sure.
753             if (GetType() != typeof(StreamWriter))
754             {
755                 return base.WriteLineAsync();
756             }
757
758             if (_stream == null)
759             {
760                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
761             }
762
763             CheckAsyncTaskInProgress();
764
765             Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
766             _asyncWriteTask = task;
767
768             return task;
769         }
770
771
772         public override Task WriteLineAsync(char value)
773         {
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))
779             {
780                 return base.WriteLineAsync(value);
781             }
782
783             if (_stream == null)
784             {
785                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
786             }
787
788             CheckAsyncTaskInProgress();
789
790             Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
791             _asyncWriteTask = task;
792
793             return task;
794         }
795
796
797         public override Task WriteLineAsync(string value)
798         {
799             if (value == null)
800             {
801                 return WriteLineAsync();
802             }
803
804             // If we have been inherited into a subclass, the following implementation could be incorrect
805             // since it does not call through to Write() which a subclass might have overridden.  
806             // To be safe we will only use this implementation in cases where we know it is safe to do so,
807             // and delegate to our base class (which will call into Write) when we are not sure.
808             if (GetType() != typeof(StreamWriter))
809             {
810                 return base.WriteLineAsync(value);
811             }
812
813             if (_stream == null)
814             {
815                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
816             }
817
818             CheckAsyncTaskInProgress();
819
820             Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
821             _asyncWriteTask = task;
822
823             return task;
824         }
825
826
827         public override Task WriteLineAsync(char[] buffer, int index, int count)
828         {
829             if (buffer == null)
830             {
831                 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
832             }
833             if (index < 0)
834             {
835                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
836             }
837             if (count < 0)
838             {
839                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
840             }
841             if (buffer.Length - index < count)
842             {
843                 throw new ArgumentException(SR.Argument_InvalidOffLen);
844             }
845
846             // If we have been inherited into a subclass, the following implementation could be incorrect
847             // since it does not call through to Write() which a subclass might have overridden.  
848             // To be safe we will only use this implementation in cases where we know it is safe to do so,
849             // and delegate to our base class (which will call into Write) when we are not sure.
850             if (GetType() != typeof(StreamWriter))
851             {
852                 return base.WriteLineAsync(buffer, index, count);
853             }
854
855             if (_stream == null)
856             {
857                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
858             }
859
860             CheckAsyncTaskInProgress();
861
862             Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
863             _asyncWriteTask = task;
864
865             return task;
866         }
867
868         public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
869         {
870             if (GetType() != typeof(StreamWriter))
871             {
872                 return base.WriteLineAsync(buffer, cancellationToken);
873             }
874
875             if (_stream == null)
876             {
877                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
878             }
879
880             CheckAsyncTaskInProgress();
881
882             if (cancellationToken.IsCancellationRequested)
883             {
884                 return Task.FromCanceled(cancellationToken);
885             }
886
887             Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
888             _asyncWriteTask = task;
889
890             return task;
891         }
892
893
894         public override Task FlushAsync()
895         {
896             // If we have been inherited into a subclass, the following implementation could be incorrect
897             // since it does not call through to Flush() which a subclass might have overridden.  To be safe 
898             // we will only use this implementation in cases where we know it is safe to do so,
899             // and delegate to our base class (which will call into Flush) when we are not sure.
900             if (GetType() != typeof(StreamWriter))
901             {
902                 return base.FlushAsync();
903             }
904
905             // flushEncoder should be true at the end of the file and if
906             // the user explicitly calls Flush (though not if AutoFlush is true).
907             // This is required to flush any dangling characters from our UTF-7 
908             // and UTF-8 encoders.  
909             if (_stream == null)
910             {
911                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
912             }
913
914             CheckAsyncTaskInProgress();
915
916             Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
917             _asyncWriteTask = task;
918
919             return task;
920         }
921
922         private int CharPos_Prop
923         {
924             set { _charPos = value; }
925         }
926
927         private bool HaveWrittenPreamble_Prop
928         {
929             set { _haveWrittenPreamble = value; }
930         }
931
932         private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
933                                         char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
934         {
935             if (cancellationToken.IsCancellationRequested)
936             {
937                 return Task.FromCanceled(cancellationToken);
938             }
939
940             // Perf boost for Flush on non-dirty writers.
941             if (sCharPos == 0 && !flushStream && !flushEncoder)
942             {
943                 return Task.CompletedTask;
944             }
945
946             Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
947                                                 _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
948
949             _charPos = 0;
950             return flushTask;
951         }
952
953
954         // We pass in private instance fields of this MarshalByRefObject-derived type as local params
955         // to ensure performant access inside the state machine that corresponds this async method.
956         private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
957                                                      char[] charBuffer, int charPos, bool haveWrittenPreamble,
958                                                      Encoding encoding, Encoder encoder, Byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
959         {
960             if (!haveWrittenPreamble)
961             {
962                 _this.HaveWrittenPreamble_Prop = true;
963                 byte[] preamble = encoding.GetPreamble();
964                 if (preamble.Length > 0)
965                 {
966                     await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false);
967                 }
968             }
969
970             int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
971             if (count > 0)
972             {
973                 await stream.WriteAsync(byteBuffer, 0, count, cancellationToken).ConfigureAwait(false);
974             }
975
976             // By definition, calling Flush should flush the stream, but this is
977             // only necessary if we passed in true for flushStream.  The Web
978             // Services guys have some perf tests where flushing needlessly hurts.
979             if (flushStream)
980             {
981                 await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
982             }
983         }
984         #endregion
985     }  // class StreamWriter
986 }  // namespace