IEnumerable vs IEnumerator in C#: One is 2x Faster - LinkedIn Edition

2/17/2025
5 minute read

During my recent browsing on LinkedIn I saw that question: IEnumerable vs IEnumerator in C#: One is 2x Faster – Which One?

Naturally, I was very suspicious. So let's find out what is going on here.

The code

Let's start with the code in question:

public class EnumerableVsEnuemrator
{
    private List<decimal> _productPrices = [19.99m, 34.50m, 50m, 12.75m, 99.99m, 14.30m, 5.99m, 20.15m];

    [Benchmark]
    public void TestIEnumerable()
    {
        IEnumerable<decimal> priceCollection = _productPrices;
        foreach (var price in priceCollection)
        {
            Debug.WriteLine(price);
        }
    }
    
    [Benchmark]
    public void TestIEnumerator()
    {
        IEnumerator<decimal> priceEnumerator = _productPrices.GetEnumerator();
        while (priceEnumerator.MoveNext())
        {
            Debug.WriteLine(priceEnumerator.Current);
        }
    }
    
    public static void Main() => BenchmarkRunner.Run<EnumerableVsEnuemrator>();
}

So the same list will be taken for the enumeration, but once with IEnumerable and once with IEnumerator. So what is the difference between those two?

IEnumerable vs IEnumerator

In very simple terms: IEnumerable tells you that something is enumerable. Like, you have a basket of apples. But how to go from one apple to the next? That's where IEnumerator comes in. The enumerator describes how to go from one apple to the next (or when you are done).

Spoiler-alert: IEnumerable is defined as (simplified):

public interface IEnumerable<out T>
{
  IEnumerator<T> GetEnumerator();
}

So, IEnumerable is just a wrapper around IEnumerator. So, why then the 2x times difference in speed?

The problem with the benchmark

The whole code has three major problems:

  • Problem 1: The Debug.WriteLine is a poor choice of "Act". Mainly because it is backed by the ConditionalAttribute.
  • Problem 2: The sample size of 8 elements is way too low. Like way way way too low.
  • Problem 3: No return value. The act (the Debug.WriteLine) is not returning anything. So the compiler can optimize the whole loop away.

So all in all, those two pieces of code are not comparable. And by the way, on my machine, the results look like this:

| Method          | Mean     | Error    | StdDev   |
|---------------- |---------:|---------:|---------:|
| TestIEnumerable | 16.90 ns | 0.044 ns | 0.034 ns |
| TestIEnumerator | 11.15 ns | 0.095 ns | 0.084 ns |

It is always important to measure for your specific problem (and that also includes platform and so on). Anyway, let's go through the problems one by one.

Problem 1: Debug.WriteLine

If we have a look at the Debug.WriteLine method, we see that it is backed by the ConditionalAttribute.

public static class Debug
{
    [Conditional("DEBUG")]
    public static void WriteLine(string message, params object?[] args) { ... }
}

The important bid here is two-folded: If you use the ConditionalAttribute on a method with "DEBUG", the compiler will remove the content of your method if the DEBUG symbol is not defined. So, in a release build, the whole Debug.WriteLine body will be removed. Therefore you have an empty method. Even if that wouldn't be the case, Debug.WriteLine is a method with side-effects (like writing to the console). This is not a good choice for a benchmark as it has a big influence on the results.

Problem 2: Sample size

The sample size is way too low. Only having a few elements will not be sufficient to filter out noise! To make that somewhat useful, take at least 10000 elements.

Problem 3: No return value

The act (the Debug.WriteLine) is not returning anything and paired with Problem 1 the compiler can optimize the whole loop away. So, the whole benchmark is not doing anything and therefore those two results are not comparable!

Lowered Code

And yes, Problem 3 is the realitiy here:

public void TestIEnumerable()
{
    IEnumerator<decimal> enumerator = ((IEnumerable<decimal>)_productPrices).GetEnumerator();
    try
    {
        while (enumerator.MoveNext())
        {
            decimal current = enumerator.Current;
        }
    }
    finally
    {
        if (enumerator != null)
        {
            enumerator.Dispose();
        }
    }
}

public void TestIEnumerator()
{
    IEnumerator<decimal> enumerator = _productPrices.GetEnumerator();
    while (enumerator.MoveNext())
    {
    }
}

Source on Sharlab.io

The whole body is getting removed! So, the whole benchmark is not doing anything and therefore those two results are not comparable!

Let's fix it

Let's address all the issues and make a proper benchmark out of it.

public class EnumerableVsEnuemrator
{
    private List<decimal> _productPrices = Enumerable.Range(0, 10_000)
        .Select(i => (decimal)i)
        .ToList();

    [Benchmark]
    public decimal TestIEnumerable()
    {
        IEnumerable<decimal> priceCollection = _productPrices;
        var sum = 0m;
        foreach (var price in priceCollection)
        {
            sum += price;
        }
        
        return sum;
    }
    
    [Benchmark]
    public decimal TestIEnumerator()
    {
        IEnumerator<decimal> priceEnumerator = _productPrices.GetEnumerator();
        var sum = 0m;
        while (priceEnumerator.MoveNext())
        {
            sum += priceEnumerator.Current;
        }
        
        return sum;
    }
    
    public static void Main() => BenchmarkRunner.Run<EnumerableVsEnuemrator>();
}

With the following result:

| Method          | Mean     | Error    | StdDev   |
|---------------- |---------:|---------:|---------:|
| TestIEnumerable | 58.12 us | 0.647 us | 0.605 us |
| TestIEnumerator | 51.97 us | 0.308 us | 0.257 us |

Not so flashy after all.It is a bit slower due to the overhead of the foreach loop (and virtual method calls). Therefore: Always measure for your specific problem and don't trust everything blindly you read on the internet.

IEnumerable vs IQueryable - What's the difference

.NET brings two types which seem very similiar

  • IEnumerable
  • IQueryable

What is the difference? Most are familiar with using IQueryable when we want to go to the database and back. But why not using IEnumerable?

How not to benchmark!

I came across a recent LinkedIn post about the let statement in LINQ and it's performance implication. And in typically influencer fashion it out right claimed that using let in LINQ is a bad idea and should be avoided. But is it a bad idea?

"Always use early returns" - LinkedIn Edition

Ahhhh dear LinkedIn - a pool of gems where everyone is expert in everything. Over the time I collected some trophies from there and today I want to discuss one of them: "Always use early returns".

An error has occurred. This application may no longer respond until reloaded. Reload x