HLQ013: Use foreach loop
July 26, 2023 ยท View on GitHub
Cause
for loop is being used to iterate an array or a span.
Severity
Warning
Rule description
Both for and foreach loops can be used to iterate an array or a span. The code generated by the compiler for both is actually very similar.
The difference is that the code generated for the foreach guarantees the following:
- The collection is a local variable.
- The terminal condition is the length of the collection.
These allow the JIT compiler to drop bounds checking making the iteration more performant.
Benchmarks
BenchmarkDotNet v0.13.6, Windows 10 (10.0.19045.3269/22H2/2022Update)
Intel Core i7-7567U CPU 3.50GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.100-preview.5.23303.2
[Host] : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
.NET 6 : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
.NET 7 : .NET 7.0.8 (7.0.823.31807), X64 RyuJIT AVX2
.NET 8 : .NET 8.0.0 (8.0.23.28008), X64 RyuJIT AVX2
| Method | Job | Runtime | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Code Size | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| For | .NET 6 | .NET 6.0 | 10 | 4.811 ns | 0.1636 ns | 0.4693 ns | 4.649 ns | baseline | **** | 67 B | - | NA |
| ForEach | .NET 6 | .NET 6.0 | 10 | 3.828 ns | 0.1716 ns | 0.4923 ns | 3.726 ns | 1.28x faster | 0.20x | NA | - | NA |
| ForEach_Span | .NET 6 | .NET 6.0 | 10 | 4.460 ns | 0.1720 ns | 0.4936 ns | 4.311 ns | 1.09x faster | 0.15x | NA | - | NA |
| For | .NET 7 | .NET 7.0 | 10 | 4.728 ns | 0.1921 ns | 0.5634 ns | 4.487 ns | baseline | 67 B | - | NA | |
| ForEach | .NET 7 | .NET 7.0 | 10 | 3.322 ns | 0.1566 ns | 0.4468 ns | 3.211 ns | 1.43x faster | 0.23x | NA | - | NA |
| ForEach_Span | .NET 7 | .NET 7.0 | 10 | 3.728 ns | 0.2446 ns | 0.6979 ns | 3.438 ns | 1.29x faster | 0.25x | NA | - | NA |
| For | .NET 8 | .NET 8.0 | 10 | 4.612 ns | 0.1770 ns | 0.4905 ns | 4.450 ns | baseline | 67 B | - | NA | |
| ForEach | .NET 8 | .NET 8.0 | 10 | 4.065 ns | 0.3462 ns | 0.9932 ns | 3.608 ns | 1.19x faster | 0.25x | NA | - | NA |
| ForEach_Span | .NET 8 | .NET 8.0 | 10 | 4.347 ns | 0.1496 ns | 0.4268 ns | 4.145 ns | 1.07x faster | 0.14x | NA | - | NA |
| For | .NET 6 | .NET 6.0 | 1000 | 623.195 ns | 11.9510 ns | 9.9796 ns | 623.367 ns | baseline | **** | 67 B | - | NA |
| ForEach | .NET 6 | .NET 6.0 | 1000 | 452.951 ns | 9.0306 ns | 25.1737 ns | 449.691 ns | 1.36x faster | 0.07x | NA | - | NA |
| ForEach_Span | .NET 6 | .NET 6.0 | 1000 | 437.701 ns | 7.5122 ns | 13.7366 ns | 433.143 ns | 1.42x faster | 0.04x | NA | - | NA |
| For | .NET 7 | .NET 7.0 | 1000 | 488.039 ns | 4.7154 ns | 4.1801 ns | 487.612 ns | baseline | 67 B | - | NA | |
| ForEach | .NET 7 | .NET 7.0 | 1000 | 392.328 ns | 5.3285 ns | 4.7236 ns | 391.128 ns | 1.24x faster | 0.02x | NA | - | NA |
| ForEach_Span | .NET 7 | .NET 7.0 | 1000 | 405.471 ns | 7.7527 ns | 12.0700 ns | 399.877 ns | 1.19x faster | 0.05x | NA | - | NA |
| For | .NET 8 | .NET 8.0 | 1000 | 492.753 ns | 8.9344 ns | 10.6358 ns | 489.309 ns | baseline | 67 B | - | NA | |
| ForEach | .NET 8 | .NET 8.0 | 1000 | 414.904 ns | 9.6482 ns | 27.8373 ns | 402.107 ns | 1.20x faster | 0.07x | NA | - | NA |
| ForEach_Span | .NET 8 | .NET 8.0 | 1000 | 414.874 ns | 9.7770 ns | 28.2089 ns | 406.918 ns | 1.19x faster | 0.09x | NA | - | NA |
How to fix violations
Use foreach instead of for when iterating over an array or a span.
For partial iterations, use a range-based foreach loop.
When to suppress warnings
When the indexing is required for some reason, e.g. when the index is used to access another array.
Example of a violation
Full iteration of an array using a for loop:
var source = new[] { 1, 2, 3 };
for (var index = 0; index < source.Length; index++)
{
var item = source[index];
Console.WriteLine(item);
}
Partial iteration of an array using a for loop:
``` csharp
var source = new[] { 1, 2, 3 };
// skiping first and last items
for (var index = 1; index < source.Length - 1; index++)
{
var item = source[index];
Console.WriteLine(item);
}
Example of how to fix
Full iteration of an array using a foreach loop:
var source = new[] { 1, 2, 3 };
foreach (var item in source)
{
Console.WriteLine(item);
}
Partial iteration of an array using a foreach loop:
var source = new[] { 1, 2, 3 };
// skiping first and last items
foreach (var item in source[1..^1])
{
Console.WriteLine(item);
}