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

Function GetAt(data As List(Of Integer), index As Integer) As Integer
    Return data.ElementAt(index)
End Function
Function GetFirst(data As List(Of Integer)) As Integer
    Return data.First()
End Function
Function GetLast(data As List(Of Integer)) As Integer
    Return data.Last()
End Function

Compliant solution

Function GetAt(data As List(Of Integer), index As Integer) As Integer
    Return data(index)
End Function
Function GetFirst(data As List(Of Integer)) As Integer
    Return data(0)
End Function
Function GetLast(data As List(Of Integer)) As Integer
    Return data(data.Count-1)
End Function

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