Skip to content

Commit bab1a20

Browse files
authored
Override all Read methods in NullTextReader and NullStreamReader (#64301)
1 parent 19291cc commit bab1a20

File tree

3 files changed

+94
-49
lines changed

3 files changed

+94
-49
lines changed

src/libraries/System.IO/tests/Stream/Stream.NullTests.cs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ public static void TestNullStream_WriteByte()
123123
Assert.Equal(0, source.Position);
124124
}
125125

126+
[Theory]
127+
[MemberData(nameof(NullReaders))]
128+
public static void TestNullTextReaderDispose(TextReader input)
129+
{
130+
// dispose should be a no-op
131+
input.Dispose();
132+
input.Dispose();
133+
Assert.Equal("", input.ReadToEnd());
134+
}
135+
126136
[Theory]
127137
[MemberData(nameof(NullReaders))]
128138
public static void TestNullTextReader(TextReader input)
@@ -131,16 +141,54 @@ public static void TestNullTextReader(TextReader input)
131141

132142
if (sr != null)
133143
Assert.True(sr.EndOfStream, "EndOfStream property didn't return true");
134-
input.ReadLine();
135-
input.Dispose();
136-
137-
input.ReadLine();
144+
Assert.Null(input.ReadLine());
138145
if (sr != null)
139146
Assert.True(sr.EndOfStream, "EndOfStream property didn't return true");
140-
input.Read();
141-
input.Peek();
142-
input.Read(new char[2], 0, 2);
143-
input.ReadToEnd();
147+
148+
Assert.Equal(-1, input.Read());
149+
Assert.Equal(-1, input.Peek());
150+
var chars = new char[2];
151+
Assert.Equal(0, input.Read(chars, 0, chars.Length));
152+
Assert.Equal(0, input.Read(chars.AsSpan()));
153+
Assert.Equal(0, input.ReadBlock(chars, 0, chars.Length));
154+
Assert.Equal(0, input.ReadBlock(chars.AsSpan()));
155+
Assert.Equal("", input.ReadToEnd());
156+
input.Dispose();
157+
}
158+
159+
[Theory]
160+
[MemberData(nameof(NullReaders))]
161+
public static async Task TestNullTextReaderAsync(TextReader input)
162+
{
163+
var chars = new char[2];
164+
Assert.Equal(0, await input.ReadAsync(chars, 0, chars.Length));
165+
Assert.Equal(0, await input.ReadAsync(chars.AsMemory(), default));
166+
Assert.Equal(0, await input.ReadBlockAsync(chars, 0, chars.Length));
167+
Assert.Equal(0, await input.ReadBlockAsync(chars.AsMemory(), default));
168+
Assert.Null(await input.ReadLineAsync());
169+
Assert.Null(await input.ReadLineAsync(default));
170+
Assert.Equal("", await input.ReadToEndAsync());
171+
Assert.Equal("", await input.ReadToEndAsync(default));
172+
input.Dispose();
173+
}
174+
175+
[Theory]
176+
[MemberData(nameof(NullReaders))]
177+
public static async Task TestCanceledNullTextReaderAsync(TextReader input)
178+
{
179+
using CancellationTokenSource tokenSource = new CancellationTokenSource();
180+
tokenSource.Cancel();
181+
var token = tokenSource.Token;
182+
var chars = new char[2];
183+
OperationCanceledException ex;
184+
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadAsync(chars.AsMemory(), token));
185+
Assert.Equal(token, ex.CancellationToken);
186+
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadBlockAsync(chars.AsMemory(), token));
187+
Assert.Equal(token, ex.CancellationToken);
188+
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadLineAsync(token));
189+
Assert.Equal(token, ex.CancellationToken);
190+
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadToEndAsync(token));
191+
Assert.Equal(token, ex.CancellationToken);
144192
input.Dispose();
145193
}
146194

src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,7 +1386,7 @@ private void ThrowIfDisposed()
13861386

13871387
// No data, class doesn't need to be serializable.
13881388
// Note this class is threadsafe.
1389-
private sealed class NullStreamReader : StreamReader
1389+
internal sealed class NullStreamReader : StreamReader
13901390
{
13911391
public override Encoding CurrentEncoding => Encoding.Unicode;
13921392

@@ -1395,35 +1395,46 @@ protected override void Dispose(bool disposing)
13951395
// Do nothing - this is essentially unclosable.
13961396
}
13971397

1398-
public override int Peek()
1399-
{
1400-
return -1;
1401-
}
1398+
public override int Peek() => -1;
14021399

1403-
public override int Read()
1404-
{
1405-
return -1;
1406-
}
1400+
public override int Read() => -1;
14071401

1408-
public override int Read(char[] buffer, int index, int count)
1409-
{
1410-
return 0;
1411-
}
1402+
public override int Read(char[] buffer, int index, int count) => 0;
14121403

1413-
public override string? ReadLine()
1414-
{
1415-
return null;
1416-
}
1404+
public override int Read(Span<char> buffer) => 0;
14171405

1418-
public override string ReadToEnd()
1419-
{
1420-
return string.Empty;
1421-
}
1406+
public override Task<int> ReadAsync(char[] buffer, int index, int count) => Task.FromResult(0);
14221407

1423-
internal override int ReadBuffer()
1424-
{
1425-
return 0;
1426-
}
1408+
public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken) =>
1409+
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) : default;
1410+
1411+
public override int ReadBlock(char[] buffer, int index, int count) => 0;
1412+
1413+
public override int ReadBlock(Span<char> buffer) => 0;
1414+
1415+
public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(0);
1416+
1417+
public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken) =>
1418+
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) : default;
1419+
1420+
public override string? ReadLine() => null;
1421+
1422+
public override Task<string?> ReadLineAsync() => Task.FromResult<string?>(null);
1423+
1424+
public override ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken) =>
1425+
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<string?>(cancellationToken) : default;
1426+
1427+
public override string ReadToEnd() => "";
1428+
1429+
public override Task<string> ReadToEndAsync() => Task.FromResult("");
1430+
1431+
public override Task<string> ReadToEndAsync(CancellationToken cancellationToken) =>
1432+
cancellationToken.IsCancellationRequested ? Task.FromCanceled<string>(cancellationToken) : Task.FromResult("");
1433+
1434+
internal override ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken) =>
1435+
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) : default;
1436+
1437+
internal override int ReadBuffer() => 0;
14271438
}
14281439
}
14291440
}

src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ namespace System.IO
1919
// There are methods on the Stream class for reading bytes.
2020
public abstract partial class TextReader : MarshalByRefObject, IDisposable
2121
{
22-
public static readonly TextReader Null = new NullTextReader();
22+
// Create our own instance to avoid static field initialization order problems on Mono.
23+
public static readonly TextReader Null = new StreamReader.NullStreamReader();
2324

2425
protected TextReader() { }
2526

@@ -338,21 +339,6 @@ internal async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, Cancel
338339
}
339340
#endregion
340341

341-
private sealed class NullTextReader : TextReader
342-
{
343-
public NullTextReader() { }
344-
345-
public override int Read(char[] buffer, int index, int count)
346-
{
347-
return 0;
348-
}
349-
350-
public override string? ReadLine()
351-
{
352-
return null;
353-
}
354-
}
355-
356342
public static TextReader Synchronized(TextReader reader)
357343
{
358344
if (reader == null)

0 commit comments

Comments
 (0)