Override Span-based Read/Write on FileStream
authorStephen Toub <stoub@microsoft.com>
Tue, 22 Aug 2017 17:05:53 +0000 (13:05 -0400)
committerStephen Toub <stoub@microsoft.com>
Tue, 22 Aug 2017 19:07:37 +0000 (15:07 -0400)
Adds overrides for the new Span-based Read/Write methods on FileStream.

A few notes:
- As with {Unmanaged}MemoryStream, FileStream isn't sealed, which means a derived type could have overridden all of the existing abstract/virtual methods, including Read(byte[],int,int).  If a consumer then switched to using that stream with Read(Span), because we now override Read(Span), the consumer should get the same behavior intended by the stream developer. As such, since we have no good/efficient way to check whether Read(byte[],int,int) is overridden, we check whether the current stream is a concrete FileStream (rather than a derived type), and if it isn't we use the default base behavior, which will call the Read(byte[],int,int) method.
- FileStream is odd in that it has a dual nature around whether it was initialized for sync vs async, a setting that on Windows ends up configuring the native handle to operate in async mode.  Sync operations on an async-configured FileStream end up delegating to the async methods and blocking, and async operations on a sync-configured FileStream end up using the synchronous behavior asynchronously.  There were some inconsistencies around how this was handled between Windows and Unix, in particular around the ReadByte method, and as part of adding these overloads, I changed that as well, as doing so made the code simpler with the new Span-based support.  Technically this is a breaking change on Unix, but it would be very niche, including calling ReadByte on an async stream while other async operations were in progress... in that case, the desktop and Windows core behavior was to allow direct access to any cached data in the buffer, whereas on Unix we would serialize the ReadByte call with other async operations in flight.

Commit migrated from https://github.com/dotnet/coreclr/commit/1cb3580858f3224f0e0981965a2da1fd61ba9e08

src/coreclr/src/mscorlib/shared/System/IO/FileStream.Unix.cs
src/coreclr/src/mscorlib/shared/System/IO/FileStream.Windows.cs
src/coreclr/src/mscorlib/shared/System/IO/FileStream.cs

index ad68a00..23e02d4 100644 (file)
@@ -286,13 +286,20 @@ namespace System.IO
             }
         }
 
+        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;
             }
         }
@@ -375,44 +382,7 @@ namespace System.IO
         }
 
         /// <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();
 
@@ -429,16 +399,16 @@ namespace System.IO
                 // 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)
                     {
@@ -454,8 +424,8 @@ namespace System.IO
 
             // 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.
@@ -466,38 +436,33 @@ namespace System.IO
             // 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;
@@ -576,7 +541,7 @@ namespace System.IO
                     {
                         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);
@@ -587,55 +552,22 @@ namespace System.IO
             }
         }
 
-        /// <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;
             }
@@ -647,21 +579,17 @@ namespace System.IO
                 // 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.
@@ -672,35 +600,33 @@ namespace System.IO
             // 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;
                 }
             }
         }
@@ -770,7 +696,7 @@ namespace System.IO
                     // 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);
@@ -779,7 +705,7 @@ namespace System.IO
                     {
                         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);
@@ -790,25 +716,6 @@ namespace System.IO
             }
         }
 
-        /// <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">
index cdf1cb0..ec6df7e 100644 (file)
@@ -324,6 +324,8 @@ namespace System.IO
             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.
@@ -355,7 +357,7 @@ namespace System.IO
             }
             else
             {
-                WriteCore(GetBuffer(), 0, _writePos);
+                WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
             }
 
             _writePos = 0;
@@ -411,14 +413,9 @@ namespace System.IO
         // 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.");
 
@@ -430,23 +427,23 @@ namespace System.IO
             {
                 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 
@@ -466,10 +463,10 @@ namespace System.IO
                 // 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.
@@ -482,28 +479,38 @@ namespace System.IO
         }
 
         [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)
             {
@@ -646,9 +653,9 @@ namespace System.IO
                 _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)
             {
@@ -672,65 +679,56 @@ namespace System.IO
                 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)
             {
@@ -870,7 +868,6 @@ namespace System.IO
             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;
 
@@ -909,7 +906,7 @@ namespace System.IO
 
             // 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:
@@ -972,13 +969,6 @@ namespace System.IO
             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 
@@ -1135,7 +1125,7 @@ namespace System.IO
 
             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
@@ -1198,11 +1188,6 @@ namespace System.IO
             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;
@@ -1223,35 +1208,19 @@ namespace System.IO
         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)
@@ -1266,37 +1235,19 @@ namespace System.IO
             }
         }
 
-        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)
@@ -1472,7 +1423,7 @@ namespace System.IO
                             }
 
                             // 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.
index 39f7b60..6e77f09 100644 (file)
@@ -292,6 +292,36 @@ namespace System.IO
             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)
@@ -319,6 +349,41 @@ namespace System.IO
             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)
@@ -594,7 +659,11 @@ namespace System.IO
             _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();
 
@@ -602,9 +671,7 @@ namespace System.IO
             if (_readPos == _readLength)
             {
                 FlushWriteBuffer();
-                Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
-
-                _readLength = ReadNative(buffer, 0, _bufferLength);
+                _readLength = FillReadBufferForReadByte();
                 _readPos = 0;
                 if (_readLength == 0)
                 {
@@ -615,13 +682,18 @@ namespace System.IO
             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;