<data name="ObjectDisposed_ObjectName_Name" xml:space="preserve">
<value>Object name: '{0}'.</value>
</data>
+ <data name="ObjectDisposed_WriterClosed" xml:space="preserve">
+ <value>Cannot write to a closed TextWriter.</value>
+ </data>
<data name="ObjectDisposed_ReaderClosed" xml:space="preserve">
<value>Cannot read from a closed TextReader.</value>
</data>
<data name="Arg_TypeNotSupported" xml:space="preserve">
<value>Specified type is not supported</value>
</data>
+ <data name="IO_InvalidReadLength" xml:space="preserve">
+ <value>The read operation returned an invalid length.</value>
+ </data>
</root>
\ No newline at end of file
<Compile Include="$(BclSourcesRoot)\System\Globalization\EncodingDataItem.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\IO\FileSystemEnumerable.cs" />
- <Compile Include="$(BclSourcesRoot)\System\IO\TextReader.cs" />
- <Compile Include="$(BclSourcesRoot)\System\IO\StreamReader.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\TimeZoneInfo.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PinnedBufferMemoryStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\SeekOrigin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\StreamHelpers.CopyValidation.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\StreamReader.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\StreamWriter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\TextReader.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\TextWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryAccessor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryStreamWrapper.cs" />
// 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.Text;
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace System.IO
// This class implements a TextReader for reading characters to a Stream.
// This is designed for character input in a particular Encoding,
// whereas the Stream class is designed for byte input and output.
- //
- internal class StreamReader : TextReader
+ public class StreamReader : TextReader
{
// StreamReader.Null is threadsafe.
public new static readonly StreamReader Null = new NullStreamReader();
- // Encoding.GetPreamble() always allocates and returns a new byte[] array for
- // encodings that have a preamble.
- // We can avoid repeated allocations for the default and commonly used Encoding.UTF8
- // encoding by using our own private cached instance of the UTF8 preamble.
- // This is lazily allocated the first time it is used.
- private static byte[] s_utf8Preamble;
-
// Using a 1K byte buffer and a 4K FileStream buffer works out pretty well
// perf-wise. On even a 40 MB text file, any perf loss by using a 4K
// buffer is negated by the win of allocating a smaller byte[], which
// saves construction time. This does break adaptive buffering,
// but this is slightly faster.
- internal static int DefaultBufferSize
- {
- get
- {
- return 1024;
- }
- }
-
+ private const int DefaultBufferSize = 1024; // Byte buffer size
private const int DefaultFileStreamBufferSize = 4096;
private const int MinBufferSize = 128;
- private const int MaxSharedBuilderCapacity = 360; // also the max capacity used in StringBuilderCache
-
- private Stream stream;
- private Encoding encoding;
- private Decoder decoder;
- private byte[] byteBuffer;
- private char[] charBuffer;
- private byte[] _preamble; // Encoding's preamble, which identifies this encoding.
- private int charPos;
- private int charLen;
+
+ private Stream _stream;
+ private Encoding _encoding;
+ private Decoder _decoder;
+ private byte[] _byteBuffer;
+ private char[] _charBuffer;
+ private int _charPos;
+ private int _charLen;
// Record the number of valid bytes in the byteBuffer, for a few checks.
- private int byteLen;
+ private int _byteLen;
// This is used only for preamble detection
- private int bytePos;
-
- private StringBuilder _builder;
+ private int _bytePos;
// This is the maximum number of chars we can get from one call to
// ReadBuffer. Used so ReadBuffer can tell when to copy data into
private bool _isBlocked;
// The intent of this field is to leave open the underlying stream when
- // disposing of this StreamReader.
- private bool _leaveOpen; // Whether to keep the underlying stream open.
+ // disposing of this StreamReader. A name like _leaveOpen is better,
+ // but this type is serializable, and this field's name was _closable.
+ private bool _closable; // Whether to close the underlying stream.
// We don't guarantee thread safety on StreamReader, but we should at
// least prevent users from trying to read anything while an Async
private volatile Task _asyncReadTask;
private void CheckAsyncTaskInProgress()
- {
+ {
// We are not locking the access to _asyncReadTask because this is not meant to guarantee thread safety.
// We are simply trying to deter calling any Read APIs while an async Read from the same thread is in progress.
-
+
Task t = _asyncReadTask;
if (t != null && !t.IsCompleted)
+ {
throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
+ }
}
// StreamReader by default will ignore illegal UTF8 characters. We don't want to
// throw here because we want to be able to read ill-formed data without choking.
// 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 error.
-
- internal StreamReader() {
+
+ internal StreamReader()
+ {
}
-
- public StreamReader(Stream stream)
- : this(stream, true) {
+
+ public StreamReader(Stream stream)
+ : this(stream, true)
+ {
}
- public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
- : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize, false) {
+ public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
+ : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
+ {
}
-
- public StreamReader(Stream stream, Encoding encoding)
- : this(stream, encoding, true, DefaultBufferSize, false) {
+
+ public StreamReader(Stream stream, Encoding encoding)
+ : this(stream, encoding, true, DefaultBufferSize, false)
+ {
}
-
+
public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks)
- : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false) {
+ : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
+ {
}
// Creates a new StreamReader for the given stream. The
// of those three match, it will use the Encoding you provided.
//
public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
- : this(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false) {
+ : this(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false)
+ {
}
public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
{
if (stream == null || encoding == null)
- throw new ArgumentNullException((stream == null ? nameof(stream) : nameof(encoding)));
+ {
+ throw new ArgumentNullException(stream == null ? nameof(stream) : nameof(encoding));
+ }
if (!stream.CanRead)
+ {
throw new ArgumentException(SR.Argument_StreamNotReadable);
+ }
if (bufferSize <= 0)
+ {
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+ }
Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen);
}
- public StreamReader(String path)
- : this(path, true) {
+ public StreamReader(string path)
+ : this(path, true)
+ {
}
- public StreamReader(String path, bool detectEncodingFromByteOrderMarks)
- : this(path, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize) {
+ public StreamReader(string path, bool detectEncodingFromByteOrderMarks)
+ : this(path, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize)
+ {
}
- public StreamReader(String path, Encoding encoding)
- : this(path, encoding, true, DefaultBufferSize) {
+ public StreamReader(string path, Encoding encoding)
+ : this(path, encoding, true, DefaultBufferSize)
+ {
}
- public StreamReader(String path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
- : this(path, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize) {
+ public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
+ : this(path, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize)
+ {
}
- public StreamReader(String path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
+ public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
{
- // Don't open a Stream before checking for invalid arguments,
- // or we'll create a FileStream on disk and we won't close it until
- // the finalizer runs, causing problems for applications.
- if (path==null || encoding==null)
- throw new ArgumentNullException((path==null ? nameof(path) : nameof(encoding)));
- if (path.Length==0)
+ 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, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
- Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false);
+ Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read,
+ DefaultFileStreamBufferSize, FileOptions.SequentialScan);
+ Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen: false);
}
-
- private void Init(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen) {
- this.stream = stream;
- this.encoding = encoding;
- decoder = encoding.GetDecoder();
- if (bufferSize < MinBufferSize) bufferSize = MinBufferSize;
- byteBuffer = new byte[bufferSize];
+
+ private void Init(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
+ {
+ _stream = stream;
+ _encoding = encoding;
+ _decoder = encoding.GetDecoder();
+ if (bufferSize < MinBufferSize)
+ {
+ bufferSize = MinBufferSize;
+ }
+
+ _byteBuffer = new byte[bufferSize];
_maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
- charBuffer = new char[_maxCharsPerBuffer];
- byteLen = 0;
- bytePos = 0;
+ _charBuffer = new char[_maxCharsPerBuffer];
+ _byteLen = 0;
+ _bytePos = 0;
_detectEncoding = detectEncodingFromByteOrderMarks;
-
- // Encoding.GetPreamble() always allocates and returns a new byte[] array for
- // encodings that have a preamble.
- // We can avoid repeated allocations for the default and commonly used Encoding.UTF8
- // encoding by using our own private cached instance of the UTF8 preamble.
- // We specifically look for Encoding.UTF8 because we know it has a preamble,
- // whereas other instances of UTF8Encoding may not have a preamble enabled, and
- // there's no public way to tell if the preamble is enabled for an instance other
- // than calling GetPreamble(), which we're trying to avoid.
- // This means that other instances of UTF8Encoding are excluded from this optimization.
- _preamble = object.ReferenceEquals(encoding, Encoding.UTF8) ?
- (s_utf8Preamble ?? (s_utf8Preamble = encoding.GetPreamble())) :
- encoding.GetPreamble();
-
- _checkPreamble = (_preamble.Length > 0);
+ _checkPreamble = encoding.Preamble.Length > 0;
_isBlocked = false;
- _leaveOpen = leaveOpen;
+ _closable = !leaveOpen;
}
// Init used by NullStreamReader, to delay load encoding
internal void Init(Stream stream)
{
- this.stream = stream;
- _leaveOpen = false;
+ _stream = stream;
+ _closable = true;
}
public override void Close()
{
Dispose(true);
}
-
+
protected override void Dispose(bool disposing)
{
// Dispose of our resources if this StreamReader is closable.
// Note that Console.In should be left open.
- try {
+ try
+ {
// Note that Stream.Close() can potentially throw here. So we need to
// ensure cleaning up internal resources, inside the finally block.
- if (!LeaveOpen && disposing && (stream != null))
- stream.Close();
- }
- finally {
- if (!LeaveOpen && (stream != null)) {
- stream = null;
- encoding = null;
- decoder = null;
- byteBuffer = null;
- charBuffer = null;
- charPos = 0;
- charLen = 0;
- _builder = null;
+ if (!LeaveOpen && disposing && (_stream != null))
+ {
+ _stream.Close();
+ }
+ }
+ finally
+ {
+ if (!LeaveOpen && (_stream != null))
+ {
+ _stream = null;
+ _encoding = null;
+ _decoder = null;
+ _byteBuffer = null;
+ _charBuffer = null;
+ _charPos = 0;
+ _charLen = 0;
base.Dispose(disposing);
}
}
}
-
- public virtual Encoding CurrentEncoding {
- get { return encoding; }
+
+ public virtual Encoding CurrentEncoding
+ {
+ get { return _encoding; }
}
-
- public virtual Stream BaseStream {
- get { return stream; }
+
+ public virtual Stream BaseStream
+ {
+ get { return _stream; }
}
- internal bool LeaveOpen {
- get { return _leaveOpen; }
+ internal bool LeaveOpen
+ {
+ get { return !_closable; }
}
// DiscardBufferedData tells StreamReader to throw away its internal
{
CheckAsyncTaskInProgress();
- byteLen = 0;
- charLen = 0;
- charPos = 0;
+ _byteLen = 0;
+ _charLen = 0;
+ _charPos = 0;
// in general we'd like to have an invariant that encoding isn't null. However,
// for startup improvements for NullStreamReader, we want to delay load encoding.
- if (encoding != null) {
- decoder = encoding.GetDecoder();
+ if (_encoding != null)
+ {
+ _decoder = _encoding.GetDecoder();
}
_isBlocked = false;
}
- public bool EndOfStream {
- get {
- if (stream == null)
+ public bool EndOfStream
+ {
+ get
+ {
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- if (charPos < charLen)
+ if (_charPos < _charLen)
+ {
return false;
+ }
// This may block on pipes!
int numRead = ReadBuffer();
}
}
- public override int Peek() {
- if (stream == null)
+ public override int Peek()
+ {
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- if (charPos == charLen)
+ if (_charPos == _charLen)
{
- if (_isBlocked || ReadBuffer() == 0) return -1;
+ if (_isBlocked || ReadBuffer() == 0)
+ {
+ return -1;
+ }
}
- return charBuffer[charPos];
+ return _charBuffer[_charPos];
}
-
- public override int Read() {
- if (stream == null)
+
+ public override int Read()
+ {
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- if (charPos == charLen) {
- if (ReadBuffer() == 0) return -1;
+ if (_charPos == _charLen)
+ {
+ if (ReadBuffer() == 0)
+ {
+ return -1;
+ }
}
- int result = charBuffer[charPos];
- charPos++;
+ int result = _charBuffer[_charPos];
+ _charPos++;
return result;
}
-
+
public override int Read(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
+ {
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
if (index < 0 || count < 0)
- throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
if (buffer.Length - index < count)
+ {
throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ return ReadSpan(new Span<char>(buffer, index, count));
+ }
- if (stream == null)
+ public override int Read(Span<char> buffer) =>
+ GetType() == typeof(StreamReader) ? ReadSpan(buffer) :
+ base.Read(buffer); // Defer to Read(char[], ...) if a derived type may have previously overridden it
+
+ private int ReadSpan(Span<char> buffer)
+ {
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
// As a perf optimization, if we had exactly one buffer's worth of
// data read in, let's try writing directly to the user's buffer.
bool readToUserBuffer = false;
- while (count > 0) {
- int n = charLen - charPos;
- if (n == 0) n = ReadBuffer(buffer, index + charsRead, count, out readToUserBuffer);
- if (n == 0) break; // We're at EOF
- if (n > count) n = count;
- if (!readToUserBuffer) {
- Buffer.BlockCopy(charBuffer, charPos * 2, buffer, (index + charsRead) * 2, n*2);
- charPos += n;
+ int count = buffer.Length;
+ while (count > 0)
+ {
+ int n = _charLen - _charPos;
+ if (n == 0)
+ {
+ n = ReadBuffer(buffer.Slice(charsRead), out readToUserBuffer);
+ }
+ if (n == 0)
+ {
+ break; // We're at EOF
+ }
+ if (n > count)
+ {
+ n = count;
+ }
+ if (!readToUserBuffer)
+ {
+ new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Slice(charsRead));
+ _charPos += n;
}
+
charsRead += n;
count -= n;
// This function shouldn't block for an indefinite amount of time,
// or reading from a network stream won't work right. If we got
// fewer bytes than we requested, then we want to break right here.
if (_isBlocked)
+ {
break;
+ }
}
return charsRead;
}
- public override String ReadToEnd()
+ public override string ReadToEnd()
{
- if (stream == null)
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
// Call ReadBuffer, then pull data out of charBuffer.
- StringBuilder sb = AcquireSharedStringBuilder(charLen - charPos);
- do {
- sb.Append(charBuffer, charPos, charLen - charPos);
- charPos = charLen; // Note we consumed these characters
+ StringBuilder sb = new StringBuilder(_charLen - _charPos);
+ do
+ {
+ sb.Append(_charBuffer, _charPos, _charLen - _charPos);
+ _charPos = _charLen; // Note we consumed these characters
ReadBuffer();
- } while (charLen > 0);
-
- return GetStringAndReleaseSharedStringBuilder(sb);
+ } while (_charLen > 0);
+ return sb.ToString();
}
public override int ReadBlock(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
+ {
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
if (index < 0 || count < 0)
- throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
if (buffer.Length - index < count)
+ {
throw new ArgumentException(SR.Argument_InvalidOffLen);
-
- if (stream == null)
+ }
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
return base.ReadBlock(buffer, index, count);
}
+ public override int ReadBlock(Span<char> buffer)
+ {
+ if (GetType() != typeof(StreamReader))
+ {
+ // Defer to Read(char[], ...) if a derived type may have previously overridden it.
+ return base.ReadBlock(buffer);
+ }
+
+ int i, n = 0;
+ do
+ {
+ i = ReadSpan(buffer.Slice(n));
+ n += i;
+ } while (i > 0 && n < buffer.Length);
+ return n;
+ }
+
// Trims n bytes from the front of the buffer.
private void CompressBuffer(int n)
{
- Debug.Assert(byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?");
- Buffer.BlockCopy(byteBuffer, n, byteBuffer, 0, byteLen - n);
- byteLen -= n;
+ Debug.Assert(_byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?");
+ Buffer.BlockCopy(_byteBuffer, n, _byteBuffer, 0, _byteLen - n);
+ _byteLen -= n;
}
private void DetectEncoding()
{
- if (byteLen < 2)
+ if (_byteLen < 2)
+ {
return;
+ }
_detectEncoding = false;
bool changedEncoding = false;
- if (byteBuffer[0]==0xFE && byteBuffer[1]==0xFF) {
+ if (_byteBuffer[0] == 0xFE && _byteBuffer[1] == 0xFF)
+ {
// Big Endian Unicode
- encoding = Encoding.BigEndianUnicode;
+ _encoding = Encoding.BigEndianUnicode;
CompressBuffer(2);
changedEncoding = true;
}
-
- else if (byteBuffer[0]==0xFF && byteBuffer[1]==0xFE) {
+
+ else if (_byteBuffer[0] == 0xFF && _byteBuffer[1] == 0xFE)
+ {
// Little Endian Unicode, or possibly little endian UTF32
- if (byteLen < 4 || byteBuffer[2] != 0 || byteBuffer[3] != 0) {
- encoding = Encoding.Unicode;
+ if (_byteLen < 4 || _byteBuffer[2] != 0 || _byteBuffer[3] != 0)
+ {
+ _encoding = Encoding.Unicode;
CompressBuffer(2);
changedEncoding = true;
}
- else {
- encoding = Encoding.UTF32;
+ else
+ {
+ _encoding = Encoding.UTF32;
CompressBuffer(4);
- changedEncoding = true;
+ changedEncoding = true;
}
}
-
- else if (byteLen >= 3 && byteBuffer[0]==0xEF && byteBuffer[1]==0xBB && byteBuffer[2]==0xBF) {
+
+ else if (_byteLen >= 3 && _byteBuffer[0] == 0xEF && _byteBuffer[1] == 0xBB && _byteBuffer[2] == 0xBF)
+ {
// UTF-8
- encoding = Encoding.UTF8;
+ _encoding = Encoding.UTF8;
CompressBuffer(3);
changedEncoding = true;
}
- else if (byteLen >= 4 && byteBuffer[0] == 0 && byteBuffer[1] == 0 &&
- byteBuffer[2] == 0xFE && byteBuffer[3] == 0xFF) {
+ else if (_byteLen >= 4 && _byteBuffer[0] == 0 && _byteBuffer[1] == 0 &&
+ _byteBuffer[2] == 0xFE && _byteBuffer[3] == 0xFF)
+ {
// Big Endian UTF32
- encoding = new UTF32Encoding(true, true);
+ _encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
CompressBuffer(4);
changedEncoding = true;
}
- else if (byteLen == 2)
+ else if (_byteLen == 2)
+ {
_detectEncoding = true;
+ }
// Note: in the future, if we change this algorithm significantly,
// we can support checking for the preamble of the given encoding.
- if (changedEncoding) {
- decoder = encoding.GetDecoder();
- _maxCharsPerBuffer = encoding.GetMaxCharCount(byteBuffer.Length);
- charBuffer = new char[_maxCharsPerBuffer];
+ if (changedEncoding)
+ {
+ _decoder = _encoding.GetDecoder();
+ int newMaxCharsPerBuffer = _encoding.GetMaxCharCount(_byteBuffer.Length);
+ if (newMaxCharsPerBuffer > _maxCharsPerBuffer)
+ {
+ _charBuffer = new char[newMaxCharsPerBuffer];
+ }
+ _maxCharsPerBuffer = newMaxCharsPerBuffer;
}
}
// leading preamble bytes
private bool IsPreamble()
{
- if (!_checkPreamble)
+ if (!_checkPreamble)
+ {
return _checkPreamble;
+ }
+
+ ReadOnlySpan<byte> preamble = _encoding.Preamble;
- Debug.Assert(bytePos <= _preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?");
- int len = (byteLen >= (_preamble.Length))? (_preamble.Length - bytePos) : (byteLen - bytePos);
+ Debug.Assert(_bytePos <= preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?");
+ int len = (_byteLen >= (preamble.Length)) ? (preamble.Length - _bytePos) : (_byteLen - _bytePos);
- for(int i=0; i<len; i++, bytePos++) {
- if (byteBuffer[bytePos] != _preamble[bytePos]) {
- bytePos = 0;
+ for (int i = 0; i < len; i++, _bytePos++)
+ {
+ if (_byteBuffer[_bytePos] != preamble[_bytePos])
+ {
+ _bytePos = 0;
_checkPreamble = false;
break;
}
}
- Debug.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ Debug.Assert(_bytePos <= preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
- if (_checkPreamble) {
- if (bytePos == _preamble.Length) {
+ if (_checkPreamble)
+ {
+ if (_bytePos == preamble.Length)
+ {
// We have a match
- CompressBuffer(_preamble.Length);
- bytePos = 0;
+ CompressBuffer(preamble.Length);
+ _bytePos = 0;
_checkPreamble = false;
_detectEncoding = false;
}
return _checkPreamble;
}
- private StringBuilder AcquireSharedStringBuilder(int capacity)
+ internal virtual int ReadBuffer()
{
- // Do not touch the shared builder if it will be removed on release
- if (capacity > MaxSharedBuilderCapacity)
- return new StringBuilder(capacity);
-
- // note that since StreamReader does not support concurrent reads it is not needed to
- // set _builder to null to avoid parallel acquisitions.
- StringBuilder sb = _builder;
-
- if (sb == null)
- return _builder = new StringBuilder(capacity);
-
- // Clear the shared builder. Does not remove the allocated buffers so they are reused.
- sb.Length = 0;
-
- // When needed, recreate the buffer backing the StringBuilder so that further Append calls
- // are less likely to internally allocate new StringBuilders (or chunks).
- if (sb.Capacity < capacity)
- sb.Capacity = capacity;
-
- return sb;
- }
-
- private string GetStringAndReleaseSharedStringBuilder(StringBuilder sb)
- {
- if (sb == _builder && sb.Capacity > MaxSharedBuilderCapacity)
- _builder = null;
-
- return sb.ToString();
- }
-
- internal int ReadBuffer() {
- charLen = 0;
- charPos = 0;
+ _charLen = 0;
+ _charPos = 0;
if (!_checkPreamble)
- byteLen = 0;
- do {
- if (_checkPreamble) {
- Debug.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
- int len = stream.Read(byteBuffer, bytePos, byteBuffer.Length - bytePos);
+ {
+ _byteLen = 0;
+ }
+
+ do
+ {
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
- if (len == 0) {
+ if (len == 0)
+ {
// EOF but we might have buffered bytes from previous
// attempt to detect preamble that needs to be decoded now
- if (byteLen > 0)
+ if (_byteLen > 0)
{
- charLen += decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, charLen);
+ _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
// Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
- bytePos = byteLen = 0;
+ _bytePos = _byteLen = 0;
}
- return charLen;
+ return _charLen;
}
- byteLen += len;
+ _byteLen += len;
}
- else {
- Debug.Assert(bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
- byteLen = stream.Read(byteBuffer, 0, byteBuffer.Length);
- Debug.Assert(byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+ else
+ {
+ Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+ _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
- if (byteLen == 0) // We're at EOF
- return charLen;
+ if (_byteLen == 0) // We're at EOF
+ {
+ return _charLen;
+ }
}
// _isBlocked == whether we read fewer bytes than we asked for.
// Note we must check it here because CompressBuffer or
// DetectEncoding will change byteLen.
- _isBlocked = (byteLen < byteBuffer.Length);
+ _isBlocked = (_byteLen < _byteBuffer.Length);
// Check for preamble before detect encoding. This is not to override the
- // user suppplied Encoding for the one we implicitly detect. The user could
+ // user supplied Encoding for the one we implicitly detect. The user could
// customize the encoding which we will loose, such as ThrowOnError on UTF8
if (IsPreamble())
+ {
continue;
+ }
// If we're supposed to detect the encoding and haven't done so yet,
// do it. Note this may need to be called more than once.
- if (_detectEncoding && byteLen >= 2)
+ if (_detectEncoding && _byteLen >= 2)
+ {
DetectEncoding();
+ }
- charLen += decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, charLen);
- } while (charLen == 0);
+ _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
+ } while (_charLen == 0);
//Console.WriteLine("ReadBuffer called. chars: "+charLen);
- return charLen;
+ return _charLen;
}
// buffer's worth of bytes could produce.
// This optimization, if run, will break SwitchEncoding, so we must not do
// this on the first call to ReadBuffer.
- private int ReadBuffer(char[] userBuffer, int userOffset, int desiredChars, out bool readToUserBuffer)
+ private int ReadBuffer(Span<char> userBuffer, out bool readToUserBuffer)
{
- charLen = 0;
- charPos = 0;
-
+ _charLen = 0;
+ _charPos = 0;
+
if (!_checkPreamble)
- byteLen = 0;
-
+ {
+ _byteLen = 0;
+ }
+
int charsRead = 0;
// As a perf optimization, we can decode characters DIRECTLY into a
// buffer optimization. This affects reads where the end of the
// Stream comes in the middle somewhere, and when you ask for
// fewer chars than your buffer could produce.
- readToUserBuffer = desiredChars >= _maxCharsPerBuffer;
+ readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
- do {
+ do
+ {
Debug.Assert(charsRead == 0);
- if (_checkPreamble) {
- Debug.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
- int len = stream.Read(byteBuffer, bytePos, byteBuffer.Length - bytePos);
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
-
- if (len == 0) {
+
+ if (len == 0)
+ {
// EOF but we might have buffered bytes from previous
// attempt to detect preamble that needs to be decoded now
- if (byteLen > 0) {
- if (readToUserBuffer) {
- charsRead = decoder.GetChars(byteBuffer, 0, byteLen, userBuffer, userOffset + charsRead);
- charLen = 0; // StreamReader's buffer is empty.
+ if (_byteLen > 0)
+ {
+ if (readToUserBuffer)
+ {
+ charsRead = _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush: false);
+ _charLen = 0; // StreamReader's buffer is empty.
}
- else {
- charsRead = decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, charsRead);
- charLen += charsRead; // Number of chars in StreamReader's buffer.
+ else
+ {
+ charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead);
+ _charLen += charsRead; // Number of chars in StreamReader's buffer.
}
}
return charsRead;
}
-
- byteLen += len;
+
+ _byteLen += len;
}
- else {
- Debug.Assert(bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+ else
+ {
+ Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+
+ _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
- byteLen = stream.Read(byteBuffer, 0, byteBuffer.Length);
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
- Debug.Assert(byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
-
- if (byteLen == 0) // EOF
+ if (_byteLen == 0) // EOF
+ {
break;
+ }
}
// _isBlocked == whether we read fewer bytes than we asked for.
// Note we must check it here because CompressBuffer or
// DetectEncoding will change byteLen.
- _isBlocked = (byteLen < byteBuffer.Length);
+ _isBlocked = (_byteLen < _byteBuffer.Length);
// Check for preamble before detect encoding. This is not to override the
- // user suppplied Encoding for the one we implicitly detect. The user could
+ // user supplied Encoding for the one we implicitly detect. The user could
// customize the encoding which we will loose, such as ThrowOnError on UTF8
// Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
// doesn't change the encoding or affect _maxCharsPerBuffer
- if (IsPreamble())
+ if (IsPreamble())
+ {
continue;
+ }
// On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
- if (_detectEncoding && byteLen >= 2) {
+ if (_detectEncoding && _byteLen >= 2)
+ {
DetectEncoding();
// DetectEncoding changes some buffer state. Recompute this.
- readToUserBuffer = desiredChars >= _maxCharsPerBuffer;
+ readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
}
- charPos = 0;
- if (readToUserBuffer) {
- charsRead += decoder.GetChars(byteBuffer, 0, byteLen, userBuffer, userOffset + charsRead);
- charLen = 0; // StreamReader's buffer is empty.
+ _charPos = 0;
+ if (readToUserBuffer)
+ {
+ charsRead += _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush:false);
+ _charLen = 0; // StreamReader's buffer is empty.
}
- else {
- charsRead = decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, charsRead);
- charLen += charsRead; // Number of chars in StreamReader's buffer.
+ else
+ {
+ charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead);
+ _charLen += charsRead; // Number of chars in StreamReader's buffer.
}
} while (charsRead == 0);
- _isBlocked &= charsRead < desiredChars;
+ _isBlocked &= charsRead < userBuffer.Length;
//Console.WriteLine("ReadBuffer: charsRead: "+charsRead+" readToUserBuffer: "+readToUserBuffer);
return charsRead;
// contain the terminating carriage return and/or line feed. The returned
// value is null if the end of the input stream has been reached.
//
- public override String ReadLine()
+ public override string ReadLine()
{
- if (stream == null)
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- if (charPos == charLen)
+ if (_charPos == _charLen)
{
- if (ReadBuffer() == 0) return null;
+ if (ReadBuffer() == 0)
+ {
+ return null;
+ }
}
StringBuilder sb = null;
- do {
- int i = charPos;
- do {
- char ch = charBuffer[i];
+ do
+ {
+ int i = _charPos;
+ do
+ {
+ char ch = _charBuffer[i];
// Note the following common line feed chars:
// \n - UNIX \r\n - DOS \r - Mac
- if (ch == '\r' || ch == '\n') {
- String s;
- if (sb != null) {
- sb.Append(charBuffer, charPos, i - charPos);
- s = GetStringAndReleaseSharedStringBuilder(sb);
+ if (ch == '\r' || ch == '\n')
+ {
+ string s;
+ if (sb != null)
+ {
+ sb.Append(_charBuffer, _charPos, i - _charPos);
+ s = sb.ToString();
}
- else {
- s = new String(charBuffer, charPos, i - charPos);
+ else
+ {
+ s = new string(_charBuffer, _charPos, i - _charPos);
}
- charPos = i + 1;
- if (ch == '\r' && (charPos < charLen || ReadBuffer() > 0)) {
- if (charBuffer[charPos] == '\n') charPos++;
+ _charPos = i + 1;
+ if (ch == '\r' && (_charPos < _charLen || ReadBuffer() > 0))
+ {
+ if (_charBuffer[_charPos] == '\n')
+ {
+ _charPos++;
+ }
}
return s;
}
i++;
- } while (i < charLen);
- i = charLen - charPos;
- if (sb == null) sb = AcquireSharedStringBuilder(i + 80);
- sb.Append(charBuffer, charPos, i);
+ } while (i < _charLen);
+ i = _charLen - _charPos;
+ if (sb == null)
+ {
+ sb = new StringBuilder(i + 80);
+ }
+ sb.Append(_charBuffer, _charPos, i);
} while (ReadBuffer() > 0);
- return GetStringAndReleaseSharedStringBuilder(sb);
+ return sb.ToString();
}
-
+
#region Task based Async APIs
- public override Task<String> ReadLineAsync()
+ public override Task<string> ReadLineAsync()
{
// If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Read() which a subclass might have overriden.
+ // since it does not call through to Read() 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 Read) when we are not sure.
- if (this.GetType() != typeof(StreamReader))
+ if (GetType() != typeof(StreamReader))
+ {
return base.ReadLineAsync();
+ }
- if (stream == null)
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- Task<String> task = ReadLineAsyncInternal();
+ Task<string> task = ReadLineAsyncInternal();
_asyncReadTask = task;
return task;
}
- private async Task<String> ReadLineAsyncInternal()
+ private async Task<string> ReadLineAsyncInternal()
{
- if (charPos == charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
+ if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
+ {
return null;
+ }
StringBuilder sb = null;
do
{
- char[] tmpCharBuffer = charBuffer;
- int tmpCharLen = charLen;
- int tmpCharPos = charPos;
+ char[] tmpCharBuffer = _charBuffer;
+ int tmpCharLen = _charLen;
+ int tmpCharPos = _charPos;
int i = tmpCharPos;
do
// \n - UNIX \r\n - DOS \r - Mac
if (ch == '\r' || ch == '\n')
{
- String s;
+ string s;
if (sb != null)
{
sb.Append(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
- s = GetStringAndReleaseSharedStringBuilder(sb);
+ s = sb.ToString();
}
else
{
- s = new String(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
+ s = new string(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
}
- charPos = tmpCharPos = i + 1;
+ _charPos = tmpCharPos = i + 1;
if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync().ConfigureAwait(false)) > 0))
{
- tmpCharPos = charPos;
- if (charBuffer[tmpCharPos] == '\n')
- charPos = ++tmpCharPos;
+ tmpCharPos = _charPos;
+ if (_charBuffer[tmpCharPos] == '\n')
+ {
+ _charPos = ++tmpCharPos;
+ }
}
return s;
}
i++;
-
} while (i < tmpCharLen);
i = tmpCharLen - tmpCharPos;
- if (sb == null) sb = AcquireSharedStringBuilder(i + 80);
+ if (sb == null)
+ {
+ sb = new StringBuilder(i + 80);
+ }
sb.Append(tmpCharBuffer, tmpCharPos, i);
-
} while (await ReadBufferAsync().ConfigureAwait(false) > 0);
- return GetStringAndReleaseSharedStringBuilder(sb);
+ return sb.ToString();
}
- public override Task<String> ReadToEndAsync()
+ public override Task<string> ReadToEndAsync()
{
// If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Read() which a subclass might have overriden.
+ // since it does not call through to Read() 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 Read) when we are not sure.
- if (this.GetType() != typeof(StreamReader))
+ if (GetType() != typeof(StreamReader))
+ {
return base.ReadToEndAsync();
+ }
- if (stream == null)
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- Task<String> task = ReadToEndAsyncInternal();
+ Task<string> task = ReadToEndAsyncInternal();
_asyncReadTask = task;
return task;
}
- private async Task<String> ReadToEndAsyncInternal()
+ private async Task<string> ReadToEndAsyncInternal()
{
// Call ReadBuffer, then pull data out of charBuffer.
- StringBuilder sb = AcquireSharedStringBuilder(charLen - charPos);
+ StringBuilder sb = new StringBuilder(_charLen - _charPos);
do
{
- int tmpCharPos = charPos;
- sb.Append(charBuffer, tmpCharPos, charLen - tmpCharPos);
- charPos = charLen; // We consumed these characters
+ int tmpCharPos = _charPos;
+ sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);
+ _charPos = _charLen; // We consumed these characters
await ReadBufferAsync().ConfigureAwait(false);
- } while (charLen > 0);
+ } while (_charLen > 0);
- return GetStringAndReleaseSharedStringBuilder(sb);
+ return sb.ToString();
}
public override Task<int> ReadAsync(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
+ {
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
if (index < 0 || count < 0)
- throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : 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 Read() which a subclass might have overriden.
+ // since it does not call through to Read() 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 Read) when we are not sure.
- if (this.GetType() != typeof(StreamReader))
+ if (GetType() != typeof(StreamReader))
+ {
return base.ReadAsync(buffer, index, count);
+ }
- if (stream == null)
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
- Task<int> task = ReadAsyncInternal(buffer, index, count);
+ Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
_asyncReadTask = task;
return task;
}
- internal override async Task<int> ReadAsyncInternal(char[] buffer, int index, int count)
+ public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (GetType() != typeof(StreamReader))
+ {
+ // Ensure we use existing overrides if a class already overrode existing overloads.
+ return base.ReadAsync(buffer, cancellationToken);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ return ReadAsyncInternal(buffer, cancellationToken);
+ }
+
+ internal override async ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
{
- if (charPos == charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
+ if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
+ {
return 0;
+ }
int charsRead = 0;
// data read in, let's try writing directly to the user's buffer.
bool readToUserBuffer = false;
- Byte[] tmpByteBuffer = byteBuffer;
- Stream tmpStream = stream;
+ Byte[] tmpByteBuffer = _byteBuffer;
+ Stream tmpStream = _stream;
+ int count = buffer.Length;
while (count > 0)
{
// n is the characters available in _charBuffer
- int n = charLen - charPos;
+ int n = _charLen - _charPos;
// charBuffer is empty, let's read from the stream
if (n == 0)
{
- charLen = 0;
- charPos = 0;
+ _charLen = 0;
+ _charPos = 0;
if (!_checkPreamble)
- byteLen = 0;
+ {
+ _byteLen = 0;
+ }
readToUserBuffer = count >= _maxCharsPerBuffer;
if (_checkPreamble)
{
- Debug.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
- int tmpBytePos = bytePos;
- int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos).ConfigureAwait(false);
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int tmpBytePos = _bytePos;
+ int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos, cancellationToken).ConfigureAwait(false);
Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
if (len == 0)
{
// EOF but we might have buffered bytes from previous
// attempts to detect preamble that needs to be decoded now
- if (byteLen > 0)
+ if (_byteLen > 0)
{
if (readToUserBuffer)
{
- n = decoder.GetChars(tmpByteBuffer, 0, byteLen, buffer, index + charsRead);
- charLen = 0; // StreamReader's buffer is empty.
+ n = _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
+ _charLen = 0; // StreamReader's buffer is empty.
}
else
{
- n = decoder.GetChars(tmpByteBuffer, 0, byteLen, charBuffer, 0);
- charLen += n; // Number of chars in StreamReader's buffer.
+ n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
+ _charLen += n; // Number of chars in StreamReader's buffer.
}
}
-
+
// How can part of the preamble yield any chars?
Debug.Assert(n == 0);
}
else
{
- byteLen += len;
+ _byteLen += len;
}
}
else
{
- Debug.Assert(bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+ Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
- byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false);
+ _byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length, cancellationToken).ConfigureAwait(false);
- Debug.Assert(byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
- if (byteLen == 0) // EOF
+ if (_byteLen == 0) // EOF
{
_isBlocked = true;
break;
// _isBlocked == whether we read fewer bytes than we asked for.
// Note we must check it here because CompressBuffer or
// DetectEncoding will change _byteLen.
- _isBlocked = (byteLen < tmpByteBuffer.Length);
+ _isBlocked = (_byteLen < tmpByteBuffer.Length);
// Check for preamble before detect encoding. This is not to override the
- // user suppplied Encoding for the one we implicitly detect. The user could
+ // user supplied Encoding for the one we implicitly detect. The user could
// customize the encoding which we will loose, such as ThrowOnError on UTF8
// Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
// doesn't change the encoding or affect _maxCharsPerBuffer
if (IsPreamble())
+ {
continue;
+ }
// On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
- if (_detectEncoding && byteLen >= 2)
+ if (_detectEncoding && _byteLen >= 2)
{
DetectEncoding();
// DetectEncoding changes some buffer state. Recompute this.
Debug.Assert(n == 0);
- charPos = 0;
+ _charPos = 0;
if (readToUserBuffer)
{
- n += decoder.GetChars(tmpByteBuffer, 0, byteLen, buffer, index + charsRead);
-
+ n += _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
+
// Why did the bytes yield no chars?
Debug.Assert(n > 0);
- charLen = 0; // StreamReader's buffer is empty.
+ _charLen = 0; // StreamReader's buffer is empty.
}
else
{
- n = decoder.GetChars(tmpByteBuffer, 0, byteLen, charBuffer, 0);
-
+ n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
+
// Why did the bytes yield no chars?
Debug.Assert(n > 0);
- charLen += n; // Number of chars in StreamReader's buffer.
+ _charLen += n; // Number of chars in StreamReader's buffer.
}
-
} while (n == 0);
- if (n == 0) break; // We're at EOF
+ if (n == 0)
+ {
+ break; // We're at EOF
+ }
} // if (n == 0)
// Got more chars in charBuffer than the user requested
if (n > count)
+ {
n = count;
+ }
if (!readToUserBuffer)
{
- Buffer.BlockCopy(charBuffer, charPos * 2, buffer, (index + charsRead) * 2, n * 2);
- charPos += n;
+ new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Span.Slice(charsRead));
+ _charPos += n;
}
charsRead += n;
// or reading from a network stream won't work right. If we got
// fewer bytes than we requested, then we want to break right here.
if (_isBlocked)
+ {
break;
+ }
} // while (count > 0)
return charsRead;
public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
+ {
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
if (index < 0 || count < 0)
- throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : 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 Read() which a subclass might have overriden.
+ // since it does not call through to Read() 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 Read) when we are not sure.
- if (this.GetType() != typeof(StreamReader))
+ if (GetType() != typeof(StreamReader))
+ {
return base.ReadBlockAsync(buffer, index, count);
+ }
- if (stream == null)
+ if (_stream == null)
+ {
throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
CheckAsyncTaskInProgress();
return task;
}
+ public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (GetType() != typeof(StreamReader))
+ {
+ // If a derived type may have overridden ReadBlockAsync(char[], ...) before this overload
+ // was introduced, defer to it.
+ return base.ReadBlockAsync(buffer, cancellationToken);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ ValueTask<int> vt = ReadBlockAsyncInternal(buffer, cancellationToken);
+ if (vt.IsCompletedSuccessfully)
+ {
+ return vt;
+ }
+
+ Task<int> t = vt.AsTask();
+ _asyncReadTask = t;
+ return new ValueTask<int>(t);
+ }
+
private async Task<int> ReadBufferAsync()
{
- charLen = 0;
- charPos = 0;
- Byte[] tmpByteBuffer = byteBuffer;
- Stream tmpStream = stream;
-
+ _charLen = 0;
+ _charPos = 0;
+ Byte[] tmpByteBuffer = _byteBuffer;
+ Stream tmpStream = _stream;
+
if (!_checkPreamble)
- byteLen = 0;
- do {
- if (_checkPreamble) {
- Debug.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
- int tmpBytePos = bytePos;
+ {
+ _byteLen = 0;
+ }
+ do
+ {
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int tmpBytePos = _bytePos;
int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos).ConfigureAwait(false);
Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
-
- if (len == 0) {
+
+ if (len == 0)
+ {
// EOF but we might have buffered bytes from previous
// attempt to detect preamble that needs to be decoded now
- if (byteLen > 0)
+ if (_byteLen > 0)
{
- charLen += decoder.GetChars(tmpByteBuffer, 0, byteLen, charBuffer, charLen);
+ _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
// Need to zero out the _byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
- bytePos = 0; byteLen = 0;
+ _bytePos = 0; _byteLen = 0;
}
-
- return charLen;
+
+ return _charLen;
}
-
- byteLen += len;
+
+ _byteLen += len;
}
- else {
- Debug.Assert(bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
- byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false);
- Debug.Assert(byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class.");
-
- if (byteLen == 0) // We're at EOF
- return charLen;
+ else
+ {
+ Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+ _byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false);
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class.");
+
+ if (_byteLen == 0) // We're at EOF
+ {
+ return _charLen;
+ }
}
// _isBlocked == whether we read fewer bytes than we asked for.
// Note we must check it here because CompressBuffer or
// DetectEncoding will change _byteLen.
- _isBlocked = (byteLen < tmpByteBuffer.Length);
-
+ _isBlocked = (_byteLen < tmpByteBuffer.Length);
+
// Check for preamble before detect encoding. This is not to override the
- // user suppplied Encoding for the one we implicitly detect. The user could
+ // user supplied Encoding for the one we implicitly detect. The user could
// customize the encoding which we will loose, such as ThrowOnError on UTF8
- if (IsPreamble())
+ if (IsPreamble())
+ {
continue;
+ }
// If we're supposed to detect the encoding and haven't done so yet,
// do it. Note this may need to be called more than once.
- if (_detectEncoding && byteLen >= 2)
+ if (_detectEncoding && _byteLen >= 2)
+ {
DetectEncoding();
+ }
- charLen += decoder.GetChars(tmpByteBuffer, 0, byteLen, charBuffer, charLen);
- } while (charLen == 0);
-
- return charLen;
+ _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
+ } while (_charLen == 0);
+
+ return _charLen;
}
- #endregion
+#endregion
+
+ // No data, class doesn't need to be serializable.
// Note this class is threadsafe.
private class NullStreamReader : StreamReader
{
// Instantiating Encoding causes unnecessary perf hit.
- internal NullStreamReader() {
+ internal NullStreamReader()
+ {
Init(Stream.Null);
}
- public override Stream BaseStream {
+ public override Stream BaseStream
+ {
get { return Stream.Null; }
}
- public override Encoding CurrentEncoding {
+ public override Encoding CurrentEncoding
+ {
get { return Encoding.Unicode; }
}
return -1;
}
- public override int Read(char[] buffer, int index, int count) {
+ [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
+ public override int Read(char[] buffer, int index, int count)
+ {
return 0;
}
-
- public override String ReadLine() {
+
+ public override string ReadLine()
+ {
return null;
}
- public override String ReadToEnd()
+ public override string ReadToEnd()
{
- return String.Empty;
+ return string.Empty;
+ }
+
+ internal override int ReadBuffer()
+ {
+ return 0;
}
}
}
--- /dev/null
+// 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.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 volatile Task _asyncWriteTask;
+
+ 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.
+
+ Task t = _asyncWriteTask;
+
+ if (t != null && !t.IsCompleted)
+ 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
+ {
+ // Dispose of our resources if this StreamWriter is closable.
+ // Note: Console.Out and other such non closable streamwriters should be left alone
+ 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 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);
+ }
+ }
+
+ public override void Write(char[] buffer)
+ {
+ if (buffer != null)
+ {
+ WriteCore(buffer, _autoFlush);
+ }
+ }
+
+ 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);
+ }
+
+ WriteCore(new ReadOnlySpan<char>(buffer, index, count), _autoFlush);
+ }
+
+ public override void Write(ReadOnlySpan<char> buffer)
+ {
+ if (GetType() == typeof(StreamWriter))
+ {
+ WriteCore(buffer, _autoFlush);
+ }
+ else
+ {
+ // If a derived class may have overridden existing Write behavior,
+ // we need to make sure we use it.
+ base.Write(buffer);
+ }
+ }
+
+ private unsafe void WriteCore(ReadOnlySpan<char> buffer, bool autoFlush)
+ {
+ 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 (autoFlush)
+ {
+ Flush(true, false);
+ }
+ }
+
+ public override void Write(string value)
+ {
+ if (value != null)
+ {
+ WriteCore(value.AsReadOnlySpan(), _autoFlush);
+ }
+ }
+
+ //
+ // Optimize the most commonly used WriteLine overload. This optimization is important for System.Console in particular
+ // because of it will make one WriteLine equal to one call to the OS instead of two in the common case.
+ //
+ public override void WriteLine(string value)
+ {
+ CheckAsyncTaskInProgress();
+ if (value != null)
+ {
+ WriteCore(value.AsReadOnlySpan(), autoFlush: false);
+ }
+ WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
+ }
+
+ public override void WriteLine(ReadOnlySpan<char> value)
+ {
+ if (GetType() == typeof(StreamWriter))
+ {
+ CheckAsyncTaskInProgress();
+ WriteCore(value, autoFlush: false);
+ WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
+ }
+ else
+ {
+ // If a derived class may have overridden existing WriteLine behavior,
+ // we need to make sure we use it.
+ base.WriteLine(value);
+ }
+ }
+
+ #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(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
+ if (count > 0)
+ {
+ await stream.WriteAsync(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
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-/*============================================================
-**
-**
-**
-**
-**
-** Purpose: Abstract base class for all Text-only Readers.
-** Subclasses will include StreamReader & StringReader.
-**
-**
-===========================================================*/
-
using System.Text;
-using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Buffers;
-namespace System.IO {
+namespace System.IO
+{
// This abstract base class represents a reader that can read a sequential
// stream of characters. This is not intended for reading bytes -
// there are methods on the Stream class to read bytes.
//
// This class is intended for character input, not bytes.
// There are methods on the Stream class for reading bytes.
- internal abstract class TextReader : MarshalByRefObject, IDisposable {
+ public abstract partial class TextReader : MarshalByRefObject, IDisposable
+ {
public static readonly TextReader Null = new NullTextReader();
-
- protected TextReader() {}
-
- // Closes this TextReader and releases any system resources associated with the
- // TextReader. Following a call to Close, any operations on the TextReader
- // may raise exceptions.
- //
- // This default method is empty, but descendant classes can override the
- // method to provide the appropriate functionality.
- public virtual void Close()
+
+ protected TextReader() { }
+
+ public virtual void Close()
{
Dispose(true);
GC.SuppressFinalize(this);
//
// This default method simply returns -1.
//
- public virtual int Peek()
+ public virtual int Peek()
{
return -1;
}
-
+
// Reads the next character from the input stream. The returned value is
// -1 if no further characters are available.
//
{
return -1;
}
-
+
// Reads a block of characters. This method will read up to
// count characters from this TextReader into the
// buffer character array starting at position
// index. Returns the actual number of characters read.
//
- public virtual int Read(char[] buffer, int index, int count)
+ public virtual int Read(char[] buffer, int index, int count)
{
- if (buffer==null)
+ 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);
-
- int n = 0;
- do {
+ }
+
+ int n;
+ for (n = 0; n < count; n++)
+ {
int ch = Read();
if (ch == -1) break;
- buffer[index + n++] = (char)ch;
- } while (n < count);
+ buffer[index + n] = (char)ch;
+ }
+
return n;
}
+ // Reads a span of characters. This method will read up to
+ // count characters from this TextReader into the
+ // span of characters Returns the actual number of characters read.
+ //
+ public virtual int Read(Span<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ int numRead = Read(array, 0, buffer.Length);
+ if ((uint)numRead > buffer.Length)
+ {
+ throw new IOException(SR.IO_InvalidReadLength);
+ }
+ new Span<char>(array, 0, numRead).CopyTo(buffer);
+ return numRead;
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
// Reads all characters from the current position to the end of the
// TextReader, and returns them as one string.
- public virtual String ReadToEnd()
+ public virtual string ReadToEnd()
{
char[] chars = new char[4096];
int len;
StringBuilder sb = new StringBuilder(4096);
- while((len=Read(chars, 0, chars.Length)) != 0)
+ while ((len = Read(chars, 0, chars.Length)) != 0)
{
sb.Append(chars, 0, len);
}
// Blocking version of read. Returns only when count
// characters have been read or the end of the file was reached.
//
- public virtual int ReadBlock(char[] buffer, int index, int count)
+ public virtual int ReadBlock(char[] buffer, int index, int count)
{
int i, n = 0;
- do {
+ do
+ {
n += (i = Read(buffer, index + n, count - n));
} while (i > 0 && n < count);
return n;
}
+ // Blocking version of read for span of characters. Returns only when count
+ // characters have been read or the end of the file was reached.
+ //
+ public virtual int ReadBlock(Span<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ int numRead = ReadBlock(array, 0, buffer.Length);
+ if ((uint)numRead > buffer.Length)
+ {
+ throw new IOException(SR.IO_InvalidReadLength);
+ }
+ new Span<char>(array, 0, numRead).CopyTo(buffer);
+ return numRead;
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
// Reads a line. A line is defined as a sequence of characters followed by
// a carriage return ('\r'), a line feed ('\n'), or a carriage return
// immediately followed by a line feed. The resulting string does not
// contain the terminating carriage return and/or line feed. The returned
// value is null if the end of the input stream has been reached.
//
- public virtual String ReadLine()
+ public virtual string ReadLine()
{
StringBuilder sb = new StringBuilder();
- while (true) {
+ while (true)
+ {
int ch = Read();
if (ch == -1) break;
- if (ch == '\r' || ch == '\n')
+ if (ch == '\r' || ch == '\n')
{
- if (ch == '\r' && Peek() == '\n') Read();
+ if (ch == '\r' && Peek() == '\n')
+ {
+ Read();
+ }
+
return sb.ToString();
}
sb.Append((char)ch);
}
- if (sb.Length > 0) return sb.ToString();
+ if (sb.Length > 0)
+ {
+ return sb.ToString();
+ }
+
return null;
}
#region Task based Async APIs
- public virtual Task<String> ReadLineAsync()
+ public virtual Task<string> ReadLineAsync()
{
return Task<String>.Factory.StartNew(state =>
{
this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}
- public async virtual Task<String> ReadToEndAsync()
+ public async virtual Task<string> ReadToEndAsync()
{
- char[] chars = new char[4096];
- int len;
- StringBuilder sb = new StringBuilder(4096);
- while((len = await ReadAsyncInternal(chars, 0, chars.Length).ConfigureAwait(false)) != 0)
+ var sb = new StringBuilder(4096);
+ char[] chars = ArrayPool<char>.Shared.Rent(4096);
+ try
{
- sb.Append(chars, 0, len);
+ int len;
+ while ((len = await ReadAsyncInternal(chars, default).ConfigureAwait(false)) != 0)
+ {
+ sb.Append(chars, 0, len);
+ }
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(chars);
}
return sb.ToString();
}
public virtual Task<int> ReadAsync(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
+ {
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
if (index < 0 || count < 0)
- throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ {
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index): nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
if (buffer.Length - index < count)
+ {
throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
- return ReadAsyncInternal(buffer, index, count);
+ return ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
}
- internal virtual Task<int> ReadAsyncInternal(char[] buffer, int index, int count)
- {
- Debug.Assert(buffer != null);
- Debug.Assert(index >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert(buffer.Length - index >= count);
+ public virtual ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ new ValueTask<int>(buffer.TryGetArray(out ArraySegment<char> array) ?
+ ReadAsync(array.Array, array.Offset, array.Count) :
+ Task<int>.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextReader, Memory<char>>)state;
+ return t.Item1.Read(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
- var tuple = new Tuple<TextReader, char[], int, int>(this, buffer, index, count);
- return Task<int>.Factory.StartNew(state =>
+ internal virtual ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
+ {
+ var tuple = new Tuple<TextReader, Memory<char>>(this, buffer);
+ return new ValueTask<int>(Task<int>.Factory.StartNew(state =>
{
- var t = (Tuple<TextReader, char[], int, int>)state;
- return t.Item1.Read(t.Item2, t.Item3, t.Item4);
+ var t = (Tuple<TextReader, Memory<char>>)state;
+ return t.Item1.Read(t.Item2.Span);
},
- tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ tuple, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
}
public virtual Task<int> ReadBlockAsync(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
+ {
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
if (index < 0 || count < 0)
- throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ {
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index): nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
if (buffer.Length - index < count)
+ {
throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+ return ReadBlockAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
+ }
- return ReadBlockAsyncInternal(buffer, index, count);
- }
+ public virtual ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ new ValueTask<int>(buffer.TryGetArray(out ArraySegment<char> array) ?
+ ReadBlockAsync(array.Array, array.Offset, array.Count) :
+ Task<int>.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextReader, Memory<char>>)state;
+ return t.Item1.ReadBlock(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
- private async Task<int> ReadBlockAsyncInternal(char[] buffer, int index, int count)
+ internal async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
{
- Debug.Assert(buffer != null);
- Debug.Assert(index >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert(buffer.Length - index >= count);
-
- int i, n = 0;
+ int n = 0, i;
do
{
- i = await ReadAsyncInternal(buffer, index + n, count - n).ConfigureAwait(false);
+ i = await ReadAsyncInternal(buffer.Slice(n), cancellationToken).ConfigureAwait(false);
n += i;
- } while (i > 0 && n < count);
+ } while (i > 0 && n < buffer.Length);
return n;
}
#endregion
- public static TextReader Synchronized(TextReader reader)
- {
- if (reader==null)
- throw new ArgumentNullException(nameof(reader));
-
- if (reader is SyncTextReader)
- return reader;
-
- return new SyncTextReader(reader);
- }
-
private sealed class NullTextReader : TextReader
{
- public NullTextReader(){}
+ public NullTextReader() { }
- public override int Read(char[] buffer, int index, int count)
+ public override int Read(char[] buffer, int index, int count)
{
return 0;
}
-
- public override String ReadLine()
+
+ public override string ReadLine()
{
return null;
}
}
-
- internal sealed class SyncTextReader : TextReader
+
+ public static TextReader Synchronized(TextReader reader)
{
- internal TextReader _in;
-
- internal SyncTextReader(TextReader t)
- {
- _in = t;
- }
-
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override void Close()
+ if (reader == null)
+ throw new ArgumentNullException(nameof(reader));
+
+ return reader is SyncTextReader ? reader : new SyncTextReader(reader);
+ }
+
+ internal sealed class SyncTextReader : TextReader
+ {
+ internal readonly TextReader _in;
+
+ internal SyncTextReader(TextReader t)
{
- // So that any overriden Close() gets run
- _in.Close();
+ _in = t;
}
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- protected override void Dispose(bool disposing)
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Close() => _in.Close();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ protected override void Dispose(bool disposing)
{
// Explicitly pick up a potentially methodimpl'ed Dispose
if (disposing)
((IDisposable)_in).Dispose();
}
-
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override int Peek()
- {
- return _in.Peek();
- }
-
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override int Read()
- {
- return _in.Read();
- }
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override int Read(char[] buffer, int index, int count)
- {
- return _in.Read(buffer, index, count);
- }
-
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override int ReadBlock(char[] buffer, int index, int count)
- {
- return _in.ReadBlock(buffer, index, count);
- }
-
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override String ReadLine()
- {
- return _in.ReadLine();
- }
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Peek() => _in.Peek();
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override String ReadToEnd()
- {
- return _in.ReadToEnd();
- }
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Read() => _in.Read();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Read(char[] buffer, int index, int count) => _in.Read(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int ReadBlock(char[] buffer, int index, int count) => _in.ReadBlock(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override string ReadLine() => _in.ReadLine();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override string ReadToEnd() => _in.ReadToEnd();
//
// On SyncTextReader all APIs should run synchronously, even the async ones.
//
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override Task<String> ReadLineAsync()
- {
- return Task.FromResult(ReadLine());
- }
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task<string> ReadLineAsync() => Task.FromResult(ReadLine());
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- public override Task<String> ReadToEndAsync()
- {
- return Task.FromResult(ReadToEnd());
- }
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task<string> ReadToEndAsync() => Task.FromResult(ReadToEnd());
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
+ [MethodImpl(MethodImplOptions.Synchronized)]
public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
if (index < 0 || count < 0)
throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
if (buffer.Length - index < count)
throw new ArgumentException(SR.Argument_InvalidOffLen);
-
return Task.FromResult(ReadBlock(buffer, index, count));
}
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
+ [MethodImpl(MethodImplOptions.Synchronized)]
public override Task<int> ReadAsync(char[] buffer, int index, int count)
{
- if (buffer==null)
+ if (buffer == null)
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
if (index < 0 || count < 0)
throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
--- /dev/null
+// 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.Text;
+using System.Threading;
+using System.Globalization;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Buffers;
+
+namespace System.IO
+{
+ // This abstract base class represents a writer that can write a sequential
+ // stream of characters. A subclass must minimally implement the
+ // Write(char) method.
+ //
+ // This class is intended for character output, not bytes.
+ // There are methods on the Stream class for writing bytes.
+ public abstract partial class TextWriter : MarshalByRefObject, IDisposable
+ {
+ public static readonly TextWriter Null = new NullTextWriter();
+
+ // We don't want to allocate on every TextWriter creation, so cache the char array.
+ private static readonly char[] s_coreNewLine = Environment.NewLine.ToCharArray();
+
+ /// <summary>
+ /// This is the 'NewLine' property expressed as a char[].
+ /// It is exposed to subclasses as a protected field for read-only
+ /// purposes. You should only modify it by using the 'NewLine' property.
+ /// In particular you should never modify the elements of the array
+ /// as they are shared among many instances of TextWriter.
+ /// </summary>
+ protected char[] CoreNewLine = s_coreNewLine;
+ private string CoreNewLineStr = Environment.NewLine;
+
+ // Can be null - if so, ask for the Thread's CurrentCulture every time.
+ private IFormatProvider _internalFormatProvider;
+
+ protected TextWriter()
+ {
+ _internalFormatProvider = null; // Ask for CurrentCulture all the time.
+ }
+
+ protected TextWriter(IFormatProvider formatProvider)
+ {
+ _internalFormatProvider = formatProvider;
+ }
+
+ public virtual IFormatProvider FormatProvider
+ {
+ get
+ {
+ if (_internalFormatProvider == null)
+ {
+ return CultureInfo.CurrentCulture;
+ }
+ else
+ {
+ return _internalFormatProvider;
+ }
+ }
+ }
+
+ public virtual void Close()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ // Clears all buffers for this TextWriter and causes any buffered data to be
+ // written to the underlying device. This default method is empty, but
+ // descendant classes can override the method to provide the appropriate
+ // functionality.
+ public virtual void Flush()
+ {
+ }
+
+ public abstract Encoding Encoding
+ {
+ get;
+ }
+
+ /// <summary>
+ /// Returns the line terminator string used by this TextWriter. The default line
+ /// terminator string is Environment.NewLine, which is platform specific.
+ /// On Windows this is a carriage return followed by a line feed ("\r\n").
+ /// On OSX and Linux this is a line feed ("\n").
+ /// </summary>
+ /// <remarks>
+ /// The line terminator string is written to the text stream whenever one of the
+ /// WriteLine methods are called. In order for text written by
+ /// the TextWriter to be readable by a TextReader, only one of the following line
+ /// terminator strings should be used: "\r", "\n", or "\r\n".
+ /// </remarks>
+ public virtual string NewLine
+ {
+ get { return CoreNewLineStr; }
+ set
+ {
+ if (value == null)
+ {
+ value = Environment.NewLine;
+ }
+
+ CoreNewLineStr = value;
+ CoreNewLine = value.ToCharArray();
+ }
+ }
+
+
+ // Writes a character to the text stream. This default method is empty,
+ // but descendant classes can override the method to provide the
+ // appropriate functionality.
+ //
+ public virtual void Write(char value)
+ {
+ }
+
+ // Writes a character array to the text stream. This default method calls
+ // Write(char) for each of the characters in the character array.
+ // If the character array is null, nothing is written.
+ //
+ public virtual void Write(char[] buffer)
+ {
+ if (buffer != null)
+ {
+ Write(buffer, 0, buffer.Length);
+ }
+ }
+
+ // Writes a range of a character array to the text stream. This method will
+ // write count characters of data into this TextWriter from the
+ // buffer character array starting at position index.
+ //
+ public virtual 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);
+ }
+
+ for (int i = 0; i < count; i++) Write(buffer[index + i]);
+ }
+
+ // Writes a span of characters to the text stream.
+ //
+ public virtual void Write(ReadOnlySpan<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ buffer.CopyTo(new Span<char>(array));
+ Write(array, 0, buffer.Length);
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
+ // Writes the text representation of a boolean to the text stream. This
+ // method outputs either Boolean.TrueString or Boolean.FalseString.
+ //
+ public virtual void Write(bool value)
+ {
+ Write(value ? "True" : "False");
+ }
+
+ // Writes the text representation of an integer to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Int32.ToString() method.
+ //
+ public virtual void Write(int value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of an integer to the text stream. The
+ // text representation of the given value is produced by calling the
+ // UInt32.ToString() method.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(uint value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of a long to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Int64.ToString() method.
+ //
+ public virtual void Write(long value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of an unsigned long to the text
+ // stream. The text representation of the given value is produced
+ // by calling the UInt64.ToString() method.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(ulong value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of a float to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Float.toString(float) method.
+ //
+ public virtual void Write(float value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of a double to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Double.toString(double) method.
+ //
+ public virtual void Write(double value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ public virtual void Write(decimal value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes a string to the text stream. If the given string is null, nothing
+ // is written to the text stream.
+ //
+ public virtual void Write(string value)
+ {
+ if (value != null)
+ {
+ Write(value.ToCharArray());
+ }
+ }
+
+ // Writes the text representation of an object to the text stream. If the
+ // given object is null, nothing is written to the text stream.
+ // Otherwise, the object's ToString method is called to produce the
+ // string representation, and the resulting string is then written to the
+ // output stream.
+ //
+ public virtual void Write(object value)
+ {
+ if (value != null)
+ {
+ IFormattable f = value as IFormattable;
+ if (f != null)
+ {
+ Write(f.ToString(null, FormatProvider));
+ }
+ else
+ Write(value.ToString());
+ }
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, object arg0)
+ {
+ Write(string.Format(FormatProvider, format, arg0));
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, object arg0, object arg1)
+ {
+ Write(string.Format(FormatProvider, format, arg0, arg1));
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, object arg0, object arg1, object arg2)
+ {
+ Write(string.Format(FormatProvider, format, arg0, arg1, arg2));
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, params object[] arg)
+ {
+ Write(string.Format(FormatProvider, format, arg));
+ }
+
+
+ // Writes a line terminator to the text stream. The default line terminator
+ // is Environment.NewLine, but this value can be changed by setting the NewLine property.
+ //
+ public virtual void WriteLine()
+ {
+ Write(CoreNewLine);
+ }
+
+ // Writes a character followed by a line terminator to the text stream.
+ //
+ public virtual void WriteLine(char value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes an array of characters followed by a line terminator to the text
+ // stream.
+ //
+ public virtual void WriteLine(char[] buffer)
+ {
+ Write(buffer);
+ WriteLine();
+ }
+
+ // Writes a range of a character array followed by a line terminator to the
+ // text stream.
+ //
+ public virtual void WriteLine(char[] buffer, int index, int count)
+ {
+ Write(buffer, index, count);
+ WriteLine();
+ }
+
+ public virtual void WriteLine(ReadOnlySpan<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ buffer.CopyTo(new Span<char>(array));
+ WriteLine(array, 0, buffer.Length);
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
+ // Writes the text representation of a boolean followed by a line
+ // terminator to the text stream.
+ //
+ public virtual void WriteLine(bool value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of an integer followed by a line
+ // terminator to the text stream.
+ //
+ public virtual void WriteLine(int value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of an unsigned integer followed by
+ // a line terminator to the text stream.
+ //
+ [CLSCompliant(false)]
+ public virtual void WriteLine(uint value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of a long followed by a line terminator
+ // to the text stream.
+ //
+ public virtual void WriteLine(long value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of an unsigned long followed by
+ // a line terminator to the text stream.
+ //
+ [CLSCompliant(false)]
+ public virtual void WriteLine(ulong value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of a float followed by a line terminator
+ // to the text stream.
+ //
+ public virtual void WriteLine(float value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of a double followed by a line terminator
+ // to the text stream.
+ //
+ public virtual void WriteLine(double value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ public virtual void WriteLine(decimal value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes a string followed by a line terminator to the text stream.
+ //
+ public virtual void WriteLine(string value)
+ {
+ if (value != null)
+ {
+ Write(value);
+ }
+ Write(CoreNewLineStr);
+ }
+
+ // Writes the text representation of an object followed by a line
+ // terminator to the text stream.
+ //
+ public virtual void WriteLine(object value)
+ {
+ if (value == null)
+ {
+ WriteLine();
+ }
+ else
+ {
+ // Call WriteLine(value.ToString), not Write(Object), WriteLine().
+ // This makes calls to WriteLine(Object) atomic.
+ IFormattable f = value as IFormattable;
+ if (f != null)
+ {
+ WriteLine(f.ToString(null, FormatProvider));
+ }
+ else
+ {
+ WriteLine(value.ToString());
+ }
+ }
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, object arg0)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg0));
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, object arg0, object arg1)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg0, arg1));
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, object arg0, object arg1, object arg2)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg0, arg1, arg2));
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, params object[] arg)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg));
+ }
+
+ #region Task based Async APIs
+ public virtual Task WriteAsync(char value)
+ {
+ var tuple = new Tuple<TextWriter, char>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char>)state;
+ t.Item1.Write(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteAsync(string value)
+ {
+ var tuple = new Tuple<TextWriter, string>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, string>)state;
+ t.Item1.Write(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public Task WriteAsync(char[] buffer)
+ {
+ if (buffer == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ return WriteAsync(buffer, 0, buffer.Length);
+ }
+
+ public virtual Task WriteAsync(char[] buffer, int index, int count)
+ {
+ var tuple = new Tuple<TextWriter, char[], int, int>(this, buffer, index, count);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char[], int, int>)state;
+ t.Item1.Write(t.Item2, t.Item3, t.Item4);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
+ WriteAsync(array.Array, array.Offset, array.Count) :
+ Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state;
+ t.Item1.Write(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+
+ public virtual Task WriteLineAsync(char value)
+ {
+ var tuple = new Tuple<TextWriter, char>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char>)state;
+ t.Item1.WriteLine(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteLineAsync(string value)
+ {
+ var tuple = new Tuple<TextWriter, string>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, string>)state;
+ t.Item1.WriteLine(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public Task WriteLineAsync(char[] buffer)
+ {
+ if (buffer == null)
+ {
+ return WriteLineAsync();
+ }
+
+ return WriteLineAsync(buffer, 0, buffer.Length);
+ }
+
+ public virtual Task WriteLineAsync(char[] buffer, int index, int count)
+ {
+ var tuple = new Tuple<TextWriter, char[], int, int>(this, buffer, index, count);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char[], int, int>)state;
+ t.Item1.WriteLine(t.Item2, t.Item3, t.Item4);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
+ WriteLineAsync(array.Array, array.Offset, array.Count) :
+ Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state;
+ t.Item1.WriteLine(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+
+ public virtual Task WriteLineAsync()
+ {
+ return WriteAsync(CoreNewLine);
+ }
+
+ public virtual Task FlushAsync()
+ {
+ return Task.Factory.StartNew(state =>
+ {
+ ((TextWriter)state).Flush();
+ },
+ this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+ #endregion
+
+ private sealed class NullTextWriter : TextWriter
+ {
+ internal NullTextWriter() : base(CultureInfo.InvariantCulture)
+ {
+ }
+
+ public override Encoding Encoding
+ {
+ get
+ {
+ return Encoding.Unicode;
+ }
+ }
+
+ public override void Write(char[] buffer, int index, int count)
+ {
+ }
+
+ public override void Write(string value)
+ {
+ }
+
+ // Not strictly necessary, but for perf reasons
+ public override void WriteLine()
+ {
+ }
+
+ // Not strictly necessary, but for perf reasons
+ public override void WriteLine(string value)
+ {
+ }
+
+ public override void WriteLine(object value)
+ {
+ }
+
+ public override void Write(char value)
+ {
+ }
+ }
+
+ public static TextWriter Synchronized(TextWriter writer)
+ {
+ if (writer == null)
+ throw new ArgumentNullException(nameof(writer));
+
+ return writer is SyncTextWriter ? writer : new SyncTextWriter(writer);
+ }
+
+ internal sealed class SyncTextWriter : TextWriter, IDisposable
+ {
+ private readonly TextWriter _out;
+
+ internal SyncTextWriter(TextWriter t) : base(t.FormatProvider)
+ {
+ _out = t;
+ }
+
+ public override Encoding Encoding => _out.Encoding;
+
+ public override IFormatProvider FormatProvider => _out.FormatProvider;
+
+ public override string NewLine
+ {
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ get { return _out.NewLine; }
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ set { _out.NewLine = value; }
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Close() => _out.Close();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ protected override void Dispose(bool disposing)
+ {
+ // Explicitly pick up a potentially methodimpl'ed Dispose
+ if (disposing)
+ ((IDisposable)_out).Dispose();
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Flush() => _out.Flush();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(char value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(char[] buffer) => _out.Write(buffer);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(char[] buffer, int index, int count) => _out.Write(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(bool value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(int value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(uint value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(long value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(ulong value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(float value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(double value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(Decimal value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(object value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object arg0) => _out.Write(format, arg0);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object arg0, object arg1) => _out.Write(format, arg0, arg1);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object arg0, object arg1, object arg2) => _out.Write(format, arg0, arg1, arg2);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object[] arg) => _out.Write(format, arg);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine() => _out.WriteLine();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(char value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(decimal value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(char[] buffer) => _out.WriteLine(buffer);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(char[] buffer, int index, int count) => _out.WriteLine(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(bool value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(int value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(uint value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(long value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(ulong value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(float value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(double value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(object value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object arg0) => _out.WriteLine(format, arg0);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object arg0, object arg1) => _out.WriteLine(format, arg0, arg1);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object arg0, object arg1, object arg2) => _out.WriteLine(format, arg0, arg1, arg2);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object[] arg) => _out.WriteLine(format, arg);
+
+ //
+ // On SyncTextWriter all APIs should run synchronously, even the async ones.
+ //
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteAsync(char value)
+ {
+ Write(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteAsync(string value)
+ {
+ Write(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteAsync(char[] buffer, int index, int count)
+ {
+ Write(buffer, index, count);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteLineAsync(char value)
+ {
+ WriteLine(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteLineAsync(string value)
+ {
+ WriteLine(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteLineAsync(char[] buffer, int index, int count)
+ {
+ WriteLine(buffer, index, count);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task FlushAsync()
+ {
+ Flush();
+ return Task.CompletedTask;
+ }
+ }
+ }
+}