From 426c0414a3a7ad2759a44acb9fd5abbb9c6505e6 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Sun, 23 Jan 2022 15:21:20 -0800 Subject: [PATCH 1/9] Override all Read methods in NullTextReader and NullStreamReader. --- .../tests/Stream/Stream.NullTests.cs | 51 +++++++++++++++-- .../src/System/IO/StreamReader.cs | 56 +++++++++++-------- .../src/System/IO/TextReader.cs | 42 +++++++++++--- 3 files changed, 112 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs index 5b2ab1f81e8789..bc16b778e58399 100644 --- a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs +++ b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs @@ -131,16 +131,57 @@ public static void TestNullTextReader(TextReader input) if (sr != null) Assert.True(sr.EndOfStream, "EndOfStream property didn't return true"); - input.ReadLine(); + Assert.Null(input.ReadLine()); input.Dispose(); input.ReadLine(); if (sr != null) Assert.True(sr.EndOfStream, "EndOfStream property didn't return true"); - input.Read(); - input.Peek(); - input.Read(new char[2], 0, 2); - input.ReadToEnd(); + Assert.Equal(-1, input.Read()); + Assert.Equal(-1, input.Peek()); + + var chars = new char[2]; + Assert.Equal(0, input.Read(chars, 0, chars.Length)); + Assert.Equal(0, input.Read(chars.AsSpan())); + Assert.Equal(0, input.ReadBlock(chars, 0, chars.Length)); + Assert.Equal(0, input.ReadBlock(chars.AsSpan())); + Assert.Equal("", input.ReadToEnd()); + input.Dispose(); + } + + [Theory] + [MemberData(nameof(NullReaders))] + public static async Task TestNullTextReaderAsync(TextReader input) + { + var chars = new char[2]; + Assert.Equal(0, await input.ReadAsync(chars, 0, chars.Length)); + Assert.Equal(0, await input.ReadAsync(chars.AsMemory(), default)); + Assert.Equal(0, await input.ReadBlockAsync(chars, 0, chars.Length)); + Assert.Equal(0, await input.ReadBlockAsync(chars.AsMemory(), default)); + Assert.Null(await input.ReadLineAsync()); + Assert.Null(await input.ReadLineAsync(default)); + Assert.Equal("", await input.ReadToEndAsync()); + Assert.Equal("", await input.ReadToEndAsync(default)); + input.Dispose(); + } + + [Theory] + [MemberData(nameof(NullReaders))] + public static async Task TestCanceledNullTextReaderAsync(TextReader input) + { + using var tokenSource = new CancellationTokenSource(); + tokenSource.Cancel(); + var token = tokenSource.Token; + var chars = new char[2]; + OperationCanceledException ex; + ex = await Assert.ThrowsAnyAsync(async () => await input.ReadAsync(chars.AsMemory(), token)); + Assert.Equal(token, ex.CancellationToken); + ex = await Assert.ThrowsAnyAsync(async () => await input.ReadBlockAsync(chars.AsMemory(), token)); + Assert.Equal(token, ex.CancellationToken); + ex = await Assert.ThrowsAnyAsync(async () => await input.ReadLineAsync(token)); + Assert.Equal(token, ex.CancellationToken); + ex = await Assert.ThrowsAnyAsync(async () => await input.ReadToEndAsync(token)); + Assert.Equal(token, ex.CancellationToken); input.Dispose(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs index b5aca3d1dfbcf7..b5d8b6aca494ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @@ -1395,35 +1395,43 @@ protected override void Dispose(bool disposing) // Do nothing - this is essentially unclosable. } - public override int Peek() - { - return -1; - } + public override int Peek() => -1; - public override int Read() - { - return -1; - } + public override int Read() => -1; - public override int Read(char[] buffer, int index, int count) - { - return 0; - } + public override int Read(char[] buffer, int index, int count) => 0; - public override string? ReadLine() - { - return null; - } + public override int Read(Span buffer) => 0; - public override string ReadToEnd() - { - return string.Empty; - } + public override Task ReadAsync(char[] buffer, int index, int count) => Task.FromResult(0); - internal override int ReadBuffer() - { - return 0; - } + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + + public override int ReadBlock(char[] buffer, int index, int count) => 0; + + public override int ReadBlock(Span buffer) => 0; + + public override Task ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(0); + + public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + + public override string? ReadLine() => null; + + public override Task ReadLineAsync() => Task.FromResult(null); + + public override ValueTask ReadLineAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + + public override string ReadToEnd() => ""; + + public override Task ReadToEndAsync() => Task.FromResult(""); + + public override Task ReadToEndAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); + + internal override int ReadBuffer() => 0; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs index 583fb3c2652ae6..7fd272aef1a92a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs @@ -342,15 +342,41 @@ private sealed class NullTextReader : TextReader { public NullTextReader() { } - public override int Read(char[] buffer, int index, int count) - { - return 0; - } + public override int Peek() => -1; - public override string? ReadLine() - { - return null; - } + public override int Read() => -1; + + public override int Read(char[] buffer, int index, int count) => 0; + + public override int Read(Span buffer) => 0; + + public override Task ReadAsync(char[] buffer, int index, int count) => Task.FromResult(0); + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + + public override int ReadBlock(char[] buffer, int index, int count) => 0; + + public override int ReadBlock(Span buffer) => 0; + + public override Task ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(0); + + public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + + public override string? ReadLine() => null; + + public override Task ReadLineAsync() => Task.FromResult(null); + + public override ValueTask ReadLineAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + + public override string ReadToEnd() => ""; + + public override Task ReadToEndAsync() => Task.FromResult(""); + + public override Task ReadToEndAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); } public static TextReader Synchronized(TextReader reader) From 9b2f3c19a0d1e4e9b4a23520a9d991e79b0472a3 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 25 Jan 2022 19:43:44 -0800 Subject: [PATCH 2/9] Cache Task.FromResult(""). --- .../System.Private.CoreLib/src/System/IO/StreamReader.cs | 4 ++-- .../System.Private.CoreLib/src/System/IO/TextReader.cs | 4 ++-- .../src/System/Threading/Tasks/TaskCache.cs | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs index b5d8b6aca494ea..ef3276676ebf94 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @@ -1426,10 +1426,10 @@ public override ValueTask ReadBlockAsync(Memory buffer, CancellationT public override string ReadToEnd() => ""; - public override Task ReadToEndAsync() => Task.FromResult(""); + public override Task ReadToEndAsync() => TaskCache.s_emptyStringTask; public override Task ReadToEndAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : TaskCache.s_emptyStringTask; internal override int ReadBuffer() => 0; } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs index 7fd272aef1a92a..bd00ba9163daba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs @@ -373,10 +373,10 @@ public override ValueTask ReadBlockAsync(Memory buffer, CancellationT public override string ReadToEnd() => ""; - public override Task ReadToEndAsync() => Task.FromResult(""); + public override Task ReadToEndAsync() => TaskCache.s_emptyStringTask; public override Task ReadToEndAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : TaskCache.s_emptyStringTask; } public static TextReader Synchronized(TextReader reader) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs index 94d54d6a1af614..92fb0b2db6e943 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs @@ -13,6 +13,8 @@ internal static class TaskCache internal static readonly Task s_trueTask = CreateCacheableTask(result: true); /// A cached Task{Boolean}.Result == false. internal static readonly Task s_falseTask = CreateCacheableTask(result: false); + /// A cached Task{String}.Result == "". + internal static readonly Task s_emptyStringTask = CreateCacheableTask(result: ""); /// The cache of Task{Int32}. internal static readonly Task[] s_int32Tasks = CreateInt32Tasks(); /// The minimum value, inclusive, for which we want a cached task. From c6dd80f54abef9ce5b51e7a0d22faa7522cbe5b4 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Wed, 26 Jan 2022 05:48:03 -0800 Subject: [PATCH 3/9] Remove cached Task. --- .../System.Private.CoreLib/src/System/IO/StreamReader.cs | 4 ++-- .../System.Private.CoreLib/src/System/IO/TextReader.cs | 4 ++-- .../src/System/Threading/Tasks/TaskCache.cs | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs index ef3276676ebf94..b5d8b6aca494ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @@ -1426,10 +1426,10 @@ public override ValueTask ReadBlockAsync(Memory buffer, CancellationT public override string ReadToEnd() => ""; - public override Task ReadToEndAsync() => TaskCache.s_emptyStringTask; + public override Task ReadToEndAsync() => Task.FromResult(""); public override Task ReadToEndAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : TaskCache.s_emptyStringTask; + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); internal override int ReadBuffer() => 0; } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs index bd00ba9163daba..7fd272aef1a92a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs @@ -373,10 +373,10 @@ public override ValueTask ReadBlockAsync(Memory buffer, CancellationT public override string ReadToEnd() => ""; - public override Task ReadToEndAsync() => TaskCache.s_emptyStringTask; + public override Task ReadToEndAsync() => Task.FromResult(""); public override Task ReadToEndAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : TaskCache.s_emptyStringTask; + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); } public static TextReader Synchronized(TextReader reader) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs index 92fb0b2db6e943..94d54d6a1af614 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskCache.cs @@ -13,8 +13,6 @@ internal static class TaskCache internal static readonly Task s_trueTask = CreateCacheableTask(result: true); /// A cached Task{Boolean}.Result == false. internal static readonly Task s_falseTask = CreateCacheableTask(result: false); - /// A cached Task{String}.Result == "". - internal static readonly Task s_emptyStringTask = CreateCacheableTask(result: ""); /// The cache of Task{Int32}. internal static readonly Task[] s_int32Tasks = CreateInt32Tasks(); /// The minimum value, inclusive, for which we want a cached task. From e94886f9699bfce781760d23b11b28b93485fdf4 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Wed, 26 Jan 2022 05:50:58 -0800 Subject: [PATCH 4/9] Improve tests based on feedback. --- .../System.IO/tests/Stream/Stream.NullTests.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs index bc16b778e58399..9dca8d7ab37cf7 100644 --- a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs +++ b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs @@ -123,6 +123,17 @@ public static void TestNullStream_WriteByte() Assert.Equal(0, source.Position); } + + [Theory] + [MemberData(nameof(NullReaders))] + public static void TestNullTextReaderDispose(TextReader input) + { + // dispose should be a no-op + input.Dispose(); + input.Dispose(); + Assert.Equal("", input.ReadToEnd()); + } + [Theory] [MemberData(nameof(NullReaders))] public static void TestNullTextReader(TextReader input) @@ -132,14 +143,11 @@ public static void TestNullTextReader(TextReader input) if (sr != null) Assert.True(sr.EndOfStream, "EndOfStream property didn't return true"); Assert.Null(input.ReadLine()); - input.Dispose(); - - input.ReadLine(); if (sr != null) Assert.True(sr.EndOfStream, "EndOfStream property didn't return true"); + Assert.Equal(-1, input.Read()); Assert.Equal(-1, input.Peek()); - var chars = new char[2]; Assert.Equal(0, input.Read(chars, 0, chars.Length)); Assert.Equal(0, input.Read(chars.AsSpan())); @@ -169,7 +177,7 @@ public static async Task TestNullTextReaderAsync(TextReader input) [MemberData(nameof(NullReaders))] public static async Task TestCanceledNullTextReaderAsync(TextReader input) { - using var tokenSource = new CancellationTokenSource(); + using CancellationTokenSource tokenSource = new CancellationTokenSource(); tokenSource.Cancel(); var token = tokenSource.Token; var chars = new char[2]; From 728a28bedd379cdc8a09c4d7fbe0b6e99059d1f3 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Wed, 26 Jan 2022 05:55:47 -0800 Subject: [PATCH 5/9] Replace NullTextReader with NullStreamReader. --- .../src/System/IO/StreamReader.cs | 3 ++ .../src/System/IO/TextReader.cs | 43 +------------------ 2 files changed, 4 insertions(+), 42 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs index b5d8b6aca494ea..af35a0ae46c83f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @@ -1431,6 +1431,9 @@ public override ValueTask ReadBlockAsync(Memory buffer, CancellationT public override Task ReadToEndAsync(CancellationToken cancellationToken) => cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); + internal override ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; + internal override int ReadBuffer() => 0; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs index 7fd272aef1a92a..9c9eb32cc99557 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs @@ -19,7 +19,7 @@ namespace System.IO // There are methods on the Stream class for reading bytes. public abstract partial class TextReader : MarshalByRefObject, IDisposable { - public static readonly TextReader Null = new NullTextReader(); + public static readonly TextReader Null = StreamReader.Null; protected TextReader() { } @@ -338,47 +338,6 @@ internal async ValueTask ReadBlockAsyncInternal(Memory buffer, Cancel } #endregion - private sealed class NullTextReader : TextReader - { - public NullTextReader() { } - - public override int Peek() => -1; - - public override int Read() => -1; - - public override int Read(char[] buffer, int index, int count) => 0; - - public override int Read(Span buffer) => 0; - - public override Task ReadAsync(char[] buffer, int index, int count) => Task.FromResult(0); - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; - - public override int ReadBlock(char[] buffer, int index, int count) => 0; - - public override int ReadBlock(Span buffer) => 0; - - public override Task ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(0); - - public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; - - public override string? ReadLine() => null; - - public override Task ReadLineAsync() => Task.FromResult(null); - - public override ValueTask ReadLineAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled(cancellationToken) : default; - - public override string ReadToEnd() => ""; - - public override Task ReadToEndAsync() => Task.FromResult(""); - - public override Task ReadToEndAsync(CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.FromResult(""); - } - public static TextReader Synchronized(TextReader reader) { if (reader == null) From d71058699153da338fcd48426c376cc7f22a993f Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Wed, 26 Jan 2022 06:15:29 -0800 Subject: [PATCH 6/9] Remove extra newline. --- src/libraries/System.IO/tests/Stream/Stream.NullTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs index 9dca8d7ab37cf7..efe700c1e9c753 100644 --- a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs +++ b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs @@ -123,7 +123,6 @@ public static void TestNullStream_WriteByte() Assert.Equal(0, source.Position); } - [Theory] [MemberData(nameof(NullReaders))] public static void TestNullTextReaderDispose(TextReader input) From 1d3e13064882fb706e36afa960cbb8f9c870f9f2 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Wed, 26 Jan 2022 09:06:46 -0800 Subject: [PATCH 7/9] Run null TextReader tests on unique instances. --- .../System.IO/tests/Stream/Stream.NullTests.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs index efe700c1e9c753..830ddbd011f53b 100644 --- a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs +++ b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs @@ -242,15 +242,10 @@ public void DisposeAsync_Nop() Stream.Null.Write(new byte[42]); // still usable } - public static IEnumerable NullReaders - { - get - { - yield return new object[] { TextReader.Null }; - yield return new object[] { StreamReader.Null }; - yield return new object[] { StringReader.Null }; - } - } + public static IEnumerable NullReaders => + new[] { TextReader.Null, StreamReader.Null, StringReader.Null } + .Distinct() + .Select(x => new object[] { x }); public static IEnumerable NullWriters { From 03b36967ced9fea23a7f1d1776162d2271cd96b7 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Wed, 26 Jan 2022 10:15:10 -0800 Subject: [PATCH 8/9] Create a NullStreamReader instance to back TextReader.Null. --- .../System.Private.CoreLib/src/System/IO/StreamReader.cs | 2 +- .../System.Private.CoreLib/src/System/IO/TextReader.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs index af35a0ae46c83f..b18aff879a87e6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @@ -1386,7 +1386,7 @@ private void ThrowIfDisposed() // No data, class doesn't need to be serializable. // Note this class is threadsafe. - private sealed class NullStreamReader : StreamReader + internal sealed class NullStreamReader : StreamReader { public override Encoding CurrentEncoding => Encoding.Unicode; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs index 9c9eb32cc99557..a0536bd022e8ee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs @@ -19,7 +19,8 @@ namespace System.IO // There are methods on the Stream class for reading bytes. public abstract partial class TextReader : MarshalByRefObject, IDisposable { - public static readonly TextReader Null = StreamReader.Null; + // Create our own instance to avoid static field initialization order problems on Mono. + public static readonly TextReader Null = new StreamReader.NullStreamReader(); protected TextReader() { } From 80dad3315bf14ab06e73a118e5c54835298b0010 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Thu, 27 Jan 2022 10:04:57 +0100 Subject: [PATCH 9/9] Apply suggestions from code review --- .../System.IO/tests/Stream/Stream.NullTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs index 830ddbd011f53b..efe700c1e9c753 100644 --- a/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs +++ b/src/libraries/System.IO/tests/Stream/Stream.NullTests.cs @@ -242,10 +242,15 @@ public void DisposeAsync_Nop() Stream.Null.Write(new byte[42]); // still usable } - public static IEnumerable NullReaders => - new[] { TextReader.Null, StreamReader.Null, StringReader.Null } - .Distinct() - .Select(x => new object[] { x }); + public static IEnumerable NullReaders + { + get + { + yield return new object[] { TextReader.Null }; + yield return new object[] { StreamReader.Null }; + yield return new object[] { StringReader.Null }; + } + } public static IEnumerable NullWriters {