Add Span/Memory overrides to StreamWriter (dotnet/corefx#24725)
authorStephen Toub <stoub@microsoft.com>
Thu, 19 Oct 2017 16:40:56 +0000 (12:40 -0400)
committerGitHub <noreply@github.com>
Thu, 19 Oct 2017 16:40:56 +0000 (12:40 -0400)
- Refactors StreamWriter to work in terms of Span/Memory.
- Adds TextWriter-overrides for the Span/Memory-based methods.
- Exposes the new members from the ref.
- Adds tests for the new methods.
- Respond to API review feedback that we should be naming parameters to match existing overloads, here using `buffer` instead of `source` or `destination`, since `buffer` is already used in overloads on these types.

Commit migrated from https://github.com/dotnet/corefx/commit/b272c1bde8d5d2e2b72772d2eb1f8b0496d05310

src/libraries/System.IO/tests/StreamWriter/StreamWriter.WriteTests.netcoreapp.cs [new file with mode: 0644]
src/libraries/System.IO/tests/System.IO.Tests.csproj
src/libraries/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs
src/libraries/System.Runtime.Extensions/src/System/IO/StreamWriter.cs
src/libraries/System.Runtime.Extensions/src/System/IO/StringReader.cs
src/libraries/System.Runtime.Extensions/src/System/IO/StringWriter.cs
src/libraries/System.Runtime.Extensions/src/System/IO/TextReader.cs
src/libraries/System.Runtime.Extensions/src/System/IO/TextWriter.cs

diff --git a/src/libraries/System.IO/tests/StreamWriter/StreamWriter.WriteTests.netcoreapp.cs b/src/libraries/System.IO/tests/StreamWriter/StreamWriter.WriteTests.netcoreapp.cs
new file mode 100644 (file)
index 0000000..6830bed
--- /dev/null
@@ -0,0 +1,205 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Tests
+{
+    public partial class StreamWriterTests
+    {
+        [Fact]
+        public void Write_EmptySpan_WritesNothing()
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s))
+            {
+                writer.Write(ReadOnlySpan<char>.Empty);
+                writer.Flush();
+                Assert.Equal(0, s.Position);
+            }
+        }
+
+        [Fact]
+        public void WriteLine_EmptySpan_WritesNewLine()
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s))
+            {
+                writer.WriteLine(ReadOnlySpan<char>.Empty);
+                writer.Flush();
+                Assert.Equal(Environment.NewLine.Length, s.Position);
+            }
+        }
+
+        [Fact]
+        public async Task WriteAsync_EmptyMemory_WritesNothing()
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s))
+            {
+                await writer.WriteAsync(ReadOnlyMemory<char>.Empty);
+                await writer.FlushAsync();
+                Assert.Equal(0, s.Position);
+            }
+        }
+
+        [Fact]
+        public async Task WriteLineAsync_EmptyMemory_WritesNothing()
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s))
+            {
+                await writer.WriteLineAsync(ReadOnlyMemory<char>.Empty);
+                await writer.FlushAsync();
+                Assert.Equal(Environment.NewLine.Length, s.Position);
+            }
+        }
+
+        [Theory]
+        [InlineData(1, 1, 1, false)]
+        [InlineData(100, 1, 100, false)]
+        [InlineData(100, 10, 3, false)]
+        [InlineData(1, 1, 1, true)]
+        [InlineData(100, 1, 100, true)]
+        [InlineData(100, 10, 3, true)]
+        public void Write_Span_WritesExpectedData(int length, int writeSize, int writerBufferSize, bool autoFlush)
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s, Encoding.ASCII, writerBufferSize) { AutoFlush = autoFlush })
+            {
+                var data = new char[length];
+                var rand = new Random(42);
+                for (int i = 0; i < data.Length; i++)
+                {
+                    data[i] = (char)(rand.Next(0, 26) + 'a');
+                }
+
+                Span<char> source = data;
+                while (source.Length > 0)
+                {
+                    int n = Math.Min(source.Length, writeSize);
+                    writer.Write(source.Slice(0, n));
+                    source = source.Slice(n);
+                }
+
+                writer.Flush();
+
+                Assert.Equal(data, s.ToArray().Select(b => (char)b));
+            }
+        }
+
+        [Theory]
+        [InlineData(1, 1, 1, false)]
+        [InlineData(100, 1, 100, false)]
+        [InlineData(100, 10, 3, false)]
+        [InlineData(1, 1, 1, true)]
+        [InlineData(100, 1, 100, true)]
+        [InlineData(100, 10, 3, true)]
+        public async Task Write_Memory_WritesExpectedData(int length, int writeSize, int writerBufferSize, bool autoFlush)
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s, Encoding.ASCII, writerBufferSize) { AutoFlush = autoFlush })
+            {
+                var data = new char[length];
+                var rand = new Random(42);
+                for (int i = 0; i < data.Length; i++)
+                {
+                    data[i] = (char)(rand.Next(0, 26) + 'a');
+                }
+
+                ReadOnlyMemory<char> source = data;
+                while (source.Length > 0)
+                {
+                    int n = Math.Min(source.Length, writeSize);
+                    await writer.WriteAsync(source.Slice(0, n));
+                    source = source.Slice(n);
+                }
+
+                await writer.FlushAsync();
+
+                Assert.Equal(data, s.ToArray().Select(b => (char)b));
+            }
+        }
+
+        [Theory]
+        [InlineData(1, 1, 1, false)]
+        [InlineData(100, 1, 100, false)]
+        [InlineData(100, 10, 3, false)]
+        [InlineData(1, 1, 1, true)]
+        [InlineData(100, 1, 100, true)]
+        [InlineData(100, 10, 3, true)]
+        public void WriteLine_Span_WritesExpectedData(int length, int writeSize, int writerBufferSize, bool autoFlush)
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s, Encoding.ASCII, writerBufferSize) { AutoFlush = autoFlush })
+            {
+                var data = new char[length];
+                var rand = new Random(42);
+                for (int i = 0; i < data.Length; i++)
+                {
+                    data[i] = (char)(rand.Next(0, 26) + 'a');
+                }
+
+                Span<char> source = data;
+                while (source.Length > 0)
+                {
+                    int n = Math.Min(source.Length, writeSize);
+                    writer.WriteLine(source.Slice(0, n));
+                    source = source.Slice(n);
+                }
+
+                writer.Flush();
+
+                Assert.Equal(length + (Environment.NewLine.Length * (length / writeSize)), s.Length);
+            }
+        }
+
+        [Theory]
+        [InlineData(1, 1, 1, false)]
+        [InlineData(100, 1, 100, false)]
+        [InlineData(100, 10, 3, false)]
+        [InlineData(1, 1, 1, true)]
+        [InlineData(100, 1, 100, true)]
+        [InlineData(100, 10, 3, true)]
+        public async Task WriteLineAsync_Memory_WritesExpectedData(int length, int writeSize, int writerBufferSize, bool autoFlush)
+        {
+            using (var s = new MemoryStream())
+            using (var writer = new StreamWriter(s, Encoding.ASCII, writerBufferSize) { AutoFlush = autoFlush })
+            {
+                var data = new char[length];
+                var rand = new Random(42);
+                for (int i = 0; i < data.Length; i++)
+                {
+                    data[i] = (char)(rand.Next(0, 26) + 'a');
+                }
+
+                ReadOnlyMemory<char> source = data;
+                while (source.Length > 0)
+                {
+                    int n = Math.Min(source.Length, writeSize);
+                    await writer.WriteLineAsync(source.Slice(0, n));
+                    source = source.Slice(n);
+                }
+
+                await writer.FlushAsync();
+
+                Assert.Equal(length + (Environment.NewLine.Length * (length / writeSize)), s.Length);
+            }
+        }
+
+        [Fact]
+        public async Task WriteAsync_Precanceled_ThrowsCancellationException()
+        {
+            using (var writer = new StreamWriter(Stream.Null))
+            {
+                await Assert.ThrowsAnyAsync<OperationCanceledException>(() => writer.WriteAsync(ReadOnlyMemory<char>.Empty, new CancellationToken(true)));
+                await Assert.ThrowsAnyAsync<OperationCanceledException>(() => writer.WriteLineAsync(ReadOnlyMemory<char>.Empty, new CancellationToken(true)));
+            }
+        }
+    }
+}
index 7273e96..621f0a9 100644 (file)
@@ -43,6 +43,7 @@
     <Compile Include="StreamWriter\StreamWriter.CtorTests.cs" />
     <Compile Include="StreamWriter\StreamWriter.FlushTests.cs" />
     <Compile Include="StreamWriter\StreamWriter.WriteTests.cs" />
+    <Compile Include="StreamWriter\StreamWriter.WriteTests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
     <Compile Include="Stream\Stream.ReadWriteSpan.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
     <Compile Include="Stream\Stream.NullTests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
     <Compile Include="Stream\Stream.NullTests.cs" />
index 312c206..506867f 100644 (file)
@@ -1227,8 +1227,8 @@ namespace System.IO
         public virtual int Read() { throw null; }
         public virtual int Read(byte[] buffer, int index, int count) { throw null; }
         public virtual int Read(char[] buffer, int index, int count) { throw null; }
-        public virtual int Read(System.Span<byte> destination) { throw null; }
-        public virtual int Read(System.Span<char> destination) { throw null; }
+        public virtual int Read(System.Span<byte> buffer) { throw null; }
+        public virtual int Read(System.Span<char> buffer) { throw null; }
         protected internal int Read7BitEncodedInt() { throw null; }
         public virtual bool ReadBoolean() { throw null; }
         public virtual byte ReadByte() { throw null; }
@@ -1287,8 +1287,8 @@ namespace System.IO
         public virtual void Write(uint value) { }
         [System.CLSCompliantAttribute(false)]
         public virtual void Write(ulong value) { }
-        public virtual void Write(System.ReadOnlySpan<byte> span) { }
-        public virtual void Write(System.ReadOnlySpan<char> span) { }
+        public virtual void Write(System.ReadOnlySpan<byte> buffer) { }
+        public virtual void Write(System.ReadOnlySpan<char> chars) { }
         protected void Write7BitEncodedInt(int value) { }
     }
     public sealed partial class BufferedStream : System.IO.Stream
@@ -1424,13 +1424,18 @@ namespace System.IO
         public override void Write(char value) { }
         public override void Write(char[] buffer) { }
         public override void Write(char[] buffer, int index, int count) { }
+        public override void Write(System.ReadOnlySpan<char> buffer) { }
         public override void Write(string value) { }
+        public override void WriteLine(string value) { }
+        public override void WriteLine(System.ReadOnlySpan<char> buffer) { }
         public override System.Threading.Tasks.Task WriteAsync(char value) { throw null; }
         public override System.Threading.Tasks.Task WriteAsync(char[] buffer, int index, int count) { throw null; }
+        public override System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> buffer, System.Threading.CancellationToken cancellationToken = default) { throw null; }
         public override System.Threading.Tasks.Task WriteAsync(string value) { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync() { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync(char value) { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync(char[] buffer, int index, int count) { throw null; }
+        public override System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> buffer, System.Threading.CancellationToken cancellationToken = default) { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync(string value) { throw null; }
     }
     public partial class StringReader : System.IO.TextReader
@@ -1441,12 +1446,12 @@ namespace System.IO
         public override int Peek() { throw null; }
         public override int Read() { throw null; }
         public override int Read(char[] buffer, int index, int count) { throw null; }
-        public override int Read(System.Span<char> destination) { throw null; }
-        public override int ReadBlock(System.Span<char> destination) { throw null; }
+        public override int Read(System.Span<char> buffer) { throw null; }
+        public override int ReadBlock(System.Span<char> buffer) { throw null; }
         public override System.Threading.Tasks.Task<int> ReadAsync(char[] buffer, int index, int count) { throw null; }
-        public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<char> destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public override System.Threading.Tasks.Task<int> ReadBlockAsync(char[] buffer, int index, int count) { throw null; }
-        public override System.Threading.Tasks.ValueTask<int> ReadBlockAsync(System.Memory<char> destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public override System.Threading.Tasks.ValueTask<int> ReadBlockAsync(System.Memory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public override string ReadLine() { throw null; }
         public override System.Threading.Tasks.Task<string> ReadLineAsync() { throw null; }
         public override string ReadToEnd() { throw null; }
@@ -1466,16 +1471,16 @@ namespace System.IO
         public override string ToString() { throw null; }
         public override void Write(char value) { }
         public override void Write(char[] buffer, int index, int count) { }
-        public override void Write(System.ReadOnlySpan<char> source) { throw null; }
+        public override void Write(System.ReadOnlySpan<char> buffer) { throw null; }
         public override void Write(string value) { }
-        public override void WriteLine(System.ReadOnlySpan<char> source) { throw null; }
+        public override void WriteLine(System.ReadOnlySpan<char> buffer) { throw null; }
         public override System.Threading.Tasks.Task WriteAsync(char value) { throw null; }
         public override System.Threading.Tasks.Task WriteAsync(char[] buffer, int index, int count) { throw null; }
-        public override System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public override System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public override System.Threading.Tasks.Task WriteAsync(string value) { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync(char value) { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync(char[] buffer, int index, int count) { throw null; }
-        public override System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public override System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public override System.Threading.Tasks.Task WriteLineAsync(string value) { throw null; }
     }
     public abstract partial class TextReader : System.MarshalByRefObject, System.IDisposable
@@ -1488,13 +1493,13 @@ namespace System.IO
         public virtual int Peek() { throw null; }
         public virtual int Read() { throw null; }
         public virtual int Read(char[] buffer, int index, int count) { throw null; }
-        public virtual int Read(System.Span<char> destination) { throw null; }
+        public virtual int Read(System.Span<char> buffer) { throw null; }
         public virtual System.Threading.Tasks.Task<int> ReadAsync(char[] buffer, int index, int count) { throw null; }
-        public virtual System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<char> destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual int ReadBlock(char[] buffer, int index, int count) { throw null; }
-        public virtual int ReadBlock(System.Span<char> destination) { throw null; }
+        public virtual int ReadBlock(System.Span<char> buffer) { throw null; }
         public virtual System.Threading.Tasks.Task<int> ReadBlockAsync(char[] buffer, int index, int count) { throw null; }
-        public virtual System.Threading.Tasks.ValueTask<int> ReadBlockAsync(System.Memory<char> destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.Threading.Tasks.ValueTask<int> ReadBlockAsync(System.Memory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual string ReadLine() { throw null; }
         public virtual System.Threading.Tasks.Task<string> ReadLineAsync() { throw null; }
         public virtual string ReadToEnd() { throw null; }
@@ -1535,11 +1540,11 @@ namespace System.IO
         public virtual void Write(uint value) { }
         [System.CLSCompliantAttribute(false)]
         public virtual void Write(ulong value) { }
-        public virtual void Write(System.ReadOnlySpan<char> source) { }
+        public virtual void Write(System.ReadOnlySpan<char> buffer) { }
         public virtual System.Threading.Tasks.Task WriteAsync(char value) { throw null; }
         public System.Threading.Tasks.Task WriteAsync(char[] buffer) { throw null; }
         public virtual System.Threading.Tasks.Task WriteAsync(char[] buffer, int index, int count) { throw null; }
-        public virtual System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task WriteAsync(string value) { throw null; }
         public virtual void WriteLine() { }
         public virtual void WriteLine(bool value) { }
@@ -1561,12 +1566,12 @@ namespace System.IO
         public virtual void WriteLine(uint value) { }
         [System.CLSCompliantAttribute(false)]
         public virtual void WriteLine(ulong value) { }
-        public virtual void WriteLine(System.ReadOnlySpan<char> source) { }
+        public virtual void WriteLine(System.ReadOnlySpan<char> buffer) { }
         public virtual System.Threading.Tasks.Task WriteLineAsync() { throw null; }
         public virtual System.Threading.Tasks.Task WriteLineAsync(char value) { throw null; }
         public System.Threading.Tasks.Task WriteLineAsync(char[] buffer) { throw null; }
         public virtual System.Threading.Tasks.Task WriteLineAsync(char[] buffer, int index, int count) { throw null; }
-        public virtual System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task WriteLineAsync(string value) { throw null; }
     }
 }
index e97070d..647c8ee 100644 (file)
@@ -2,9 +2,10 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Diagnostics;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
-using System.Diagnostics;
 
 namespace System.IO
 {
@@ -323,59 +324,9 @@ namespace System.IO
 
         public override void Write(char[] buffer)
         {
-            // This may be faster than the one with the index & count since it
-            // has to do less argument checking.
-            if (buffer == null)
+            if (buffer != null)
             {
-                return;
-            }
-
-            CheckAsyncTaskInProgress();
-
-            // Threshold of 4 was chosen after running perf tests
-            if (buffer.Length <= 4)
-            {
-                for (int i = 0; i < buffer.Length; i++)
-                {
-                    if (_charPos == _charLen)
-                    {
-                        Flush(false, false);
-                    }
-
-                    Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(char[]) isn't making progress!  This is most likely a race in user code.");
-                    _charBuffer[_charPos] = buffer[i];
-                    _charPos++;
-                }
-            }
-            else
-            {
-                int count = buffer.Length;
-
-                int index = 0;
-                while (count > 0)
-                {
-                    if (_charPos == _charLen)
-                    {
-                        Flush(false, false);
-                    }
-
-                    int n = _charLen - _charPos;
-                    if (n > count)
-                    {
-                        n = count;
-                    }
-
-                    Debug.Assert(n > 0, "StreamWriter::Write(char[]) isn't making progress!  This is most likely a race in user code.");
-                    Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char));
-                    _charPos += n;
-                    index += n;
-                    count -= n;
-                }
-            }
-
-            if (_autoFlush)
-            {
-                Flush(true, false);
+                WriteCore(buffer, _autoFlush);
             }
         }
 
@@ -398,160 +349,119 @@ namespace System.IO
                 throw new ArgumentException(SR.Argument_InvalidOffLen);
             }
 
-            CheckAsyncTaskInProgress();
+            WriteCore(new ReadOnlySpan<char>(buffer, index, count), _autoFlush);
+        }
 
-            // Threshold of 4 was chosen after running perf tests
-            if (count <= 4)
+        public override void Write(ReadOnlySpan<char> buffer)
+        {
+            if (GetType() == typeof(StreamWriter))
             {
-                while (count > 0)
-                {
-                    if (_charPos == _charLen)
-                    {
-                        Flush(false, false);
-                    }
-
-                    Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(char[]) isn't making progress!  This is most likely a race in user code.");
-                    _charBuffer[_charPos] = buffer[index];
-                    _charPos++;
-                    index++;
-                    count--;
-                }
+                WriteCore(buffer, _autoFlush);
             }
             else
             {
-                while (count > 0)
-                {
-                    if (_charPos == _charLen)
-                    {
-                        Flush(false, false);
-                    }
-
-                    int n = _charLen - _charPos;
-                    if (n > count)
-                    {
-                        n = count;
-                    }
-
-                    Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress!  This is most likely a race condition in user code.");
-                    Buffer.BlockCopy(buffer, index * sizeof(char), _charBuffer, _charPos * sizeof(char), n * sizeof(char));
-                    _charPos += n;
-                    index += n;
-                    count -= n;
-                }
-            }
-
-            if (_autoFlush)
-            {
-                Flush(true, false);
+                // If a derived class may have overridden existing Write behavior,
+                // we need to make sure we use it.
+                base.Write(buffer);
             }
         }
 
-        public override void Write(string value)
+        private unsafe void WriteCore(ReadOnlySpan<char> buffer, bool autoFlush)
         {
-            if (value == null)
-            {
-                return;
-            }
-
             CheckAsyncTaskInProgress();
 
-            int count = value.Length;
-
-            // Threshold of 4 was chosen after running perf tests
-            if (count <= 4)
+            if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
+                buffer.Length <= _charLen - _charPos)
             {
-                for (int i = 0; i < count; i++)
+                // For very short buffers and when we don't need to worry about running out of space
+                // in the char buffer, just copy the chars individually.
+                for (int i = 0; i < buffer.Length; i++)
                 {
-                    if (_charPos == _charLen)
-                    {
-                        Flush(false, false);
-                    }
-
-                    Debug.Assert(_charLen - _charPos > 0, "StreamWriter::Write(String) isn't making progress!  This is most likely a race condition in user code.");
-                    _charBuffer[_charPos] = value[i];
-                    _charPos++;
+                    _charBuffer[_charPos++] = buffer[i];
                 }
             }
             else
             {
-                int index = 0;
-                while (count > 0)
+                // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
+                // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
+                // resulting in significant overhead (even when the if branch above is taken rather than this
+                // else) due to temporaries that need to be cleared.  Given the use of unsafe code, we also
+                // make local copies of instance state to protect against potential concurrent misuse.
+
+                char[] charBuffer = _charBuffer;
+                if (charBuffer == null)
                 {
-                    if (_charPos == _charLen)
-                    {
-                        Flush(false, false);
-                    }
+                    throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+                }
 
-                    int n = _charLen - _charPos;
-                    if (n > count)
+                fixed (char* bufferPtr = &buffer.DangerousGetPinnableReference())
+                fixed (char* dstPtr = &charBuffer[0])
+                {
+                    char* srcPtr = bufferPtr;
+                    int count = buffer.Length;
+                    int dstPos = _charPos; // use a local copy of _charPos for safety
+                    while (count > 0)
                     {
-                        n = count;
-                    }
+                        if (dstPos == charBuffer.Length)
+                        {
+                            Flush(false, false);
+                            dstPos = 0;
+                        }
+
+                        int n = Math.Min(charBuffer.Length - dstPos, count);
+                        int bytesToCopy = n * sizeof(char);
+
+                        Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
 
-                    Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress!  This is most likely a race condition in user code.");
-                    value.CopyTo(index, _charBuffer, _charPos, n);
-                    _charPos += n;
-                    index += n;
-                    count -= n;
+                        _charPos += n;
+                        dstPos += n;
+                        srcPtr += n;
+                        count -= n;
+                    }
                 }
             }
 
-            if (_autoFlush)
+            if (autoFlush)
             {
                 Flush(true, false);
             }
         }
 
+        public override void Write(string value)
+        {
+            if (value != null)
+            {
+                WriteCore(value.AsReadOnlySpan(), _autoFlush);
+            }
+        }
+
         //
         // Optimize the most commonly used WriteLine overload. This optimization is important for System.Console in particular
         // because of it will make one WriteLine equal to one call to the OS instead of two in the common case.
         //
         public override void WriteLine(string value)
         {
-            if (value == null)
-            {
-                value = String.Empty;
-            }
-
             CheckAsyncTaskInProgress();
-
-            int count = value.Length;
-            int index = 0;
-            while (count > 0)
+            if (value != null)
             {
-                if (_charPos == _charLen)
-                {
-                    Flush(false, false);
-                }
-
-                int n = _charLen - _charPos;
-                if (n > count)
-                {
-                    n = count;
-                }
-
-                Debug.Assert(n > 0, "StreamWriter::WriteLine(String) isn't making progress!  This is most likely a race condition in user code.");
-                value.CopyTo(index, _charBuffer, _charPos, n);
-                _charPos += n;
-                index += n;
-                count -= n;
+                WriteCore(value.AsReadOnlySpan(), autoFlush: false);
             }
+            WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
+        }
 
-            char[] coreNewLine = CoreNewLine;
-            for (int i = 0; i < coreNewLine.Length; i++)   // Expect 2 iterations, no point calling BlockCopy
+        public override void WriteLine(ReadOnlySpan<char> value)
+        {
+            if (GetType() == typeof(StreamWriter))
             {
-                if (_charPos == _charLen)
-                {
-                    Flush(false, false);
-                }
-
-                _charBuffer[_charPos] = coreNewLine[i];
-                _charPos++;
+                CheckAsyncTaskInProgress();
+                WriteCore(value, autoFlush: false);
+                WriteCore(new ReadOnlySpan<char>(CoreNewLine), autoFlush: true);
             }
-
-            if (_autoFlush)
+            else
             {
-                Flush(true, false);
+                // If a derived class may have overridden existing WriteLine behavior,
+                // we need to make sure we use it.
+                base.WriteLine(value);
             }
         }
 
@@ -751,46 +661,60 @@ namespace System.IO
 
             CheckAsyncTaskInProgress();
 
-            Task task = WriteAsyncInternal(this, buffer, index, count, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
+            Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
             _asyncWriteTask = task;
 
             return task;
         }
 
+        public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
+        {
+            if (GetType() != typeof(StreamWriter))
+            {
+                // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
+                return base.WriteAsync(buffer, cancellationToken);
+            }
+
+            if (_stream == null)
+            {
+                throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+            }
+
+            CheckAsyncTaskInProgress();
+
+            if (cancellationToken.IsCancellationRequested)
+            {
+                return Task.FromCanceled(cancellationToken);
+            }
+
+            Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
+            _asyncWriteTask = task;
+            return task;
+        }
+
         // We pass in private instance fields of this MarshalByRefObject-derived type as local params
         // to ensure performant access inside the state machine that corresponds this async method.
         // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
-        private static async Task WriteAsyncInternal(StreamWriter _this, char[] buffer, int index, int count,
+        private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
                                                      char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
-                                                     bool autoFlush, bool appendNewLine)
+                                                     bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
         {
-            Debug.Assert(count == 0 || (count > 0 && buffer != null));
-            Debug.Assert(index >= 0);
-            Debug.Assert(count >= 0);
-            Debug.Assert(buffer == null || (buffer != null && buffer.Length - index >= count));
-
-            while (count > 0)
+            while (source.Length > 0)
             {
                 if (charPos == charLen)
                 {
-                    await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
+                    await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
                     Debug.Assert(_this._charPos == 0);
                     charPos = 0;
                 }
 
-                int n = charLen - charPos;
-                if (n > count)
-                {
-                    n = count;
-                }
-
+                int n = Math.Min(charLen - charPos, source.Length);
                 Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress!  This is most likely a race condition in user code.");
 
-                Buffer.BlockCopy(buffer, index * sizeof(char), charBuffer, charPos * sizeof(char), n * sizeof(char));
+                source.Span.Slice(0, n).CopyTo(new Span<char>(charBuffer, charPos, n));
 
+                source = source.Slice(n);
                 charPos += n;
-                index += n;
-                count -= n;
             }
 
             if (appendNewLine)
@@ -799,7 +723,7 @@ namespace System.IO
                 {
                     if (charPos == charLen)
                     {
-                        await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
+                        await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
                         Debug.Assert(_this._charPos == 0);
                         charPos = 0;
                     }
@@ -811,7 +735,7 @@ namespace System.IO
 
             if (autoFlush)
             {
-                await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
+                await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
                 Debug.Assert(_this._charPos == 0);
                 charPos = 0;
             }
@@ -837,7 +761,7 @@ namespace System.IO
 
             CheckAsyncTaskInProgress();
 
-            Task task = WriteAsyncInternal(this, null, 0, 0, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
+            Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
             _asyncWriteTask = task;
 
             return task;
@@ -934,7 +858,32 @@ namespace System.IO
 
             CheckAsyncTaskInProgress();
 
-            Task task = WriteAsyncInternal(this, buffer, index, count, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
+            Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
+            _asyncWriteTask = task;
+
+            return task;
+        }
+
+        public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
+        {
+            if (GetType() != typeof(StreamWriter))
+            {
+                return base.WriteLineAsync(buffer, cancellationToken);
+            }
+
+            if (_stream == null)
+            {
+                throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+            }
+
+            CheckAsyncTaskInProgress();
+
+            if (cancellationToken.IsCancellationRequested)
+            {
+                return Task.FromCanceled(cancellationToken);
+            }
+
+            Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
             _asyncWriteTask = task;
 
             return task;
@@ -980,8 +929,13 @@ namespace System.IO
         }
 
         private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
-                                        char[] sCharBuffer, int sCharPos)
+                                        char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
         {
+            if (cancellationToken.IsCancellationRequested)
+            {
+                return Task.FromCanceled(cancellationToken);
+            }
+
             // Perf boost for Flush on non-dirty writers.
             if (sCharPos == 0 && !flushStream && !flushEncoder)
             {
@@ -989,7 +943,7 @@ namespace System.IO
             }
 
             Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
-                                                _encoding, _encoder, _byteBuffer, _stream);
+                                                _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
 
             _charPos = 0;
             return flushTask;
@@ -1000,7 +954,7 @@ namespace System.IO
         // to ensure performant access inside the state machine that corresponds this async method.
         private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
                                                      char[] charBuffer, int charPos, bool haveWrittenPreamble,
-                                                     Encoding encoding, Encoder encoder, Byte[] byteBuffer, Stream stream)
+                                                     Encoding encoding, Encoder encoder, Byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
         {
             if (!haveWrittenPreamble)
             {
@@ -1008,14 +962,14 @@ namespace System.IO
                 byte[] preamble = encoding.GetPreamble();
                 if (preamble.Length > 0)
                 {
-                    await stream.WriteAsync(preamble, 0, preamble.Length).ConfigureAwait(false);
+                    await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false);
                 }
             }
 
             int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
             if (count > 0)
             {
-                await stream.WriteAsync(byteBuffer, 0, count).ConfigureAwait(false);
+                await stream.WriteAsync(byteBuffer, 0, count, cancellationToken).ConfigureAwait(false);
             }
 
             // By definition, calling Flush should flush the stream, but this is
@@ -1023,7 +977,7 @@ namespace System.IO
             // Services guys have some perf tests where flushing needlessly hurts.
             if (flushStream)
             {
-                await stream.FlushAsync().ConfigureAwait(false);
+                await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
             }
         }
         #endregion
index f2d5a1c..63845b3 100644 (file)
@@ -118,13 +118,13 @@ namespace System.IO
             return n;
         }
 
-        public override int Read(Span<char> destination)
+        public override int Read(Span<char> buffer)
         {
             if (GetType() != typeof(StringReader))
             {
                 // This overload was added affter the Read(char[], ...) overload, and so in case
                 // a derived type may have overridden it, we need to delegate to it, which the base does.
-                return base.Read(destination);
+                return base.Read(buffer);
             }
 
             if (_s == null)
@@ -135,19 +135,19 @@ namespace System.IO
             int n = _length - _pos;
             if (n > 0)
             {
-                if (n > destination.Length)
+                if (n > buffer.Length)
                 {
-                    n = destination.Length;
+                    n = buffer.Length;
                 }
 
-                _s.AsReadOnlySpan().Slice(_pos, n).CopyTo(destination);
+                _s.AsReadOnlySpan().Slice(_pos, n).CopyTo(buffer);
                 _pos += n;
             }
 
             return n;
         }
 
-        public override int ReadBlock(Span<char> destination) => Read(destination);
+        public override int ReadBlock(Span<char> buffer) => Read(buffer);
 
         public override string ReadToEnd()
         {
@@ -241,9 +241,9 @@ namespace System.IO
             return Task.FromResult(ReadBlock(buffer, index, count));
         }
 
-        public override ValueTask<int> ReadBlockAsync(Memory<char> destination, CancellationToken cancellationToken = default) =>
+        public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
             cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) :
-            new ValueTask<int>(ReadBlock(destination.Span));
+            new ValueTask<int>(ReadBlock(buffer.Span));
 
         public override Task<int> ReadAsync(char[] buffer, int index, int count)
         {
@@ -263,9 +263,9 @@ namespace System.IO
             return Task.FromResult(Read(buffer, index, count));
         }
 
-        public override ValueTask<int> ReadAsync(Memory<char> destination, CancellationToken cancellationToken = default) =>
+        public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
             cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) :
-            new ValueTask<int>(Read(destination.Span));
+            new ValueTask<int>(Read(buffer.Span));
         #endregion
     }
 }
index 8d7cec6..92c7b86 100644 (file)
@@ -125,13 +125,13 @@ namespace System.IO
             _sb.Append(buffer, index, count);
         }
 
-        public override void Write(ReadOnlySpan<char> source)
+        public override void Write(ReadOnlySpan<char> buffer)
         {
             if (GetType() != typeof(StringWriter))
             {
                 // This overload was added affter the Write(char[], ...) overload, and so in case
                 // a derived type may have overridden it, we need to delegate to it, which the base does.
-                base.Write(source);
+                base.Write(buffer);
                 return;
             }
 
@@ -140,7 +140,7 @@ namespace System.IO
                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
             }
 
-            _sb.Append(source);
+            _sb.Append(buffer);
         }
 
         // Writes a string to the underlying string buffer. If the given string is
@@ -159,13 +159,13 @@ namespace System.IO
             }
         }
 
-        public override void WriteLine(ReadOnlySpan<char> source)
+        public override void WriteLine(ReadOnlySpan<char> buffer)
         {
             if (GetType() != typeof(StringWriter))
             {
                 // This overload was added affter the WriteLine(char[], ...) overload, and so in case
                 // a derived type may have overridden it, we need to delegate to it, which the base does.
-                base.WriteLine(source);
+                base.WriteLine(buffer);
                 return;
             }
 
@@ -174,7 +174,7 @@ namespace System.IO
                 throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
             }
 
-            _sb.Append(source);
+            _sb.Append(buffer);
             WriteLine();
         }
 
@@ -198,14 +198,14 @@ namespace System.IO
             return Task.CompletedTask;
         }
 
-        public override Task WriteAsync(ReadOnlyMemory<char> source, CancellationToken cancellationToken = default)
+        public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
         {
             if (cancellationToken.IsCancellationRequested)
             {
                 return Task.FromCanceled(cancellationToken);
             }
 
-            Write(source.Span);
+            Write(buffer.Span);
             return Task.CompletedTask;
         }
 
@@ -227,14 +227,14 @@ namespace System.IO
             return Task.CompletedTask;
         }
 
-        public override Task WriteLineAsync(ReadOnlyMemory<char> source, CancellationToken cancellationToken = default)
+        public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
         {
             if (cancellationToken.IsCancellationRequested)
             {
                 return Task.FromCanceled(cancellationToken);
             }
 
-            WriteLine(source.Span);
+            WriteLine(buffer.Span);
             return Task.CompletedTask;
         }
 
index cf0257a..3008a07 100644 (file)
@@ -105,23 +105,23 @@ namespace System.IO
         // count characters from this TextReader into the
         // span of characters Returns the actual number of characters read.
         //
-        public virtual int Read(Span<char> destination)
+        public virtual int Read(Span<char> buffer)
         {
-            char[] buffer = ArrayPool<char>.Shared.Rent(destination.Length);
+            char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
 
             try
             {
-                int numRead = Read(buffer, 0, destination.Length);
-                if ((uint)numRead > destination.Length)
+                int numRead = Read(array, 0, buffer.Length);
+                if ((uint)numRead > buffer.Length)
                 {
                     throw new IOException(SR.IO_InvalidReadLength);
                 }
-                new Span<char>(buffer, 0, numRead).CopyTo(destination);
+                new Span<char>(array, 0, numRead).CopyTo(buffer);
                 return numRead;
             }
             finally
             {
-                ArrayPool<char>.Shared.Return(buffer);
+                ArrayPool<char>.Shared.Return(array);
             }
         }
 
@@ -155,23 +155,23 @@ namespace System.IO
         // Blocking version of read for span of characters.  Returns only when count
         // characters have been read or the end of the file was reached.
         //
-        public virtual int ReadBlock(Span<char> destination)
+        public virtual int ReadBlock(Span<char> buffer)
         {
-            char[] buffer = ArrayPool<char>.Shared.Rent(destination.Length);
+            char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
 
             try
             {
-                int numRead = ReadBlock(buffer, 0, destination.Length);
-                if ((uint)numRead > destination.Length)
+                int numRead = ReadBlock(array, 0, buffer.Length);
+                if ((uint)numRead > buffer.Length)
                 {
                     throw new IOException(SR.IO_InvalidReadLength);
                 }
-                new Span<char>(buffer, 0, numRead).CopyTo(destination);
+                new Span<char>(array, 0, numRead).CopyTo(buffer);
                 return numRead;
             }
             finally
             {
-                ArrayPool<char>.Shared.Return(buffer);
+                ArrayPool<char>.Shared.Return(array);
             }
         }
 
@@ -247,14 +247,14 @@ namespace System.IO
             return ReadAsyncInternal(buffer, index, count);
         }
 
-        public virtual ValueTask<int> ReadAsync(Memory<char> destination, CancellationToken cancellationToken = default(CancellationToken)) =>
-            new ValueTask<int>(destination.TryGetArray(out ArraySegment<char> array) ?
+        public virtual ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+            new ValueTask<int>(buffer.TryGetArray(out ArraySegment<char> array) ?
                 ReadAsync(array.Array, array.Offset, array.Count) :
                 Task<int>.Factory.StartNew(state =>
                 {
                     var t = (Tuple<TextReader, Memory<char>>)state;
                     return t.Item1.Read(t.Item2.Span);
-                }, Tuple.Create(this, destination), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+                }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
 
         internal virtual Task<int> ReadAsyncInternal(char[] buffer, int index, int count)
         {
@@ -290,14 +290,14 @@ namespace System.IO
             return ReadBlockAsyncInternal(buffer, index, count);
         }
 
-        public virtual ValueTask<int> ReadBlockAsync(Memory<char> destination, CancellationToken cancellationToken = default(CancellationToken)) =>
-            new ValueTask<int>(destination.TryGetArray(out ArraySegment<char> array) ?
+        public virtual ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+            new ValueTask<int>(buffer.TryGetArray(out ArraySegment<char> array) ?
                 ReadBlockAsync(array.Array, array.Offset, array.Count) :
                 Task<int>.Factory.StartNew(state =>
                 {
                     var t = (Tuple<TextReader, Memory<char>>)state;
                     return t.Item1.ReadBlock(t.Item2.Span);
-                }, Tuple.Create(this, destination), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+                }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
 
         private async Task<int> ReadBlockAsyncInternal(char[] buffer, int index, int count)
         {
index 490c14d..4bde990 100644 (file)
@@ -164,18 +164,18 @@ namespace System.IO
 
         // Writes a span of characters to the text stream.
         //
-        public virtual void Write(ReadOnlySpan<char> source)
+        public virtual void Write(ReadOnlySpan<char> buffer)
         {
-            char[] buffer = ArrayPool<char>.Shared.Rent(source.Length);
+            char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
 
             try
             {
-                source.CopyTo(new Span<char>(buffer));
-                Write(buffer, 0, source.Length);
+                buffer.CopyTo(new Span<char>(array));
+                Write(array, 0, buffer.Length);
             }
             finally
             {
-                ArrayPool<char>.Shared.Return(buffer);
+                ArrayPool<char>.Shared.Return(array);
             }
         }
 
@@ -346,18 +346,18 @@ namespace System.IO
             WriteLine();
         }
 
-        public virtual void WriteLine(ReadOnlySpan<char> source)
+        public virtual void WriteLine(ReadOnlySpan<char> buffer)
         {
-            char[] buffer = ArrayPool<char>.Shared.Rent(source.Length);
+            char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
 
             try
             {
-                source.CopyTo(new Span<char>(buffer));
-                WriteLine(buffer, 0, source.Length);
+                buffer.CopyTo(new Span<char>(array));
+                WriteLine(array, 0, buffer.Length);
             }
             finally
             {
-                ArrayPool<char>.Shared.Return(buffer);
+                ArrayPool<char>.Shared.Return(array);
             }
         }
 
@@ -544,14 +544,14 @@ namespace System.IO
             tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
         }
 
-        public virtual Task WriteAsync(ReadOnlyMemory<char> source, CancellationToken cancellationToken = default(CancellationToken)) =>
-            source.DangerousTryGetArray(out ArraySegment<char> array) ?
+        public virtual Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+            buffer.DangerousTryGetArray(out ArraySegment<char> array) ?
                 WriteAsync(array.Array, array.Offset, array.Count) :
                 Task.Factory.StartNew(state =>
                 {
                     var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state;
                     t.Item1.Write(t.Item2.Span);
-                }, Tuple.Create(this, source), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+                }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 
         public virtual Task WriteLineAsync(char value)
         {
@@ -596,14 +596,14 @@ namespace System.IO
             tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
         }
 
-        public virtual Task WriteLineAsync(ReadOnlyMemory<char> source, CancellationToken cancellationToken = default(CancellationToken)) =>
-            source.DangerousTryGetArray(out ArraySegment<char> array) ?
+        public virtual Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+            buffer.DangerousTryGetArray(out ArraySegment<char> array) ?
                 WriteLineAsync(array.Array, array.Offset, array.Count) :
                 Task.Factory.StartNew(state =>
                 {
                     var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state;
                     t.Item1.WriteLine(t.Item2.Span);
-                }, Tuple.Create(this, source), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+                }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 
         public virtual Task WriteLineAsync()
         {