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 Standard Deviation

ElementAt

3,403.4 ns

28.52 ns

26.67 ns

Index

478.0 ns

6.93 ns

6.48 ns

First

6,160.0 ns

57.66 ns

53.93 ns

First_Index

485.7 ns

5.81 ns

5.15 ns

Last

6,034.3 ns

20.34 ns

16.98 ns

Last_Index

408.3 ns

2.54 ns

2.38 ns

Glossary

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 int ElementAt()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.ElementAt(i);
    }

    return result;
}

[Benchmark]
public int Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[i];
    }

    return result;
}

[Benchmark]
public int First()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.First();
    }

    return result;
}

[Benchmark]
public int First_Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[0];
    }

    return result;
}

[Benchmark]
public int Last()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.Last();
    }

    return result;
}

[Benchmark]
public int Last_Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[data.Count - 1];
    }

    return result;
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.4412/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=8.0.301
  [Host]   : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2