This package enables the usage of System.Range
in foreach
expressions and provides extensions to integrate it with LINQ as a faster replacement to Enumerable.Range
.
- Correctness is verified against
IEnumerable<int>
andEnumerable.Range
behavior; - Implementation tries its best to make abstractions either zero-cost or reasonably close to that. For critical paths, performance is tuned to be allocation-free and on par with regular
for
loops
foreach (var i in ..100) // you can write 0..Length as just ..Length
{
Console.WriteLine(i);
}
for (var i = 100 - 1; i >= 0; i--)
{
Console.WriteLine(i);
}
// Can be written as
foreach (var i in 100..0)
{
Console.WriteLine(i);
}
var floats = (0..100).Select(i => (float)i);
var odd = (0..100).Where(i => i % 2 != 0);
var randomNumbers = (0..1000)
.Select(_ => Random.Shared.Next())
.ToArray();
var numbers = (0..100).ToArray();
var digits = (0..10)
.Aggregate(new StringBuilder(), (sb, i) => sb.Append(i))
.ToString();
Assert.Equal("0123456789", digits);
var enumerable = (..100).AsEnumerable();
var sum = enumerable.Sum();
var count = enumerable.Count;
var average = enumerable.Average();
var firstTen = enumerable.Take(10);
var reversed = enumerable.Reverse();
// and others
In .NET 7, foreach (var i in 0..Length)
has the same performance as for
loop. Otherwise, RangeExtensions
is 2 to >10 times faster than Enumerable.Range
. Using DynamicPGO significantly improves the performance of both.
BenchmarkDotNet=v0.13.2, OS=macOS 13.1 (22C65) [Darwin 22.2.0]
Apple M1 Pro, 1 CPU, 8 logical and 8 physical cores
.NET SDK=8.0.100-alpha.1.22620.11
[Host] : .NET 7.0.1 (7.0.122.56804), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 7.0.1 (7.0.122.56804), Arm64 RyuJIT AdvSIMD
Job=ShortRun IterationCount=3 LaunchCount=1
WarmupCount=3
DOTNET_TieredPGO=1
DOTNET_ReadyToRun=0
Method | Length | Mean | Error | Ratio | Allocated |
---|---|---|---|---|---|
For | 1 | 0.0000 ns | 0.0000 ns | ? | - |
Range | 1 | 0.6531 ns | 0.0051 ns | ? | - |
EnumerableRange | 1 | 8.1135 ns | 0.0198 ns | ? | 40 B |
RangeSelect | 1 | 1.1588 ns | 0.0048 ns | ? | - |
EnumerableSelect | 1 | 40.7948 ns | 0.7697 ns | ? | 88 B |
RangeSelectTwice | 1 | 19.4165 ns | 0.0480 ns | ? | 96 B |
EnumerableSelectTwice | 1 | 43.6399 ns | 0.0908 ns | ? | 232 B |
RangeWhere | 1 | 1.3954 ns | 0.0036 ns | ? | - |
EnumerableWhere | 1 | 26.1945 ns | 0.0534 ns | ? | 96 B |
For | 100 | 36.1897 ns | 0.0618 ns | 1.00 | - |
Range | 100 | 36.9244 ns | 2.0789 ns | 1.00 | - |
EnumerableRange | 100 | 211.4774 ns | 0.6430 ns | 5.85 | 40 B |
RangeSelect | 100 | 42.6852 ns | 0.1689 ns | 1.18 | - |
EnumerableSelect | 100 | 235.7174 ns | 0.5161 ns | 6.51 | 88 B |
RangeSelectTwice | 100 | 110.1340 ns | 0.1667 ns | 3.04 | 96 B |
EnumerableSelectTwice | 100 | 298.2976 ns | 2.7831 ns | 8.22 | 232 B |
RangeWhere | 100 | 56.1455 ns | 0.1701 ns | 1.55 | - |
EnumerableWhere | 100 | 249.1264 ns | 1.5890 ns | 6.89 | 96 B |
For | 100000 | 31,167.5682 ns | 37.3266 ns | 1.00 | - |
Range | 100000 | 31,173.8688 ns | 13.0666 ns | 1.00 | - |
EnumerableRange | 100000 | 212,925.2827 ns | 156.8182 ns | 6.83 | 40 B |
RangeSelect | 100000 | 50,086.3342 ns | 39.0657 ns | 1.61 | - |
EnumerableSelect | 100000 | 204,113.5813 ns | 100.0221 ns | 6.55 | 88 B |
RangeSelectTwice | 100000 | 94,302.1444 ns | 230.6254 ns | 3.02 | 96 B |
EnumerableSelectTwice | 100000 | 203,946.9247 ns | 908.5243 ns | 6.56 | 232 B |
RangeWhere | 100000 | 47,165.0569 ns | 36.7208 ns | 1.51 | - |
EnumerableWhere | 100000 | 209,918.1519 ns | 3,298.4418 ns | 6.76 | 96 B |
More details on performance: Releases tab contains notes on improvements introduced with each version.