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

Source: https://github.com/NetFabric/NetFabric.Hyperlinq.Analyzer/blob/master/NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ013_UseForEachLoop.cs


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


MethodJobRuntimeCountMeanErrorStdDevMedianRatioRatioSDCode SizeAllocatedAlloc Ratio
For.NET 6.NET 6.0104.811 ns0.1636 ns0.4693 ns4.649 nsbaseline****67 B-NA
ForEach.NET 6.NET 6.0103.828 ns0.1716 ns0.4923 ns3.726 ns1.28x faster0.20xNA-NA
ForEach_Span.NET 6.NET 6.0104.460 ns0.1720 ns0.4936 ns4.311 ns1.09x faster0.15xNA-NA
For.NET 7.NET 7.0104.728 ns0.1921 ns0.5634 ns4.487 nsbaseline67 B-NA
ForEach.NET 7.NET 7.0103.322 ns0.1566 ns0.4468 ns3.211 ns1.43x faster0.23xNA-NA
ForEach_Span.NET 7.NET 7.0103.728 ns0.2446 ns0.6979 ns3.438 ns1.29x faster0.25xNA-NA
For.NET 8.NET 8.0104.612 ns0.1770 ns0.4905 ns4.450 nsbaseline67 B-NA
ForEach.NET 8.NET 8.0104.065 ns0.3462 ns0.9932 ns3.608 ns1.19x faster0.25xNA-NA
ForEach_Span.NET 8.NET 8.0104.347 ns0.1496 ns0.4268 ns4.145 ns1.07x faster0.14xNA-NA
For.NET 6.NET 6.01000623.195 ns11.9510 ns9.9796 ns623.367 nsbaseline****67 B-NA
ForEach.NET 6.NET 6.01000452.951 ns9.0306 ns25.1737 ns449.691 ns1.36x faster0.07xNA-NA
ForEach_Span.NET 6.NET 6.01000437.701 ns7.5122 ns13.7366 ns433.143 ns1.42x faster0.04xNA-NA
For.NET 7.NET 7.01000488.039 ns4.7154 ns4.1801 ns487.612 nsbaseline67 B-NA
ForEach.NET 7.NET 7.01000392.328 ns5.3285 ns4.7236 ns391.128 ns1.24x faster0.02xNA-NA
ForEach_Span.NET 7.NET 7.01000405.471 ns7.7527 ns12.0700 ns399.877 ns1.19x faster0.05xNA-NA
For.NET 8.NET 8.01000492.753 ns8.9344 ns10.6358 ns489.309 nsbaseline67 B-NA
ForEach.NET 8.NET 8.01000414.904 ns9.6482 ns27.8373 ns402.107 ns1.20x faster0.07xNA-NA
ForEach_Span.NET 8.NET 8.01000414.874 ns9.7770 ns28.2089 ns406.918 ns1.19x faster0.09xNA-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);
}