DEV Community

yashaswi
yashaswi

Posted on

[Post-4][LINQ] Yield-Performance

[Post-4][LINQ] Yield-Performance

Using yield return has a great performance benefit when querying the data. That is because, the data is returned only when it is iterated.

For example If you have to run multiple queries on a collection, one way is to iterate the collection for each query. The other way is to compile all the queries together and then run them once on the collection. The second way is better in performance and also has less memory footprint.

Lets look into an example to establish the above point. [Source code is provided in the end of the blog as a github gist]. In the example we will filter a list of numbers and get multiples of 6.

Below is the list of numbers from 1 to 20.

List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
Enter fullscreen mode Exit fullscreen mode

Lets do the filtering as a two step process.

  1. First we will filter the numbers that are divisible by 2.

  2. Then, we will filter the numbers that are divisible by 3.

Below are two functions bool DivideBy2(int x) and bool DivideBy3(int x) that return true if the number is divisible by 2 or 3 respectively.

private bool DivideBy2(int x)       
{           
 if (x % 2 == 0)
 {                
   return true;         
 }           
 else 
 {               
   return false;           
 }       
}

private bool DivideBy3(int x)       
{           
 if (x % 3== 0)
 {                
   return true;         
 }           
 else 
 {               
   return false;           
 }       
}
Enter fullscreen mode Exit fullscreen mode

Now lets create the a Filter extension on IEnumerable that filters the given enumerable based on a given predicate.

public static IEnumerable<int> **FilterWithOutYield**(
this IEnumerable<int> **enumerable**, Func<int, bool> **func**)        
{
  IEnumerator<int> **enumerator **= enumerable.GetEnumerator();
  List<int> result = new List<int>();
  **while (enumerator.MoveNext())**
   {
      **if (func(enumerator.Current))**
      {
        **result.Add(enumerator.Current);**
      }
   }
  **return result as IEnumerable<int>;**
}
Enter fullscreen mode Exit fullscreen mode

The above function **FilterWithOutYield **has two parameters

  1. enumerable: The data on which this **FilterWithOutYield **is executed on.

  2. func: This function acts as a filter and helps decide whether the item filtered or not.

So the function is doing 3 things here

  1. Iterating over the enumerable **in the while loop using its enumerator. [while loop**]

  2. Filtering those numbers that satisfy the given predicate func. **[If **]

  3. Adding them to a list and returning them*. [result]*

Lets call the above **FilterWithOutYield **on the list of 20 numbers that we have created in the beginning.

List<int> **numbers **= new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
            List<int> multiplesOfSix =
                (
numbers.FilterWithOutYield(**DivideBy2**) // returns a list 
       .FilterWithOutYield(**DivideBy3**) // returns a list

) as List<int>;
Enter fullscreen mode Exit fullscreen mode

In the above code FilterWithOutYield *is called *twice on the numbers.

  1. First time, **DivideBy2 **is used as the predicate. The function filters all the numbers divisible by 2 and returns a list.

  2. Second time, **DivideBy3 **is used as the predicate. The function filters all the numbers divisible by 3 from the above list of numbers and returns another list.

At last a list of numbers that are divisible by 6 are returned. Here if you observe we are iterating through the collection twice and returning a new List at each stage.

I.e. If FilterWithOutYield is chained n times , the iterations are also proportionately increased and also the number of lists returned.

Now, lets look at a better way of doing the same using Yield. Below is the FilterWithYield *function that filters an enumerable and uses **yield return.*

public static IEnumerable<int> FilterWithYield(
this IEnumerable<int> enumerable, Func<int,bool> func)
{
   IEnumerator<int> enumerator = enumerable.GetEnumerator();
   while (enumerator.MoveNext()) 
   {
     if (**func(enumerator.Current)**)
     {
       **yield return enumerator.Current;**
     }
   }
 }
Enter fullscreen mode Exit fullscreen mode

If you note, in the above method we are not returning any list instead we are doing an yield return.

Lets call this method on an enumerable and see how it works.

List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };

IEnumerable<int> multiplesOfSixUsingYield =
numbers.FilterWithYield(**DivideBy2**) // The list is not iterated.
       .FilterWithYield(**DivideBy3**); //Instead enumerator is returned
Enter fullscreen mode Exit fullscreen mode

The above code does not iterate over **numbers, **and therefore does not go into either of **DivideBy2 **or **DivideBy3 **methods. Instead an enumerator is created that uses both **DivideBy2 **and **DivideBy3 **methods to filter the enumerable.

To fetch a collection from the above code, the multiplesOfSixUsingYield *has to be iterated using a **foreach* as below, or using extension methods like **ToList() *or *ToArray()**.

foreach (int x in multiplesOfSixUsingYield) 
{              
   Console.WriteLine(x);
}
Enter fullscreen mode Exit fullscreen mode

Because of yield return, the enumerable is iterated only once irrespective of the number of times the FilterWithYield method is used, improving the time and also decreasing the memory foot print.



Hope, you have liked the article. Keep reading. Thanks.

Top comments (0)