Skip to content

Override all Read methods in NullTextReader and NullStreamReader #64301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 27, 2022
Merged
64 changes: 56 additions & 8 deletions src/libraries/System.IO/tests/Stream/Stream.NullTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ 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)
Expand All @@ -131,16 +141,54 @@ public static void TestNullTextReader(TextReader input)

if (sr != null)
Assert.True(sr.EndOfStream, "EndOfStream property didn't return true");
input.ReadLine();
input.Dispose();

input.ReadLine();
Assert.Null(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 CancellationTokenSource tokenSource = new CancellationTokenSource();
tokenSource.Cancel();
var token = tokenSource.Token;
var chars = new char[2];
OperationCanceledException ex;
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadAsync(chars.AsMemory(), token));
Assert.Equal(token, ex.CancellationToken);
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadBlockAsync(chars.AsMemory(), token));
Assert.Equal(token, ex.CancellationToken);
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadLineAsync(token));
Assert.Equal(token, ex.CancellationToken);
ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await input.ReadToEndAsync(token));
Assert.Equal(token, ex.CancellationToken);
input.Dispose();
}

Expand Down
61 changes: 36 additions & 25 deletions src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -1395,35 +1395,46 @@ 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<char> buffer) => 0;

public override string ReadToEnd()
{
return string.Empty;
}
public override Task<int> ReadAsync(char[] buffer, int index, int count) => Task.FromResult(0);

internal override int ReadBuffer()
{
return 0;
}
public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) : default;

public override int ReadBlock(char[] buffer, int index, int count) => 0;

public override int ReadBlock(Span<char> buffer) => 0;

public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(0);

public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) : default;

public override string? ReadLine() => null;

public override Task<string?> ReadLineAsync() => Task.FromResult<string?>(null);

public override ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<string?>(cancellationToken) : default;

public override string ReadToEnd() => "";

public override Task<string> ReadToEndAsync() => Task.FromResult("");

public override Task<string> ReadToEndAsync(CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? Task.FromCanceled<string>(cancellationToken) : Task.FromResult("");

internal override ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>(cancellationToken) : default;

internal override int ReadBuffer() => 0;
}
}
}
18 changes: 2 additions & 16 deletions src/libraries/System.Private.CoreLib/src/System/IO/TextReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = new NullTextReader();
// Create our own instance to avoid static field initialization order problems on Mono.
public static readonly TextReader Null = new StreamReader.NullStreamReader();

protected TextReader() { }

Expand Down Expand Up @@ -338,21 +339,6 @@ internal async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, Cancel
}
#endregion

private sealed class NullTextReader : TextReader
{
public NullTextReader() { }

public override int Read(char[] buffer, int index, int count)
{
return 0;
}

public override string? ReadLine()
{
return null;
}
}

public static TextReader Synchronized(TextReader reader)
{
if (reader == null)
Expand Down