Search code examples
c#linqienumerableienumerator

Linq: Order of execution chain query


I want to understand how chain query is processed. For example, let us consider the following query

var sumOfRoots = numbers           //IEnum0
     .Where(x => x > 0)            //IEnum1
     .Select(x => Math.Sqrt(x))    //IEnum2
     .Select(x => Math.Exp(x))     //IEnum3
     .Sum();

where e.g. numbers={-1, 4, 9 }.

Is this what happends behind the scene:

1. Getting all enumerators (forward pass)

  • numbers calls GetEnumerator() which returns (let us denote it with) IEnum0 instance
  • IEnum0 calls GetEnumerator() which returns IEnum1 instance
  • IEnum1 calls GetEnumerator() which returns IEnum2 instance
  • IEnum2 calls GetEnumerator() which returns IEnum3 instance

2. Calling MoveNext (backward pass)

  • .Sum() calls MoveNext() on IEnum3
  • IEnum3 calls MoveNext() on IEnum2
  • IEnum2 calls MoveNext() on IEnum1
  • IEnum1 calls MoveNext() on IEnum0

3. Returning from MoveNext (forward-backward pass)

  • IEnum0 moves to element -1 and return true.
  • IEnum1 check if -1 satisfy condition (which is not true) so IEnum1 calls MoveNext() on IEnum0.
  • IEnum0 moves to element 4 and return true.
  • IEnum1 check if 4 satisfy condition (which is true) and returns true
  • IEnum2 does nothing, just return output of IEnum1 which is true
  • IEnum2 does nothing, just return output of IEnum2 which is true

4. Calling Current (backward pass)

  • .Sum() calls Current on IEnum3.
  • IEnum3 calls Current on IEnum2
  • IEnum2 calls Current on IEnum1
  • IEnum1 calls Current on IEnum0

5. Returning Current (forward pass)

  • IEnum0 returns 4
  • IEnum1 returns 4
  • IEnum2 returns sqrt(4)=2
  • IEnum3 returns exp(2)

6. Repeat steps 2.-5. until step 3. returns false

Please correct me if a chain query is processed in a different way.


Solution

  • You can use delegates to understand the order of execution. Example:

    static void Main(string[] args)
    {
        var numbers = new []{ -1, 4, 9 };
    
        double sumOfRoots = numbers.Where(IsGreaterThanZero)   
                                   .Select(ToSquareRoot)      
                                   .Select(ToExp)              
                                   .Sum(x => ToNumber(x));
    
        Console.WriteLine($"sumOfRoots = {sumOfRoots}");
    
        Console.Read();
    }
    
    private static double ToNumber(double number)
    {
        Console.WriteLine($"SumNumber({number})");
    
        return number;
    }
    
    private static double ToSquareRoot(int number)
    {
        double value =  Math.Sqrt(number);
    
        Console.WriteLine($"Math.Sqrt({number}): {value}");
    
        return value;
    }
    
    private static double ToExp(double number)
    {
        double value =  Math.Exp(number);
    
        Console.WriteLine($"Math.Exp({number}): {value}");
    
        return value;
    }
    
    private static bool IsGreaterThanZero(int number)
    {
        bool isGreater = number > 0;
    
        Console.WriteLine($"{number} > 0: {isGreater}");
    
        return isGreater;
    }
    

    Output:

    -1 > 0: False
    
    4 > 0: True
    
    Math.Sqrt(4): 2
    
    Math.Exp(2): 7.38905609893065
    
    SumNumber(7.38905609893065)
    
    9 > 0: True
    
    Math.Sqrt(9): 3
    
    Math.Exp(3): 20.0855369231877
    
    SumNumber(20.0855369231877)
    
    sumOfRoots = 27.4745930221183