| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using System.Diagnostics;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- namespace System.IO
- {
- // This class implements a TextWriter for writing characters to a Stream.
- // This is designed for character output in a particular Encoding,
- // whereas the Stream class is designed for byte input and output.
- public class StreamWriter : TextWriter
- {
- // For UTF-8, the values of 1K for the default buffer size and 4K for the
- // file stream buffer size are reasonable & give very reasonable
- // performance for in terms of construction time for the StreamWriter and
- // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
- // which means we take advantage of adaptive buffering code.
- // The performance using UnicodeEncoding is acceptable.
- private const int DefaultBufferSize = 1024; // char[]
- private const int DefaultFileStreamBufferSize = 4096;
- private const int MinBufferSize = 128;
- private const int DontCopyOnWriteLineThreshold = 512;
- // Bit bucket - Null has no backing store. Non closable.
- public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, true);
- private Stream _stream;
- private Encoding _encoding;
- private Encoder _encoder;
- private byte[] _byteBuffer;
- private char[] _charBuffer;
- private int _charPos;
- private int _charLen;
- private bool _autoFlush;
- private bool _haveWrittenPreamble;
- private bool _closable;
- // We don't guarantee thread safety on StreamWriter, but we should at
- // least prevent users from trying to write anything while an Async
- // write from the same thread is in progress.
- private Task _asyncWriteTask = Task.CompletedTask;
- private void CheckAsyncTaskInProgress()
- {
- // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety.
- // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
- if (!_asyncWriteTask.IsCompleted)
- {
- ThrowAsyncIOInProgress();
- }
- }
- private static void ThrowAsyncIOInProgress() =>
- throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
- // The high level goal is to be tolerant of encoding errors when we read and very strict
- // when we write. Hence, default StreamWriter encoding will throw on encoding error.
- // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
- // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
- // internal StreamWriter's state to be irrecoverable as it would have buffered the
- // illegal chars and any subsequent call to Flush() would hit the encoding error again.
- // Even Close() will hit the exception as it would try to flush the unwritten data.
- // Maybe we can add a DiscardBufferedData() method to get out of such situation (like
- // StreamReader though for different reason). Either way, the buffered data will be lost!
- private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
- internal StreamWriter() : base(null)
- { // Ask for CurrentCulture all the time
- }
- public StreamWriter(Stream stream)
- : this(stream, UTF8NoBOM, DefaultBufferSize, false)
- {
- }
- public StreamWriter(Stream stream, Encoding encoding)
- : this(stream, encoding, DefaultBufferSize, false)
- {
- }
- // Creates a new StreamWriter for the given stream. The
- // character encoding is set by encoding and the buffer size,
- // in number of 16-bit characters, is set by bufferSize.
- //
- public StreamWriter(Stream stream, Encoding encoding, int bufferSize)
- : this(stream, encoding, bufferSize, false)
- {
- }
- public StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool leaveOpen)
- : base(null) // Ask for CurrentCulture all the time
- {
- if (stream == null || encoding == null)
- {
- throw new ArgumentNullException(stream == null ? nameof(stream) : nameof(encoding));
- }
- if (!stream.CanWrite)
- {
- throw new ArgumentException(SR.Argument_StreamNotWritable);
- }
- if (bufferSize <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
- }
- Init(stream, encoding, bufferSize, leaveOpen);
- }
- public StreamWriter(string path)
- : this(path, false, UTF8NoBOM, DefaultBufferSize)
- {
- }
- public StreamWriter(string path, bool append)
- : this(path, append, UTF8NoBOM, DefaultBufferSize)
- {
- }
- public StreamWriter(string path, bool append, Encoding encoding)
- : this(path, append, encoding, DefaultBufferSize)
- {
- }
- public StreamWriter(string path, bool append, Encoding encoding, int bufferSize)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (encoding == null)
- throw new ArgumentNullException(nameof(encoding));
- if (path.Length == 0)
- throw new ArgumentException(SR.Argument_EmptyPath);
- if (bufferSize <= 0)
- throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
- Stream stream = new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read,
- DefaultFileStreamBufferSize, FileOptions.SequentialScan);
- Init(stream, encoding, bufferSize, shouldLeaveOpen: false);
- }
- private void Init(Stream streamArg, Encoding encodingArg, int bufferSize, bool shouldLeaveOpen)
- {
- _stream = streamArg;
- _encoding = encodingArg;
- _encoder = _encoding.GetEncoder();
- if (bufferSize < MinBufferSize)
- {
- bufferSize = MinBufferSize;
- }
- _charBuffer = new char[bufferSize];
- _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)];
- _charLen = bufferSize;
- // If we're appending to a Stream that already has data, don't write
- // the preamble.
- if (_stream.CanSeek && _stream.Position > 0)
- {
- _haveWrittenPreamble = true;
- }
- _closable = !shouldLeaveOpen;
- }
- public override void Close()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- protected override void Dispose(bool disposing)
- {
- try
- {
- // We need to flush any buffered data if we are being closed/disposed.
- // Also, we never close the handles for stdout & friends. So we can safely
- // write any buffered data to those streams even during finalization, which
- // is generally the right thing to do.
- if (_stream != null)
- {
- // Note: flush on the underlying stream can throw (ex., low disk space)
- if (disposing /* || (LeaveOpen && stream is __ConsoleStream) */)
- {
- CheckAsyncTaskInProgress();
- Flush(true, true);
- }
- }
- }
- finally
- {
- CloseStreamFromDispose(disposing);
- }
- }
- private void CloseStreamFromDispose(bool disposing)
- {
- // Dispose of our resources if this StreamWriter is closable.
- if (!LeaveOpen && _stream != null)
- {
- try
- {
- // Attempt to close the stream even if there was an IO error from Flushing.
- // Note that Stream.Close() can potentially throw here (may or may not be
- // due to the same Flush error). In this case, we still need to ensure
- // cleaning up internal resources, hence the finally block.
- if (disposing)
- {
- _stream.Close();
- }
- }
- finally
- {
- _stream = null;
- _byteBuffer = null;
- _charBuffer = null;
- _encoding = null;
- _encoder = null;
- _charLen = 0;
- base.Dispose(disposing);
- }
- }
- }
- public override ValueTask DisposeAsync() =>
- GetType() != typeof(StreamWriter) ?
- base.DisposeAsync() :
- DisposeAsyncCore();
- private async ValueTask DisposeAsyncCore()
- {
- // Same logic as in Dispose(), but with async flushing.
- Debug.Assert(GetType() == typeof(StreamWriter));
- try
- {
- if (_stream != null)
- {
- await FlushAsync().ConfigureAwait(false);
- }
- }
- finally
- {
- CloseStreamFromDispose(disposing: true);
- }
- GC.SuppressFinalize(this);
- }
- public override void Flush()
- {
- CheckAsyncTaskInProgress();
- Flush(true, true);
- }
- private void Flush(bool flushStream, bool flushEncoder)
- {
- // flushEncoder should be true at the end of the file and if
- // the user explicitly calls Flush (though not if AutoFlush is true).
- // This is required to flush any dangling characters from our UTF-7
- // and UTF-8 encoders.
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- // Perf boost for Flush on non-dirty writers.
- if (_charPos == 0 && !flushStream && !flushEncoder)
- {
- return;
- }
- if (!_haveWrittenPreamble)
- {
- _haveWrittenPreamble = true;
- ReadOnlySpan<byte> preamble = _encoding.Preamble;
- if (preamble.Length > 0)
- {
- _stream.Write(preamble);
- }
- }
- int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder);
- _charPos = 0;
- if (count > 0)
- {
- _stream.Write(_byteBuffer, 0, count);
- }
- // By definition, calling Flush should flush the stream, but this is
- // only necessary if we passed in true for flushStream. The Web
- // Services guys have some perf tests where flushing needlessly hurts.
- if (flushStream)
- {
- _stream.Flush();
- }
- }
- public virtual bool AutoFlush
- {
- get { return _autoFlush; }
- set
- {
- CheckAsyncTaskInProgress();
- _autoFlush = value;
- if (value)
- {
- Flush(true, false);
- }
- }
- }
- public virtual Stream BaseStream
- {
- get { return _stream; }
- }
- internal bool LeaveOpen
- {
- get { return !_closable; }
- }
- internal bool HaveWrittenPreamble
- {
- set { _haveWrittenPreamble = value; }
- }
- public override Encoding Encoding
- {
- get { return _encoding; }
- }
- public override void Write(char value)
- {
- CheckAsyncTaskInProgress();
- if (_charPos == _charLen)
- {
- Flush(false, false);
- }
- _charBuffer[_charPos] = value;
- _charPos++;
- if (_autoFlush)
- {
- Flush(true, false);
- }
- }
- [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
- public override void Write(char[] buffer)
- {
- WriteSpan(buffer, appendNewLine: false);
- }
- [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
- public override void Write(char[] buffer, int index, int count)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
- }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
- }
- if (buffer.Length - index < count)
- {
- throw new ArgumentException(SR.Argument_InvalidOffLen);
- }
- WriteSpan(buffer.AsSpan(index, count), appendNewLine: false);
- }
- [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
- public override void Write(ReadOnlySpan<char> buffer)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteSpan(buffer, appendNewLine: false);
- }
- else
- {
- // If a derived class may have overridden existing Write behavior,
- // we need to make sure we use it.
- base.Write(buffer);
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine)
- {
- CheckAsyncTaskInProgress();
- if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
- buffer.Length <= _charLen - _charPos)
- {
- // For very short buffers and when we don't need to worry about running out of space
- // in the char buffer, just copy the chars individually.
- for (int i = 0; i < buffer.Length; i++)
- {
- _charBuffer[_charPos++] = buffer[i];
- }
- }
- else
- {
- // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
- // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
- // resulting in significant overhead (even when the if branch above is taken rather than this
- // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also
- // make local copies of instance state to protect against potential concurrent misuse.
- char[] charBuffer = _charBuffer;
- if (charBuffer == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
- fixed (char* dstPtr = &charBuffer[0])
- {
- char* srcPtr = bufferPtr;
- int count = buffer.Length;
- int dstPos = _charPos; // use a local copy of _charPos for safety
- while (count > 0)
- {
- if (dstPos == charBuffer.Length)
- {
- Flush(false, false);
- dstPos = 0;
- }
- int n = Math.Min(charBuffer.Length - dstPos, count);
- int bytesToCopy = n * sizeof(char);
- Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
- _charPos += n;
- dstPos += n;
- srcPtr += n;
- count -= n;
- }
- }
- }
- if (appendNewLine)
- {
- char[] coreNewLine = CoreNewLine;
- for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
- {
- if (_charPos == _charLen)
- {
- Flush(false, false);
- }
- _charBuffer[_charPos] = coreNewLine[i];
- _charPos++;
- }
- }
- if (_autoFlush)
- {
- Flush(true, false);
- }
- }
- [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
- public override void Write(string value)
- {
- WriteSpan(value, appendNewLine: false);
- }
- [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
- public override void WriteLine(string value)
- {
- CheckAsyncTaskInProgress();
- WriteSpan(value, appendNewLine: true);
- }
- [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
- public override void WriteLine(ReadOnlySpan<char> value)
- {
- if (GetType() == typeof(StreamWriter))
- {
- CheckAsyncTaskInProgress();
- WriteSpan(value, appendNewLine: true);
- }
- else
- {
- // If a derived class may have overridden existing WriteLine behavior,
- // we need to make sure we use it.
- base.WriteLine(value);
- }
- }
- private void WriteFormatHelper(string format, ParamsArray args, bool appendNewLine)
- {
- StringBuilder sb =
- StringBuilderCache.Acquire(format.Length + args.Length * 8)
- .AppendFormatHelper(null, format, args);
- StringBuilder.ChunkEnumerator chunks = sb.GetChunks();
- bool more = chunks.MoveNext();
- while (more)
- {
- ReadOnlySpan<char> current = chunks.Current.Span;
- more = chunks.MoveNext();
- // If final chunk, include the newline if needed
- WriteSpan(current, appendNewLine: more ? false : appendNewLine);
- }
- StringBuilderCache.Release(sb);
- }
- public override void Write(string format, object arg0)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: false);
- }
- else
- {
- base.Write(format, arg0);
- }
- }
- public override void Write(string format, object arg0, object arg1)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: false);
- }
- else
- {
- base.Write(format, arg0, arg1);
- }
- }
- public override void Write(string format, object arg0, object arg1, object arg2)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: false);
- }
- else
- {
- base.Write(format, arg0, arg1, arg2);
- }
- }
- public override void Write(string format, params object[] arg)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: false);
- }
- else
- {
- base.Write(format, arg);
- }
- }
- public override void WriteLine(string format, object arg0)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: true);
- }
- else
- {
- base.WriteLine(format, arg0);
- }
- }
- public override void WriteLine(string format, object arg0, object arg1)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: true);
- }
- else
- {
- base.WriteLine(format, arg0, arg1);
- }
- }
- public override void WriteLine(string format, object arg0, object arg1, object arg2)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: true);
- }
- else
- {
- base.WriteLine(format, arg0, arg1, arg2);
- }
- }
- public override void WriteLine(string format, params object[] arg)
- {
- if (GetType() == typeof(StreamWriter))
- {
- WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: true);
- }
- else
- {
- base.WriteLine(format, arg);
- }
- }
- #region Task based Async APIs
- public override Task WriteAsync(char value)
- {
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteAsync(value);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
- _asyncWriteTask = task;
- return task;
- }
- // We pass in private instance fields of this MarshalByRefObject-derived type as local params
- // to ensure performant access inside the state machine that corresponds this async method.
- // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
- private static async Task WriteAsyncInternal(StreamWriter _this, char value,
- char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
- bool autoFlush, bool appendNewLine)
- {
- if (charPos == charLen)
- {
- await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- charBuffer[charPos] = value;
- charPos++;
- if (appendNewLine)
- {
- for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
- {
- if (charPos == charLen)
- {
- await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- charBuffer[charPos] = coreNewLine[i];
- charPos++;
- }
- }
- if (autoFlush)
- {
- await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- _this.CharPos_Prop = charPos;
- }
- public override Task WriteAsync(string value)
- {
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteAsync(value);
- }
- if (value != null)
- {
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
- _asyncWriteTask = task;
- return task;
- }
- else
- {
- return Task.CompletedTask;
- }
- }
- // We pass in private instance fields of this MarshalByRefObject-derived type as local params
- // to ensure performant access inside the state machine that corresponds this async method.
- // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
- private static async Task WriteAsyncInternal(StreamWriter _this, string value,
- char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
- bool autoFlush, bool appendNewLine)
- {
- Debug.Assert(value != null);
- int count = value.Length;
- int index = 0;
- while (count > 0)
- {
- if (charPos == charLen)
- {
- await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- int n = charLen - charPos;
- if (n > count)
- {
- n = count;
- }
- Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
- value.CopyTo(index, charBuffer, charPos, n);
- charPos += n;
- index += n;
- count -= n;
- }
- if (appendNewLine)
- {
- for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
- {
- if (charPos == charLen)
- {
- await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- charBuffer[charPos] = coreNewLine[i];
- charPos++;
- }
- }
- if (autoFlush)
- {
- await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- _this.CharPos_Prop = charPos;
- }
- public override Task WriteAsync(char[] buffer, int index, int count)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
- }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
- }
- if (buffer.Length - index < count)
- {
- throw new ArgumentException(SR.Argument_InvalidOffLen);
- }
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteAsync(buffer, index, count);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
- _asyncWriteTask = task;
- return task;
- }
- public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
- {
- if (GetType() != typeof(StreamWriter))
- {
- // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
- return base.WriteAsync(buffer, cancellationToken);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
- Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
- _asyncWriteTask = task;
- return task;
- }
- // We pass in private instance fields of this MarshalByRefObject-derived type as local params
- // to ensure performant access inside the state machine that corresponds this async method.
- // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
- private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
- char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
- bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
- {
- int copied = 0;
- while (copied < source.Length)
- {
- if (charPos == charLen)
- {
- await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- int n = Math.Min(charLen - charPos, source.Length - copied);
- Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
- source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
- charPos += n;
- copied += n;
- }
- if (appendNewLine)
- {
- for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
- {
- if (charPos == charLen)
- {
- await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- charBuffer[charPos] = coreNewLine[i];
- charPos++;
- }
- }
- if (autoFlush)
- {
- await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
- Debug.Assert(_this._charPos == 0);
- charPos = 0;
- }
- _this.CharPos_Prop = charPos;
- }
- public override Task WriteLineAsync()
- {
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteLineAsync();
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
- _asyncWriteTask = task;
- return task;
- }
- public override Task WriteLineAsync(char value)
- {
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteLineAsync(value);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
- _asyncWriteTask = task;
- return task;
- }
- public override Task WriteLineAsync(string value)
- {
- if (value == null)
- {
- return WriteLineAsync();
- }
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteLineAsync(value);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
- _asyncWriteTask = task;
- return task;
- }
- public override Task WriteLineAsync(char[] buffer, int index, int count)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
- }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
- }
- if (buffer.Length - index < count)
- {
- throw new ArgumentException(SR.Argument_InvalidOffLen);
- }
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() which a subclass might have overridden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteLineAsync(buffer, index, count);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
- _asyncWriteTask = task;
- return task;
- }
- public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
- {
- if (GetType() != typeof(StreamWriter))
- {
- return base.WriteLineAsync(buffer, cancellationToken);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
- Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
- _asyncWriteTask = task;
- return task;
- }
- public override Task FlushAsync()
- {
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Flush() which a subclass might have overridden. To be safe
- // we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Flush) when we are not sure.
- if (GetType() != typeof(StreamWriter))
- {
- return base.FlushAsync();
- }
- // flushEncoder should be true at the end of the file and if
- // the user explicitly calls Flush (though not if AutoFlush is true).
- // This is required to flush any dangling characters from our UTF-7
- // and UTF-8 encoders.
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
- }
- CheckAsyncTaskInProgress();
- Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
- _asyncWriteTask = task;
- return task;
- }
- private int CharPos_Prop
- {
- set { _charPos = value; }
- }
- private bool HaveWrittenPreamble_Prop
- {
- set { _haveWrittenPreamble = value; }
- }
- private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
- char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
- {
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
- // Perf boost for Flush on non-dirty writers.
- if (sCharPos == 0 && !flushStream && !flushEncoder)
- {
- return Task.CompletedTask;
- }
- Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
- _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
- _charPos = 0;
- return flushTask;
- }
- // We pass in private instance fields of this MarshalByRefObject-derived type as local params
- // to ensure performant access inside the state machine that corresponds this async method.
- private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
- char[] charBuffer, int charPos, bool haveWrittenPreamble,
- Encoding encoding, Encoder encoder, byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
- {
- if (!haveWrittenPreamble)
- {
- _this.HaveWrittenPreamble_Prop = true;
- byte[] preamble = encoding.GetPreamble();
- if (preamble.Length > 0)
- {
- await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false);
- }
- }
- int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
- if (count > 0)
- {
- await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false);
- }
- // By definition, calling Flush should flush the stream, but this is
- // only necessary if we passed in true for flushStream. The Web
- // Services guys have some perf tests where flushing needlessly hurts.
- if (flushStream)
- {
- await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
- }
- }
- #endregion
- } // class StreamWriter
- } // namespace
|