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);
638 //Console.WriteLine("ReadBuffer called. chars: "+charLen);
643 // This version has a perf optimization to decode data DIRECTLY into the
644 // user's buffer, bypassing StreamReader's own buffer.
645 // This gives a > 20% perf improvement for our encodings across the board,
646 // but only when asking for at least the number of characters that one
647 // buffer's worth of bytes could produce.
648 // This optimization, if run, will break SwitchEncoding, so we must not do
649 // this on the first call to ReadBuffer.
650 private int ReadBuffer(Span
<char> userBuffer
, out bool readToUserBuffer
)
662 // As a perf optimization, we can decode characters DIRECTLY into a
663 // user's char[]. We absolutely must not write more characters
664 // into the user's buffer than they asked for. Calculating
665 // encoding.GetMaxCharCount(byteLen) each time is potentially very
666 // expensive - instead, cache the number of chars a full buffer's
667 // worth of data may produce. Yes, this makes the perf optimization
668 // less aggressive, in that all reads that asked for fewer than AND
669 // returned fewer than _maxCharsPerBuffer chars won't get the user
670 // buffer optimization. This affects reads where the end of the
671 // Stream comes in the middle somewhere, and when you ask for
672 // fewer chars than your buffer could produce.
673 readToUserBuffer
= userBuffer
.Length
>= _maxCharsPerBuffer
;
677 Debug
.Assert(charsRead
== 0);
681 Debug
.Assert(_bytePos
<= _encoding
.Preamble
.Length
, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
682 int len
= _stream
.Read(_byteBuffer
, _bytePos
, _byteBuffer
.Length
- _bytePos
);
683 Debug
.Assert(len
>= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
687 // EOF but we might have buffered bytes from previous
688 // attempt to detect preamble that needs to be decoded now
691 if (readToUserBuffer
)
693 charsRead
= _decoder
.GetChars(new ReadOnlySpan
<byte>(_byteBuffer
, 0, _byteLen
), userBuffer
.Slice(charsRead
), flush
: false);
694 _charLen
= 0; // StreamReader's buffer is empty.
698 charsRead
= _decoder
.GetChars(_byteBuffer
, 0, _byteLen
, _charBuffer
, charsRead
);
699 _charLen
+= charsRead
; // Number of chars in StreamReader's buffer.
710 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?");
712 _byteLen
= _stream
.Read(_byteBuffer
, 0, _byteBuffer
.Length
);
714 Debug
.Assert(_byteLen
>= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
716 if (_byteLen
== 0) // EOF
722 // _isBlocked == whether we read fewer bytes than we asked for.
723 // Note we must check it here because CompressBuffer or
724 // DetectEncoding will change byteLen.
725 _isBlocked
= (_byteLen
< _byteBuffer
.Length
);
727 // Check for preamble before detect encoding. This is not to override the
728 // user supplied Encoding for the one we implicitly detect. The user could
729 // customize the encoding which we will loose, such as ThrowOnError on UTF8
730 // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
731 // doesn't change the encoding or affect _maxCharsPerBuffer
737 // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
738 if (_detectEncoding
&& _byteLen
>= 2)
741 // DetectEncoding changes some buffer state. Recompute this.
742 readToUserBuffer
= userBuffer
.Length
>= _maxCharsPerBuffer
;
746 if (readToUserBuffer
)
748 charsRead
+= _decoder
.GetChars(new ReadOnlySpan
<byte>(_byteBuffer
, 0, _byteLen
), userBuffer
.Slice(charsRead
), flush
:false);
749 _charLen
= 0; // StreamReader's buffer is empty.
753 charsRead
= _decoder
.GetChars(_byteBuffer
, 0, _byteLen
, _charBuffer
, charsRead
);
754 _charLen
+= charsRead
; // Number of chars in StreamReader's buffer.
756 } while (charsRead
== 0);
758 _isBlocked
&= charsRead
< userBuffer
.Length
;
760 //Console.WriteLine("ReadBuffer: charsRead: "+charsRead+" readToUserBuffer: "+readToUserBuffer);
765 // Reads a line. A line is defined as a sequence of characters followed by
766 // a carriage return ('\r'), a line feed ('\n'), or a carriage return
767 // immediately followed by a line feed. The resulting string does not
768 // contain the terminating carriage return and/or line feed. The returned
769 // value is null if the end of the input stream has been reached.
771 public override string? ReadLine()
774 CheckAsyncTaskInProgress();
776 if (_charPos
== _charLen
)
778 if (ReadBuffer() == 0)
784 StringBuilder
? sb
= null;
790 char ch
= _charBuffer
[i
];
791 // Note the following common line feed chars:
792 // \n - UNIX \r\n - DOS \r - Mac
793 if (ch
== '\r' || ch
== '\n')
798 sb
.Append(_charBuffer
, _charPos
, i
- _charPos
);
803 s
= new string(_charBuffer
, _charPos
, i
- _charPos
);
806 if (ch
== '\r' && (_charPos
< _charLen
|| ReadBuffer() > 0))
808 if (_charBuffer
[_charPos
] == '\n')
816 } while (i
< _charLen
);
817 i
= _charLen
- _charPos
;
820 sb
= new StringBuilder(i
+ 80);
822 sb
.Append(_charBuffer
, _charPos
, i
);
823 } while (ReadBuffer() > 0);
824 return sb
.ToString();
827 public override Task
<string?> ReadLineAsync()
829 // If we have been inherited into a subclass, the following implementation could be incorrect
830 // since it does not call through to Read() which a subclass might have overridden.
831 // To be safe we will only use this implementation in cases where we know it is safe to do so,
832 // and delegate to our base class (which will call into Read) when we are not sure.
833 if (GetType() != typeof(StreamReader
))
835 return base.ReadLineAsync();
839 CheckAsyncTaskInProgress();
841 Task
<string?> task
= ReadLineAsyncInternal();
842 _asyncReadTask
= task
;
847 private async Task
<string?> ReadLineAsyncInternal()
849 if (_charPos
== _charLen
&& (await ReadBufferAsync().ConfigureAwait(false)) == 0)
854 StringBuilder
? sb
= null;
858 char[] tmpCharBuffer
= _charBuffer
;
859 int tmpCharLen
= _charLen
;
860 int tmpCharPos
= _charPos
;
865 char ch
= tmpCharBuffer
[i
];
867 // Note the following common line feed chars:
868 // \n - UNIX \r\n - DOS \r - Mac
869 if (ch
== '\r' || ch
== '\n')
875 sb
.Append(tmpCharBuffer
, tmpCharPos
, i
- tmpCharPos
);
880 s
= new string(tmpCharBuffer
, tmpCharPos
, i
- tmpCharPos
);
883 _charPos
= tmpCharPos
= i
+ 1;
885 if (ch
== '\r' && (tmpCharPos
< tmpCharLen
|| (await ReadBufferAsync().ConfigureAwait(false)) > 0))
887 tmpCharPos
= _charPos
;
888 if (_charBuffer
[tmpCharPos
] == '\n')
890 _charPos
= ++tmpCharPos
;
898 } while (i
< tmpCharLen
);
900 i
= tmpCharLen
- tmpCharPos
;
903 sb
= new StringBuilder(i
+ 80);
905 sb
.Append(tmpCharBuffer
, tmpCharPos
, i
);
906 } while (await ReadBufferAsync().ConfigureAwait(false) > 0);
908 return sb
.ToString();
911 public override Task
<string> ReadToEndAsync()
913 // If we have been inherited into a subclass, the following implementation could be incorrect
914 // since it does not call through to Read() which a subclass might have overridden.
915 // To be safe we will only use this implementation in cases where we know it is safe to do so,
916 // and delegate to our base class (which will call into Read) when we are not sure.
917 if (GetType() != typeof(StreamReader
))
919 return base.ReadToEndAsync();
923 CheckAsyncTaskInProgress();
925 Task
<string> task
= ReadToEndAsyncInternal();
926 _asyncReadTask
= task
;
931 private async Task
<string> ReadToEndAsyncInternal()
933 // Call ReadBuffer, then pull data out of charBuffer.
934 StringBuilder sb
= new StringBuilder(_charLen
- _charPos
);
937 int tmpCharPos
= _charPos
;
938 sb
.Append(_charBuffer
, tmpCharPos
, _charLen
- tmpCharPos
);
939 _charPos
= _charLen
; // We consumed these characters
940 await ReadBufferAsync().ConfigureAwait(false);
941 } while (_charLen
> 0);
943 return sb
.ToString();
946 public override Task
<int> ReadAsync(char[] buffer
, int index
, int count
)
950 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
952 if (index
< 0 || count
< 0)
954 throw new ArgumentOutOfRangeException(index
< 0 ? nameof(index
) : nameof(count
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
956 if (buffer
.Length
- index
< count
)
958 throw new ArgumentException(SR
.Argument_InvalidOffLen
);
961 // If we have been inherited into a subclass, the following implementation could be incorrect
962 // since it does not call through to Read() which a subclass might have overridden.
963 // To be safe we will only use this implementation in cases where we know it is safe to do so,
964 // and delegate to our base class (which will call into Read) when we are not sure.
965 if (GetType() != typeof(StreamReader
))
967 return base.ReadAsync(buffer
, index
, count
);
971 CheckAsyncTaskInProgress();
973 Task
<int> task
= ReadAsyncInternal(new Memory
<char>(buffer
, index
, count
), default).AsTask();
974 _asyncReadTask
= task
;
979 public override ValueTask
<int> ReadAsync(Memory
<char> buffer
, CancellationToken cancellationToken
= default)
981 if (GetType() != typeof(StreamReader
))
983 // Ensure we use existing overrides if a class already overrode existing overloads.
984 return base.ReadAsync(buffer
, cancellationToken
);
988 CheckAsyncTaskInProgress();
990 if (cancellationToken
.IsCancellationRequested
)
992 return new ValueTask
<int>(Task
.FromCanceled
<int>(cancellationToken
));
995 return ReadAsyncInternal(buffer
, cancellationToken
);
998 internal override async ValueTask
<int> ReadAsyncInternal(Memory
<char> buffer
, CancellationToken cancellationToken
)
1000 if (_charPos
== _charLen
&& (await ReadBufferAsync().ConfigureAwait(false)) == 0)
1007 // As a perf optimization, if we had exactly one buffer's worth of
1008 // data read in, let's try writing directly to the user's buffer.
1009 bool readToUserBuffer
= false;
1011 byte[] tmpByteBuffer
= _byteBuffer
;
1012 Stream tmpStream
= _stream
;
1014 int count
= buffer
.Length
;
1017 // n is the characters available in _charBuffer
1018 int n
= _charLen
- _charPos
;
1020 // charBuffer is empty, let's read from the stream
1026 if (!_checkPreamble
)
1031 readToUserBuffer
= count
>= _maxCharsPerBuffer
;
1033 // We loop here so that we read in enough bytes to yield at least 1 char.
1034 // We break out of the loop if the stream is blocked (EOF is reached).
1037 Debug
.Assert(n
== 0);
1041 Debug
.Assert(_bytePos
<= _encoding
.Preamble
.Length
, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
1042 int tmpBytePos
= _bytePos
;
1043 int len
= await tmpStream
.ReadAsync(new Memory
<byte>(tmpByteBuffer
, tmpBytePos
, tmpByteBuffer
.Length
- tmpBytePos
), cancellationToken
).ConfigureAwait(false);
1044 Debug
.Assert(len
>= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
1048 // EOF but we might have buffered bytes from previous
1049 // attempts to detect preamble that needs to be decoded now
1052 if (readToUserBuffer
)
1054 n
= _decoder
.GetChars(new ReadOnlySpan
<byte>(tmpByteBuffer
, 0, _byteLen
), buffer
.Span
.Slice(charsRead
), flush
: false);
1055 _charLen
= 0; // StreamReader's buffer is empty.
1059 n
= _decoder
.GetChars(tmpByteBuffer
, 0, _byteLen
, _charBuffer
, 0);
1060 _charLen
+= n
; // Number of chars in StreamReader's buffer.
1064 // How can part of the preamble yield any chars?
1065 Debug
.Assert(n
== 0);
1077 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?");
1079 _byteLen
= await tmpStream
.ReadAsync(new Memory
<byte>(tmpByteBuffer
), cancellationToken
).ConfigureAwait(false);
1081 Debug
.Assert(_byteLen
>= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
1083 if (_byteLen
== 0) // EOF
1090 // _isBlocked == whether we read fewer bytes than we asked for.
1091 // Note we must check it here because CompressBuffer or
1092 // DetectEncoding will change _byteLen.
1093 _isBlocked
= (_byteLen
< tmpByteBuffer
.Length
);
1095 // Check for preamble before detect encoding. This is not to override the
1096 // user supplied Encoding for the one we implicitly detect. The user could
1097 // customize the encoding which we will loose, such as ThrowOnError on UTF8
1098 // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
1099 // doesn't change the encoding or affect _maxCharsPerBuffer
1105 // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
1106 if (_detectEncoding
&& _byteLen
>= 2)
1109 // DetectEncoding changes some buffer state. Recompute this.
1110 readToUserBuffer
= count
>= _maxCharsPerBuffer
;
1113 Debug
.Assert(n
== 0);
1116 if (readToUserBuffer
)
1118 n
+= _decoder
.GetChars(new ReadOnlySpan
<byte>(tmpByteBuffer
, 0, _byteLen
), buffer
.Span
.Slice(charsRead
), flush
: false);
1120 // Why did the bytes yield no chars?
1121 Debug
.Assert(n
> 0);
1123 _charLen
= 0; // StreamReader's buffer is empty.
1127 n
= _decoder
.GetChars(tmpByteBuffer
, 0, _byteLen
, _charBuffer
, 0);
1129 // Why did the bytes yield no chars?
1130 Debug
.Assert(n
> 0);
1132 _charLen
+= n
; // Number of chars in StreamReader's buffer.
1138 break; // We're at EOF
1142 // Got more chars in charBuffer than the user requested
1148 if (!readToUserBuffer
)
1150 new Span
<char>(_charBuffer
, _charPos
, n
).CopyTo(buffer
.Span
.Slice(charsRead
));
1157 // This function shouldn't block for an indefinite amount of time,
1158 // or reading from a network stream won't work right. If we got
1159 // fewer bytes than we requested, then we want to break right here.
1164 } // while (count > 0)
1169 public override Task
<int> ReadBlockAsync(char[] buffer
, int index
, int count
)
1173 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
1175 if (index
< 0 || count
< 0)
1177 throw new ArgumentOutOfRangeException(index
< 0 ? nameof(index
) : nameof(count
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
1179 if (buffer
.Length
- index
< count
)
1181 throw new ArgumentException(SR
.Argument_InvalidOffLen
);
1184 // If we have been inherited into a subclass, the following implementation could be incorrect
1185 // since it does not call through to Read() which a subclass might have overridden.
1186 // To be safe we will only use this implementation in cases where we know it is safe to do so,
1187 // and delegate to our base class (which will call into Read) when we are not sure.
1188 if (GetType() != typeof(StreamReader
))
1190 return base.ReadBlockAsync(buffer
, index
, count
);
1194 CheckAsyncTaskInProgress();
1196 Task
<int> task
= base.ReadBlockAsync(buffer
, index
, count
);
1197 _asyncReadTask
= task
;
1202 public override ValueTask
<int> ReadBlockAsync(Memory
<char> buffer
, CancellationToken cancellationToken
= default)
1204 if (GetType() != typeof(StreamReader
))
1206 // If a derived type may have overridden ReadBlockAsync(char[], ...) before this overload
1207 // was introduced, defer to it.
1208 return base.ReadBlockAsync(buffer
, cancellationToken
);
1212 CheckAsyncTaskInProgress();
1214 if (cancellationToken
.IsCancellationRequested
)
1216 return new ValueTask
<int>(Task
.FromCanceled
<int>(cancellationToken
));
1219 ValueTask
<int> vt
= ReadBlockAsyncInternal(buffer
, cancellationToken
);
1220 if (vt
.IsCompletedSuccessfully
)
1225 Task
<int> t
= vt
.AsTask();
1227 return new ValueTask
<int>(t
);
1230 private async ValueTask
<int> ReadBufferAsync()
1234 byte[] tmpByteBuffer
= _byteBuffer
;
1235 Stream tmpStream
= _stream
;
1237 if (!_checkPreamble
)
1245 Debug
.Assert(_bytePos
<= _encoding
.Preamble
.Length
, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
1246 int tmpBytePos
= _bytePos
;
1247 int len
= await tmpStream
.ReadAsync(new Memory
<byte>(tmpByteBuffer
, tmpBytePos
, tmpByteBuffer
.Length
- tmpBytePos
)).ConfigureAwait(false);
1248 Debug
.Assert(len
>= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
1252 // EOF but we might have buffered bytes from previous
1253 // attempt to detect preamble that needs to be decoded now
1256 _charLen
+= _decoder
.GetChars(tmpByteBuffer
, 0, _byteLen
, _charBuffer
, _charLen
);
1257 // Need to zero out the _byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
1258 _bytePos
= 0; _byteLen
= 0;
1268 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?");
1269 _byteLen
= await tmpStream
.ReadAsync(new Memory
<byte>(tmpByteBuffer
)).ConfigureAwait(false);
1270 Debug
.Assert(_byteLen
>= 0, "Stream.Read returned a negative number! Bug in stream class.");
1272 if (_byteLen
== 0) // We're at EOF
1278 // _isBlocked == whether we read fewer bytes than we asked for.
1279 // Note we must check it here because CompressBuffer or
1280 // DetectEncoding will change _byteLen.
1281 _isBlocked
= (_byteLen
< tmpByteBuffer
.Length
);
1283 // Check for preamble before detect encoding. This is not to override the
1284 // user supplied Encoding for the one we implicitly detect. The user could
1285 // customize the encoding which we will loose, such as ThrowOnError on UTF8
1291 // If we're supposed to detect the encoding and haven't done so yet,
1292 // do it. Note this may need to be called more than once.
1293 if (_detectEncoding
&& _byteLen
>= 2)
1298 _charLen
+= _decoder
.GetChars(tmpByteBuffer
, 0, _byteLen
, _charBuffer
, _charLen
);
1299 } while (_charLen
== 0);
1304 private void ThrowIfDisposed()
1308 ThrowObjectDisposedException();
1311 void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name
, SR
.ObjectDisposed_ReaderClosed
);
1314 // No data, class doesn't need to be serializable.
1315 // Note this class is threadsafe.
1316 private sealed class NullStreamReader
: StreamReader
1318 public override Encoding CurrentEncoding
=> Encoding
.Unicode
;
1320 protected override void Dispose(bool disposing
)
1322 // Do nothing - this is essentially unclosable.
1325 public override int Peek()
1330 public override int Read()
1335 [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
1336 public override int Read(char[] buffer
, int index
, int count
)
1341 public override string? ReadLine()
1346 public override string ReadToEnd()
1348 return string.Empty
;
1351 internal override int ReadBuffer()