| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422 |
- // 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.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.
- public class StreamReader : TextReader
- {
- // StreamReader.Null is threadsafe.
- public new static readonly StreamReader Null = new NullStreamReader();
- // 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.
- private const int DefaultBufferSize = 1024; // Byte buffer size
- private const int DefaultFileStreamBufferSize = 4096;
- private const int MinBufferSize = 128;
- 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;
- // This is used only for preamble detection
- 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
- // a user's char[] directly, instead of our internal char[].
- private int _maxCharsPerBuffer;
- // We will support looking for byte order marks in the stream and trying
- // to decide what the encoding might be from the byte order marks, IF they
- // exist. But that's all we'll do.
- private bool _detectEncoding;
- // Whether we must still check for the encoding's given preamble at the
- // beginning of this file.
- private bool _checkPreamble;
- // Whether the stream is most likely not going to give us back as much
- // data as we want the next time we call it. We must do the computation
- // before we do any byte order mark handling and save the result. Note
- // that we need this to allow users to handle streams used for an
- // interactive protocol, where they block waiting for the remote end
- // to send a response, like logging in on a Unix machine.
- private bool _isBlocked;
- // The intent of this field is to leave open the underlying stream when
- // 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
- // read from the same thread is in progress.
- private Task _asyncReadTask = Task.CompletedTask;
- 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.
- if (!_asyncReadTask.IsCompleted)
- {
- ThrowAsyncIOInProgress();
- }
- }
- private static void ThrowAsyncIOInProgress() =>
- 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()
- {
- }
- public StreamReader(Stream stream)
- : this(stream, true)
- {
- }
- 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, bool detectEncodingFromByteOrderMarks)
- : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
- {
- }
- // Creates a new StreamReader 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.
- //
- // Note that detectEncodingFromByteOrderMarks is a very
- // loose attempt at detecting the encoding by looking at the first
- // 3 bytes of the stream. It will recognize UTF-8, little endian
- // unicode, and big endian unicode text, but that's it. If neither
- // 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)
- {
- }
- 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));
- }
- 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, 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, bool detectEncodingFromByteOrderMarks)
- : this(path, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize)
- {
- }
- public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, 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, 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)
- {
- _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;
- _detectEncoding = detectEncodingFromByteOrderMarks;
- _checkPreamble = encoding.Preamble.Length > 0;
- _isBlocked = false;
- _closable = !leaveOpen;
- }
- // Init used by NullStreamReader, to delay load encoding
- internal void Init(Stream stream)
- {
- _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
- {
- // 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;
- base.Dispose(disposing);
- }
- }
- }
- public virtual Encoding CurrentEncoding
- {
- get { return _encoding; }
- }
- public virtual Stream BaseStream
- {
- get { return _stream; }
- }
- internal bool LeaveOpen
- {
- get { return !_closable; }
- }
- // DiscardBufferedData tells StreamReader to throw away its internal
- // buffer contents. This is useful if the user needs to seek on the
- // underlying stream to a known location then wants the StreamReader
- // to start reading from this new point. This method should be called
- // very sparingly, if ever, since it can lead to very poor performance.
- // However, it may be the only way of handling some scenarios where
- // users need to re-read the contents of a StreamReader a second time.
- public void DiscardBufferedData()
- {
- CheckAsyncTaskInProgress();
- _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();
- }
- _isBlocked = false;
- }
- public bool EndOfStream
- {
- get
- {
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- if (_charPos < _charLen)
- {
- return false;
- }
- // This may block on pipes!
- int numRead = ReadBuffer();
- return numRead == 0;
- }
- }
- public override int Peek()
- {
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- if (_charPos == _charLen)
- {
- if (_isBlocked || ReadBuffer() == 0)
- {
- return -1;
- }
- }
- return _charBuffer[_charPos];
- }
- public override int Read()
- {
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- if (_charPos == _charLen)
- {
- if (ReadBuffer() == 0)
- {
- return -1;
- }
- }
- int result = _charBuffer[_charPos];
- _charPos++;
- return result;
- }
- public override int Read(char[] buffer, int index, int count)
- {
- 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 ReadSpan(new Span<char>(buffer, index, count));
- }
- 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();
- int charsRead = 0;
- // 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;
- 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()
- {
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- // Call ReadBuffer, then pull data out of charBuffer.
- 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 sb.ToString();
- }
- public override int ReadBlock(char[] buffer, int index, int count)
- {
- 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);
- }
- 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;
- }
- private void DetectEncoding()
- {
- if (_byteLen < 2)
- {
- return;
- }
- _detectEncoding = false;
- bool changedEncoding = false;
- if (_byteBuffer[0] == 0xFE && _byteBuffer[1] == 0xFF)
- {
- // Big Endian Unicode
- _encoding = Encoding.BigEndianUnicode;
- CompressBuffer(2);
- changedEncoding = true;
- }
- 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;
- CompressBuffer(2);
- changedEncoding = true;
- }
- else
- {
- _encoding = Encoding.UTF32;
- CompressBuffer(4);
- changedEncoding = true;
- }
- }
- else if (_byteLen >= 3 && _byteBuffer[0] == 0xEF && _byteBuffer[1] == 0xBB && _byteBuffer[2] == 0xBF)
- {
- // UTF-8
- _encoding = Encoding.UTF8;
- CompressBuffer(3);
- changedEncoding = true;
- }
- else if (_byteLen >= 4 && _byteBuffer[0] == 0 && _byteBuffer[1] == 0 &&
- _byteBuffer[2] == 0xFE && _byteBuffer[3] == 0xFF)
- {
- // Big Endian UTF32
- _encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
- CompressBuffer(4);
- changedEncoding = true;
- }
- 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();
- int newMaxCharsPerBuffer = _encoding.GetMaxCharCount(_byteBuffer.Length);
- if (newMaxCharsPerBuffer > _maxCharsPerBuffer)
- {
- _charBuffer = new char[newMaxCharsPerBuffer];
- }
- _maxCharsPerBuffer = newMaxCharsPerBuffer;
- }
- }
- // Trims the preamble bytes from the byteBuffer. This routine can be called multiple times
- // and we will buffer the bytes read until the preamble is matched or we determine that
- // there is no match. If there is no match, every byte read previously will be available
- // for further consumption. If there is a match, we will compress the buffer for the
- // leading preamble bytes
- private bool IsPreamble()
- {
- 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);
- 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?");
- if (_checkPreamble)
- {
- if (_bytePos == preamble.Length)
- {
- // We have a match
- CompressBuffer(preamble.Length);
- _bytePos = 0;
- _checkPreamble = false;
- _detectEncoding = false;
- }
- }
- return _checkPreamble;
- }
- internal virtual int ReadBuffer()
- {
- _charLen = 0;
- _charPos = 0;
- if (!_checkPreamble)
- {
- _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)
- {
- // EOF but we might have buffered bytes from previous
- // attempt to detect preamble that needs to be decoded now
- if (_byteLen > 0)
- {
- _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;
- }
- return _charLen;
- }
- _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.");
- 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);
- // Check for preamble before detect encoding. This is not to override the
- // 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)
- {
- DetectEncoding();
- }
- _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
- } while (_charLen == 0);
- //Console.WriteLine("ReadBuffer called. chars: "+charLen);
- return _charLen;
- }
- // This version has a perf optimization to decode data DIRECTLY into the
- // user's buffer, bypassing StreamReader's own buffer.
- // This gives a > 20% perf improvement for our encodings across the board,
- // but only when asking for at least the number of characters that one
- // 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(Span<char> userBuffer, out bool readToUserBuffer)
- {
- _charLen = 0;
- _charPos = 0;
- if (!_checkPreamble)
- {
- _byteLen = 0;
- }
- int charsRead = 0;
- // As a perf optimization, we can decode characters DIRECTLY into a
- // user's char[]. We absolutely must not write more characters
- // into the user's buffer than they asked for. Calculating
- // encoding.GetMaxCharCount(byteLen) each time is potentially very
- // expensive - instead, cache the number of chars a full buffer's
- // worth of data may produce. Yes, this makes the perf optimization
- // less aggressive, in that all reads that asked for fewer than AND
- // returned fewer than _maxCharsPerBuffer chars won't get the user
- // 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 = userBuffer.Length >= _maxCharsPerBuffer;
- do
- {
- Debug.Assert(charsRead == 0);
- 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)
- {
- // 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(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.
- }
- }
- return charsRead;
- }
- _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.");
- 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);
- // Check for preamble before detect encoding. This is not to override the
- // 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)
- {
- DetectEncoding();
- // DetectEncoding changes some buffer state. Recompute this.
- readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
- }
- _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.
- }
- } while (charsRead == 0);
- _isBlocked &= charsRead < userBuffer.Length;
- //Console.WriteLine("ReadBuffer: charsRead: "+charsRead+" readToUserBuffer: "+readToUserBuffer);
- return charsRead;
- }
- // 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 override string ReadLine()
- {
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- if (_charPos == _charLen)
- {
- if (ReadBuffer() == 0)
- {
- return null;
- }
- }
- StringBuilder sb = null;
- 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 = sb.ToString();
- }
- else
- {
- s = new string(_charBuffer, _charPos, i - _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 = new StringBuilder(i + 80);
- }
- sb.Append(_charBuffer, _charPos, i);
- } while (ReadBuffer() > 0);
- return sb.ToString();
- }
- #region Task based Async APIs
- 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 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 (GetType() != typeof(StreamReader))
- {
- return base.ReadLineAsync();
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- Task<string> task = ReadLineAsyncInternal();
- _asyncReadTask = task;
- return task;
- }
- private async Task<string> ReadLineAsyncInternal()
- {
- if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
- {
- return null;
- }
- StringBuilder sb = null;
- do
- {
- char[] tmpCharBuffer = _charBuffer;
- int tmpCharLen = _charLen;
- int tmpCharPos = _charPos;
- int i = tmpCharPos;
- do
- {
- char ch = tmpCharBuffer[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(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
- s = sb.ToString();
- }
- else
- {
- s = new string(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
- }
- _charPos = tmpCharPos = i + 1;
- if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync().ConfigureAwait(false)) > 0))
- {
- tmpCharPos = _charPos;
- if (_charBuffer[tmpCharPos] == '\n')
- {
- _charPos = ++tmpCharPos;
- }
- }
- return s;
- }
- i++;
- } while (i < tmpCharLen);
- i = tmpCharLen - tmpCharPos;
- if (sb == null)
- {
- sb = new StringBuilder(i + 80);
- }
- sb.Append(tmpCharBuffer, tmpCharPos, i);
- } while (await ReadBufferAsync().ConfigureAwait(false) > 0);
- return sb.ToString();
- }
- 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 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 (GetType() != typeof(StreamReader))
- {
- return base.ReadToEndAsync();
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- Task<string> task = ReadToEndAsyncInternal();
- _asyncReadTask = task;
- return task;
- }
- private async Task<string> ReadToEndAsyncInternal()
- {
- // Call ReadBuffer, then pull data out of charBuffer.
- StringBuilder sb = new StringBuilder(_charLen - _charPos);
- do
- {
- int tmpCharPos = _charPos;
- sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);
- _charPos = _charLen; // We consumed these characters
- await ReadBufferAsync().ConfigureAwait(false);
- } while (_charLen > 0);
- return sb.ToString();
- }
- public override Task<int> ReadAsync(char[] buffer, int index, int count)
- {
- 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);
- }
- // 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 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 (GetType() != typeof(StreamReader))
- {
- return base.ReadAsync(buffer, index, count);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
- _asyncReadTask = task;
- return task;
- }
- 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)
- {
- return 0;
- }
- int charsRead = 0;
- // 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;
- byte[] tmpByteBuffer = _byteBuffer;
- Stream tmpStream = _stream;
- int count = buffer.Length;
- while (count > 0)
- {
- // n is the characters available in _charBuffer
- int n = _charLen - _charPos;
- // charBuffer is empty, let's read from the stream
- if (n == 0)
- {
- _charLen = 0;
- _charPos = 0;
- if (!_checkPreamble)
- {
- _byteLen = 0;
- }
- readToUserBuffer = count >= _maxCharsPerBuffer;
- // We loop here so that we read in enough bytes to yield at least 1 char.
- // We break out of the loop if the stream is blocked (EOF is reached).
- do
- {
- Debug.Assert(n == 0);
- 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(new Memory<byte>(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 (readToUserBuffer)
- {
- 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.
- }
- }
- // How can part of the preamble yield any chars?
- Debug.Assert(n == 0);
- _isBlocked = true;
- break;
- }
- else
- {
- _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(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);
- Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
- 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);
- // Check for preamble before detect encoding. This is not to override the
- // 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)
- {
- DetectEncoding();
- // DetectEncoding changes some buffer state. Recompute this.
- readToUserBuffer = count >= _maxCharsPerBuffer;
- }
- Debug.Assert(n == 0);
- _charPos = 0;
- if (readToUserBuffer)
- {
- 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.
- }
- else
- {
- 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.
- }
- } while (n == 0);
- 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)
- {
- new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Span.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;
- }
- } // while (count > 0)
- return charsRead;
- }
- public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
- {
- 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);
- }
- // 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 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 (GetType() != typeof(StreamReader))
- {
- return base.ReadBlockAsync(buffer, index, count);
- }
- if (_stream == null)
- {
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
- }
- CheckAsyncTaskInProgress();
- Task<int> task = base.ReadBlockAsync(buffer, index, count);
- _asyncReadTask = task;
- 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;
- if (!_checkPreamble)
- {
- _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(new Memory<byte>(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)
- {
- // EOF but we might have buffered bytes from previous
- // attempt to detect preamble that needs to be decoded now
- if (_byteLen > 0)
- {
- _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;
- }
- return _charLen;
- }
- _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(new Memory<byte>(tmpByteBuffer)).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);
- // Check for preamble before detect encoding. This is not to override the
- // 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)
- {
- DetectEncoding();
- }
- _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
- } while (_charLen == 0);
- return _charLen;
- }
- #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()
- {
- Init(Stream.Null);
- }
- public override Stream BaseStream
- {
- get { return Stream.Null; }
- }
- public override Encoding CurrentEncoding
- {
- get { return Encoding.Unicode; }
- }
- protected override void Dispose(bool disposing)
- {
- // Do nothing - this is essentially unclosable.
- }
- public override int Peek()
- {
- return -1;
- }
- public override int Read()
- {
- return -1;
- }
- [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()
- {
- return null;
- }
- public override string ReadToEnd()
- {
- return string.Empty;
- }
- internal override int ReadBuffer()
- {
- return 0;
- }
- }
- }
- }
|