Understanding LINQ Deferred, Immediate, Streaming and Non-Streaming Executions
LINQ
26 Articles
In this article, let's learn about how Execution
and Operators
works in
LINQ
in .NET.
Note: If you have not done so already, I recommend you read the article on Using LINQ For Each to iterate Collections.
Table of Contents
- Introduction
- Immediate Execution
- Deferred Execution
- Streaming Operators
- Non-Streaming Operators
- Classification Table
- Summary
Introduction
LINQ to Objects has two ways of running its standard query operator methods: right away or later on. When using the later option, it can either stream the data or not. Knowing which option you're using can help you understand the results you get from your query, especially if the data is changing or if you're building a query on top of another one. This topic organizes the standard query operators by how they work.
LINQ has four Manners of Execution. They are,
Immediate
Deferred
Streaming
Non-Streaming
Understanding LINQ Execution Manners
and Operators
has the following advantages
- Better Performance.
- Less Iterations.
Immediate Execution
Immediate
execution in LINQ to Objects means that the data is read and the operation is done right away. Standard query operators that
return a single / scalar value always work this way. You can make a query execute immediately by using either Enumerable.ToList()
or
Enumerable.ToArray()
methods. Immediate execution helps you save time by reusing the results of your query instead of running it again.
The query results are stored for later use after being retrieved once.
Code Sample - LINQ Immediate Execution
Demo - LINQ Immeduate Execution Demo
Let's try LINQ Immediate Execution with Objects
- We create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
- Then, we use the LINQ extension method Sum to calculate the sum of all the numbers in the array.
- Since Sum is an immediate execution operator, the result is calculated immediately and assigned to the sum variable.
- Click on Immediate Execution Button.
- Click on reset to try other combination.
Deferred Execution
Deferred
execution in LINQ to Objects means that the query doesn't run when it's defined in the code. Instead, it runs only when you
actually use the query results, for example in a foreach statement. This means that the query results can change depending on what's in the data source at the time the query
is used, not when it's written. If you use the query results multiple times, you might get different results each time. Most standard query operators that return
IEnumerable<T>
or IOrderedEnumerable<TElement>
work this way. Deferred execution lets
you reuse queries, and it fetches updated data from the data source each time you iterate through the query results.
Query operators that use deferred execution can also be divided into two types: streaming and non-streaming.
Code Sample - Deferred Execution
Demo - LINQ Deferred Execution Demo
Let's try LINQ Deferred Execution with Objects
- We create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
- Then, we define a LINQ query using the Where operator to select only the even numbers from the numbers array.
- Since Where is a deferred execution operator, the query is not actually executed until we enumerate it
- Next, we change the value of the fourth element in the numbers array to 8. This means that the original source data has changed since we defined the query
- Finally, we enumerate the query results using a foreach loop and print them to the console using Console.WriteLine. Notice that the query results only include the even numbers in the original array (2 and 4), even though we changed one of the source elements to 8. This is because the query execution is deferred until we enumerate the query, and it uses the updated source data at that time
- Click on Deferred Execution Button.
- Click on reset to try other combination.
Query Result:
Streaming Operators
Streaming
operators in LINQ to Objects don't need to read all the source data before they start returning elements. Instead, they work on
each source element one at a time and return the result immediately if applicable. A streaming operator keeps reading source elements until it can produce a result element.
This means that sometimes it has to read more than one source element to make one result element.
Code Sample - LINQ Streaming Operators
Demo - LINQ Streaming Operators Demo
Let's try LINQ Streaming Operators with Objects
- We create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
- Then, we define a LINQ query using the Where and Select operators.
- The Where operator is a deferred execution streaming operator that selects only the even numbers from the numbers array.
- The Select operator is also a deferred execution streaming operator that multiplies each even number by 2.
- Next, we change the value of the fourth element in the numbers array to 8.
- This means that the original source data has changed since we defined the query.
- Finally, we enumerate the query results using a foreach loop and print them to the console using Console.WriteLine.
- Notice that the query results include the even numbers in the original array (2 and 4), but multiplied by 2 (4 and 8), even though we changed one of the source elements to 8. This is because the query execution is deferred until we enumerate the query, and it uses the updated source data at that time. Also, the query execution is streaming, so it doesn't have to read all the source data before yielding elements.
- Click on Streaming Execution Button.
- Click on reset to try other combination.
Query Result:
Non-Streaming Operators
Non-streaming
operators in LINQ to Objects need to read all the source data before they can start returning (yielding) any result elements.
Sorting and grouping are examples of non-streaming operators. When a non-streaming operator runs, it reads all the source data, puts it into a data structure, performs the
operation, and then returns (yields) the resulting elements.
Code Sample - LINQ Non-Streaming Operators
Demo - LINQ Non-Streaming Operators Demo
Let's try LINQ Non-Streaming Operators with Objects
- We create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
- Then, we define a LINQ query using the OrderBy and Take operators.
- The OrderBy operator is a deferred execution non-streaming operator that sorts the numbers array in ascending order.
- The Take operator is also a deferred execution non-streaming operator that selects the first 3 elements of the sorted array.
- Next, we change the value of the fourth element in the numbers array to 8.
- This means that the original source data has changed since we defined the query.
- Finally, we enumerate the query results using a foreach loop and print them to the console using Console.WriteLine.
- Notice that the query results include the first 3 elements of the sorted numbers array (1, 2, and 3), even though we changed one of the source elements to 8. This is because the query execution is deferred until we enumerate the query, and it uses the updated source data at that time. Also, the query execution is non-streaming, so it has to read all the source data before yielding elements.
- Click on Non-Streaming Execution Button.
- Click on reset to try other combination.
Query Result:
Classification Table
The following table classifies each standard query operator method according to its method of execution.
Manner of Execution | Query Operators |
---|---|
Immediate Execution | Aggregate, All, Any, Average, Cast, Concat, Contains, Count, DefaultIfEmpty, Distinct, ElementAt, ElementAtOrDefault, Except, First, FirstOrDefault, GroupBy (with overload), GroupJoin, Intersect, Join, Last, LastOrDefault, LongCount, Max, Min, OfType, OrderBy, OrderByDescending, Range, Repeat, Reverse, Select, SelectMany, SequenceEqual, Single, SingleOrDefault, Skip, SkipWhile, Sum, Take, TakeWhile, ThenBy, ThenByDescending, ToArray, ToDictionary, ToList, Union, Where, Zip |
Deferred Execution - Streaming | AsEnumerable, AsQueryable, Cast, Concat, DefaultIfEmpty, Distinct, GroupBy (without overload), Join, OfType, Reverse, Select, SelectMany, Take, TakeWhile, Union, Where |
Deferred Execution - Non-Streaming | Aggregate, All, Any, Average, Contains, Count, ElementAt, ElementAtOrDefault, Except, First, FirstOrDefault, GroupBy (with overload), GroupJoin, Intersect, Last, LastOrDefault, LongCount, Max, Min, OrderBy, OrderByDescending, Single, SingleOrDefault, Skip, SkipWhile, Sum, ThenBy, ThenByDescending, ToArray, ToDictionary, ToList, Zip |
Summary
In this article we learn't how LINQ Execution
and Operators
work. We saw how
Deferred
and Immediate
execution works along with Streaming
and Non-Streaming
operators. We learnt the advantages of deferred execution and how to use yield
keyword in streaming operations with a live demo. With this I'm completing my LINQ series. Feel free to checkout LINQ learning path in
LINQ Channel.