Linq: Order of execution chain query
Asked Answered
P

1

8

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.

Ptolemaic answered 9/8, 2018 at 10:10 Comment(4)
The processes are completed within every block of chain. Where(x => x > 0) filters numbers and leaves out {4,9} for next block and it goes on like that.Sheatfish
You could use the 'debug framework code' feature of Visual Studio and see it live while debugging. Alternatively, go to reference sources, copy the methods to your solution and debug it.Hamnet
Mostly correct, but I never think about chain query like that because LINQ is invented to preventing us thinking data pipeline as loop/iterator.Hay
@CetinBasoz I don't understand your comment, how processing within every block will work? This seems incorrect to me.Ptolemaic
P
11

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
Perspicuous answered 9/8, 2018 at 10:33 Comment(1)
Damn, I never thought LINQ queries chain like this, good idea to use the delegates.Provisional

© 2022 - 2024 — McMap. All rights reserved.