Why is this an issue?
Indexes in C# provide direct access to an element at a specific position within an array or collection. When compared to Enumerable
methods, indexing can be more efficient for certain scenarios, such as iterating over a large collection, due to avoiding the overhead of checking the
underlying collection type before accessing it.
This applies to types that implement one of these interfaces:
What is the potential impact?
We measured a significant improvement in execution time. For more details see the Benchmarks
section from the More info
tab.
How to fix it
If the type you are using implements IList
, IList<T>
or IReadonlyList<T>
, it implements
this[int index]
. This means calls to First
, Last
, or ElementAt(index)
can be replaced with
indexing at 0
, Count-1
and index
respectively.
Code examples
Noncompliant code example
int GetAt(List<int> data, int index)
=> data.ElementAt(index);
int GetFirst(List<int> data)
=> data.First();
int GetLast(List<int> data)
=> data.Last();
Compliant solution
int GetAt(List<int> data, int index)
=> data[index];
int GetFirst(List<int> data)
=> data[0];
int GetLast(List<int> data)
=> data[data.Count-1];
Resources
Documentation
Benchmarks
Method |
Runtime |
Mean |
StdDev |
ElementAt |
.NET 7.0 |
15,193.1 ns |
233.47 ns |
Index |
.NET 7.0 |
9,465.6 ns |
148.16 ns |
First |
.NET 7.0 |
7,790.2 ns |
165.70 ns |
First_Index |
.NET 7.0 |
398.5 ns |
5.36 ns |
Last |
.NET 7.0 |
7,398.2 ns |
152.48 ns |
Last_Index |
.NET 7.0 |
347.3 ns |
5.47 ns |
ElementAt |
.NET Framework 4.6.2 |
12,205.7 ns |
298.49 ns |
Index |
.NET Framework 4.6.2 |
8,917.8 ns |
51.55 ns |
First |
.NET Framework 4.6.2 |
5,109.1 ns |
100.13 ns |
First_Index |
.NET Framework 4.6.2 |
566.0 ns |
6.56 ns |
Last |
.NET Framework 4.6.2 |
5,052.7 ns |
76.02 ns |
Last_Index |
.NET Framework 4.6.2 |
680.7 ns |
9.56 ns |
The results were generated by running the following snippet with BenchmarkDotNet:
private List<byte> data;
private Random random;
[Params(1_000_000)]
public int SampleSize;
[Params(1_000)]
public int LoopSize;
[GlobalSetup]
public void Setup()
{
random = new Random(42);
var bytes = new byte[SampleSize];
random.NextBytes(bytes);
data = bytes.ToList();
}
[Benchmark]
public void ElementAt()
{
for (int i = 0; i < LoopSize; i++)
{
var index = random.Next(0, SampleSize);
_ = data.ElementAt(index);
}
}
[Benchmark]
public void Index()
{
for (int i = 0; i < LoopSize; i++)
{
var index = random.Next(0, SampleSize);
_ = data[index];
}
}
[Benchmark]
public void First()
{
for (int i = 0; i < LoopSize; i++)
{
_ = data.First();
}
}
[Benchmark]
public void First_Index()
{
for (int i = 0; i < LoopSize; i++)
{
_ = data[0];
}
}
[Benchmark]
public void Last()
{
for (int i = 0; i < LoopSize; i++)
{
_ = data.Last();
}
}
[Benchmark]
public void Last_Index()
{
for (int i = 0; i < LoopSize; i++)
{
_ = data[data.Count - 1];
}
}
Hardware configuration:
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
[Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
.NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256