Skip to Content
Course content

172: The yield Keyword and Iterator Methods

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're at a candy store. There are two ways you could buy your favorite treats. The first way is to grab a massive basket, walk through every single aisle, pick out every single piece of candy you want, and then carry that heavy basket to the checkout. You don't get to eat a single piece until the entire shopping trip is finished and the bag is packed.

The second way is like using a vending machine. You press a button, one piece of candy drops, and you eat it immediately. Then you press the button again, and another one drops. You never have to carry a heavy basket, and you start enjoying your snacks the second you hit that first button.

In C#, returning a List<T> is like the grocery basket. You build the entire collection in memory first, and only then do you hand it over to the caller. The yield keyword, however, turns your method into a vending machine. It allows you to return one item at a time, pausing the method's execution until the caller asks for the next item.

Stop Packing the Whole Bag

When you use yield return, you aren't returning a collection; you're returning an iterator. The code inside your method doesn't actually run the moment you call the method. It only starts running when you start looping over the result (usually with a foreach loop).

Let's look at a real scenario. Suppose you have a massive log file with millions of lines, and you only want to find the lines that contain the word "CRITICAL". If you put those into a List<string>, you might crash your app by running out of memory before you even process the first error.

public IEnumerable<string> FilterCriticalLogs(IEnumerable<string> logLines)
{
    foreach (var line in logLines)
    {
        if (line.Contains("CRITICAL"))
        {
            // We "yield" the value and PAUSE here.
            // The method stays alive, remembering exactly where it was.
            yield return line; 
        }
    }
}

I love this pattern because it's incredibly efficient. If the caller only needs the first three critical errors to show in a UI notification, the method stops running as soon as the third one is found. It doesn't matter if there are ten million more lines in the file—your code never even looks at them.

The Magic of the State Machine

You might be wondering: "How does the method remember where it left off?" This is where C# does some heavy lifting for you. When the compiler sees yield, it doesn't treat your method like a normal function. Instead, it generates a hidden "state machine" class behind the scenes.

When the foreach loop asks for the next item, the state machine jumps back into your method exactly where the last yield return occurred. It's a bit like a bookmark in a book. You read a page, put the bookmark in, and close the book. When you're ready for more, you open it exactly to that bookmark.

Ending the Stream Early

Sometimes you want to stop the vending machine entirely before you've hit the end of your data. For that, we use yield break. This tells the iterator that there are no more items coming, and the foreach loop should terminate immediately.

public IEnumerable<int> GetNumbersUntilNegative(IEnumerable<int> numbers)
{
    foreach (var n in numbers)
    {
        if (n < 0) 
        {
            yield break; // Stop everything. No more numbers will be returned.
        }
        yield return n;
    }
}

Just a heads-up: because of this "deferred execution," you can't use yield return in methods that return a List<T> or an array. It must return IEnumerable<T> (or IEnumerator<T>). If you absolutely need a list at the end, you can always call .ToList() on the result of your iterator method, but keep in mind that doing so effectively "packs the basket," defeating the memory-saving purpose of yield.




📋 Practical Task

Implementing a Memory-Efficient Transaction Filter

You are building a financial auditing tool. You have a list of thousands of Transaction objects, but you only want to process transactions that are flagged as "Suspicious" and are over $10,000. To save memory, you must implement this using an iterator method rather than creating a temporary list.

Your Task:

  • Create a Transaction class with properties Amount (decimal) and IsSuspicious (bool).
  • Write a method called GetHighValueSuspiciousTransactions that takes an IEnumerable<Transaction> as input and returns an IEnumerable<Transaction>.
  • Use the yield return keyword to return only the transactions that meet both criteria (Suspicious AND > 10,000).
  • In your Main method, create a list of test transactions and use a foreach loop to print the amounts of the filtered results.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.