Exemplo n.º 1
0
        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
            }
            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (buffer.Length - offset < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
            }

            if (GetType() != typeof(FileStream))
            {
                // If we have been inherited into a subclass, the following implementation could be incorrect
                // since it does not call through to Write() or WriteAsync() which a subclass might have overridden.
                // To be safe we will only use this implementation in cases where we know it is safe to do so,
                // and delegate to our base class (which will call into Write/WriteAsync) when we are not sure.
                return(base.WriteAsync(buffer, offset, count, cancellationToken));
            }

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }

            if (IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            if (!_useAsyncIO)
            {
                // If we weren't opened for asynchronous I/O, we still call to the base implementation so that
                // Write is invoked asynchronously.  But we can do so using the base Stream's internal helper
                // that bypasses delegating to BeginWrite, since we already know this is FileStream rather
                // than something derived from it and what our BeginWrite implementation is going to do.
                return((Task)base.BeginWriteInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false));
            }

            return(WriteAsyncInternal(new ReadOnlyMemory <byte>(buffer, offset, count), cancellationToken).AsTask());
        }
        public override int Read(Span <byte> buffer)
        {
            if (!_useAsyncIO)
            {
                if (_fileHandle.IsClosed)
                {
                    throw Error.GetFileNotOpen();
                }

                return(ReadSpan(buffer));
            }

            // If the stream is in async mode, we can't call the synchronous ReadSpan, so we similarly call the base Read,
            // which will turn delegate to Read(byte[],int,int), which will do the right thing if we're in async mode.
            return(base.Read(buffer));
        }
Exemplo n.º 3
0
        public virtual byte ReadByte()
        {
            // Inlined to avoid some method call overhead with FillBuffer.
            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            int b = _stream.ReadByte();

            if (b == -1)
            {
                throw Error.GetEndOfFile();
            }
            return((byte)b);
        }
Exemplo n.º 4
0
        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            ValidateBufferArguments(buffer, offset, count);

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }

            if (_strategy.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            return(_strategy.WriteAsync(buffer, offset, count, cancellationToken));
        }
Exemplo n.º 5
0
        public virtual int PeekChar()
        {
            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            if (!_stream.CanSeek)
            {
                return(-1);
            }
            long origPos = _stream.Position;
            int  ch      = Read();

            _stream.Position = origPos;
            return(ch);
        }
Exemplo n.º 6
0
        private byte InternalReadByte()
        {
            // Inlined to avoid some method call overhead with InternalRead.
            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            int b = _stream.ReadByte();

            if (b == -1)
            {
                throw Error.GetEndOfFile();
            }

            return((byte)b);
        }
        public override void Write(ReadOnlySpan <byte> buffer)
        {
            if (!_useAsyncIO)
            {
                if (_fileHandle.IsClosed)
                {
                    throw Error.GetFileNotOpen();
                }

                WriteSpan(buffer);
            }
            else
            {
                // If the stream is in async mode, we can't call the synchronous WriteSpan, so we similarly call the base Write,
                // which will turn delegate to Write(byte[],int,int), which will do the right thing if we're in async mode.
                base.Write(buffer);
            }
        }
        public override Task <int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
            }
            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (buffer.Length - offset < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
            }

            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Read() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Read/ReadAsync) when we are not sure.
            // Similarly, if we weren't opened for asynchronous I/O, call to the base implementation so that
            // Read is invoked asynchronously.
            if (GetType() != typeof(FileStream) || !_useAsyncIO)
            {
                return(base.ReadAsync(buffer, offset, count, cancellationToken));
            }

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <int>(cancellationToken));
            }

            if (IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            return(ReadAsyncTask(buffer, offset, count, cancellationToken));
        }
Exemplo n.º 9
0
        /// <summary>
        /// Validates that we're ready to write to the stream,
        /// including flushing a read buffer if necessary.
        /// </summary>
        private void PrepareForWriting()
        {
            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            // Make sure we're good to write.  We only need to do this if there's nothing already
            // in our write buffer, since if there is something in the buffer, we've already done
            // this checking and flushing.
            if (_writePos == 0)
            {
                if (!CanWrite)
                {
                    throw Error.GetWriteNotSupported();
                }
                FlushReadBuffer();
                Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
            }
        }
Exemplo n.º 10
0
        public virtual int ReadInt32()
        {
            if (_isMemoryStream)
            {
                if (_stream == null)
                {
                    throw Error.GetFileNotOpen();
                }
                // read directly from MemoryStream buffer
                MemoryStream mStream = _stream as MemoryStream;
                Debug.Assert(mStream != null, "_stream as MemoryStream != null");

                return(mStream.InternalReadInt32());
            }
            else
            {
                FillBuffer(4);
                return((int)(_buffer[0] | _buffer[1] << 8 | _buffer[2] << 16 | _buffer[3] << 24));
            }
        }
Exemplo n.º 11
0
        public virtual byte[] ReadBytes(int count)
        {
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            if (count == 0)
            {
                return(Array.Empty <Byte>());
            }

            byte[] result = new byte[count];

            int numRead = 0;

            do
            {
                int n = _stream.Read(result, numRead, count);
                if (n == 0)
                {
                    break;
                }
                numRead += n;
                count   -= n;
            } while (count > 0);

            if (numRead != result.Length)
            {
                // Trim array.  This should happen on EOF & possibly net streams.
                byte[] copy = new byte[numRead];
                Buffer.BlockCopy(result, 0, copy, 0, numRead);
                result = copy;
            }

            return(result);
        }
Exemplo n.º 12
0
        /// <summary>Sets the length of this stream to the given value.</summary>
        /// <param name="value">The new length of the stream.</param>
        public override void SetLength(long value)
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }
            if (!CanSeek)
            {
                throw Error.GetSeekNotSupported();
            }
            if (!CanWrite)
            {
                throw Error.GetWriteNotSupported();
            }

            SetLengthInternal(value);
        }
Exemplo n.º 13
0
 public override int Read(Span <byte> destination)
 {
     if (GetType() == typeof(FileStream) && !_useAsyncIO)
     {
         if (_fileHandle.IsClosed)
         {
             throw Error.GetFileNotOpen();
         }
         return(ReadSpan(destination));
     }
     else
     {
         // This type is derived from FileStream and/or the stream is in async mode.  If this is a
         // derived type, it may have overridden Read(byte[], int, int) prior to this Read(Span<byte>)
         // overload being introduced.  In that case, this Read(Span<byte>) overload should use the behavior
         // of Read(byte[],int,int) overload.  Or if the stream is in async mode, we can't call the
         // synchronous ReadSpan, so we similarly call the base Read, which will turn delegate to
         // Read(byte[],int,int), which will do the right thing if we're in async mode.
         return(base.Read(destination));
     }
 }
Exemplo n.º 14
0
 public override void Write(ReadOnlySpan <byte> buffer)
 {
     if (GetType() == typeof(FileStream) && !_useAsyncIO)
     {
         if (_fileHandle.IsClosed)
         {
             throw Error.GetFileNotOpen();
         }
         WriteSpan(buffer);
     }
     else
     {
         // This type is derived from FileStream and/or the stream is in async mode.  If this is a
         // derived type, it may have overridden Write(byte[], int, int) prior to this Write(ReadOnlySpan<byte>)
         // overload being introduced.  In that case, this Write(ReadOnlySpan<byte>) overload should use the behavior
         // of Write(byte[],int,int) overload.  Or if the stream is in async mode, we can't call the
         // synchronous WriteSpan, so we similarly call the base Write, which will turn delegate to
         // Write(byte[],int,int), which will do the right thing if we're in async mode.
         base.Write(buffer);
     }
 }
        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
            }
            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
            }
            if (buffer.Length - offset < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
            }

            // If we have been inherited into a subclass, the following implementation could be incorrect
            // since it does not call through to Write() or WriteAsync() which a subclass might have overridden.
            // To be safe we will only use this implementation in cases where we know it is safe to do so,
            // and delegate to our base class (which will call into Write/WriteAsync) when we are not sure.
            if (!_useAsyncIO || GetType() != typeof(FileStream))
            {
                return(base.WriteAsync(buffer, offset, count, cancellationToken));
            }

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }

            if (IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            return(WriteAsyncInternal(new ReadOnlyMemory <byte>(buffer, offset, count), cancellationToken).AsTask());
        }
Exemplo n.º 16
0
        protected virtual void FillBuffer(int numBytes)
        {
            if (_buffer != null && (numBytes < 0 || numBytes > _buffer.Length))
            {
                throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_BinaryReaderFillBuffer);
            }

            int bytesRead = 0;
            int n         = 0;

            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            // Need to find a good threshold for calling ReadByte() repeatedly
            // vs. calling Read(byte[], int, int) for both buffered & unbuffered
            // streams.
            if (numBytes == 1)
            {
                n = _stream.ReadByte();
                if (n == -1)
                {
                    throw Error.GetEndOfFile();
                }

                _buffer[0] = (byte)n;
                return;
            }

            do
            {
                n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead);
                if (n == 0)
                {
                    throw Error.GetEndOfFile();
                }
                bytesRead += n;
            } while (bytesRead < numBytes);
        }
        public override ValueTask WriteAsync(ReadOnlyMemory <byte> buffer, CancellationToken cancellationToken = default)
        {
            if (!_useAsyncIO || GetType() != typeof(FileStream))
            {
                // If we're not using async I/O, delegate to the base, which will queue a call to Write.
                // Or if this isn't a concrete FileStream, a derived type may have overridden WriteAsync(byte[],...),
                // which was introduced first, so delegate to the base which will delegate to that.
                return(base.WriteAsync(buffer, cancellationToken));
            }

            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask(Task.FromCanceled <int>(cancellationToken)));
            }

            if (IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            return(WriteAsyncInternal(buffer, cancellationToken));
        }
Exemplo n.º 18
0
        /// <summary>
        /// Asynchronously writes a sequence of bytes to the current stream, advances
        /// the current position within this stream by the number of bytes written, and
        /// monitors cancellation requests.
        /// </summary>
        /// <param name="source">The buffer to write data from.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A task that represents the asynchronous write operation.</returns>
        private ValueTask WriteAsyncInternal(ReadOnlyMemory <byte> source, CancellationToken cancellationToken)
        {
            Debug.Assert(_useAsyncIO);
            Debug.Assert(_asyncState != null);

            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask(Task.FromCanceled(cancellationToken)));
            }

            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            if (!CanWrite) // match Windows behavior; this gets thrown synchronously
            {
                throw Error.GetWriteNotSupported();
            }

            // Serialize operations using the semaphore.
            Task waitTask = _asyncState.WaitAsync();

            // If we got ownership immediately, and if there's enough space in our buffer
            // to buffer the entire write request, then do so and we're done.
            if (waitTask.Status == TaskStatus.RanToCompletion)
            {
                int spaceRemaining = _bufferLength - _writePos;
                if (spaceRemaining >= source.Length)
                {
                    try
                    {
                        PrepareForWriting();

                        source.Span.CopyTo(new Span <byte>(GetBuffer(), _writePos, source.Length));
                        _writePos += source.Length;

                        return(default);
Exemplo n.º 19
0
 /// <summary>Validates arguments to Read and Write and throws resulting exceptions.</summary>
 /// <param name="array">The buffer to read from or write to.</param>
 /// <param name="offset">The zero-based offset into the array.</param>
 /// <param name="count">The maximum number of bytes to read or write.</param>
 private void ValidateReadWriteArgs(byte[] array, int offset, int count)
 {
     if (array == null)
     {
         throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
     }
     if (offset < 0)
     {
         throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
     }
     if (count < 0)
     {
         throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
     }
     if (array.Length - offset < count)
     {
         throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
     }
     if (_fileHandle.IsClosed)
     {
         throw Error.GetFileNotOpen();
     }
 }
Exemplo n.º 20
0
        /// <summary>Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.</summary>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A task that represents the asynchronous flush operation.</returns>
        private Task FlushAsyncInternal(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }
            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            // As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
            try
            {
                FlushInternalBuffer();
            }
            catch (Exception e)
            {
                return(Task.FromException(e));
            }

            // We then separately flush to disk asynchronously.  This is only
            // necessary if we support writing; otherwise, we're done.
            if (CanWrite)
            {
                return(Task.Factory.StartNew(
                           state => ((FileStream)state).FlushOSBuffer(),
                           this,
                           cancellationToken,
                           TaskCreationOptions.DenyChildAttach,
                           TaskScheduler.Default));
            }
            else
            {
                return(Task.CompletedTask);
            }
        }
Exemplo n.º 21
0
        public override ValueTask <int> ReadAsync(Memory <byte> buffer, CancellationToken cancellationToken = default)
        {
            if (GetType() != typeof(FileStream))
            {
                // If this isn't a concrete FileStream, a derived type may have overridden ReadAsync(byte[],...),
                // which was introduced first, so delegate to the base which will delegate to that.
                return(base.ReadAsync(buffer, cancellationToken));
            }

            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask <int>(Task.FromCanceled <int>(cancellationToken)));
            }

            if (IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            if (!_useAsyncIO)
            {
                // If we weren't opened for asynchronous I/O, we still call to the base implementation so that
                // Read is invoked asynchronously.  But if we have a byte[], we can do so using the base Stream's
                // internal helper that bypasses delegating to BeginRead, since we already know this is FileStream
                // rather than something derived from it and what our BeginRead implementation is going to do.
                return(MemoryMarshal.TryGetArray(buffer, out ArraySegment <byte> segment) ?
                       new ValueTask <int>((Task <int>)base.BeginReadInternal(segment.Array !, segment.Offset, segment.Count, null, null, serializeAsynchronously: true, apm: false)) :
                       base.ReadAsync(buffer, cancellationToken));
            }

            Task <int>?t = ReadAsyncInternal(buffer, cancellationToken, out int synchronousResult);

            return(t != null ?
                   new ValueTask <int>(t) :
                   new ValueTask <int>(synchronousResult));
        }
Exemplo n.º 22
0
        /// <summary>
        /// Asynchronously writes a sequence of bytes to the current stream, advances
        /// the current position within this stream by the number of bytes written, and
        /// monitors cancellation requests.
        /// </summary>
        /// <param name="source">The buffer to write data from.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A task that represents the asynchronous write operation.</returns>
        private Task WriteAsyncInternal(ReadOnlyMemory <byte> source, CancellationToken cancellationToken)
        {
            Debug.Assert(_useAsyncIO);

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }

            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            if (!CanWrite) // match Windows behavior; this gets thrown synchronously
            {
                throw Error.GetWriteNotSupported();
            }

            // Serialize operations using the semaphore.
            Task waitTask = _asyncState.WaitAsync();

            // If we got ownership immediately, and if there's enough space in our buffer
            // to buffer the entire write request, then do so and we're done.
            if (waitTask.Status == TaskStatus.RanToCompletion)
            {
                int spaceRemaining = _bufferLength - _writePos;
                if (spaceRemaining >= source.Length)
                {
                    try
                    {
                        PrepareForWriting();

                        source.Span.CopyTo(new Span <byte>(GetBuffer(), _writePos, source.Length));
                        _writePos += source.Length;

                        return(Task.CompletedTask);
                    }
                    catch (Exception exc)
                    {
                        return(Task.FromException(exc));
                    }
                    finally
                    {
                        _asyncState.Release();
                    }
                }
            }

            // Otherwise, issue the whole request asynchronously.
            _asyncState.ReadOnlyMemory = source;
            return(waitTask.ContinueWith((t, s) =>
            {
                // The options available on Unix for writing asynchronously to an arbitrary file
                // handle typically amount to just using another thread to do the synchronous write,
                // which is exactly  what this implementation does. This does mean there are subtle
                // differences in certain FileStream behaviors between Windows and Unix when multiple
                // asynchronous operations are issued against the stream to execute concurrently; on
                // Unix the operations will be serialized due to the usage of a semaphore, but the
                // position/length information won't be updated until after the write has completed,
                // whereas on Windows it may happen before the write has completed.

                Debug.Assert(t.Status == TaskStatus.RanToCompletion);
                var thisRef = (FileStream)s;
                try
                {
                    ReadOnlyMemory <byte> readOnlyMemory = thisRef._asyncState.ReadOnlyMemory;
                    thisRef._asyncState.ReadOnlyMemory = default(ReadOnlyMemory <byte>);
                    thisRef.WriteSpan(readOnlyMemory.Span);
                }
                finally { thisRef._asyncState.Release(); }
            }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default));
        }
Exemplo n.º 23
0
        public virtual int Read()
        {
            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            int  charsRead = 0;
            int  numBytes  = 0;
            long posSav    = posSav = 0;

            if (_stream.CanSeek)
            {
                posSav = _stream.Position;
            }

            if (_charBytes == null)
            {
                _charBytes = new byte[MaxCharBytesSize]; //REVIEW: We need at most 2 bytes/char here?
            }
            if (_singleChar == null)
            {
                _singleChar = new char[1];
            }

            while (charsRead == 0)
            {
                // We really want to know what the minimum number of bytes per char
                // is for our encoding.  Otherwise for UnicodeEncoding we'd have to
                // do ~1+log(n) reads to read n characters.
                // Assume 1 byte can be 1 char unless _2BytesPerChar is true.
                numBytes = _2BytesPerChar ? 2 : 1;

                int r = _stream.ReadByte();
                _charBytes[0] = (byte)r;
                if (r == -1)
                {
                    numBytes = 0;
                }
                if (numBytes == 2)
                {
                    r             = _stream.ReadByte();
                    _charBytes[1] = (byte)r;
                    if (r == -1)
                    {
                        numBytes = 1;
                    }
                }

                if (numBytes == 0)
                {
                    return(-1);
                }

                Debug.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::ReadOneChar assumes it's reading one or 2 bytes only.");

                try
                {
                    charsRead = _decoder.GetChars(_charBytes, 0, numBytes, _singleChar, 0);
                }
                catch
                {
                    // Handle surrogate char

                    if (_stream.CanSeek)
                    {
                        _stream.Seek((posSav - _stream.Position), SeekOrigin.Current);
                    }
                    // else - we can't do much here

                    throw;
                }

                Debug.Assert(charsRead < 2, "BinaryReader::ReadOneChar - assuming we only got 0 or 1 char, not 2!");
            }
            Debug.Assert(charsRead > 0);
            return(_singleChar[0]);
        }
Exemplo n.º 24
0
        public virtual string ReadString()
        {
            if (_stream == null)
            {
                throw Error.GetFileNotOpen();
            }

            int currPos = 0;
            int n;
            int stringLength;
            int readLength;
            int charsRead;

            // Length of the string in bytes, not chars
            stringLength = Read7BitEncodedInt();
            if (stringLength < 0)
            {
                throw new IOException(SR.Format(SR.IO_InvalidStringLen_Len, stringLength));
            }

            if (stringLength == 0)
            {
                return(string.Empty);
            }

            if (_charBytes == null)
            {
                _charBytes = new byte[MaxCharBytesSize];
            }

            if (_charBuffer == null)
            {
                _charBuffer = new char[_maxCharsSize];
            }

            StringBuilder sb = null;

            do
            {
                readLength = ((stringLength - currPos) > MaxCharBytesSize) ? MaxCharBytesSize : (stringLength - currPos);

                n = _stream.Read(_charBytes, 0, readLength);
                if (n == 0)
                {
                    throw Error.GetEndOfFile();
                }

                charsRead = _decoder.GetChars(_charBytes, 0, n, _charBuffer, 0);

                if (currPos == 0 && n == stringLength)
                {
                    return(new string(_charBuffer, 0, charsRead));
                }

                if (sb == null)
                {
                    sb = StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller.
                }

                sb.Append(_charBuffer, 0, charsRead);
                currPos += n;
            } while (currPos < stringLength);

            return(StringBuilderCache.GetStringAndRelease(sb));
        }