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 instanceIEnum0 calls GetEnumerator() which returns IEnum1 instanceIEnum1 calls GetEnumerator() which returns IEnum2 instanceIEnum2 calls GetEnumerator() which returns IEnum3 instance2. 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.
LINQ queries are always executed when the query variable is iterated over, not when the query variable is created. This is called deferred execution. You can also force a query to execute immediately, which is useful for caching query results.
Materializing LINQ Queries Once When writing LINQ queries using IEnumerable or IQueryable interfaces, developers can materialize (call ToList , ToArray or similar methods) or not to materialize queries immediately (Lazy loading). The lack of materialization allows developers to work with collections lazily.
Deferred execution means that the evaluation of an expression is delayed until its realized value is actually required. It greatly improves performance by avoiding unnecessary execution.
LINQ uses a Deferred Execution model which means that resulting sequence is not returned at the time the Linq operators are called, but instead these operators return an object which then yields elements of a sequence only when we enumerate this object.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With