Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / IO / BinaryReader.cs
blob686462022eacbd8d05994309437e65f85e9c487c
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 /*============================================================
6 **
7 **
8 **
9 **
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;
21 using System.Text;
23 namespace System.IO
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)
52 if (input == null)
54 throw new ArgumentNullException(nameof(input));
56 if (encoding == null)
58 throw new ArgumentNullException(nameof(encoding));
60 if (!input.CanRead)
62 throw new ArgumentException(SR.Argument_StreamNotReadable);
65 _stream = input;
66 _decoder = encoding.GetDecoder();
67 _maxCharsSize = encoding.GetMaxCharCount(MaxCharBytesSize);
68 int minBufferSize = encoding.GetMaxByteCount(1); // max bytes per one char
69 if (minBufferSize < 16)
71 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)
92 if (!_disposed)
94 if (disposing && !_leaveOpen)
96 _stream.Close();
98 _disposed = true;
102 public void Dispose()
104 Dispose(true);
107 /// <remarks>
108 /// Override Dispose(bool) instead of Close(). This API exists for compatibility purposes.
109 /// </remarks>
110 public virtual void Close()
112 Dispose(true);
115 private void ThrowIfDisposed()
117 if (_disposed)
119 throw Error.GetFileNotOpen();
123 public virtual int PeekChar()
125 ThrowIfDisposed();
127 if (!_stream.CanSeek)
129 return -1;
132 long origPos = _stream.Position;
133 int ch = Read();
134 _stream.Position = origPos;
135 return ch;
138 public virtual int Read()
140 ThrowIfDisposed();
142 int charsRead = 0;
143 int numBytes;
144 long posSav = 0;
146 if (_stream.CanSeek)
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;
168 if (r == -1)
170 numBytes = 0;
172 if (numBytes == 2)
174 r = _stream.ReadByte();
175 _charBytes[1] = (byte)r;
176 if (r == -1)
178 numBytes = 1;
182 if (numBytes == 0)
184 return -1;
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);
193 catch
195 // Handle surrogate char
197 if (_stream.CanSeek)
199 _stream.Seek((posSav - _stream.Position), SeekOrigin.Current);
201 // else - we can't do much here
203 throw;
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()
217 ThrowIfDisposed();
219 int b = _stream.ReadByte();
220 if (b == -1)
222 throw Error.GetEndOfFile();
225 return (byte)b;
228 [CLSCompliant(false)]
229 public virtual sbyte ReadSByte() => (sbyte)InternalReadByte();
230 public virtual bool ReadBoolean() => InternalReadByte() != 0;
232 public virtual char ReadChar()
234 int value = Read();
235 if (value == -1)
237 throw Error.GetEndOfFile();
239 return (char)value;
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()
272 ThrowIfDisposed();
274 int currPos = 0;
275 int n;
276 int stringLength;
277 int readLength;
278 int charsRead;
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)
289 return string.Empty;
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);
308 if (n == 0)
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);
320 if (sb == null)
322 sb = StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller.
325 sb.Append(_charBuffer, 0, charsRead);
326 currPos += n;
327 } while (currPos < stringLength);
329 return StringBuilderCache.GetStringAndRelease(sb);
332 public virtual int Read(char[] buffer, int index, int count)
334 if (buffer == null)
336 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
338 if (index < 0)
340 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
342 if (count < 0)
344 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
346 if (buffer.Length - index < count)
348 throw new ArgumentException(SR.Argument_InvalidOffLen);
350 ThrowIfDisposed();
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)
358 ThrowIfDisposed();
359 return InternalReadChars(buffer);
362 private int InternalReadChars(Span<char> buffer)
364 Debug.Assert(!_disposed);
366 int numBytes = 0;
367 int index = 0;
368 int charsRemaining = buffer.Length;
370 if (_charBytes == null)
372 _charBytes = new byte[MaxCharBytesSize];
375 while (charsRemaining > 0)
377 int charsRead = 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;
383 if (_2BytesPerChar)
385 numBytes <<= 1;
387 if (numBytes > MaxCharBytesSize)
389 numBytes = MaxCharBytesSize;
392 int position = 0;
393 byte[]? byteBuffer = null;
394 if (_isMemoryStream)
396 Debug.Assert(_stream is MemoryStream);
397 MemoryStream mStream = (MemoryStream)_stream;
399 position = mStream.InternalGetPosition();
400 numBytes = mStream.InternalEmulateRead(numBytes);
401 byteBuffer = mStream.InternalGetBuffer();
403 else
405 numBytes = _stream.Read(_charBytes, 0, numBytes);
406 byteBuffer = _charBytes;
409 if (numBytes == 0)
411 return (buffer.Length - charsRemaining);
414 Debug.Assert(byteBuffer != null, "expected byteBuffer to be non-null");
415 checked
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));
425 unsafe
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;
436 index += 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)
449 if (count < 0)
451 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
453 ThrowIfDisposed();
455 if (count == 0)
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));
463 if (n != count)
465 char[] copy = new char[n];
466 Buffer.BlockCopy(chars, 0, copy, 0, 2 * n); // sizeof(char)
467 chars = copy;
470 return chars;
473 public virtual int Read(byte[] buffer, int index, int count)
475 if (buffer == null)
477 throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
479 if (index < 0)
481 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
483 if (count < 0)
485 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
487 if (buffer.Length - index < count)
489 throw new ArgumentException(SR.Argument_InvalidOffLen);
491 ThrowIfDisposed();
493 return _stream.Read(buffer, index, count);
496 public virtual int Read(Span<byte> buffer)
498 ThrowIfDisposed();
499 return _stream.Read(buffer);
502 public virtual byte[] ReadBytes(int count)
504 if (count < 0)
506 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
508 ThrowIfDisposed();
510 if (count == 0)
512 return Array.Empty<byte>();
515 byte[] result = new byte[count];
516 int numRead = 0;
519 int n = _stream.Read(result, numRead, count);
520 if (n == 0)
522 break;
525 numRead += n;
526 count -= n;
527 } while (count > 0);
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);
534 result = copy;
537 return result;
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");
544 if (_isMemoryStream)
546 // read directly from MemoryStream buffer
547 Debug.Assert(_stream is MemoryStream);
548 return ((MemoryStream)_stream).InternalReadSpan(numBytes);
550 else
552 ThrowIfDisposed();
554 int bytesRead = 0;
557 int n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead);
558 if (n == 0)
560 throw Error.GetEndOfFile();
562 bytesRead += n;
563 } while (bytesRead < numBytes);
565 return _buffer;
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);
580 int bytesRead = 0;
581 int n = 0;
583 ThrowIfDisposed();
585 // Need to find a good threshold for calling ReadByte() repeatedly
586 // vs. calling Read(byte[], int, int) for both buffered & unbuffered
587 // streams.
588 if (numBytes == 1)
590 n = _stream.ReadByte();
591 if (n == -1)
593 throw Error.GetEndOfFile();
596 _buffer[0] = (byte)n;
597 return;
602 n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead);
603 if (n == 0)
605 throw Error.GetEndOfFile();
607 bytesRead += n;
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.
615 int count = 0;
616 int shift = 0;
617 byte b;
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.
628 b = ReadByte();
629 count |= (b & 0x7F) << shift;
630 shift += 7;
631 } while ((b & 0x80) != 0);
632 return count;