Skip to content
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