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