AsSpan Instead Of Range
ID |
csharp.asspan_instead_of_range |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Efficiency |
Language |
CSharp |
Tags |
efficiency, performance, string |
Description
Reports range slicing on a string receiver (s[a..b]) when the sliced value is
passed directly as an argument to a method with a ReadOnlySpan<char> overload
(Parse / TryParse of the numeric and date types, StringBuilder.Append). In that
position the range indexer allocates a substring that the callee immediately consumes;
s.AsSpan(a, length) passes a non-allocating window over the same backing storage and
the call compiles unchanged.
Rationale
In hot paths (parsing, log processing, request decoding) substring allocations
dominate garbage-collection pressure. Switching the argument to AsSpan removes the
allocation and the copy entirely. Slices that flow anywhere else — returned,
concatenated, stored in a string variable — are not reported: those sinks require a
string, so AsSpan does not apply there.
public class Slicer
{
public void ParseArguments(string s)
{
int.TryParse(s[..5], out var x); // FLAW — TryParse accepts ReadOnlySpan<char>
var y = int.Parse(s[2..5]); // FLAW
}
public void ParseSpans(string s)
{
int.TryParse(s.AsSpan(0, 5), out var x); // OK — non-allocating
}
public string FirstFive(string s)
{
return s[..5]; // OK — result used as string
}
public char ByIndex(string s)
{
return s[0]; // OK, scalar index
}
}