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 /*============================================================
11 ** Purpose: Wraps a stream and provides convenient read functionality
12 ** for strings and primitive types.
15 ============================================================*/
17 using System
.Buffers
.Binary
;
18 using System
.Diagnostics
;
19 using System
.Runtime
.CompilerServices
;
20 using System
.Runtime
.InteropServices
;
25 public class BinaryReader
: IDisposable
27 private const int MaxCharBytesSize
= 128;
29 private readonly Stream _stream
;
30 private readonly byte[] _buffer
;
31 private readonly Decoder _decoder
;
32 private byte[]? _charBytes
;
33 private char[]? _charBuffer
;
34 private readonly int _maxCharsSize
; // From MaxCharBytesSize & Encoding
36 // Performance optimization for Read() w/ Unicode. Speeds us up by ~40%
37 private readonly bool _2BytesPerChar
;
38 private readonly bool _isMemoryStream
; // "do we sit on MemoryStream?" for Read/ReadInt32 perf
39 private readonly bool _leaveOpen
;
40 private bool _disposed
;
42 public BinaryReader(Stream input
) : this(input
, Encoding
.UTF8
, false)
46 public BinaryReader(Stream input
, Encoding encoding
) : this(input
, encoding
, false)
50 public BinaryReader(Stream input
, Encoding encoding
, bool leaveOpen
)
54 throw new ArgumentNullException(nameof(input
));
58 throw new ArgumentNullException(nameof(encoding
));
62 throw new ArgumentException(SR
.Argument_StreamNotReadable
);
66 _decoder
= encoding
.GetDecoder();
67 _maxCharsSize
= encoding
.GetMaxCharCount(MaxCharBytesSize
);
68 int minBufferSize
= encoding
.GetMaxByteCount(1); // max bytes per one char
69 if (minBufferSize
< 16)
74 _buffer
= new byte[minBufferSize
];
75 // _charBuffer and _charBytes will be left null.
77 // For Encodings that always use 2 bytes per char (or more),
78 // special case them here to make Read() & Peek() faster.
79 _2BytesPerChar
= encoding
is UnicodeEncoding
;
80 // check if BinaryReader is based on MemoryStream, and keep this for it's life
81 // we cannot use "as" operator, since derived classes are not allowed
82 _isMemoryStream
= (_stream
.GetType() == typeof(MemoryStream
));
83 _leaveOpen
= leaveOpen
;
85 Debug
.Assert(_decoder
!= null, "[BinaryReader.ctor]_decoder!=null");
88 public virtual Stream BaseStream
=> _stream
;
90 protected virtual void Dispose(bool disposing
)
94 if (disposing
&& !_leaveOpen
)
102 public void Dispose()
108 /// Override Dispose(bool) instead of Close(). This API exists for compatibility purposes.
110 public virtual void Close()
115 private void ThrowIfDisposed()
119 throw Error
.GetFileNotOpen();
123 public virtual int PeekChar()
127 if (!_stream
.CanSeek
)
132 long origPos
= _stream
.Position
;
134 _stream
.Position
= origPos
;
138 public virtual int Read()
148 posSav
= _stream
.Position
;
151 if (_charBytes
== null)
153 _charBytes
= new byte[MaxCharBytesSize
]; //REVIEW: We need at most 2 bytes/char here?
156 Span
<char> singleChar
= stackalloc char[1];
158 while (charsRead
== 0)
160 // We really want to know what the minimum number of bytes per char
161 // is for our encoding. Otherwise for UnicodeEncoding we'd have to
162 // do ~1+log(n) reads to read n characters.
163 // Assume 1 byte can be 1 char unless _2BytesPerChar is true.
164 numBytes
= _2BytesPerChar
? 2 : 1;
166 int r
= _stream
.ReadByte();
167 _charBytes
[0] = (byte)r
;
174 r
= _stream
.ReadByte();
175 _charBytes
[1] = (byte)r
;
187 Debug
.Assert(numBytes
== 1 || numBytes
== 2, "BinaryReader::ReadOneChar assumes it's reading one or 2 bytes only.");
191 charsRead
= _decoder
.GetChars(new ReadOnlySpan
<byte>(_charBytes
, 0, numBytes
), singleChar
, flush
: false);
195 // Handle surrogate char
199 _stream
.Seek((posSav
- _stream
.Position
), SeekOrigin
.Current
);
201 // else - we can't do much here
206 Debug
.Assert(charsRead
< 2, "BinaryReader::ReadOneChar - assuming we only got 0 or 1 char, not 2!");
208 Debug
.Assert(charsRead
> 0);
209 return singleChar
[0];
212 public virtual byte ReadByte() => InternalReadByte();
214 [MethodImpl(MethodImplOptions
.AggressiveInlining
)] // Inlined to avoid some method call overhead with InternalRead.
215 private byte InternalReadByte()
219 int b
= _stream
.ReadByte();
222 throw Error
.GetEndOfFile();
228 [CLSCompliant(false)]
229 public virtual sbyte ReadSByte() => (sbyte)InternalReadByte();
230 public virtual bool ReadBoolean() => InternalReadByte() != 0;
232 public virtual char ReadChar()
237 throw Error
.GetEndOfFile();
242 public virtual short ReadInt16() => BinaryPrimitives
.ReadInt16LittleEndian(InternalRead(2));
244 [CLSCompliant(false)]
245 public virtual ushort ReadUInt16() => BinaryPrimitives
.ReadUInt16LittleEndian(InternalRead(2));
247 public virtual int ReadInt32() => BinaryPrimitives
.ReadInt32LittleEndian(InternalRead(4));
248 [CLSCompliant(false)]
249 public virtual uint ReadUInt32() => BinaryPrimitives
.ReadUInt32LittleEndian(InternalRead(4));
250 public virtual long ReadInt64() => BinaryPrimitives
.ReadInt64LittleEndian(InternalRead(8));
251 [CLSCompliant(false)]
252 public virtual ulong ReadUInt64() => BinaryPrimitives
.ReadUInt64LittleEndian(InternalRead(8));
253 public virtual unsafe float ReadSingle() => BitConverter
.Int32BitsToSingle(BinaryPrimitives
.ReadInt32LittleEndian(InternalRead(4)));
254 public virtual unsafe double ReadDouble() => BitConverter
.Int64BitsToDouble(BinaryPrimitives
.ReadInt64LittleEndian(InternalRead(8)));
256 public virtual decimal ReadDecimal()
258 ReadOnlySpan
<byte> span
= InternalRead(16);
261 return decimal.ToDecimal(span
);
263 catch (ArgumentException e
)
265 // ReadDecimal cannot leak out ArgumentException
266 throw new IOException(SR
.Arg_DecBitCtor
, e
);
270 public virtual string ReadString()
280 // Length of the string in bytes, not chars
281 stringLength
= Read7BitEncodedInt();
282 if (stringLength
< 0)
284 throw new IOException(SR
.Format(SR
.IO_InvalidStringLen_Len
, stringLength
));
287 if (stringLength
== 0)
292 if (_charBytes
== null)
294 _charBytes
= new byte[MaxCharBytesSize
];
297 if (_charBuffer
== null)
299 _charBuffer
= new char[_maxCharsSize
];
302 StringBuilder
? sb
= null;
305 readLength
= ((stringLength
- currPos
) > MaxCharBytesSize
) ? MaxCharBytesSize
: (stringLength
- currPos
);
307 n
= _stream
.Read(_charBytes
, 0, readLength
);
310 throw Error
.GetEndOfFile();
313 charsRead
= _decoder
.GetChars(_charBytes
, 0, n
, _charBuffer
, 0);
315 if (currPos
== 0 && n
== stringLength
)
317 return new string(_charBuffer
, 0, charsRead
);
322 sb
= StringBuilderCache
.Acquire(stringLength
); // Actual string length in chars may be smaller.
325 sb
.Append(_charBuffer
, 0, charsRead
);
327 } while (currPos
< stringLength
);
329 return StringBuilderCache
.GetStringAndRelease(sb
);
332 public virtual int Read(char[] buffer
, int index
, int count
)
336 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
340 throw new ArgumentOutOfRangeException(nameof(index
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
344 throw new ArgumentOutOfRangeException(nameof(count
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
346 if (buffer
.Length
- index
< count
)
348 throw new ArgumentException(SR
.Argument_InvalidOffLen
);
352 // SafeCritical: index and count have already been verified to be a valid range for the buffer
353 return InternalReadChars(new Span
<char>(buffer
, index
, count
));
356 public virtual int Read(Span
<char> buffer
)
359 return InternalReadChars(buffer
);
362 private int InternalReadChars(Span
<char> buffer
)
364 Debug
.Assert(!_disposed
);
368 int charsRemaining
= buffer
.Length
;
370 if (_charBytes
== null)
372 _charBytes
= new byte[MaxCharBytesSize
];
375 while (charsRemaining
> 0)
378 // We really want to know what the minimum number of bytes per char
379 // is for our encoding. Otherwise for UnicodeEncoding we'd have to
380 // do ~1+log(n) reads to read n characters.
381 numBytes
= charsRemaining
;
387 if (numBytes
> MaxCharBytesSize
)
389 numBytes
= MaxCharBytesSize
;
393 byte[]? byteBuffer
= null;
396 Debug
.Assert(_stream
is MemoryStream
);
397 MemoryStream mStream
= (MemoryStream
)_stream
;
399 position
= mStream
.InternalGetPosition();
400 numBytes
= mStream
.InternalEmulateRead(numBytes
);
401 byteBuffer
= mStream
.InternalGetBuffer();
405 numBytes
= _stream
.Read(_charBytes
, 0, numBytes
);
406 byteBuffer
= _charBytes
;
411 return (buffer
.Length
- charsRemaining
);
414 Debug
.Assert(byteBuffer
!= null, "expected byteBuffer to be non-null");
417 if (position
< 0 || numBytes
< 0 || position
> byteBuffer
.Length
- numBytes
)
419 throw new ArgumentOutOfRangeException(nameof(numBytes
));
421 if (index
< 0 || charsRemaining
< 0 || index
> buffer
.Length
- charsRemaining
)
423 throw new ArgumentOutOfRangeException(nameof(charsRemaining
));
427 fixed (byte* pBytes
= byteBuffer
)
428 fixed (char* pChars
= &MemoryMarshal
.GetReference(buffer
))
430 charsRead
= _decoder
.GetChars(pBytes
+ position
, numBytes
, pChars
+ index
, charsRemaining
, flush
: false);
435 charsRemaining
-= charsRead
;
439 // this should never fail
440 Debug
.Assert(charsRemaining
>= 0, "We read too many characters.");
442 // we may have read fewer than the number of characters requested if end of stream reached
443 // or if the encoding makes the char count too big for the buffer (e.g. fallback sequence)
444 return (buffer
.Length
- charsRemaining
);
447 public virtual char[] ReadChars(int count
)
451 throw new ArgumentOutOfRangeException(nameof(count
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
457 return Array
.Empty
<char>();
460 // SafeCritical: we own the chars buffer, and therefore can guarantee that the index and count are valid
461 char[] chars
= new char[count
];
462 int n
= InternalReadChars(new Span
<char>(chars
));
465 char[] copy
= new char[n
];
466 Buffer
.BlockCopy(chars
, 0, copy
, 0, 2 * n
); // sizeof(char)
473 public virtual int Read(byte[] buffer
, int index
, int count
)
477 throw new ArgumentNullException(nameof(buffer
), SR
.ArgumentNull_Buffer
);
481 throw new ArgumentOutOfRangeException(nameof(index
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
485 throw new ArgumentOutOfRangeException(nameof(count
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
487 if (buffer
.Length
- index
< count
)
489 throw new ArgumentException(SR
.Argument_InvalidOffLen
);
493 return _stream
.Read(buffer
, index
, count
);
496 public virtual int Read(Span
<byte> buffer
)
499 return _stream
.Read(buffer
);
502 public virtual byte[] ReadBytes(int count
)
506 throw new ArgumentOutOfRangeException(nameof(count
), SR
.ArgumentOutOfRange_NeedNonNegNum
);
512 return Array
.Empty
<byte>();
515 byte[] result
= new byte[count
];
519 int n
= _stream
.Read(result
, numRead
, count
);
529 if (numRead
!= result
.Length
)
531 // Trim array. This should happen on EOF & possibly net streams.
532 byte[] copy
= new byte[numRead
];
533 Buffer
.BlockCopy(result
, 0, copy
, 0, numRead
);
540 private ReadOnlySpan
<byte> InternalRead(int numBytes
)
542 Debug
.Assert(numBytes
>= 2 && numBytes
<= 16, "value of 1 should use ReadByte. value > 16 requires to change the minimal _buffer size");
546 // read directly from MemoryStream buffer
547 Debug
.Assert(_stream
is MemoryStream
);
548 return ((MemoryStream
)_stream
).InternalReadSpan(numBytes
);
557 int n
= _stream
.Read(_buffer
, bytesRead
, numBytes
- bytesRead
);
560 throw Error
.GetEndOfFile();
563 } while (bytesRead
< numBytes
);
569 // FillBuffer is not performing well when reading from MemoryStreams as it is using the public Stream interface.
570 // We introduced new function InternalRead which can work directly on the MemoryStream internal buffer or using the public Stream
571 // interface when working with all other streams. This function is not needed anymore but we decided not to delete it for compatibility
572 // reasons. More about the subject in: https://github.com/dotnet/coreclr/pull/22102
573 protected virtual void FillBuffer(int numBytes
)
575 if (numBytes
< 0 || numBytes
> _buffer
.Length
)
577 throw new ArgumentOutOfRangeException(nameof(numBytes
), SR
.ArgumentOutOfRange_BinaryReaderFillBuffer
);
585 // Need to find a good threshold for calling ReadByte() repeatedly
586 // vs. calling Read(byte[], int, int) for both buffered & unbuffered
590 n
= _stream
.ReadByte();
593 throw Error
.GetEndOfFile();
596 _buffer
[0] = (byte)n
;
602 n
= _stream
.Read(_buffer
, bytesRead
, numBytes
- bytesRead
);
605 throw Error
.GetEndOfFile();
608 } while (bytesRead
< numBytes
);
611 protected internal int Read7BitEncodedInt()
613 // Read out an Int32 7 bits at a time. The high bit
614 // of the byte when on means to continue reading more bytes.
620 // Check for a corrupted stream. Read a max of 5 bytes.
621 // In a future version, add a DataFormatException.
622 if (shift
== 5 * 7) // 5 bytes max per Int32, shift += 7
624 throw new FormatException(SR
.Format_Bad7BitInt32
);
627 // ReadByte handles end of stream cases for us.
629 count
|= (b
& 0x7F) << shift
;
631 } while ((b
& 0x80) != 0);