Fix StyleCop warning SA1005 (single line comment spacing)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / IO / StreamReader.cs
blob9db4769817b38565e60fd04ec2d1c2d650408be8
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Text;
8 using System.Threading;
9 using System.Threading.Tasks;
11 namespace System.IO
13 // This class implements a TextReader for reading characters to a Stream.
14 // This is designed for character input in a particular Encoding,
15 // whereas the Stream class is designed for byte input and output.
16 public class StreamReader : TextReader
18 // StreamReader.Null is threadsafe.
19 public static new readonly StreamReader Null = new NullStreamReader();
21 // Using a 1K byte buffer and a 4K FileStream buffer works out pretty well
22 // perf-wise. On even a 40 MB text file, any perf loss by using a 4K
23 // buffer is negated by the win of allocating a smaller byte[], which
24 // saves construction time. This does break adaptive buffering,
25 // but this is slightly faster.
26 private const int DefaultBufferSize = 1024; // Byte buffer size
27 private const int DefaultFileStreamBufferSize = 4096;
28 private const int MinBufferSize = 128;
30 private readonly Stream _stream;
31 private Encoding _encoding = null!; // only null in NullStreamReader where this is never used
32 private Decoder _decoder = null!; // only null in NullStreamReader where this is never used
33 private readonly byte[] _byteBuffer = null!; // only null in NullStreamReader where this is never used
34 private char[] _charBuffer = null!; // only null in NullStreamReader where this is never used
35 private int _charPos;
36 private int _charLen;
37 // Record the number of valid bytes in the byteBuffer, for a few checks.
38 private int _byteLen;
39 // This is used only for preamble detection
40 private int _bytePos;
42 // This is the maximum number of chars we can get from one call to
43 // ReadBuffer. Used so ReadBuffer can tell when to copy data into
44 // a user's char[] directly, instead of our internal char[].
45 private int _maxCharsPerBuffer;
47 /// <summary>True if the writer has been disposed; otherwise, false.</summary>
48 private bool _disposed;
50 // We will support looking for byte order marks in the stream and trying
51 // to decide what the encoding might be from the byte order marks, IF they
52 // exist. But that's all we'll do.
53 private bool _detectEncoding;
55 // Whether we must still check for the encoding's given preamble at the
56 // beginning of this file.
57 private bool _checkPreamble;
59 // Whether the stream is most likely not going to give us back as much
60 // data as we want the next time we call it. We must do the computation
61 // before we do any byte order mark handling and save the result. Note
62 // that we need this to allow users to handle streams used for an
63 // interactive protocol, where they block waiting for the remote end
64 // to send a response, like logging in on a Unix machine.
65 private bool _isBlocked;
67 // The intent of this field is to leave open the underlying stream when
68 // disposing of this StreamReader. A name like _leaveOpen is better,
69 // but this type is serializable, and this field's name was _closable.
70 private readonly bool _closable; // Whether to close the underlying stream.
72 // We don't guarantee thread safety on StreamReader, but we should at
73 // least prevent users from trying to read anything while an Async
74 // read from the same thread is in progress.
75 private Task _asyncReadTask = Task.CompletedTask;
77 private void CheckAsyncTaskInProgress()
79 // We are not locking the access to _asyncReadTask because this is not meant to guarantee thread safety.
80 // We are simply trying to deter calling any Read APIs while an async Read from the same thread is in progress.
81 if (!_asyncReadTask.IsCompleted)
83 ThrowAsyncIOInProgress();
87 [DoesNotReturn]
88 private static void ThrowAsyncIOInProgress() =>
89 throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
91 // StreamReader by default will ignore illegal UTF8 characters. We don't want to
92 // throw here because we want to be able to read ill-formed data without choking.
93 // The high level goal is to be tolerant of encoding errors when we read and very strict
94 // when we write. Hence, default StreamWriter encoding will throw on error.
96 private StreamReader()
98 Debug.Assert(this is NullStreamReader);
99 _stream = Stream.Null;
100 _closable = true;
103 public StreamReader(Stream stream)
104 : this(stream, true)
108 public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
109 : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
113 public StreamReader(Stream stream, Encoding encoding)
114 : this(stream, encoding, true, DefaultBufferSize, false)
118 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks)
119 : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
123 // Creates a new StreamReader for the given stream. The
124 // character encoding is set by encoding and the buffer size,
125 // in number of 16-bit characters, is set by bufferSize.
127 // Note that detectEncodingFromByteOrderMarks is a very
128 // loose attempt at detecting the encoding by looking at the first
129 // 3 bytes of the stream. It will recognize UTF-8, little endian
130 // unicode, and big endian unicode text, but that's it. If neither
131 // of those three match, it will use the Encoding you provided.
133 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
134 : this(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false)
138 public StreamReader(Stream stream, Encoding? encoding = null, bool detectEncodingFromByteOrderMarks = true, int bufferSize = -1, bool leaveOpen = false)
140 if (stream == null)
142 throw new ArgumentNullException(nameof(stream));
144 if (encoding == null)
146 encoding = Encoding.UTF8;
148 if (!stream.CanRead)
150 throw new ArgumentException(SR.Argument_StreamNotReadable);
152 if (bufferSize == -1)
154 bufferSize = DefaultBufferSize;
156 else if (bufferSize <= 0)
158 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
161 _stream = stream;
162 _encoding = encoding;
163 _decoder = encoding.GetDecoder();
164 if (bufferSize < MinBufferSize)
166 bufferSize = MinBufferSize;
169 _byteBuffer = new byte[bufferSize];
170 _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
171 _charBuffer = new char[_maxCharsPerBuffer];
172 _byteLen = 0;
173 _bytePos = 0;
174 _detectEncoding = detectEncodingFromByteOrderMarks;
175 _checkPreamble = encoding.Preamble.Length > 0;
176 _isBlocked = false;
177 _closable = !leaveOpen;
180 public StreamReader(string path)
181 : this(path, true)
185 public StreamReader(string path, bool detectEncodingFromByteOrderMarks)
186 : this(path, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize)
190 public StreamReader(string path, Encoding encoding)
191 : this(path, encoding, true, DefaultBufferSize)
195 public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
196 : this(path, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize)
200 public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) :
201 this(ValidateArgsAndOpenPath(path, encoding, bufferSize), encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen: false)
205 private static Stream ValidateArgsAndOpenPath(string path, Encoding encoding, int bufferSize)
207 if (path == null)
208 throw new ArgumentNullException(nameof(path));
209 if (encoding == null)
210 throw new ArgumentNullException(nameof(encoding));
211 if (path.Length == 0)
212 throw new ArgumentException(SR.Argument_EmptyPath);
213 if (bufferSize <= 0)
214 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
216 return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
219 public override void Close()
221 Dispose(true);
224 protected override void Dispose(bool disposing)
226 if (_disposed)
228 return;
230 _disposed = true;
232 // Dispose of our resources if this StreamReader is closable.
233 if (_closable)
237 // Note that Stream.Close() can potentially throw here. So we need to
238 // ensure cleaning up internal resources, inside the finally block.
239 if (disposing)
241 _stream.Close();
244 finally
246 _charPos = 0;
247 _charLen = 0;
248 base.Dispose(disposing);
253 public virtual Encoding CurrentEncoding => _encoding;
255 public virtual Stream BaseStream => _stream;
257 // DiscardBufferedData tells StreamReader to throw away its internal
258 // buffer contents. This is useful if the user needs to seek on the
259 // underlying stream to a known location then wants the StreamReader
260 // to start reading from this new point. This method should be called
261 // very sparingly, if ever, since it can lead to very poor performance.
262 // However, it may be the only way of handling some scenarios where
263 // users need to re-read the contents of a StreamReader a second time.
264 public void DiscardBufferedData()
266 CheckAsyncTaskInProgress();
268 _byteLen = 0;
269 _charLen = 0;
270 _charPos = 0;
271 // in general we'd like to have an invariant that encoding isn't null. However,
272 // for startup improvements for NullStreamReader, we want to delay load encoding.
273 if (_encoding != null)
275 _decoder = _encoding.GetDecoder();
277 _isBlocked = false;
280 public bool EndOfStream
284 ThrowIfDisposed();
285 CheckAsyncTaskInProgress();
287 if (_charPos < _charLen)
289 return false;
292 // This may block on pipes!
293 int numRead = ReadBuffer();
294 return numRead == 0;
298 public override int Peek()
300 ThrowIfDisposed();
301 CheckAsyncTaskInProgress();
303 if (_charPos == _charLen)
305 if (_isBlocked || ReadBuffer() == 0)
307 return -1;
310 return _charBuffer[_charPos];
313 public override int Read()
315 ThrowIfDisposed();
316 CheckAsyncTaskInProgress();
318 if (_charPos == _charLen)
320 if (ReadBuffer() == 0)
322 return -1;
325 int result = _charBuffer[_charPos];
326 _charPos++;
327 return result;
330 public override int Read(char[] buffer, int index, int count)
332 if (buffer == null)
334 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
336 if (index < 0 || count < 0)
338 throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
340 if (buffer.Length - index < count)
342 throw new ArgumentException(SR.Argument_InvalidOffLen);
345 return ReadSpan(new Span<char>(buffer, index, count));
348 public override int Read(Span<char> buffer) =>
349 GetType() == typeof(StreamReader) ? ReadSpan(buffer) :
350 base.Read(buffer); // Defer to Read(char[], ...) if a derived type may have previously overridden it
352 private int ReadSpan(Span<char> buffer)
354 ThrowIfDisposed();
355 CheckAsyncTaskInProgress();
357 int charsRead = 0;
358 // As a perf optimization, if we had exactly one buffer's worth of
359 // data read in, let's try writing directly to the user's buffer.
360 bool readToUserBuffer = false;
361 int count = buffer.Length;
362 while (count > 0)
364 int n = _charLen - _charPos;
365 if (n == 0)
367 n = ReadBuffer(buffer.Slice(charsRead), out readToUserBuffer);
369 if (n == 0)
371 break; // We're at EOF
373 if (n > count)
375 n = count;
377 if (!readToUserBuffer)
379 new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Slice(charsRead));
380 _charPos += n;
383 charsRead += n;
384 count -= n;
385 // This function shouldn't block for an indefinite amount of time,
386 // or reading from a network stream won't work right. If we got
387 // fewer bytes than we requested, then we want to break right here.
388 if (_isBlocked)
390 break;
394 return charsRead;
397 public override string ReadToEnd()
399 ThrowIfDisposed();
400 CheckAsyncTaskInProgress();
402 // Call ReadBuffer, then pull data out of charBuffer.
403 StringBuilder sb = new StringBuilder(_charLen - _charPos);
406 sb.Append(_charBuffer, _charPos, _charLen - _charPos);
407 _charPos = _charLen; // Note we consumed these characters
408 ReadBuffer();
409 } while (_charLen > 0);
410 return sb.ToString();
413 public override int ReadBlock(char[] buffer, int index, int count)
415 if (buffer == null)
417 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
419 if (index < 0 || count < 0)
421 throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
423 if (buffer.Length - index < count)
425 throw new ArgumentException(SR.Argument_InvalidOffLen);
427 ThrowIfDisposed();
428 CheckAsyncTaskInProgress();
430 return base.ReadBlock(buffer, index, count);
433 public override int ReadBlock(Span<char> buffer)
435 if (GetType() != typeof(StreamReader))
437 // Defer to Read(char[], ...) if a derived type may have previously overridden it.
438 return base.ReadBlock(buffer);
441 int i, n = 0;
444 i = ReadSpan(buffer.Slice(n));
445 n += i;
446 } while (i > 0 && n < buffer.Length);
447 return n;
450 // Trims n bytes from the front of the buffer.
451 private void CompressBuffer(int n)
453 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?");
454 Buffer.BlockCopy(_byteBuffer, n, _byteBuffer, 0, _byteLen - n);
455 _byteLen -= n;
458 private void DetectEncoding()
460 if (_byteLen < 2)
462 return;
464 _detectEncoding = false;
465 bool changedEncoding = false;
466 if (_byteBuffer[0] == 0xFE && _byteBuffer[1] == 0xFF)
468 // Big Endian Unicode
470 _encoding = Encoding.BigEndianUnicode;
471 CompressBuffer(2);
472 changedEncoding = true;
475 else if (_byteBuffer[0] == 0xFF && _byteBuffer[1] == 0xFE)
477 // Little Endian Unicode, or possibly little endian UTF32
478 if (_byteLen < 4 || _byteBuffer[2] != 0 || _byteBuffer[3] != 0)
480 _encoding = Encoding.Unicode;
481 CompressBuffer(2);
482 changedEncoding = true;
484 else
486 _encoding = Encoding.UTF32;
487 CompressBuffer(4);
488 changedEncoding = true;
492 else if (_byteLen >= 3 && _byteBuffer[0] == 0xEF && _byteBuffer[1] == 0xBB && _byteBuffer[2] == 0xBF)
494 // UTF-8
495 _encoding = Encoding.UTF8;
496 CompressBuffer(3);
497 changedEncoding = true;
499 else if (_byteLen >= 4 && _byteBuffer[0] == 0 && _byteBuffer[1] == 0 &&
500 _byteBuffer[2] == 0xFE && _byteBuffer[3] == 0xFF)
502 // Big Endian UTF32
503 _encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
504 CompressBuffer(4);
505 changedEncoding = true;
507 else if (_byteLen == 2)
509 _detectEncoding = true;
511 // Note: in the future, if we change this algorithm significantly,
512 // we can support checking for the preamble of the given encoding.
514 if (changedEncoding)
516 _decoder = _encoding.GetDecoder();
517 int newMaxCharsPerBuffer = _encoding.GetMaxCharCount(_byteBuffer.Length);
518 if (newMaxCharsPerBuffer > _maxCharsPerBuffer)
520 _charBuffer = new char[newMaxCharsPerBuffer];
522 _maxCharsPerBuffer = newMaxCharsPerBuffer;
526 // Trims the preamble bytes from the byteBuffer. This routine can be called multiple times
527 // and we will buffer the bytes read until the preamble is matched or we determine that
528 // there is no match. If there is no match, every byte read previously will be available
529 // for further consumption. If there is a match, we will compress the buffer for the
530 // leading preamble bytes
531 private bool IsPreamble()
533 if (!_checkPreamble)
535 return _checkPreamble;
538 ReadOnlySpan<byte> preamble = _encoding.Preamble;
540 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?");
541 int len = (_byteLen >= (preamble.Length)) ? (preamble.Length - _bytePos) : (_byteLen - _bytePos);
543 for (int i = 0; i < len; i++, _bytePos++)
545 if (_byteBuffer[_bytePos] != preamble[_bytePos])
547 _bytePos = 0;
548 _checkPreamble = false;
549 break;
553 Debug.Assert(_bytePos <= preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
555 if (_checkPreamble)
557 if (_bytePos == preamble.Length)
559 // We have a match
560 CompressBuffer(preamble.Length);
561 _bytePos = 0;
562 _checkPreamble = false;
563 _detectEncoding = false;
567 return _checkPreamble;
570 internal virtual int ReadBuffer()
572 _charLen = 0;
573 _charPos = 0;
575 if (!_checkPreamble)
577 _byteLen = 0;
582 if (_checkPreamble)
584 Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
585 int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
586 Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
588 if (len == 0)
590 // EOF but we might have buffered bytes from previous
591 // attempt to detect preamble that needs to be decoded now
592 if (_byteLen > 0)
594 _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
595 // Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
596 _bytePos = _byteLen = 0;
599 return _charLen;
602 _byteLen += len;
604 else
606 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?");
607 _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
608 Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
610 if (_byteLen == 0) // We're at EOF
612 return _charLen;
616 // _isBlocked == whether we read fewer bytes than we asked for.
617 // Note we must check it here because CompressBuffer or
618 // DetectEncoding will change byteLen.
619 _isBlocked = (_byteLen < _byteBuffer.Length);
621 // Check for preamble before detect encoding. This is not to override the
622 // user supplied Encoding for the one we implicitly detect. The user could
623 // customize the encoding which we will loose, such as ThrowOnError on UTF8
624 if (IsPreamble())
626 continue;
629 // If we're supposed to detect the encoding and haven't done so yet,
630 // do it. Note this may need to be called more than once.
631 if (_detectEncoding && _byteLen >= 2)
633 DetectEncoding();
636 _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
637 } while (_charLen == 0);
638 return _charLen;
642 // This version has a perf optimization to decode data DIRECTLY into the
643 // user's buffer, bypassing StreamReader's own buffer.
644 // This gives a > 20% perf improvement for our encodings across the board,
645 // but only when asking for at least the number of characters that one
646 // buffer's worth of bytes could produce.
647 // This optimization, if run, will break SwitchEncoding, so we must not do
648 // this on the first call to ReadBuffer.
649 private int ReadBuffer(Span<char> userBuffer, out bool readToUserBuffer)
651 _charLen = 0;
652 _charPos = 0;
654 if (!_checkPreamble)
656 _byteLen = 0;
659 int charsRead = 0;
661 // As a perf optimization, we can decode characters DIRECTLY into a
662 // user's char[]. We absolutely must not write more characters
663 // into the user's buffer than they asked for. Calculating
664 // encoding.GetMaxCharCount(byteLen) each time is potentially very
665 // expensive - instead, cache the number of chars a full buffer's
666 // worth of data may produce. Yes, this makes the perf optimization
667 // less aggressive, in that all reads that asked for fewer than AND
668 // returned fewer than _maxCharsPerBuffer chars won't get the user
669 // buffer optimization. This affects reads where the end of the
670 // Stream comes in the middle somewhere, and when you ask for
671 // fewer chars than your buffer could produce.
672 readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
676 Debug.Assert(charsRead == 0);
678 if (_checkPreamble)
680 Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
681 int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
682 Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
684 if (len == 0)
686 // EOF but we might have buffered bytes from previous
687 // attempt to detect preamble that needs to be decoded now
688 if (_byteLen > 0)
690 if (readToUserBuffer)
692 charsRead = _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush: false);
693 _charLen = 0; // StreamReader's buffer is empty.
695 else
697 charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead);
698 _charLen += charsRead; // Number of chars in StreamReader's buffer.
702 return charsRead;
705 _byteLen += len;
707 else
709 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?");
711 _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
713 Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
715 if (_byteLen == 0) // EOF
717 break;
721 // _isBlocked == whether we read fewer bytes than we asked for.
722 // Note we must check it here because CompressBuffer or
723 // DetectEncoding will change byteLen.
724 _isBlocked = (_byteLen < _byteBuffer.Length);
726 // Check for preamble before detect encoding. This is not to override the
727 // user supplied Encoding for the one we implicitly detect. The user could
728 // customize the encoding which we will loose, such as ThrowOnError on UTF8
729 // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
730 // doesn't change the encoding or affect _maxCharsPerBuffer
731 if (IsPreamble())
733 continue;
736 // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
737 if (_detectEncoding && _byteLen >= 2)
739 DetectEncoding();
740 // DetectEncoding changes some buffer state. Recompute this.
741 readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
744 _charPos = 0;
745 if (readToUserBuffer)
747 charsRead += _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush: false);
748 _charLen = 0; // StreamReader's buffer is empty.
750 else
752 charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead);
753 _charLen += charsRead; // Number of chars in StreamReader's buffer.
755 } while (charsRead == 0);
757 _isBlocked &= charsRead < userBuffer.Length;
759 return charsRead;
763 // Reads a line. A line is defined as a sequence of characters followed by
764 // a carriage return ('\r'), a line feed ('\n'), or a carriage return
765 // immediately followed by a line feed. The resulting string does not
766 // contain the terminating carriage return and/or line feed. The returned
767 // value is null if the end of the input stream has been reached.
769 public override string? ReadLine()
771 ThrowIfDisposed();
772 CheckAsyncTaskInProgress();
774 if (_charPos == _charLen)
776 if (ReadBuffer() == 0)
778 return null;
782 StringBuilder? sb = null;
785 int i = _charPos;
788 char ch = _charBuffer[i];
789 // Note the following common line feed chars:
790 // \n - UNIX \r\n - DOS \r - Mac
791 if (ch == '\r' || ch == '\n')
793 string s;
794 if (sb != null)
796 sb.Append(_charBuffer, _charPos, i - _charPos);
797 s = sb.ToString();
799 else
801 s = new string(_charBuffer, _charPos, i - _charPos);
803 _charPos = i + 1;
804 if (ch == '\r' && (_charPos < _charLen || ReadBuffer() > 0))
806 if (_charBuffer[_charPos] == '\n')
808 _charPos++;
811 return s;
813 i++;
814 } while (i < _charLen);
815 i = _charLen - _charPos;
816 if (sb == null)
818 sb = new StringBuilder(i + 80);
820 sb.Append(_charBuffer, _charPos, i);
821 } while (ReadBuffer() > 0);
822 return sb.ToString();
825 public override Task<string?> ReadLineAsync()
827 // If we have been inherited into a subclass, the following implementation could be incorrect
828 // since it does not call through to Read() which a subclass might have overridden.
829 // To be safe we will only use this implementation in cases where we know it is safe to do so,
830 // and delegate to our base class (which will call into Read) when we are not sure.
831 if (GetType() != typeof(StreamReader))
833 return base.ReadLineAsync();
836 ThrowIfDisposed();
837 CheckAsyncTaskInProgress();
839 Task<string?> task = ReadLineAsyncInternal();
840 _asyncReadTask = task;
842 return task;
845 private async Task<string?> ReadLineAsyncInternal()
847 if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
849 return null;
852 StringBuilder? sb = null;
856 char[] tmpCharBuffer = _charBuffer;
857 int tmpCharLen = _charLen;
858 int tmpCharPos = _charPos;
859 int i = tmpCharPos;
863 char ch = tmpCharBuffer[i];
865 // Note the following common line feed chars:
866 // \n - UNIX \r\n - DOS \r - Mac
867 if (ch == '\r' || ch == '\n')
869 string s;
871 if (sb != null)
873 sb.Append(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
874 s = sb.ToString();
876 else
878 s = new string(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
881 _charPos = tmpCharPos = i + 1;
883 if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync().ConfigureAwait(false)) > 0))
885 tmpCharPos = _charPos;
886 if (_charBuffer[tmpCharPos] == '\n')
888 _charPos = ++tmpCharPos;
892 return s;
895 i++;
896 } while (i < tmpCharLen);
898 i = tmpCharLen - tmpCharPos;
899 if (sb == null)
901 sb = new StringBuilder(i + 80);
903 sb.Append(tmpCharBuffer, tmpCharPos, i);
904 } while (await ReadBufferAsync().ConfigureAwait(false) > 0);
906 return sb.ToString();
909 public override Task<string> ReadToEndAsync()
911 // If we have been inherited into a subclass, the following implementation could be incorrect
912 // since it does not call through to Read() which a subclass might have overridden.
913 // To be safe we will only use this implementation in cases where we know it is safe to do so,
914 // and delegate to our base class (which will call into Read) when we are not sure.
915 if (GetType() != typeof(StreamReader))
917 return base.ReadToEndAsync();
920 ThrowIfDisposed();
921 CheckAsyncTaskInProgress();
923 Task<string> task = ReadToEndAsyncInternal();
924 _asyncReadTask = task;
926 return task;
929 private async Task<string> ReadToEndAsyncInternal()
931 // Call ReadBuffer, then pull data out of charBuffer.
932 StringBuilder sb = new StringBuilder(_charLen - _charPos);
935 int tmpCharPos = _charPos;
936 sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);
937 _charPos = _charLen; // We consumed these characters
938 await ReadBufferAsync().ConfigureAwait(false);
939 } while (_charLen > 0);
941 return sb.ToString();
944 public override Task<int> ReadAsync(char[] buffer, int index, int count)
946 if (buffer == null)
948 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
950 if (index < 0 || count < 0)
952 throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
954 if (buffer.Length - index < count)
956 throw new ArgumentException(SR.Argument_InvalidOffLen);
959 // If we have been inherited into a subclass, the following implementation could be incorrect
960 // since it does not call through to Read() which a subclass might have overridden.
961 // To be safe we will only use this implementation in cases where we know it is safe to do so,
962 // and delegate to our base class (which will call into Read) when we are not sure.
963 if (GetType() != typeof(StreamReader))
965 return base.ReadAsync(buffer, index, count);
968 ThrowIfDisposed();
969 CheckAsyncTaskInProgress();
971 Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
972 _asyncReadTask = task;
974 return task;
977 public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
979 if (GetType() != typeof(StreamReader))
981 // Ensure we use existing overrides if a class already overrode existing overloads.
982 return base.ReadAsync(buffer, cancellationToken);
985 ThrowIfDisposed();
986 CheckAsyncTaskInProgress();
988 if (cancellationToken.IsCancellationRequested)
990 return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
993 return ReadAsyncInternal(buffer, cancellationToken);
996 internal override async ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
998 if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
1000 return 0;
1003 int charsRead = 0;
1005 // As a perf optimization, if we had exactly one buffer's worth of
1006 // data read in, let's try writing directly to the user's buffer.
1007 bool readToUserBuffer = false;
1009 byte[] tmpByteBuffer = _byteBuffer;
1010 Stream tmpStream = _stream;
1012 int count = buffer.Length;
1013 while (count > 0)
1015 // n is the characters available in _charBuffer
1016 int n = _charLen - _charPos;
1018 // charBuffer is empty, let's read from the stream
1019 if (n == 0)
1021 _charLen = 0;
1022 _charPos = 0;
1024 if (!_checkPreamble)
1026 _byteLen = 0;
1029 readToUserBuffer = count >= _maxCharsPerBuffer;
1031 // We loop here so that we read in enough bytes to yield at least 1 char.
1032 // We break out of the loop if the stream is blocked (EOF is reached).
1035 Debug.Assert(n == 0);
1037 if (_checkPreamble)
1039 Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
1040 int tmpBytePos = _bytePos;
1041 int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false);
1042 Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
1044 if (len == 0)
1046 // EOF but we might have buffered bytes from previous
1047 // attempts to detect preamble that needs to be decoded now
1048 if (_byteLen > 0)
1050 if (readToUserBuffer)
1052 n = _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
1053 _charLen = 0; // StreamReader's buffer is empty.
1055 else
1057 n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
1058 _charLen += n; // Number of chars in StreamReader's buffer.
1062 // How can part of the preamble yield any chars?
1063 Debug.Assert(n == 0);
1065 _isBlocked = true;
1066 break;
1068 else
1070 _byteLen += len;
1073 else
1075 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?");
1077 _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);
1079 Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
1081 if (_byteLen == 0) // EOF
1083 _isBlocked = true;
1084 break;
1088 // _isBlocked == whether we read fewer bytes than we asked for.
1089 // Note we must check it here because CompressBuffer or
1090 // DetectEncoding will change _byteLen.
1091 _isBlocked = (_byteLen < tmpByteBuffer.Length);
1093 // Check for preamble before detect encoding. This is not to override the
1094 // user supplied Encoding for the one we implicitly detect. The user could
1095 // customize the encoding which we will loose, such as ThrowOnError on UTF8
1096 // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
1097 // doesn't change the encoding or affect _maxCharsPerBuffer
1098 if (IsPreamble())
1100 continue;
1103 // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
1104 if (_detectEncoding && _byteLen >= 2)
1106 DetectEncoding();
1107 // DetectEncoding changes some buffer state. Recompute this.
1108 readToUserBuffer = count >= _maxCharsPerBuffer;
1111 Debug.Assert(n == 0);
1113 _charPos = 0;
1114 if (readToUserBuffer)
1116 n += _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
1118 // Why did the bytes yield no chars?
1119 Debug.Assert(n > 0);
1121 _charLen = 0; // StreamReader's buffer is empty.
1123 else
1125 n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
1127 // Why did the bytes yield no chars?
1128 Debug.Assert(n > 0);
1130 _charLen += n; // Number of chars in StreamReader's buffer.
1132 } while (n == 0);
1134 if (n == 0)
1136 break; // We're at EOF
1138 } // if (n == 0)
1140 // Got more chars in charBuffer than the user requested
1141 if (n > count)
1143 n = count;
1146 if (!readToUserBuffer)
1148 new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Span.Slice(charsRead));
1149 _charPos += n;
1152 charsRead += n;
1153 count -= n;
1155 // This function shouldn't block for an indefinite amount of time,
1156 // or reading from a network stream won't work right. If we got
1157 // fewer bytes than we requested, then we want to break right here.
1158 if (_isBlocked)
1160 break;
1162 } // while (count > 0)
1164 return charsRead;
1167 public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
1169 if (buffer == null)
1171 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
1173 if (index < 0 || count < 0)
1175 throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
1177 if (buffer.Length - index < count)
1179 throw new ArgumentException(SR.Argument_InvalidOffLen);
1182 // If we have been inherited into a subclass, the following implementation could be incorrect
1183 // since it does not call through to Read() which a subclass might have overridden.
1184 // To be safe we will only use this implementation in cases where we know it is safe to do so,
1185 // and delegate to our base class (which will call into Read) when we are not sure.
1186 if (GetType() != typeof(StreamReader))
1188 return base.ReadBlockAsync(buffer, index, count);
1191 ThrowIfDisposed();
1192 CheckAsyncTaskInProgress();
1194 Task<int> task = base.ReadBlockAsync(buffer, index, count);
1195 _asyncReadTask = task;
1197 return task;
1200 public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
1202 if (GetType() != typeof(StreamReader))
1204 // If a derived type may have overridden ReadBlockAsync(char[], ...) before this overload
1205 // was introduced, defer to it.
1206 return base.ReadBlockAsync(buffer, cancellationToken);
1209 ThrowIfDisposed();
1210 CheckAsyncTaskInProgress();
1212 if (cancellationToken.IsCancellationRequested)
1214 return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
1217 ValueTask<int> vt = ReadBlockAsyncInternal(buffer, cancellationToken);
1218 if (vt.IsCompletedSuccessfully)
1220 return vt;
1223 Task<int> t = vt.AsTask();
1224 _asyncReadTask = t;
1225 return new ValueTask<int>(t);
1228 private async ValueTask<int> ReadBufferAsync()
1230 _charLen = 0;
1231 _charPos = 0;
1232 byte[] tmpByteBuffer = _byteBuffer;
1233 Stream tmpStream = _stream;
1235 if (!_checkPreamble)
1237 _byteLen = 0;
1241 if (_checkPreamble)
1243 Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
1244 int tmpBytePos = _bytePos;
1245 int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos)).ConfigureAwait(false);
1246 Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
1248 if (len == 0)
1250 // EOF but we might have buffered bytes from previous
1251 // attempt to detect preamble that needs to be decoded now
1252 if (_byteLen > 0)
1254 _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
1255 // Need to zero out the _byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
1256 _bytePos = 0; _byteLen = 0;
1259 return _charLen;
1262 _byteLen += len;
1264 else
1266 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?");
1267 _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer)).ConfigureAwait(false);
1268 Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class.");
1270 if (_byteLen == 0) // We're at EOF
1272 return _charLen;
1276 // _isBlocked == whether we read fewer bytes than we asked for.
1277 // Note we must check it here because CompressBuffer or
1278 // DetectEncoding will change _byteLen.
1279 _isBlocked = (_byteLen < tmpByteBuffer.Length);
1281 // Check for preamble before detect encoding. This is not to override the
1282 // user supplied Encoding for the one we implicitly detect. The user could
1283 // customize the encoding which we will loose, such as ThrowOnError on UTF8
1284 if (IsPreamble())
1286 continue;
1289 // If we're supposed to detect the encoding and haven't done so yet,
1290 // do it. Note this may need to be called more than once.
1291 if (_detectEncoding && _byteLen >= 2)
1293 DetectEncoding();
1296 _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
1297 } while (_charLen == 0);
1299 return _charLen;
1302 private void ThrowIfDisposed()
1304 if (_disposed)
1306 ThrowObjectDisposedException();
1309 void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_ReaderClosed);
1312 // No data, class doesn't need to be serializable.
1313 // Note this class is threadsafe.
1314 private sealed class NullStreamReader : StreamReader
1316 public override Encoding CurrentEncoding => Encoding.Unicode;
1318 protected override void Dispose(bool disposing)
1320 // Do nothing - this is essentially unclosable.
1323 public override int Peek()
1325 return -1;
1328 public override int Read()
1330 return -1;
1333 [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
1334 public override int Read(char[] buffer, int index, int count)
1336 return 0;
1339 public override string? ReadLine()
1341 return null;
1344 public override string ReadToEnd()
1346 return string.Empty;
1349 internal override int ReadBuffer()
1351 return 0;