Introduction

The yield keyword in C# allows you to create iterators that return values one at a time, instead of generating a full collection upfront. This is classic example of lazy loading, meaning elements are only processed when needed, leading to better performance and reduced memory usage. It's especially useful for handling large data sets.

The two primary uses of yield are

How Yield Works?

Let’s consider a basic example demonstrating how yield returns elements lazily.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        foreach (int num in GetNumbers())
        {
            Console.WriteLine(num);
        }
    }

    static IEnumerable<int> GetNumbers()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
}

//Output
1
2
3

Code Snippet 1

How does it Work?

  1. The foreach loop calls GetNumbers().
  2. The method runs until it encounters the first yield return, then pauses and gives control back to the loop.
  3. Each time the loop requests the next item, execution resumes right where it left off.
  4. This continues until there are no more yield return statements.

Example 1. Real-World Example: Processing Large Files

A common use case for yield return is when dealing with large files where loading all data into memory at once would be inefficient.

Benefits

  1. Reads-only: one line at a time instead of loading the whole file.
  2. Memory-Saving: Prevents high memory consumption for large files.
  3. Stateful: If interrupted, it can resume from where it left off instead of restarting.
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
    static void Main()
    {
        foreach (string line in ReadLargeFile("bigfile.txt"))
        {
            Console.WriteLine(line); // Processes one line at a time
        }
    }

    static IEnumerable<string> ReadLargeFile(string filePath)
    {
        using (StreamReader reader = new(filePath))
        {
            string? line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line; // Returns one line at a time
            }
        }
    }
}

Code Snippet 2

Yield vs. Normal Return

If we remove yield return, we would have to store all results in a List and return it, leading to higher memory usage.

Feature Using yield return Using List<T>
Execution Returns one item at a time Processes everything at once
Memory Usage Efficient (lazy loading) Stores everything in memory
Performance Faster for large datasets Slower for large datasets
Complexity Simple to implement Requires manual list creation


Example 2. Filtering using Yield

Let’s now look at how yield return can simplify filtering operations.

public IEnumerable<Product> FilterBySize(IEnumerable<Product> products, Size size)
{
    foreach (var p in products)
    {
        if (p.Size == size)
            yield return p;
    }
}

Code Snippet 3

Filtering without Yield

public IEnumerable<Product> FilterBySize(IEnumerable<Product> products, Size size)
{
    List<Product> result = new List<Product>();
    foreach (var p in products)
    {
        if (p.Size == size)
            result.Add(p);
    }
    return result;
}

Code Snippet 4

Drawbacks

Conclusion

The yield keyword in C# simplifies iterator creation, improves performance, and reduces memory usage. It is especially useful for:

By leveraging yield return, you can write simpler, more efficient, and scalable C# code!

Cheers,

See you around.