How and when to use yield keyword in C#

Rupen Anjaria
4 min readAug 8, 2024

Yield is useful when we need to return values without iterating through entire sequence. This can be benificial in many differfent scenarios. We will see a basic example and then another example with real life scenario.

Note: You can try examples shown in this articles using your favorite online C# compiler like ShartLab.io.

Example 1: Using yield, print even numbers from first one million numbers.

Without yield

Here is how one way to solve above requirement:

using System;
using System.Collections.Generic;

var num = GetEvenNumbers(10);// change it to simulate one million
foreach(int n in num){
Console.WriteLine(n);
}

static IEnumerable<int> GetEvenNumbers(int max){
List<int> numbers = new List<int>();
for (int i = 0; i < max; i++){
if (i % 2 == 0){
numbers.Add(i);
}
}
return numbers;
}

Explanation:

We created GetEvenNumbers function, which will return list of even numbers within the max number provided. So, if we provide 10 as max number then the function will return 0,2,4,6,8.

After we have the numbers, we are simply printing them in foreach loop.

However, let’s say we provide one million as a max number. In that case, the program will take longer to execute, in fact it will take longer to even reach the first foreach statement. This is because, GetEvenNumbers function will loop through one million numbers while maintaining a internal list of numbers.

Did you see the problem? User has to wait till the program does it’s all the calculation. What if we can start printing even numbers as and when we are calculating? That’s exactly what yield will help us to achieve.

Here is a revised version of above example (using yield):

using System;
using System.Collections.Generic;

var num = GetEvenNumbers(10);
foreach(int n in num){
Console.WriteLine(n);
}

static IEnumerable<int> GetEvenNumbers(int max){

for (int i = 0; i < max; i++){
if (i % 2 == 0){
yield return i;

}
}
}

Explanation:

As you can see, we updated GetEvenNumbers function by removing internal List<int> and returning using yield right where we are at the point of assurity.

At var num = GetEvenNumbers(10); the C# compiler will not even call the function GetEvenNumbers . This is because it’s returning yield. Rather, it will be called when we do the real iteration in foreach loop. Note that, the compiler will keep track of the value of i inside GetEvenNumbers function as it moves in and out while iterating using foreach. This makes our program more responsive.

Now with that understanding, let’s see real world example.

Let’s say we have a employee management system. In the system, one of the page lists employees. If the total number of records are small then it won’t be any issues. However, consider a large company where the list can contain thousands of employees. If page tries to load all of them then system may experience performance issues.

This scenario can be handle using combination of database pagination and yield. Here is an example:

DBContext:

using System;
using System.Collections.Generic;
using System.Linq;

public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}

public class EmployeeDbContext
{
private readonly List<Employee> _employee;

public EmployeeDbContext()
{
// Simulate a database with 100 Employees
_employee = Enumerable
.Range(1, 100)
.Select(i => new Employee {
Id = i,
Name = $"Employee {i}" })
.ToList();
}

public List<Employee> GetEmployees(int pageNumber, int pageSize)
{
return _employee
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
}
}

Here we are simulating that we have 100 employees in the EmployeeDbContext function and GetEmployees will return specific set of employees based on page number and page size.

Next, we have Service class which will use DBContext to return employees.

public class EmployeeService
{
private readonly EmployeeDbContext _dbContext;

public EmployeeService(EmployeeDbContext dbContext)
{
_dbContext = dbContext;
}

public IEnumerable<Employee> GetEmployees(int pageSize)
{
int pageNumber = 1;
while (true)
{
var employees = _dbContext.GetEmployees(pageNumber, pageSize);
if (employees.Count == 0)
yield break;

foreach (var employee in employees)
{
yield return employee;
}

pageNumber++;
}
}
}

public class Program
{
public static void Main()
{
var dbContext = new EmployeeDbContext();
var employeeService = new EmployeeService(dbContext);

foreach (var empl in employeeService.GetEmployees(10))
{
Console.WriteLine($"Processing Employee: {empl.Name}");
}
}
}

The Main function in Program class, creates dbContext and calls GetEmployees using foreach iterator.

Within GetEmployees , we are calling GetEmployees from DBConext. Note that, it returns yield. This will result in memory efficiency as you process each employee as they are fetched, without loading all employees into memory. This way yield + DB Pagination will make our code run faster and improve memory utilization.

Further, note that we have use yield break to simulate scenario where the source is empty.

To run this, you can copy and paste both snippet in SharpLab.io editor.

Conclusion

Yield is best suited for scenarios where we are iterating over a list or sequence and the list is from database or an API response. Instead of fetching all the data in loading in memory, yield can improve memory efficiency and useful for real-time data processing scenarios.

--

--

Responses (1)