Search code examples
c#linqienumerable

How many times is the IEnumerable ordered in this case?


I know that an IEnumerable<T> does not iterate until it is called.

Suppose that I have this code:

foreach(int iteratorInt in myIEnumerable.OrderBy(x => x))
{
    if(iteratorInt == myIEnumerable.First())
    {
        // do something
    }
}

In the if I am checking the first element, so does the myIEnumerable have to be ordered at each iteration to see which one is the first element or is it ordered just once?


Solution

  • When using the LINQ extensions, the query will only be executed when requested, otherwise known as deferred execution. When the same query is requested multiple times, the underlying query is re-evaluated each time, unless the initial query has been materialized with something like .ToArrary() or .ToList().

    The question isn't entirely clear, so I'll provide a few examples demonstrating various behavior.

    Ex 1:

    • Setting the initial request to a local variable.
    • Apply a LINQ query within the foreach to Order the collection.
    • Use the initial local variable to find the first result.
    • Do not materialize any of the results.

    Code:

    private static void Ex1()
    {
        Console.WriteLine("A");
    
        IEnumerable<int> myIEnumerable = GetEnumerable();
    
        Console.WriteLine("B");
    
        foreach (int i in myIEnumerable.OrderBy(x => x))
        {
            Console.WriteLine("*** foreach : " + i);
            if (i == myIEnumerable.First())
            {
                Console.WriteLine("=== Matched .First() : " + i);
            }
        }
    
        Console.WriteLine("C");
    }
    

    Ex 2:

    • Setting the initial request to a local variable.
    • Apply a LINQ query outside the foreach to Order the collection without materializing the result.
    • Use the ordered query to find the first result
    • Do not materialize any of the results.

    Code:

    private static void Ex2()
    {
        Console.WriteLine("A");
    
        IEnumerable<int> myIEnumerable = GetEnumerable();
    
        Console.WriteLine("B");
    
        var ordered = myIEnumerable.OrderBy(x => x);
    
        foreach (int i in ordered)
        {
            Console.WriteLine("*** foreach : " + i);
            if (i == ordered.First())
            {
                Console.WriteLine("=== Matched .First() : " + i);
            }
        }
    
        Console.WriteLine("C");
    }
    

    Ex 3:

    • Setting the initial request to a local variable.
    • Apply a LINQ query outside the foreach to Order the collection and materialize the result.
    • Use the ordered query to find the first result.

    Code:

    private static void Ex3()
    {
        Console.WriteLine("A");
    
        IEnumerable<int> myIEnumerable = GetEnumerable();
    
        Console.WriteLine("B");
    
        var ordered = myIEnumerable.OrderBy(x => x).ToArray();
    
        foreach (int i in ordered)
        {
            Console.WriteLine("*** foreach : " + i);
            if (i == ordered.First())
            {
                Console.WriteLine("=== Matched .First() : " + i);
            }
        }
    
        Console.WriteLine("C");
    }
    

    All queries use the same method to get the enumerable:

    private static IEnumerable<int> GetEnumerable()
    {
        Console.WriteLine("~~~ GetEnumerable Start");
        foreach (int i in new[]{3, 2, 1})
        {
            Console.WriteLine(">>> yield return : " + i);
            yield return i;
        }
    
        Console.WriteLine("~~~ GetEnumerable End");
    }
    

    The results will end up as:

    ====================
    Ex A
    ====================
    A
    B
    ~~~ GetEnumerable Start
    >>> yield return : 3
    >>> yield return : 2
    >>> yield return : 1
    ~~~ GetEnumerable End
    *** foreach : 1
    ~~~ GetEnumerable Start
    >>> yield return : 3
    *** foreach : 2
    ~~~ GetEnumerable Start
    >>> yield return : 3
    *** foreach : 3
    ~~~ GetEnumerable Start
    >>> yield return : 3
    === Matched .First() : 3
    C
    
    ====================
    Ex B
    ====================
    
    A
    B
    ~~~ GetEnumerable Start
    >>> yield return : 3
    >>> yield return : 2
    >>> yield return : 1
    ~~~ GetEnumerable End
    *** foreach : 1
    ~~~ GetEnumerable Start
    >>> yield return : 3
    >>> yield return : 2
    >>> yield return : 1
    ~~~ GetEnumerable End
    === Matched .First() : 1
    *** foreach : 2
    ~~~ GetEnumerable Start
    >>> yield return : 3
    >>> yield return : 2
    >>> yield return : 1
    ~~~ GetEnumerable End
    *** foreach : 3
    ~~~ GetEnumerable Start
    >>> yield return : 3
    >>> yield return : 2
    >>> yield return : 1
    ~~~ GetEnumerable End
    C
    
    ====================
    Ex C
    ====================
    
    A
    B
    ~~~ GetEnumerable Start
    >>> yield return : 3
    >>> yield return : 2
    >>> yield return : 1
    ~~~ GetEnumerable End
    *** foreach : 1
    === Matched .First() : 1
    *** foreach : 2
    *** foreach : 3
    C