Bending .NET - ReadLine Patterns
or how to make it rain so puny humans get soaked.
In this post, part of the Bending .NET series, I take a quick look at code patterns
for reading lines from
TextReader
using
ReadLine
,
which has existed from the early beginning of .NET (Framework 1.1 as far as I
can tell) based on a pattern of returning null
when there are no more lines to
read. Let’s make it rain 🌧
TLDR: is
pattern matching syntax can be used to simplify ReadLine
code via
implicit null check to:
1
2
3
4
5
using var reader = new StringReader(text);
while (reader.ReadLine() is string line)
{
Console.WriteLine(line);
}
Source: wikimedia
ReadLine
code examples shown below with both synchronous and asynchronous
versions and a version using var
that results in infinite loop. Be careful of
that 😅 See code in
sharplab.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#nullable enable
using System;
using System.IO;
Action<string?> log = Console.WriteLine;
var text = "1\n2\n3\n";
log("SYNC - classic");
{
using var reader = new StringReader(text);
string? line;
while ((line = reader.ReadLine()) != null) { log(line); }
}
log("SYNC - is string");
{
using var reader = new StringReader(text);
while (reader.ReadLine() is string line) { log(line); }
}
log("SYNC - is {}");
{
using var reader = new StringReader(text);
while (reader.ReadLine() is { } line) { log(line); }
}
log("ASYNC - classic");
{
using var reader = new StringReader(text);
string? line;
while ((line = await reader.ReadLineAsync()) != null) { log(line); }
}
log("ASYNC - is string");
{
using var reader = new StringReader(text);
while (await reader.ReadLineAsync() is string line) { log(line); }
}
log("ASYNC - is {}");
{
using var reader = new StringReader(text);
while (await reader.ReadLineAsync() is { } line) { log(line); }
}
log("DONE");
// Infinite loop version since `is var`
// does not perform implicit null check
var infinite = false;
if (infinite)
{
using var reader = new StringReader(text);
while (reader.ReadLine() is var line) { log(line); }
}
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
SYNC - classic
1
2
3
SYNC - is string
1
2
3
SYNC - is {}
1
2
3
ASYNC - classic
1
2
3
ASYNC - is string
1
2
3
ASYNC - is {}
1
2
3
DONE
If you remove the async/await
code and look at the lowered C# code e.g. via
sharplab.io
then the patterns result in similar code with slight and probably neglible differences.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
Action<string> action = <>O.<0>__WriteLine ??
(<>O.<0>__WriteLine = new Action<string>(Console.WriteLine));
string s = "1\n2\n3\n";
action("SYNC - classic");
StringReader stringReader = new StringReader(s);
try
{
string obj;
while ((obj = stringReader.ReadLine()) != null)
{
action(obj);
}
}
finally
{
if (stringReader != null)
{
((IDisposable)stringReader).Dispose();
}
}
action("SYNC - is string");
StringReader stringReader2 = new StringReader(s);
try
{
while (true)
{
string text = stringReader2.ReadLine();
if (text != null)
{
action(text);
continue;
}
break;
}
}
finally
{
if (stringReader2 != null)
{
((IDisposable)stringReader2).Dispose();
}
}
action("SYNC - is {}");
StringReader stringReader3 = new StringReader(s);
try
{
while (true)
{
string text2 = stringReader3.ReadLine();
if (text2 != null)
{
action(text2);
continue;
}
break;
}
}
finally
{
if (stringReader3 != null)
{
((IDisposable)stringReader3).Dispose();
}
}
action("DONE");
if (0 == 0)
{
return;
}
StringReader stringReader4 = new StringReader(s);
try
{
while (true)
{
string obj2 = stringReader4.ReadLine();
action(obj2);
}
}
finally
{
if (stringReader4 != null)
{
((IDisposable)stringReader4).Dispose();
}
}
That’s all!