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
;
8 using System
.Threading
;
9 using System
.Threading
.Tasks
;
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
37 // Record the number of valid bytes in the byteBuffer, for a few checks.
39 // This is used only for preamble detection
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();
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
;
103 public StreamReader(Stream stream
)
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)
142 throw new ArgumentNullException(nameof(stream
));
144 if (encoding
== null)
146 encoding
= Encoding
.UTF8
;
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
);
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
];
174 _detectEncoding
= detectEncodingFromByteOrderMarks
;
175 _checkPreamble
= encoding
.Preamble
.Length
> 0;
177 _closable
= !leaveOpen
;
180 public StreamReader(string path
)
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
)
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
);
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()
224 protected override void Dispose(bool disposing
)
232 // Dispose of our resources if this StreamReader is closable.
237 // Note that Stream.Close() can potentially throw here. So we need to
238 // ensure cleaning up internal resources, inside the finally block.
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();
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();
280 public bool EndOfStream
285 CheckAsyncTaskInProgress();
287 if (_charPos
< _charLen
)
292 // This may block on pipes!
293 int numRead
= ReadBuffer();
298 public override int Peek()
301 CheckAsyncTaskInProgress();
303 if (_charPos
== _charLen
)
305 if (_isBlocked
|| ReadBuffer() == 0)
310 return _charBuffer
[_charPos
];
313 public override int Read()
316 CheckAsyncTaskInProgress();
318 if (_charPos
== _charLen
)
320 if (ReadBuffer() == 0)
325 int result
= _charBuffer
[_charPos
];
330 public override int Read(char[] buffer
, int index
, int count
)
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
)
355 CheckAsyncTaskInProgress();
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
;
364 int n
= _charLen
- _charPos
;
367 n
= ReadBuffer(buffer
.Slice(charsRead
), out readToUserBuffer
);
371 break; // We're at EOF
377 if (!readToUserBuffer
)
379 new Span
<char>(_charBuffer
, _charPos
, n
).CopyTo(buffer
.Slice(charsRead
));
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.
397 public override string ReadToEnd()
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
409 } while (_charLen
> 0);
410 return sb
.ToString();
413 public override int ReadBlock(char[] buffer
, int index
, int count
)
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
);
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
);
444 i
= ReadSpan(buffer
.Slice(n
));
446 } while (i
> 0 && n
< buffer
.Length
);
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
);
458 private void DetectEncoding()
464 _detectEncoding
= false;
465 bool changedEncoding
= false;
466 if (_byteBuffer
[0] == 0xFE && _byteBuffer
[1] == 0xFF)
468 // Big Endian Unicode
470 _encoding
= Encoding
.BigEndianUnicode
;
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
;
482 changedEncoding
= true;
486 _encoding
= Encoding
.UTF32
;
488 changedEncoding
= true;
492 else if (_byteLen
>= 3 && _byteBuffer
[0] == 0xEF && _byteBuffer
[1] == 0xBB && _byteBuffer
[2] == 0xBF)
495 _encoding
= Encoding
.UTF8
;
497 changedEncoding
= true;
499 else if (_byteLen
>= 4 && _byteBuffer
[0] == 0 && _byteBuffer
[1] == 0 &&
500 _byteBuffer
[2] == 0xFE && _byteBuffer
[3] == 0xFF)
503 _encoding
= new UTF32Encoding(bigEndian
: true, byteOrderMark
: true);
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.
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()
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
])
548 _checkPreamble
= false;
553 Debug
.Assert(_bytePos
<= preamble
.Length
, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
557 if (_bytePos
== preamble
.Length
)
560 CompressBuffer(preamble
.Length
);
562 _checkPreamble
= false;
563 _detectEncoding
= false;
567 return _checkPreamble
;
570 internal virtual int ReadBuffer()
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.");
590 // EOF but we might have buffered bytes from previous
591 // attempt to detect preamble that needs to be decoded now
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;
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
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
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)
636 _charLen
+= _decoder
.GetChars(_byteBuffer
, 0, _byteLen
, _charBuffer
, _charLen
);
637 } while (_charLen
== 0);
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
)
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);
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.");
686 // EOF but we might have buffered bytes from previous
687 // attempt to detect preamble that needs to be decoded now
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.
697 charsRead
= _decoder
.GetChars(_byteBuffer
, 0, _byteLen
, _charBuffer
, charsRead
);
698 _charLen
+= charsRead
; // Number of chars in StreamReader's buffer.
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
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
736 // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
737 if (_detectEncoding
&& _byteLen
>= 2)
740 // DetectEncoding changes some buffer state. Recompute this.
741 readToUserBuffer
= userBuffer
.Length
>= _maxCharsPerBuffer
;
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.
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
;
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()
772 CheckAsyncTaskInProgress();
774 if (_charPos
== _charLen
)
776 if (ReadBuffer() == 0)
782 StringBuilder
? sb
= null;
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')
796 sb
.Append(_charBuffer
, _charPos
, i
- _charPos
);
801 s
= new string(_charBuffer
, _charPos
, i
- _charPos
);
804 if (ch
== '\r' && (_charPos
< _charLen
|| ReadBuffer() > 0))
806 if (_charBuffer
[_charPos
] == '\n')
814 } while (i
< _charLen
);
815 i
= _charLen
- _charPos
;
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();
837 CheckAsyncTaskInProgress();
839 Task
<string?> task
= ReadLineAsyncInternal();
840 _asyncReadTask
= task
;
845 private async Task
<string?> ReadLineAsyncInternal()
847 if (_charPos
== _charLen
&& (await ReadBufferAsync().ConfigureAwait(false)) == 0)
852 StringBuilder
? sb
= null;
856 char[] tmpCharBuffer
= _charBuffer
;
857 int tmpCharLen
= _charLen
;
858 int tmpCharPos
= _charPos
;
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')
873 sb
.Append(tmpCharBuffer
, tmpCharPos
, i
- tmpCharPos
);
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
;
896 } while (i
< tmpCharLen
);
898 i
= tmpCharLen
- tmpCharPos
;
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();
921 CheckAsyncTaskInProgress();
923 Task
<string> task
= ReadToEndAsyncInternal();
924 _asyncReadTask
= 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
)
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
);
969 CheckAsyncTaskInProgress();
971 Task
<int> task
= ReadAsyncInternal(new Memory
<char>(buffer
, index
, count
), default).AsTask();
972 _asyncReadTask
= 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
);
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)
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
;
1015 // n is the characters available in _charBuffer
1016 int n
= _charLen
- _charPos
;
1018 // charBuffer is empty, let's read from the stream
1024 if (!_checkPreamble
)
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);
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.");
1046 // EOF but we might have buffered bytes from previous
1047 // attempts to detect preamble that needs to be decoded now
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.
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);
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
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
1103 // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
1104 if (_detectEncoding
&& _byteLen
>= 2)
1107 // DetectEncoding changes some buffer state. Recompute this.
1108 readToUserBuffer
= count
>= _maxCharsPerBuffer
;
1111 Debug
.Assert(n
== 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.
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.
1136 break; // We're at EOF
1140 // Got more chars in charBuffer than the user requested
1146 if (!readToUserBuffer
)
1148 new Span
<char>(_charBuffer
, _charPos
, n
).CopyTo(buffer
.Span
.Slice(charsRead
));
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.
1162 } // while (count > 0)
1167 public override Task
<int> ReadBlockAsync(char[] buffer
, int index
, int count
)
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
);
1192 CheckAsyncTaskInProgress();
1194 Task
<int> task
= base.ReadBlockAsync(buffer
, index
, count
);
1195 _asyncReadTask
= 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
);
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
)
1223 Task
<int> t
= vt
.AsTask();
1225 return new ValueTask
<int>(t
);
1228 private async ValueTask
<int> ReadBufferAsync()
1232 byte[] tmpByteBuffer
= _byteBuffer
;
1233 Stream tmpStream
= _stream
;
1235 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.");
1250 // EOF but we might have buffered bytes from previous
1251 // attempt to detect preamble that needs to be decoded now
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;
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
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
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)
1296 _charLen
+= _decoder
.GetChars(tmpByteBuffer
, 0, _byteLen
, _charBuffer
, _charLen
);
1297 } while (_charLen
== 0);
1302 private void ThrowIfDisposed()
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()
1328 public override int Read()
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
)
1339 public override string? ReadLine()
1344 public override string ReadToEnd()
1346 return string.Empty
;
1349 internal override int ReadBuffer()