Replies: 3 comments 1 reply
-
There is a little problem here: How would ReadLineAsSpan() be able to signal end of the string being reached vs. reading an empty line? This is not correct: As a span type (ref struct) can't be wrapped in Nullable<T> nor be used in a value tuple, you would either need to introduce a boolean out parameter (or something similar) or use |
Beta Was this translation helpful? Give feedback.
-
Yeas I understand the issue with nullable, and I thought exactly the same thing and had the same concern about design rules. So I checked the first few results on Bing 😉 to see how people use
while(true)
{
aLine = strReader.ReadLine();
if(aLine != null)
{
// ...
}
// ...
}
while ((line = reader.ReadLine()) != null)
{
// ...
}
while (reader.Peek() != -1)
{
var line = reader.ReadLine()
// ...
} To be honest, I don’t like any of these patterns (of course, this is just a style preference):
I suggest providing a better API by creating a method (I’m not sure if public bool IsEndOfString()
{
if (_s == null)
{
ThrowObjectDisposedException_ReaderClosed();
}
return _pos >= _s.Length;
} You can then use while (!reader.IsEndOfString())
{
var line = reader.ReadLineAsSpan()
// ...
} When the end of the string is reached, it would work in the same way as ReadLine. ReadLine will return null indefinitely, just like ReadLineAsSpan would indefinitely return an empty span. Here are the proposed minimal changes: public override string? ReadLine()
{
if (_s == null)
{
ThrowObjectDisposedException_ReaderClosed();
}
if ((uint)_pos >= (uint)s.Length)
{
return null;
}
return ReadLineAsSpan().ToString();
}
public bool IsEndOfString()
{
return _pos >= _s.Length;
}
public ReadOnlySpan<char> ReadLineAsSpan()
{
string? s = _s;
if (s == null)
{
ThrowObjectDisposedException_ReaderClosed();
}
int pos = _pos;
if ((uint)pos >= (uint)s.Length)
{
return ReadOnlySpan<char>.Empty; //CHANGED
}
ReadOnlySpan<char> remaining = s.AsSpan(pos);
int foundLineLength = remaining.IndexOfAny('\r', '\n');
if (foundLineLength >= 0)
{
ReadOnlySpan<char> result = remaining.Slice(0, foundLineLength);//CHANGED
char ch = remaining[foundLineLength];
pos += foundLineLength + 1;
if (ch == '\r')
{
if ((uint)pos < (uint)s.Length && s[pos] == '\n')
{
pos++;
}
}
_pos = pos;
return result;
}
else
{
//string result = s.Substring(pos); REMOVED
_pos = s.Length;
return remaining;//CHANGED
}
} |
Beta Was this translation helpful? Give feedback.
-
I've run a benchmark for code above and I got results as below. ReadLineAsSpan would result in almost no allocation and 30% speed improvement. [Benchmark]
public void ReaderReadLine()
{
var reader = new StringReader(content);
while (true)
{
var line = reader.ReadLine();
if (line == null)
{
break;
}
}
}
[Benchmark]
public void ReaderReadLineAsSpan()
{
var reader = new StringReaderNew(content);
while (!reader.IsEndOfString())
{
var line = reader.ReadLineAsSpan();
}
} BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4169/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-1280P, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.402
[Host] : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
Job-YXRZRJ : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
IterationCount=50 LaunchCount=10 RunStrategy=ColdStart
| Method | Mean | Error | StdDev | Median | Allocated |
|--------------------- |---------:|---------:|---------:|----------:|----------:|
| ReaderReadLine | 508.3 us | 59.14 us | 399.5 us | 442.50 us | 3120440 B |
| ReaderReadLineAsSpan | 336.2 us | 35.34 us | 238.7 us | 296.05 us | 432 B | |
Beta Was this translation helpful? Give feedback.
-
I am considering the addition of a ReadLineAsSpan method to the StringReader class. The current
ReadLine
method returns a string, likely for backward compatibility reasons. However, this approach seems to undermine the benefits of using spans within theStringReader
class.When reading a string line by line, the current implementation reallocates the entire string in chunks, which could be avoided by returning a ReadOnlySpan instead.
With minimal modifications, the existing method
public override string? ReadLine()
would becomepublic ReadOnlySpan<char> ReadLineAsSpan()
and newReadLine()
would be createdAnd previous
public override string? ReadLine()
becomesThis approach maintains backward compatibility while leveraging the performance benefits of spans.
Does this proposal make any sense? Am I missing something?
Beta Was this translation helpful? Give feedback.
All reactions