}
}
+ private void FlushWriteBufferForWriteByte()
+ {
+ _asyncState?.Wait();
+ try { FlushWriteBuffer(); }
+ finally { _asyncState?.Release(); }
+ }
+
/// <summary>Writes any data in the write buffer to the underlying stream and resets the buffer.</summary>
private void FlushWriteBuffer()
{
AssertBufferInvariants();
if (_writePos > 0)
{
- WriteNative(GetBuffer(), 0, _writePos);
+ WriteNative(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
_writePos = 0;
}
}
}
/// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
- /// <param name="array">
- /// When this method returns, contains the specified byte array with the values between offset and
- /// (offset + count - 1) replaced by the bytes read from the current source.
- /// </param>
- /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
- /// <param name="count">The maximum number of bytes to read. </param>
- /// <returns>
- /// The total number of bytes read into the buffer. This might be less than the number of bytes requested
- /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
- /// </returns>
- public override int Read(byte[] array, int offset, int count)
- {
- ValidateReadWriteArgs(array, offset, count);
-
- if (_useAsyncIO)
- {
- _asyncState.Wait();
- try { return ReadCore(array, offset, count); }
- finally { _asyncState.Release(); }
- }
- else
- {
- return ReadCore(array, offset, count);
- }
- }
-
- /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
- /// <param name="array">
- /// When this method returns, contains the specified byte array with the values between offset and
- /// (offset + count - 1) replaced by the bytes read from the current source.
- /// </param>
- /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
- /// <param name="count">The maximum number of bytes to read. </param>
- /// <returns>
- /// The total number of bytes read into the buffer. This might be less than the number of bytes requested
- /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
- /// </returns>
- private int ReadCore(byte[] array, int offset, int count)
+ private int ReadSpan(Span<byte> destination)
{
PrepareForReading();
// If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
// a read buffer), in which case we don't want to use a read buffer. Similarly, if
// the user has asked for more data than we can buffer, we also want to skip the buffer.
- if (!CanSeek || (count >= _bufferLength))
+ if (!CanSeek || (destination.Length >= _bufferLength))
{
// Read directly into the user's buffer
_readPos = _readLength = 0;
- return ReadNative(array, offset, count);
+ return ReadNative(destination);
}
else
{
// Read into our buffer.
- _readLength = numBytesAvailable = ReadNative(GetBuffer(), 0, _bufferLength);
+ _readLength = numBytesAvailable = ReadNative(GetBuffer());
_readPos = 0;
if (numBytesAvailable == 0)
{
// Now that we know there's data in the buffer, read from it into the user's buffer.
Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
- int bytesRead = Math.Min(numBytesAvailable, count);
- Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, bytesRead);
+ int bytesRead = Math.Min(numBytesAvailable, destination.Length);
+ new Span<byte>(GetBuffer(), _readPos, bytesRead).CopyTo(destination);
_readPos += bytesRead;
// We may not have had enough data in the buffer to completely satisfy the user's request.
// behavior, we do the same thing here on Unix. Note that we may still get less the requested
// amount, as the OS may give us back fewer than we request, either due to reaching the end of
// file, or due to its own whims.
- if (!readFromOS && bytesRead < count)
+ if (!readFromOS && bytesRead < destination.Length)
{
- Debug.Assert(_readPos == _readLength, "bytesToRead should only be < count if numBytesAvailable < count");
+ Debug.Assert(_readPos == _readLength, "bytesToRead should only be < destination.Length if numBytesAvailable < destination.Length");
_readPos = _readLength = 0; // no data left in the read buffer
- bytesRead += ReadNative(array, offset + bytesRead, count - bytesRead);
+ bytesRead += ReadNative(destination.Slice(bytesRead));
}
return bytesRead;
}
- /// <summary>Unbuffered, reads a block of bytes from the stream and writes the data in a given buffer.</summary>
- /// <param name="array">
- /// When this method returns, contains the specified byte array with the values between offset and
- /// (offset + count - 1) replaced by the bytes read from the current source.
- /// </param>
- /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
- /// <param name="count">The maximum number of bytes to read. </param>
+ /// <summary>Unbuffered, reads a block of bytes from the file handle into the given buffer.</summary>
+ /// <param name="buffer">The buffer into which data from the file is read.</param>
/// <returns>
/// The total number of bytes read into the buffer. This might be less than the number of bytes requested
/// if that number of bytes are not currently available, or zero if the end of the stream is reached.
/// </returns>
- private unsafe int ReadNative(byte[] array, int offset, int count)
+ private unsafe int ReadNative(Span<byte> buffer)
{
FlushWriteBuffer(); // we're about to read; dump the write buffer
VerifyOSHandlePosition();
int bytesRead;
- fixed (byte* bufPtr = array)
+ fixed (byte* bufPtr = &buffer.DangerousGetPinnableReference())
{
- bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr + offset, count));
- Debug.Assert(bytesRead <= count);
+ bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr, buffer.Length));
+ Debug.Assert(bytesRead <= buffer.Length);
}
_filePosition += bytesRead;
return bytesRead;
{
byte[] b = thisRef._asyncState._buffer;
thisRef._asyncState._buffer = null; // remove reference to user's buffer
- return thisRef.ReadCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
+ return thisRef.ReadSpan(new Span<byte>(b, thisRef._asyncState._offset, thisRef._asyncState._count));
}
finally { thisRef._asyncState.Release(); }
}, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
}
}
- /// <summary>
- /// Reads a byte from the stream and advances the position within the stream
- /// by one byte, or returns -1 if at the end of the stream.
- /// </summary>
- /// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
- public override int ReadByte()
- {
- if (_useAsyncIO)
- {
- _asyncState.Wait();
- try { return ReadByteCore(); }
- finally { _asyncState.Release(); }
- }
- else
- {
- return ReadByteCore();
- }
- }
-
- /// <summary>Writes a block of bytes to the file stream.</summary>
- /// <param name="array">The buffer containing data to write to the stream.</param>
- /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
- /// <param name="count">The maximum number of bytes to write.</param>
- public override void Write(byte[] array, int offset, int count)
+ /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
+ private int FillReadBufferForReadByte()
{
- ValidateReadWriteArgs(array, offset, count);
-
- if (_useAsyncIO)
- {
- _asyncState.Wait();
- try { WriteCore(array, offset, count); }
- finally { _asyncState.Release(); }
- }
- else
- {
- WriteCore(array, offset, count);
- }
+ _asyncState?.Wait();
+ try { return ReadNative(_buffer); }
+ finally { _asyncState?.Release(); }
}
/// <summary>Writes a block of bytes to the file stream.</summary>
- /// <param name="array">The buffer containing data to write to the stream.</param>
- /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
- /// <param name="count">The maximum number of bytes to write.</param>
- private void WriteCore(byte[] array, int offset, int count)
+ /// <param name="source">The buffer containing data to write to the stream.</param>
+ private void WriteSpan(ReadOnlySpan<byte> source)
{
PrepareForWriting();
// If no data is being written, nothing more to do.
- if (count == 0)
+ if (source.Length == 0)
{
return;
}
// If there's space remaining in the buffer, then copy as much as
// we can from the user's buffer into ours.
int spaceRemaining = _bufferLength - _writePos;
- if (spaceRemaining > 0)
+ if (spaceRemaining >= source.Length)
{
- int bytesToCopy = Math.Min(spaceRemaining, count);
- Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, bytesToCopy);
- _writePos += bytesToCopy;
-
- // If we've successfully copied all of the user's data, we're done.
- if (count == bytesToCopy)
- {
- return;
- }
-
- // Otherwise, keep track of how much more data needs to be handled.
- offset += bytesToCopy;
- count -= bytesToCopy;
+ source.CopyTo(new Span<byte>(GetBuffer(), _writePos));
+ _writePos += source.Length;
+ return;
+ }
+ else if (spaceRemaining > 0)
+ {
+ source.Slice(0, spaceRemaining).CopyTo(new Span<byte>(GetBuffer(), _writePos));
+ _writePos += spaceRemaining;
+ source = source.Slice(spaceRemaining);
}
// At this point, the buffer is full, so flush it out.
// the user's looking to write more data than we can store in the buffer),
// skip the buffer. Otherwise, put the remaining data into the buffer.
Debug.Assert(_writePos == 0);
- if (count >= _bufferLength)
+ if (source.Length >= _bufferLength)
{
- WriteNative(array, offset, count);
+ WriteNative(source);
}
else
{
- Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
- _writePos = count;
+ source.CopyTo(new Span<byte>(GetBuffer()));
+ _writePos = source.Length;
}
}
/// <summary>Unbuffered, writes a block of bytes to the file stream.</summary>
- /// <param name="array">The buffer containing data to write to the stream.</param>
- /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
- /// <param name="count">The maximum number of bytes to write.</param>
- private unsafe void WriteNative(byte[] array, int offset, int count)
+ /// <param name="source">The buffer containing data to write to the stream.</param>
+ private unsafe void WriteNative(ReadOnlySpan<byte> source)
{
VerifyOSHandlePosition();
- fixed (byte* bufPtr = array)
+ fixed (byte* bufPtr = &source.DangerousGetPinnableReference())
{
+ int offset = 0;
+ int count = source.Length;
while (count > 0)
{
int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
- Debug.Assert(bytesWritten <= count);
-
_filePosition += bytesWritten;
- count -= bytesWritten;
offset += bytesWritten;
+ count -= bytesWritten;
}
}
}
// 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,
+ // 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);
{
byte[] b = thisRef._asyncState._buffer;
thisRef._asyncState._buffer = null; // remove reference to user's buffer
- thisRef.WriteCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
+ thisRef.WriteSpan(new ReadOnlySpan<byte>(b, thisRef._asyncState._offset, thisRef._asyncState._count));
}
finally { thisRef._asyncState.Release(); }
}, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
}
}
- /// <summary>
- /// Writes a byte to the current position in the stream and advances the position
- /// within the stream by one byte.
- /// </summary>
- /// <param name="value">The byte to write to the stream.</param>
- public override void WriteByte(byte value) // avoids an array allocation in the base implementation
- {
- if (_useAsyncIO)
- {
- _asyncState.Wait();
- try { WriteByteCore(value); }
- finally { _asyncState.Release(); }
- }
- else
- {
- WriteByteCore(value);
- }
- }
-
/// <summary>Sets the current position of this stream to the given value.</summary>
/// <param name="offset">The point relative to origin from which to begin seeking. </param>
/// <param name="origin">
return flushTask;
}
+ private void FlushWriteBufferForWriteByte() => FlushWriteBuffer();
+
// Writes are buffered. Anytime the buffer fills up
// (_writePos + delta > _bufferSize) or the buffer switches to reading
// and there is left over data (_writePos > 0), this function must be called.
}
else
{
- WriteCore(GetBuffer(), 0, _writePos);
+ WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
}
_writePos = 0;
// accessing its fields by ref. This avoids a compiler warning.
private FileStreamCompletionSource CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource newSource, FileStreamCompletionSource existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource);
- public override int Read(byte[] array, int offset, int count)
- {
- ValidateReadWriteArgs(array, offset, count);
- return ReadCore(array, offset, count);
- }
-
- private int ReadCore(byte[] array, int offset, int count)
+ private int ReadSpan(Span<byte> destination)
{
+ Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode");
Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength),
"We're either reading or writing, but not both.");
{
if (!CanRead) throw Error.GetReadNotSupported();
if (_writePos > 0) FlushWriteBuffer();
- if (!CanSeek || (count >= _bufferLength))
+ if (!CanSeek || (destination.Length >= _bufferLength))
{
- n = ReadNative(array, offset, count);
+ n = ReadNative(destination);
// Throw away read buffer.
_readPos = 0;
_readLength = 0;
return n;
}
- n = ReadNative(GetBuffer(), 0, _bufferLength);
+ n = ReadNative(GetBuffer());
if (n == 0) return 0;
isBlocked = n < _bufferLength;
_readPos = 0;
_readLength = n;
}
// Now copy min of count or numBytesAvailable (i.e. near EOF) to array.
- if (n > count) n = count;
- Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
+ if (n > destination.Length) n = destination.Length;
+ new ReadOnlySpan<byte>(GetBuffer(), _readPos, n).CopyTo(destination);
_readPos += n;
// We may have read less than the number of bytes the user asked
// read some more from the underlying stream. However, if we got
// fewer bytes from the underlying stream than we asked for (i.e. we're
// probably blocked), don't ask for more bytes.
- if (n < count && !isBlocked)
+ if (n < destination.Length && !isBlocked)
{
Debug.Assert(_readPos == _readLength, "Read buffer should be empty!");
- int moreBytesRead = ReadNative(array, offset + n, count - n);
+ int moreBytesRead = ReadNative(destination.Slice(n));
n += moreBytesRead;
// We've just made our buffer inconsistent with our position
// pointer. We must throw away the read buffer.
}
[Conditional("DEBUG")]
- private void AssertCanRead(byte[] buffer, int offset, int count)
+ private void AssertCanRead()
{
Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed");
Debug.Assert(CanRead, "CanRead");
+ }
+
+ [Conditional("DEBUG")]
+ private void AssertCanRead(byte[] buffer, int offset, int count)
+ {
+ AssertCanRead();
Debug.Assert(buffer != null, "buffer != null");
Debug.Assert(_writePos == 0, "_writePos == 0");
Debug.Assert(offset >= 0, "offset is negative");
Debug.Assert(count >= 0, "count is negative");
}
- private unsafe int ReadNative(byte[] buffer, int offset, int count)
- {
- AssertCanRead(buffer, offset, count);
+ /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
+ private int FillReadBufferForReadByte() =>
+ _useAsyncIO ?
+ ReadNativeAsync(_buffer, 0, _bufferLength, 0, CancellationToken.None).GetAwaiter().GetResult() :
+ ReadNative(_buffer);
- if (_useAsyncIO)
- return ReadNativeAsync(buffer, offset, count, 0, CancellationToken.None).GetAwaiter().GetResult();
+ private unsafe int ReadNative(Span<byte> buffer)
+ {
+ Debug.Assert(!_useAsyncIO, $"{nameof(ReadNative)} doesn't work on asynchronous file streams.");
+ AssertCanRead();
// Make sure we are reading from the right spot
VerifyOSHandlePosition();
int errorCode = 0;
- int r = ReadFileNative(_fileHandle, buffer, offset, count, null, out errorCode);
+ int r = ReadFileNative(_fileHandle, buffer, null, out errorCode);
if (r == -1)
{
_preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer);
}
- public override void Write(byte[] array, int offset, int count)
+ private void WriteSpan(ReadOnlySpan<byte> source)
{
- ValidateReadWriteArgs(array, offset, count);
+ Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode");
if (_writePos == 0)
{
int numBytes = _bufferLength - _writePos; // space left in buffer
if (numBytes > 0)
{
- if (numBytes > count)
- numBytes = count;
- Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, numBytes);
- _writePos += numBytes;
- if (count == numBytes) return;
- offset += numBytes;
- count -= numBytes;
+ if (numBytes >= source.Length)
+ {
+ source.CopyTo(new Span<byte>(GetBuffer(), _writePos));
+ _writePos += source.Length;
+ return;
+ }
+ else
+ {
+ source.Slice(0, numBytes).CopyTo(new Span<byte>(GetBuffer(), _writePos));
+ _writePos += numBytes;
+ source = source.Slice(numBytes);
+ }
}
// Reset our buffer. We essentially want to call FlushWrite
// without calling Flush on the underlying Stream.
- if (_useAsyncIO)
- {
- WriteInternalCoreAsync(GetBuffer(), 0, _writePos, CancellationToken.None).GetAwaiter().GetResult();
- }
- else
- {
- WriteCore(GetBuffer(), 0, _writePos);
- }
+ WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
_writePos = 0;
}
+
// If the buffer would slow writes down, avoid buffer completely.
- if (count >= _bufferLength)
+ if (source.Length >= _bufferLength)
{
Debug.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted.");
- WriteCore(array, offset, count);
+ WriteCore(source);
return;
}
- else if (count == 0)
+ else if (source.Length == 0)
{
return; // Don't allocate a buffer then call memcpy for 0 bytes.
}
// Copy remaining bytes into buffer, to write at a later date.
- Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
- _writePos = count;
+ source.CopyTo(new Span<byte>(GetBuffer(), _writePos));
+ _writePos = source.Length;
return;
}
- private unsafe void WriteCore(byte[] buffer, int offset, int count)
+ private unsafe void WriteCore(ReadOnlySpan<byte> source)
{
+ Debug.Assert(!_useAsyncIO);
Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
Debug.Assert(CanWrite, "_parent.CanWrite");
-
- Debug.Assert(buffer != null, "buffer != null");
Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
- Debug.Assert(offset >= 0, "offset is negative");
- Debug.Assert(count >= 0, "count is negative");
- if (_useAsyncIO)
- {
- WriteInternalCoreAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
- return;
- }
// Make sure we are writing to the position that we think we are
VerifyOSHandlePosition();
int errorCode = 0;
- int r = WriteFileNative(_fileHandle, buffer, offset, count, null, out errorCode);
+ int r = WriteFileNative(_fileHandle, source, null, out errorCode);
if (r == -1)
{
Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!");
// Create and store async stream class library specific data in the async result
-
FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, numBufferedBytesRead, bytes, cancellationToken);
NativeOverlapped* intOverlapped = completionSource.Overlapped;
// queue an async ReadFile operation and pass in a packed overlapped
int errorCode = 0;
- int r = ReadFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
+ int r = ReadFileNative(_fileHandle, new Span<byte>(bytes, offset, numBytes), intOverlapped, out errorCode);
// ReadFile, the OS version, will return 0 on failure. But
// my ReadFileNative wrapper returns -1. My wrapper will return
// the following:
return completionSource.Task;
}
- // Reads a byte from the file stream. Returns the byte cast to an int
- // or -1 if reading from the end of the stream.
- public override int ReadByte()
- {
- return ReadByteCore();
- }
-
private Task WriteAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken)
{
// If async IO is not supported on this platform or
int errorCode = 0;
// queue an async WriteFile operation and pass in a packed overlapped
- int r = WriteFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
+ int r = WriteFileNative(_fileHandle, new ReadOnlySpan<byte>(bytes, offset, numBytes), intOverlapped, out errorCode);
// WriteFile, the OS version, will return 0 on failure. But
// my WriteFileNative wrapper returns -1. My wrapper will return
return completionSource.Task;
}
- public override void WriteByte(byte value)
- {
- WriteByteCore(value);
- }
-
// Windows API definitions, from winbase.h and others
private const int FILE_ATTRIBUTE_NORMAL = 0x00000080;
private const int ERROR_IO_PENDING = 997;
// __ConsoleStream also uses this code.
- private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode)
+ private unsafe int ReadFileNative(SafeFileHandle handle, Span<byte> bytes, NativeOverlapped* overlapped, out int errorCode)
{
Debug.Assert(handle != null, "handle != null");
- Debug.Assert(offset >= 0, "offset >= 0");
- Debug.Assert(count >= 0, "count >= 0");
- Debug.Assert(bytes != null, "bytes != null");
- // Don't corrupt memory when multiple threads are erroneously writing
- // to this stream simultaneously.
- if (bytes.Length - offset < count)
- throw new IndexOutOfRangeException(SR.IndexOutOfRange_IORaceCondition);
-
Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative.");
- // You can't use the fixed statement on an array of length 0.
- if (bytes.Length == 0)
- {
- errorCode = 0;
- return 0;
- }
-
- int r = 0;
+ int r;
int numBytesRead = 0;
- fixed (byte* p = &bytes[0])
+ fixed (byte* p = &bytes.DangerousGetPinnableReference())
{
- if (_useAsyncIO)
- r = Interop.Kernel32.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped);
- else
- r = Interop.Kernel32.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero);
+ r = _useAsyncIO ?
+ Interop.Kernel32.ReadFile(handle, p, bytes.Length, IntPtr.Zero, overlapped) :
+ Interop.Kernel32.ReadFile(handle, p, bytes.Length, out numBytesRead, IntPtr.Zero);
}
if (r == 0)
}
}
- private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode)
+ private unsafe int WriteFileNative(SafeFileHandle handle, ReadOnlySpan<byte> buffer, NativeOverlapped* overlapped, out int errorCode)
{
Debug.Assert(handle != null, "handle != null");
- Debug.Assert(offset >= 0, "offset >= 0");
- Debug.Assert(count >= 0, "count >= 0");
- Debug.Assert(bytes != null, "bytes != null");
- // Don't corrupt memory when multiple threads are erroneously writing
- // to this stream simultaneously. (the OS is reading from
- // the array we pass to WriteFile, but if we read beyond the end and
- // that memory isn't allocated, we could get an AV.)
- if (bytes.Length - offset < count)
- throw new IndexOutOfRangeException(SR.IndexOutOfRange_IORaceCondition);
-
Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative.");
- // You can't use the fixed statement on an array of length 0.
- if (bytes.Length == 0)
- {
- errorCode = 0;
- return 0;
- }
-
int numBytesWritten = 0;
- int r = 0;
+ int r;
- fixed (byte* p = &bytes[0])
+ fixed (byte* p = &buffer.DangerousGetPinnableReference())
{
- if (_useAsyncIO)
- r = Interop.Kernel32.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped);
- else
- r = Interop.Kernel32.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero);
+ r = _useAsyncIO ?
+ Interop.Kernel32.WriteFile(handle, p, buffer.Length, IntPtr.Zero, overlapped) :
+ Interop.Kernel32.WriteFile(handle, p, buffer.Length, out numBytesWritten, IntPtr.Zero);
}
if (r == 0)
}
// Kick off the read.
- synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, 0, copyBuffer.Length, readAwaitable._nativeOverlapped, out errorCode) >= 0;
+ synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, readAwaitable._nativeOverlapped, out errorCode) >= 0;
}
// If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation.
return FlushAsyncInternal(cancellationToken);
}
+ public override int Read(byte[] array, int offset, int count)
+ {
+ ValidateReadWriteArgs(array, offset, count);
+ return _useAsyncIO ?
+ ReadAsyncInternal(array, offset, count, CancellationToken.None).GetAwaiter().GetResult() :
+ ReadSpan(new Span<byte>(array, offset, count));
+ }
+
+ 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);
+ }
+ }
+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
return ReadAsyncInternal(buffer, offset, count, cancellationToken);
}
+ public override void Write(byte[] array, int offset, int count)
+ {
+ ValidateReadWriteArgs(array, offset, count);
+ if (_useAsyncIO)
+ {
+ WriteAsyncInternal(array, offset, count, CancellationToken.None).GetAwaiter().GetResult();
+ }
+ else
+ {
+ WriteSpan(new ReadOnlySpan<byte>(array, offset, count));
+ }
+ }
+
+ public override void Write(ReadOnlySpan<byte> destination)
+ {
+ if (GetType() == typeof(FileStream) && !_useAsyncIO)
+ {
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+ WriteSpan(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 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(destination);
+ }
+ }
+
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
_readPos = _readLength = 0;
}
- private int ReadByteCore()
+ /// <summary>
+ /// Reads a byte from the file stream. Returns the byte cast to an int
+ /// or -1 if reading from the end of the stream.
+ /// </summary>
+ public override int ReadByte()
{
PrepareForReading();
if (_readPos == _readLength)
{
FlushWriteBuffer();
- Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
-
- _readLength = ReadNative(buffer, 0, _bufferLength);
+ _readLength = FillReadBufferForReadByte();
_readPos = 0;
if (_readLength == 0)
{
return buffer[_readPos++];
}
- private void WriteByteCore(byte value)
+ /// <summary>
+ /// Writes a byte to the current position in the stream and advances the position
+ /// within the stream by one byte.
+ /// </summary>
+ /// <param name="value">The byte to write to the stream.</param>
+ public override void WriteByte(byte value)
{
PrepareForWriting();
// Flush the write buffer if it's full
if (_writePos == _bufferLength)
- FlushWriteBuffer();
+ FlushWriteBufferForWriteByte();
// We now have space in the buffer. Store the byte.
GetBuffer()[_writePos++] = value;