PYnative

Python Programming

  • Learn Python ▼
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises ▼
    • Python Exercises
    • C++ Exercises
    • C Programming Exercises
    • Java Exercises
    • C# Exercises
  • Quizzes
  • Code Editor ▼
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
    • Online Java Compiler
    • Online C# Compiler
Home » C# Exercises » C# Iterators and Yield Exercises: 25 Coding Problems with Solutions

C# Iterators and Yield Exercises: 25 Coding Problems with Solutions

Updated on: July 15, 2026 | Leave a Comment

The yield keyword is one of C#’s more underused features, letting you build custom, lazily-evaluated sequences without manually implementing IEnumerator.

This collection of 25 C# iterator exercises covers writing your own yield return methods, building infinite sequences, and understanding how iterators generate values on demand instead of all at once.

Each exercise includes a Practice Problem, Purpose, Hint, and a full Solution with Explanation, so you understand exactly when and why lazy evaluation matters.

Also, See: C# Exercises with  22 topic-wise sets and 620+ practice questions.

What You’ll Practice

  • Fundamentals: yield return and yield break.
  • Custom Sequences: Building your own IEnumerable<T> methods.
  • Lazy Evaluation: Understanding deferred execution versus eager loading.
  • Practical Use: Infinite sequences and filtered iteration pipelines.
+ Table Of Contents (25 Exercises)

Table of contents

  • Exercise 1: Generate Even Numbers with Yield Return
  • Exercise 2: Count Down with an Early Exit
  • Exercise 3: Filter Vowels from a String
  • Exercise 4: Generate an Infinite Sequence Lazily
  • Exercise 5: Generate Powers of Two
  • Exercise 6: Take Every Nth Element from an Array
  • Exercise 7: Stop an Iterator at a Specific Word
  • Exercise 8: Reimplement LINQ’s Where Method
  • Exercise 9: Reimplement LINQ’s Select Method
  • Exercise 10: Generate the Fibonacci Sequence
  • Exercise 11: Calculate a Running Total (Prefix Sum)
  • Exercise 12: Split a Collection into Chunks
  • Exercise 13: Track State Between Two Signal Markers
  • Exercise 14: Flatten a Nested Collection
  • Exercise 15: Generate Adjacent Pairs from a Sequence
  • Exercise 16: Visualize Deferred Execution
  • Exercise 17: Observe Finally Block Behavior on Early Exit
  • Exercise 18: Build a Zero-Allocation Custom Enumerator
  • Exercise 19: Read a Large File Line by Line
  • Exercise 20: Interleave Two Sequences
  • Exercise 21: Simulate a Paginated API Fetcher
  • Exercise 22: Build a Peekable Iterator
  • Exercise 23: Loop Through a Collection Forever
  • Exercise 24: Chain Multiple Iterators into a Pipeline
  • Exercise 25: Stream Data Asynchronously with IAsyncEnumerable

Exercise 1: Generate Even Numbers with Yield Return

Practice Problem: Create a method GetEvens(int max) that uses yield return to generate even numbers from 0 up to max.

Purpose: This exercise helps you practice writing a basic iterator method using yield return, which lets a method produce a sequence of values one at a time without building a list up front.

Given Input: max = 10

Expected Output: Even Numbers Up To 10 = 0, 2, 4, 6, 8, 10

▼ Hint
  • Declare the method’s return type as IEnumerable<int>, since yield return requires the method to return an iterator type like IEnumerable<T>.
  • Use a for loop starting at 0 that increases by 2 on each pass, calling yield return i instead of collecting the values into a list.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> GetEvens(int max)
        {
            for (int i = 0; i <= max; i += 2)
            {
                yield return i;
            }
        }

        static void Main(string[] args)
        {
            int max = 10;
            List<int> evens = new List<int>(GetEvens(max));

            Console.WriteLine($"Even Numbers Up To {max} = {string.Join(", ", evens)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • IEnumerable<int> GetEvens(int max): A method containing yield return is automatically compiled into a state machine that implements IEnumerable<int> behind the scenes.
  • yield return i: Produces the current value to the caller and pauses execution right there, resuming from that exact point the next time a value is requested.
  • new List<int>(GetEvens(max)): Fully enumerates the iterator into a concrete list so it can be joined into a single output string.

Exercise 2: Count Down with an Early Exit

Practice Problem: Write an iterator Countdown(int start) that counts down from the starting number to 1, and then uses yield break to stop execution early if a negative number is passed.

Purpose: This exercise helps you practice using yield break to end an iterator immediately, producing an empty sequence without ever entering the main loop.

Given Input: start = 5

Expected Output: Countdown from 5 = 5, 4, 3, 2, 1

▼ Hint
  • Check if (start < 0) as the very first statement in the method, and use yield break; there to exit before the loop even starts.
  • For the normal case, use a for loop starting at start and decrementing down to 1, calling yield return i each time.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> Countdown(int start)
        {
            if (start < 0)
            {
                yield break;
            }

            for (int i = start; i >= 1; i--)
            {
                yield return i;
            }
        }

        static void Main(string[] args)
        {
            int start = 5;
            List<int> countdown = new List<int>(Countdown(start));

            Console.WriteLine($"Countdown from {start} = {string.Join(", ", countdown)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • if (start < 0) yield break;: Immediately ends the iterator without producing any values whenever a negative starting number is passed in.
  • for (int i = start; i >= 1; i--): Counts downward from the starting value to 1 inclusive, yielding each number along the way.
  • Guard clause style: Placing the yield break check before the loop keeps the invalid input case separate from the main counting logic.

Exercise 3: Filter Vowels from a String

Practice Problem: Create a method FilterVowels(string input) that iterates through a string and yields only the vowels found in it.

Purpose: This exercise helps you practice combining yield return with a filtering condition inside a foreach loop over individual characters.

Given Input: input = "Programming"

Expected Output: Vowels = o, a, i

▼ Hint
  • Store the set of vowels, upper and lower case, in a small string like "aeiouAEIOU", then use .Contains() to check each character against it.
  • Loop through the input string with a foreach over char, calling yield return character only when it is a vowel.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<char> FilterVowels(string input)
        {
            string vowels = "aeiouAEIOU";

            foreach (char character in input)
            {
                if (vowels.Contains(character))
                {
                    yield return character;
                }
            }
        }

        static void Main(string[] args)
        {
            string input = "Programming";
            List<char> vowelsFound = new List<char>(FilterVowels(input));

            Console.WriteLine($"Input = {input}");
            Console.WriteLine($"Vowels = {string.Join(", ", vowelsFound)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • foreach (char character in input): Iterates through the string one character at a time, since a string is itself enumerable as a sequence of char values.
  • vowels.Contains(character): Checks whether the current character appears anywhere in the small reference string of vowels.
  • yield return character: Only runs when the condition is true, so consonants and other characters are simply skipped without interrupting the loop.

Exercise 4: Generate an Infinite Sequence Lazily

Practice Problem: Write an infinite loop iterator InfiniteNumbers() that yields numbers starting from 1 and increments forever. (Demonstrates how yield evaluates lazily).

Purpose: This exercise helps you practice lazy evaluation, where an iterator with no natural end point is still safe to use as long as the caller only ever asks for a limited number of values.

Given Input: The first 5 values requested from an iterator that would otherwise run forever.

Expected Output: First Five Numbers = 1, 2, 3, 4, 5

▼ Hint
  • Use a while (true) loop inside the iterator method, yielding the current number and then incrementing it on every pass.
  • Call .Take(5) on the result before enumerating it, since evaluating an infinite iterator directly into a list would never finish.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> InfiniteNumbers()
        {
            int current = 1;

            while (true)
            {
                yield return current;
                current++;
            }
        }

        static void Main(string[] args)
        {
            List<int> firstFive = InfiniteNumbers().Take(5).ToList();

            Console.WriteLine($"First Five Numbers = {string.Join(", ", firstFive)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • while (true) { yield return current; current++; }: The loop genuinely has no exit condition, but that is safe because each value is only produced when the caller actually asks for the next one.
  • InfiniteNumbers().Take(5): Stops pulling values from the iterator after exactly 5 have been produced, so the underlying infinite loop never actually completes, it is simply abandoned once enough values exist.
  • Lazy evaluation: Calling InfiniteNumbers() alone does not run any code inside the method yet, execution only begins once something starts enumerating the result, such as .Take(5).ToList().

Exercise 5: Generate Powers of Two

Practice Problem: Implement a method GetPowersOfTwo(int exponentLimit) that yields 2 raised to the power of n for each step until it reaches the limit (2^0, 2^1, 2^2, and so on).

Purpose: This exercise helps you practice yielding a computed value on each iteration, rather than yielding the loop variable itself directly.

Given Input: exponentLimit = 5

Expected Output: Powers of Two (up to 2^5) = 1, 2, 4, 8, 16, 32

▼ Hint
  • Loop exponent from 0 up to and including exponentLimit, and yield Math.Pow(2, exponent) cast to a whole number type on each pass.
  • Use long as the return type instead of int, since powers of two can grow large quickly for bigger exponent limits.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<long> GetPowersOfTwo(int exponentLimit)
        {
            for (int exponent = 0; exponent <= exponentLimit; exponent++)
            {
                yield return (long)Math.Pow(2, exponent);
            }
        }

        static void Main(string[] args)
        {
            int exponentLimit = 5;
            List<long> powers = new List<long>(GetPowersOfTwo(exponentLimit));

            Console.WriteLine($"Powers of Two (up to 2^{exponentLimit}) = {string.Join(", ", powers)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • Math.Pow(2, exponent): Calculates 2 raised to the current exponent, returning a double that is then cast down to long.
  • yield return (long)Math.Pow(2, exponent): Yields the computed power rather than the raw loop variable, showing that the yielded value does not have to match the loop counter directly.
  • exponent <= exponentLimit: Includes the limit itself in the sequence, so an exponentLimit of 5 produces 6 total values, from 2^0 through 2^5.

Exercise 6: Take Every Nth Element from an Array

Practice Problem: Write a method TakeEveryNth<T>(T[] array, int n) that iterates through an array and yields every n-th element.

Purpose: This exercise helps you practice writing a generic iterator method that works with any element type, controlling the loop’s step size instead of always moving one index at a time.

Given Input: numbers = { 10, 20, 30, 40, 50, 60, 70, 80, 90 }, n = 3

Expected Output: Every 3rd Element = 10, 40, 70

▼ Hint
  • Use a for loop with an index variable that increases by n on each pass instead of by 1, so it lands on every n-th position starting from index 0.
  • Make the method generic with <T>, so it works on arrays of any type, not just integers.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<T> TakeEveryNth<T>(T[] array, int n)
        {
            for (int i = 0; i < array.Length; i += n)
            {
                yield return array[i];
            }
        }

        static void Main(string[] args)
        {
            int[] numbers = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };
            int n = 3;

            List<int> everyThird = new List<int>(TakeEveryNth(numbers, n));

            Console.WriteLine($"Every {n}rd Element = {string.Join(", ", everyThird)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • for (int i = 0; i < array.Length; i += n): Steps through the array n positions at a time instead of one at a time, landing on index 0, then n, then 2n, and so on.
  • TakeEveryNth<T>(T[] array, int n): Being generic over T means this same method works equally well on an array of strings, objects, or any other type.
  • yield return array[i]: Produces the element at the current stepped index without needing to build an intermediate array or list of results.

Exercise 7: Stop an Iterator at a Specific Word

Practice Problem: Create an iterator that yields words from a list but uses yield break the moment it encounters a specific “stop word” (e.g., "END").

Purpose: This exercise helps you practice using yield break partway through a loop, rather than only at the very start, to end the sequence as soon as a certain condition is met.

Given Input: words = { "Apple", "Banana", "END", "Cherry", "Date" }, stopWord = "END"

Expected Output: Words Before Stop = Apple, Banana

▼ Hint
  • Inside the foreach loop, check if the current word equals the stop word before yielding it, and call yield break; if it matches.
  • Only reach the yield return statement for words that are not the stop word, so the stop word itself is never included in the output.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<string> ReadUntilStopWord(List<string> words, string stopWord)
        {
            foreach (string word in words)
            {
                if (word == stopWord)
                {
                    yield break;
                }

                yield return word;
            }
        }

        static void Main(string[] args)
        {
            List<string> words = new List<string> { "Apple", "Banana", "END", "Cherry", "Date" };

            List<string> result = new List<string>(ReadUntilStopWord(words, "END"));

            Console.WriteLine($"Words Before Stop = {string.Join(", ", result)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • if (word == stopWord) yield break;: Ends the iterator entirely the moment the stop word is reached, so “Cherry” and “Date” are never even visited by the loop.
  • yield return word;: Only runs for words that come before the stop word is encountered.
  • Ordering matters: Checking for the stop word before the yield return line ensures the stop word itself is excluded from the final sequence.

Exercise 8: Reimplement LINQ’s Where Method

Practice Problem: Write your own extension method MyWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate) using yield return to filter elements.

Purpose: This exercise helps you practice recreating LINQ’s Where() from scratch, revealing that it is really just an iterator method filtering elements with yield return underneath the familiar syntax.

Given Input: numbers = { 1, 2, 3, 4, 5, 6, 7, 8 }, predicate n => n % 2 == 0

Expected Output: Even Numbers = 2, 4, 6, 8

▼ Hint
  • Loop through source with a foreach, and call yield return item only when predicate(item) returns true.
  • Since the method uses the this modifier on its first parameter, it can be called directly on any IEnumerable<T>, exactly like the real Where().
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    static class EnumerableExtensions
    {
        public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (T item in source)
            {
                if (predicate(item))
                {
                    yield return item;
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };

            List<int> evenNumbers = new List<int>(numbers.MyWhere(n => n % 2 == 0));

            Console.WriteLine($"Even Numbers = {string.Join(", ", evenNumbers)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • this IEnumerable<T> source, Func<T, bool> predicate: Extends any sequence with a filtering method that accepts a condition function, exactly mirroring the signature of the built-in Where().
  • if (predicate(item)) yield return item;: Only produces elements for which the supplied condition function returns true, skipping everything else.
  • numbers.MyWhere(n => n % 2 == 0): Called fluently on a list, exactly as you would use the real Where() method from LINQ.

Exercise 9: Reimplement LINQ’s Select Method

Practice Problem: Write your own extension method MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) to transform elements.

Purpose: This exercise helps you practice recreating LINQ’s Select(), this time with two separate generic type parameters since the output type can differ from the input type.

Given Input: numbers = { 1, 2, 3, 4 }, selector n => n * n

Expected Output: Squares = 1, 4, 9, 16

▼ Hint
  • Loop through source with a foreach, calling yield return selector(item) for every element, with no filtering condition involved.
  • Use two type parameters, TSource for the input element type and TResult for the transformed output type, since Select() can change the type entirely.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    static class EnumerableExtensions
    {
        public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
        {
            foreach (TSource item in source)
            {
                yield return selector(item);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3, 4 };

            List<int> squares = new List<int>(numbers.MySelect(n => n * n));

            Console.WriteLine($"Squares = {string.Join(", ", squares)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • TSource and TResult: Two separate type parameters allow the method to transform a sequence of one type into a sequence of a completely different type, not just filter the original type.
  • yield return selector(item): Yields the transformed result of applying the selector function to each element, rather than the original element itself.
  • numbers.MySelect(n => n * n): In this call both TSource and TResult happen to be int, but they could just as easily differ, for example projecting integers into strings.

Exercise 10: Generate the Fibonacci Sequence

Practice Problem: Implement a GetFibonacci(int count) iterator. Maintain the internal state of the previous two numbers to yield the sequence up to the specified count.

Purpose: This exercise helps you practice how an iterator method can maintain its own running state between each yield return, without needing any external variables or a class field.

Given Input: count = 8

Expected Output: First 8 Fibonacci Numbers = 0, 1, 1, 2, 3, 5, 8, 13

▼ Hint
  • Keep two local variables, previous and current, starting at 0 and 1, updating both together on every iteration.
  • Loop count times, yielding previous each time, then computing the next Fibonacci number as previous + current before shifting both variables forward.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> GetFibonacci(int count)
        {
            int previous = 0;
            int current = 1;

            for (int i = 0; i < count; i++)
            {
                yield return previous;

                int next = previous + current;
                previous = current;
                current = next;
            }
        }

        static void Main(string[] args)
        {
            int count = 8;
            List<int> fibonacci = new List<int>(GetFibonacci(count));

            Console.WriteLine($"First {count} Fibonacci Numbers = {string.Join(", ", fibonacci)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • int previous = 0; int current = 1;: Initializes the two running values needed to compute each subsequent Fibonacci number.
  • yield return previous;: Yields the current Fibonacci number before it gets updated for the next iteration.
  • previous = current; current = next;: Shifts both tracked values forward by one position in the sequence, ready for the following iteration.
  • Persisted state: Because this is a compiler generated state machine, previous and current retain their values automatically between each paused and resumed call, with no extra class fields required.

Exercise 11: Calculate a Running Total (Prefix Sum)

Practice Problem: Create an iterator RunningTotal(IEnumerable<int> numbers) that yields the cumulative sum at each step. For input [1, 2, 3], it yields 1, then 3, then 6.

Purpose: This exercise helps you practice keeping a running accumulator inside an iterator, yielding an updated value on every pass instead of yielding the raw input elements.

Given Input: numbers = { 1, 2, 3 }

Expected Output: Running Totals = 1, 3, 6

▼ Hint
  • Declare a total variable outside the loop, initialized to 0, so its value persists across every yield return.
  • Inside the foreach, add the current number to total first, then yield the updated total, not the original number.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> RunningTotal(IEnumerable<int> numbers)
        {
            int total = 0;

            foreach (int number in numbers)
            {
                total += number;
                yield return total;
            }
        }

        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3 };
            List<int> totals = new List<int>(RunningTotal(numbers));

            Console.WriteLine($"Running Totals = {string.Join(", ", totals)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • int total = 0;: Declared once, outside the loop, so the compiler generated state machine keeps this value alive between every paused and resumed iteration.
  • total += number; yield return total;: Adds the current element to the accumulator before yielding it, so each yielded value reflects the sum of everything seen so far.
  • Input flexibility: Accepting IEnumerable<int> rather than List<int> means this method works on arrays, other iterators, or any other sequence of integers.

Exercise 12: Split a Collection into Chunks

Practice Problem: Write an iterator Chunk<T>(IEnumerable<T> source, int size) that breaks a flat collection into smaller arrays or lists of the specified size.

Purpose: This exercise helps you practice building up a temporary buffer inside an iterator and yielding it whenever it reaches a target size, then starting a fresh buffer for the next group.

Given Input: numbers = { 1, 2, 3, 4, 5, 6, 7 }, size = 3

Expected Output:

Chunk = 1, 2, 3
Chunk = 4, 5, 6
Chunk = 7
▼ Hint
  • Keep a List<T> buffer, adding one element per pass, and yield return it once its count reaches size, then replace it with a brand new empty list.
  • After the main loop ends, check if the buffer still has any leftover elements and yield that final partial chunk too, since it may not have reached full size.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<List<T>> Chunk<T>(IEnumerable<T> source, int size)
        {
            List<T> currentChunk = new List<T>();

            foreach (T item in source)
            {
                currentChunk.Add(item);

                if (currentChunk.Count == size)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Count > 0)
            {
                yield return currentChunk;
            }
        }

        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
            int size = 3;

            foreach (List<int> chunk in Chunk(numbers, size))
            {
                Console.WriteLine($"Chunk = {string.Join(", ", chunk)}");
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • if (currentChunk.Count == size): Yields the buffer the instant it reaches the requested chunk size, then immediately starts a fresh, empty buffer for the next group.
  • if (currentChunk.Count > 0) yield return currentChunk;: Handles the final, possibly incomplete chunk after the loop finishes, so no trailing elements are silently dropped.
  • IEnumerable<List<T>>: The return type is a sequence of lists, since each yielded value is itself an entire group of elements rather than a single item.

Exercise 13: Track State Between Two Signal Markers

Practice Problem: Create a method that reads a list of transactional logs and only yields logs that occur after a "START" signal and before a "STOP" signal.

Purpose: This exercise helps you practice maintaining a simple boolean flag inside an iterator to track whether the current position falls within a target range defined by marker values.

Given Input: logs = { "Init", "START", "Login", "Purchase", "STOP", "Logout" }

Expected Output: Logs Between START and STOP = Login, Purchase

▼ Hint
  • Track an isRecording boolean, flipping it to true when the loop reaches "START" and back to false at "STOP".
  • Use continue after handling the marker values themselves, so the markers are never yielded, only the logs strictly between them.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<string> GetLogsBetweenSignals(List<string> logs)
        {
            bool isRecording = false;

            foreach (string log in logs)
            {
                if (log == "START")
                {
                    isRecording = true;
                    continue;
                }

                if (log == "STOP")
                {
                    isRecording = false;
                    continue;
                }

                if (isRecording)
                {
                    yield return log;
                }
            }
        }

        static void Main(string[] args)
        {
            List<string> logs = new List<string> { "Init", "START", "Login", "Purchase", "STOP", "Logout" };

            List<string> recordedLogs = new List<string>(GetLogsBetweenSignals(logs));

            Console.WriteLine($"Logs Between START and STOP = {string.Join(", ", recordedLogs)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • bool isRecording = false;: A simple flag that represents whether the iterator is currently inside the START and STOP range.
  • if (log == "START") { isRecording = true; continue; }: Flips the flag on and skips to the next log without yielding the marker itself.
  • if (isRecording) yield return log;: Only produces logs while the flag is currently true, meaning strictly between a START and its matching STOP.

Exercise 14: Flatten a Nested Collection

Practice Problem: Write an iterator Flatten<T>(IEnumerable<IEnumerable<T>> nestedList) that takes a list of lists and yields a single, flat stream of elements.

Purpose: This exercise helps you practice nesting one foreach loop inside another within an iterator method, yielding from the innermost loop to collapse two levels of structure into one.

Given Input: nestedNumbers = { { 1, 2, 3 }, { 4, 5 }, { 6, 7, 8, 9 } }

Expected Output: Flattened = 1, 2, 3, 4, 5, 6, 7, 8, 9

▼ Hint
  • Use an outer foreach to walk through each inner list, then a nested inner foreach to walk through each element within that inner list.
  • Call yield return item from inside the innermost loop, so every element from every sub-list ends up in a single continuous output sequence.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<T> Flatten<T>(IEnumerable<IEnumerable<T>> nestedList)
        {
            foreach (IEnumerable<T> innerList in nestedList)
            {
                foreach (T item in innerList)
                {
                    yield return item;
                }
            }
        }

        static void Main(string[] args)
        {
            List<List<int>> nestedNumbers = new List<List<int>>
            {
                new List<int> { 1, 2, 3 },
                new List<int> { 4, 5 },
                new List<int> { 6, 7, 8, 9 }
            };

            List<int> flatList = new List<int>(Flatten(nestedNumbers));

            Console.WriteLine($"Flattened = {string.Join(", ", flatList)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • foreach (IEnumerable<T> innerList in nestedList): Walks through each individual sub-collection contained in the outer sequence.
  • foreach (T item in innerList) { yield return item; }: Yields every element from the current sub-collection before the outer loop moves on to the next one.
  • IEnumerable<IEnumerable<T>>: The parameter type explicitly represents a sequence of sequences, which is exactly what “a list of lists” means in generic terms.

Exercise 15: Generate Adjacent Pairs from a Sequence

Practice Problem: Implement ToPairs<T>(IEnumerable<T> source) that yields tuples of adjacent elements. For [1, 2, 3, 4], it yields (1, 2), (2, 3), and (3, 4).

Purpose: This exercise helps you practice manually driving an IEnumerator<T> with MoveNext() inside an iterator method, useful whenever the logic needs to look at two elements at once instead of just one.

Given Input: numbers = { 1, 2, 3, 4 }

Expected Output:

(1, 2)
(2, 3)
(3, 4)
▼ Hint
  • Get the enumerator manually with source.GetEnumerator(), call MoveNext() once up front to read the very first element, and exit early with yield break if the sequence is empty.
  • Loop by calling MoveNext() again for each subsequent element, yielding a tuple of the previous and current values, then updating previous before the next pass.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<(T First, T Second)> ToPairs<T>(IEnumerable<T> source)
        {
            using (IEnumerator<T> enumerator = source.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }

                T previous = enumerator.Current;

                while (enumerator.MoveNext())
                {
                    T current = enumerator.Current;
                    yield return (previous, current);
                    previous = current;
                }
            }
        }

        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3, 4 };

            foreach ((int first, int second) in ToPairs(numbers))
            {
                Console.WriteLine($"({first}, {second})");
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • using (IEnumerator<T> enumerator = source.GetEnumerator()): Manually obtains the sequence’s own enumerator, giving direct control over when to advance instead of relying on an automatic foreach.
  • if (!enumerator.MoveNext()) yield break;: Immediately ends the iterator with no output if the source sequence turns out to be empty.
  • yield return (previous, current);: Produces a value tuple pairing the previously seen element with the one just read, then previous is updated so the next pair overlaps correctly.

Exercise 16: Visualize Deferred Execution

Practice Problem: Write an iterator that prints a message to the console inside the yield return loop. Write a test program that calls the method but doesn’t loop through it with foreach to prove that code isn’t executed until iteration begins.

Purpose: This exercise helps you practice observing deferred execution directly, by printing markers before, during, and after the call to confirm exactly when the iterator’s own code actually runs.

Given Input: None, the program itself demonstrates the timing through console output.

Expected Output:

Calling GetNumbersWithLogging()...
Method call returned, but no logging has happened yet.
Now starting to iterate:
Iterator method started executing.
About to yield 1
Received 1
About to yield 2
Received 2
About to yield 3
Received 3
▼ Hint
  • Store the result of calling the iterator method in a variable first, and print a message right after that call but before any foreach begins.
  • Notice that none of the iterator’s own Console.WriteLine() calls appear until the foreach loop actually starts pulling values.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> GetNumbersWithLogging()
        {
            Console.WriteLine("Iterator method started executing.");

            for (int i = 1; i <= 3; i++)
            {
                Console.WriteLine($"About to yield {i}");
                yield return i;
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Calling GetNumbersWithLogging()...");
            IEnumerable<int> numbers = GetNumbersWithLogging();
            Console.WriteLine("Method call returned, but no logging has happened yet.");

            Console.WriteLine("Now starting to iterate:");
            foreach (int number in numbers)
            {
                Console.WriteLine($"Received {number}");
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • IEnumerable<int> numbers = GetNumbersWithLogging();: This line only creates the compiler generated state machine object, it does not run any code inside the method body yet.
  • Deferred start: “Iterator method started executing.” only prints once the foreach loop calls MoveNext() for the first time, which is the true starting point of the method’s logic.
  • Interleaved output: “About to yield” and “Received” alternate for each number, showing that execution pauses inside the iterator after every single yield return and resumes only when the next value is requested.

Exercise 17: Observe Finally Block Behavior on Early Exit

Practice Problem: Create an iterator that wraps its logic in a try-finally block. Observe and document when the finally block executes if the consumer breaks out of the foreach loop early.

Purpose: This exercise helps you practice understanding that breaking out of a foreach loop over an iterator still triggers any finally block inside it, since the loop calls Dispose() on the enumerator behind the scenes.

Given Input: A loop that breaks as soon as it receives the number 3, out of a possible 5.

Expected Output:

Received 1
Received 2
Received 3
Breaking out of the loop early.
Finally block executed: cleanup ran.
▼ Hint
  • Wrap the entire for loop containing the yield return statements in a try block, with a finally block printing a cleanup message underneath it.
  • Break out of the consuming foreach loop partway through, then notice that the finally message still appears immediately afterward, even though the for loop never reached its natural end.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<int> GetNumbersWithCleanup()
        {
            try
            {
                for (int i = 1; i <= 5; i++)
                {
                    yield return i;
                }
            }
            finally
            {
                Console.WriteLine("Finally block executed: cleanup ran.");
            }
        }

        static void Main(string[] args)
        {
            foreach (int number in GetNumbersWithCleanup())
            {
                Console.WriteLine($"Received {number}");

                if (number == 3)
                {
                    Console.WriteLine("Breaking out of the loop early.");
                    break;
                }
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • try { ... } finally { ... }: Guarantees the cleanup message prints whenever the iterator stops running, whether it finishes naturally or is abandoned partway through.
  • break; inside the consuming foreach: Causes the compiler generated foreach code to call Dispose() on the iterator’s enumerator as it exits, and that Dispose() call is what triggers the pending finally block.
  • Timing: The cleanup message appears right after “Breaking out of the loop early.” rather than at the very end of the program, since disposal happens immediately when the loop is exited.

Exercise 18: Build a Zero-Allocation Custom Enumerator

Practice Problem: Avoid IEnumerable entirely. Implement a custom GetEnumerator() method on a custom struct that returns a custom struct enumerator with MoveNext() and a Current property. (This prevents boxing allocations).

Purpose: This exercise helps you practice the “duck typing” pattern that foreach actually relies on: any type with a public GetEnumerator() method returning something with MoveNext() and Current works in a foreach loop, with no interface required at all.

Given Input: range = new NumberRange(1, 5)

Expected Output:

Number = 1
Number = 2
Number = 3
Number = 4
Number = 5
▼ Hint
  • Define a NumberRange struct storing a start and end value, with a public GetEnumerator() method that returns a separate RangeEnumerator struct.
  • The RangeEnumerator struct needs a Current property and a MoveNext() method that advances an internal counter and returns whether a valid value is available.
▼ Solution & Explanation
using System;

namespace IteratorExercises
{
    struct NumberRange
    {
        private readonly int start;
        private readonly int end;

        public NumberRange(int start, int end)
        {
            this.start = start;
            this.end = end;
        }

        public RangeEnumerator GetEnumerator()
        {
            return new RangeEnumerator(start, end);
        }
    }

    struct RangeEnumerator
    {
        private readonly int end;
        private int current;

        public RangeEnumerator(int start, int end)
        {
            this.end = end;
            current = start - 1;
        }

        public int Current => current;

        public bool MoveNext()
        {
            current++;
            return current <= end;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            NumberRange range = new NumberRange(1, 5);

            foreach (int number in range)
            {
                Console.WriteLine($"Number = {number}");
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • public RangeEnumerator GetEnumerator(): The compiler only requires a method with this exact name and shape, it never checks whether NumberRange actually implements IEnumerable.
  • public int Current => current; and public bool MoveNext(): These two members are exactly what foreach looks for on the returned enumerator type, matching the pattern without implementing any interface.
  • Zero allocation: Since both NumberRange and RangeEnumerator are struct types used directly rather than through an IEnumerable or IEnumerator interface reference, no boxing allocation occurs on the heap when the foreach loop runs.

Exercise 19: Read a Large File Line by Line

Practice Problem: Write a method ReadLines(string filePath) that opens a StreamReader and yields lines one by one. Ensure the file stream is properly disposed of if the caller stops iterating early.

Purpose: This exercise helps you practice combining yield return with a using block around a file handle, so the file is guaranteed to close whether the caller reads every line or abandons the loop partway through.

Given Input: filePath = "data.txt", assumed to contain a line with the text "STOP" partway through the file.

Expected Output: Each line prints as it is read, until a line containing "STOP" is found, at which point the loop breaks early.

Note: since this program reads an actual file from disk, the real output depends entirely on data.txt‘s contents and would throw a FileNotFoundException if that file does not exist at the given path.

▼ Hint
  • Wrap the StreamReader in a using block that surrounds the entire loop, so its Dispose() runs automatically once the iterator stops, for any reason.
  • Use a while ((line = reader.ReadLine()) != null) loop, calling yield return line for each one, since ReadLine() returns null once the end of the file is reached.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.IO;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<string> ReadLines(string filePath)
        {
            using (StreamReader reader = new StreamReader(filePath))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    yield return line;
                }
            }
        }

        static void Main(string[] args)
        {
            string filePath = "data.txt";

            foreach (string line in ReadLines(filePath))
            {
                Console.WriteLine($"Line = {line}");

                if (line.Contains("STOP"))
                {
                    Console.WriteLine("Stopping early, the file stream will still be disposed correctly.");
                    break;
                }
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • using (StreamReader reader = new StreamReader(filePath)): Wrapping the entire loop in this block ensures the file handle is released whenever the iterator stops, whether that happens by reaching the end of the file or by the consumer breaking out early.
  • break; in the consuming loop: Just like the earlier try-finally exercise, breaking out of the foreach triggers disposal of the iterator, which in turn runs the using block’s cleanup and closes the file.
  • Memory efficiency: Because lines are yielded one at a time instead of being loaded into a list all at once, this approach can handle files far larger than what would comfortably fit in memory.

Exercise 20: Interleave Two Sequences

Practice Problem: Implement Interleave<T>(IEnumerable<T> first, IEnumerable<T> second) which alternates yielding an element from the first collection, then the second collection, until both are exhausted.

Purpose: This exercise helps you practice manually driving two enumerators side by side within a single iterator, handling the case where one sequence runs out before the other.

Given Input: first = { 1, 3, 5 }, second = { 2, 4, 6, 8, 10 }

Expected Output: Interleaved = 1, 2, 3, 4, 5, 6, 8, 10

▼ Hint
  • Get an enumerator for each sequence and call MoveNext() on both once before entering the main loop, tracking whether each one still has more elements.
  • Inside a loop that continues while either sequence still has elements, yield from whichever ones are still available, advancing each enumerator independently right after it is used.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<T> Interleave<T>(IEnumerable<T> first, IEnumerable<T> second)
        {
            using (IEnumerator<T> firstEnumerator = first.GetEnumerator())
            using (IEnumerator<T> secondEnumerator = second.GetEnumerator())
            {
                bool firstHasNext = firstEnumerator.MoveNext();
                bool secondHasNext = secondEnumerator.MoveNext();

                while (firstHasNext || secondHasNext)
                {
                    if (firstHasNext)
                    {
                        yield return firstEnumerator.Current;
                        firstHasNext = firstEnumerator.MoveNext();
                    }

                    if (secondHasNext)
                    {
                        yield return secondEnumerator.Current;
                        secondHasNext = secondEnumerator.MoveNext();
                    }
                }
            }
        }

        static void Main(string[] args)
        {
            List<int> first = new List<int> { 1, 3, 5 };
            List<int> second = new List<int> { 2, 4, 6, 8, 10 };

            List<int> interleaved = new List<int>(Interleave(first, second));

            Console.WriteLine($"Interleaved = {string.Join(", ", interleaved)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • bool firstHasNext = firstEnumerator.MoveNext();: Primes both enumerators before the loop starts, capturing whether each one has at least a first element available.
  • while (firstHasNext || secondHasNext): Keeps looping as long as at least one of the two sequences still has remaining elements, not requiring both to still be active.
  • Uneven lengths: Once first runs out after its third element, firstHasNext becomes false and its block is simply skipped on every remaining pass, letting second finish yielding its own leftover values on its own.

Exercise 21: Simulate a Paginated API Fetcher

Practice Problem: Simulate fetching data from a paginated API. Write an iterator that yields items one by one, but under the hood, it fetches the next page of 100 items only when the current page is completely consumed.

Purpose: This exercise helps you practice a real world use of deferred execution, where an expensive operation like a network call only happens exactly when it is actually needed, and not a moment sooner.

Given Input: pageSize = 100, totalPages = 3, but only the first 5 items are ever consumed.

Expected Output:

Fetching page 1 from the API...
Consumed item 1
Consumed item 2
Consumed item 3
Consumed item 4
Consumed item 5

Note: “Fetching page 2” and “Fetching page 3” never print at all, since consuming only 5 of the 100 items on page 1 never forces the iterator to request the next page.

▼ Hint
  • Write a separate FetchPage() helper method that simulates the network call and prints a message every time it runs, so its calls are easy to spot in the output.
  • Inside the iterator, call FetchPage() once per page in an outer loop, then yield each item from that page in an inner loop before moving on to fetch the next page.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static List<int> FetchPage(int pageNumber, int pageSize)
        {
            Console.WriteLine($"Fetching page {pageNumber} from the API...");

            List<int> page = new List<int>();
            int startItem = (pageNumber - 1) * pageSize + 1;

            for (int i = 0; i < pageSize; i++)
            {
                page.Add(startItem + i);
            }

            return page;
        }

        static IEnumerable<int> GetAllItems(int pageSize, int totalPages)
        {
            for (int pageNumber = 1; pageNumber <= totalPages; pageNumber++)
            {
                List<int> page = FetchPage(pageNumber, pageSize);

                foreach (int item in page)
                {
                    yield return item;
                }
            }
        }

        static void Main(string[] args)
        {
            int pageSize = 100;
            int totalPages = 3;
            int itemsToConsume = 5;

            int count = 0;
            foreach (int item in GetAllItems(pageSize, totalPages))
            {
                Console.WriteLine($"Consumed item {item}");
                count++;

                if (count == itemsToConsume)
                {
                    break;
                }
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • FetchPage(pageNumber, pageSize): Simulates the actual API call, only running when the outer for loop inside the iterator reaches a new page number.
  • Lazy fetching: Because the inner foreach only yields items from the page already in memory, page 2 is never fetched at all if the caller stops consuming before exhausting page 1.
  • Real world parallel: This is exactly the mechanism many client libraries use to page through large API results without ever loading more data than the caller actually asked for.

Exercise 22: Build a Peekable Iterator

Practice Problem: Create a custom iterator class that allows the consumer to “Peek” at the next element in the sequence without advancing the actual cursor of the iteration.

Purpose: This exercise helps you practice building a small stateful wrapper class around a plain IEnumerator<T>, buffering a single lookahead value so it can be inspected without being consumed.

Given Input: numbers = { 10, 20, 30 }

Expected Output:

Current = 10
Peeked = 20
Current = 20
Current = 30
▼ Hint
  • Store a hasBuffered flag and a bufferedValue field. Peek() should only advance the underlying enumerator if nothing is already buffered.
  • Your own MoveNext() equivalent should first check the buffer, returning and clearing it if present, and only fall back to advancing the underlying enumerator directly when the buffer is empty.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class PeekableIterator<T>
    {
        private readonly IEnumerator<T> source;
        private bool hasBuffered;
        private T bufferedValue;

        public PeekableIterator(IEnumerable<T> source)
        {
            this.source = source.GetEnumerator();
        }

        public T Peek()
        {
            if (!hasBuffered)
            {
                if (source.MoveNext())
                {
                    bufferedValue = source.Current;
                    hasBuffered = true;
                }
                else
                {
                    throw new InvalidOperationException("No more elements to peek.");
                }
            }

            return bufferedValue;
        }

        public bool MoveNext(out T current)
        {
            if (hasBuffered)
            {
                current = bufferedValue;
                hasBuffered = false;
                return true;
            }

            if (source.MoveNext())
            {
                current = source.Current;
                return true;
            }

            current = default;
            return false;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 10, 20, 30 };
            PeekableIterator<int> iterator = new PeekableIterator<int>(numbers);

            iterator.MoveNext(out int first);
            Console.WriteLine($"Current = {first}");

            int peeked = iterator.Peek();
            Console.WriteLine($"Peeked = {peeked}");

            iterator.MoveNext(out int second);
            Console.WriteLine($"Current = {second}");

            iterator.MoveNext(out int third);
            Console.WriteLine($"Current = {third}");
        }
    }
}Code language: C# (cs)

Explanation:

  • Peek(): Advances the underlying enumerator only if nothing is buffered yet, storing the value it finds rather than handing it directly to the caller as the current element.
  • MoveNext(out T current): Checks the buffer first, if a peeked value is waiting there it is returned and cleared, otherwise the method falls through to pulling a fresh value from the underlying enumerator.
  • Why the trace works: Calling Peek() after reading 10 buffers 20 without consuming it, so the very next MoveNext() still returns that same buffered 20 rather than skipping ahead to 30.

Exercise 23: Loop Through a Collection Forever

Practice Problem: Implement an iterator LoopForever<T>(IEnumerable<T> source) that cycles through the collection indefinitely, starting back at the beginning when it reaches the end.

Purpose: This exercise helps you practice wrapping a finite sequence inside an infinite outer loop, similar in spirit to the earlier infinite numbers exercise but repeating existing data instead of generating new values.

Given Input: colors = { "Red", "Green", "Blue" }, taking the first 8 values.

Expected Output: First 8 Items = Red, Green, Blue, Red, Green, Blue, Red, Green

▼ Hint
  • Wrap a foreach (T item in source) loop inside an outer while (true), so once the inner loop reaches the end of source it simply starts over from the beginning.
  • Since this iterator never ends on its own, always call .Take(n) on it before enumerating, exactly as with any other infinite iterator.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<T> LoopForever<T>(IEnumerable<T> source)
        {
            while (true)
            {
                foreach (T item in source)
                {
                    yield return item;
                }
            }
        }

        static void Main(string[] args)
        {
            List<string> colors = new List<string> { "Red", "Green", "Blue" };

            List<string> firstEight = LoopForever(colors).Take(8).ToList();

            Console.WriteLine($"First 8 Items = {string.Join(", ", firstEight)}");
        }
    }
}Code language: C# (cs)

Explanation:

  • while (true) { foreach (T item in source) { yield return item; } }: The outer loop has no exit condition, so once the inner foreach finishes one full pass through source, it restarts automatically from the first element again.
  • LoopForever(colors).Take(8): Pulls exactly 8 values total, which is enough to wrap around the 3 element list and start a third partial cycle.
  • Same safety rule as infinite sequences: Enumerating this iterator directly into a list without a Take() call first would never terminate, exactly like the earlier InfiniteNumbers() exercise.

Exercise 24: Chain Multiple Iterators into a Pipeline

Practice Problem: Create a system where multiple iterator methods are chained together (e.g., ReadInput() -> FilterInvalid() -> TransformData() -> CleanOutput()). Write a test demonstrating how an item passes through the entire pipeline before the next item is processed.

Purpose: This exercise helps you practice how chained iterators behave as a “pull” pipeline: each stage only asks the previous stage for a new value when it actually needs one, so a single item flows all the way through before the next item is even read.

Given Input: rawItems = { "12", "abc", "45", "-3", "78" }

Expected Output:

[ReadInput] Read: 12
[FilterInvalid] Passed: 12
[TransformData] 12 -> 24
[CleanOutput] Formatted: Result: 24
Final: Result: 24
---
[ReadInput] Read: abc
[FilterInvalid] Rejected: abc
[ReadInput] Read: 45
[FilterInvalid] Passed: 45
[TransformData] 45 -> 90
[CleanOutput] Formatted: Result: 90
Final: Result: 90
---
[ReadInput] Read: -3
[FilterInvalid] Rejected: -3
[ReadInput] Read: 78
[FilterInvalid] Passed: 78
[TransformData] 78 -> 156
[CleanOutput] Formatted: Result: 156
Final: Result: 156
---
▼ Hint
  • Each stage should accept the previous stage’s IEnumerable<T> as a parameter and foreach over it, printing a log line and yielding whenever an item passes through that stage.
  • Compose the whole chain by nesting the calls: CleanOutput(TransformData(FilterInvalid(ReadInput()))), then enumerate only the outermost result in Main().
▼ Solution & Explanation
using System;
using System.Collections.Generic;

namespace IteratorExercises
{
    class Program
    {
        static IEnumerable<string> ReadInput()
        {
            string[] rawItems = { "12", "abc", "45", "-3", "78" };

            foreach (string raw in rawItems)
            {
                Console.WriteLine($"[ReadInput] Read: {raw}");
                yield return raw;
            }
        }

        static IEnumerable<int> FilterInvalid(IEnumerable<string> source)
        {
            foreach (string raw in source)
            {
                if (int.TryParse(raw, out int value) && value > 0)
                {
                    Console.WriteLine($"[FilterInvalid] Passed: {raw}");
                    yield return value;
                }
                else
                {
                    Console.WriteLine($"[FilterInvalid] Rejected: {raw}");
                }
            }
        }

        static IEnumerable<int> TransformData(IEnumerable<int> source)
        {
            foreach (int value in source)
            {
                int transformed = value * 2;
                Console.WriteLine($"[TransformData] {value} -> {transformed}");
                yield return transformed;
            }
        }

        static IEnumerable<string> CleanOutput(IEnumerable<int> source)
        {
            foreach (int value in source)
            {
                string formatted = $"Result: {value}";
                Console.WriteLine($"[CleanOutput] Formatted: {formatted}");
                yield return formatted;
            }
        }

        static void Main(string[] args)
        {
            IEnumerable<string> pipeline = CleanOutput(TransformData(FilterInvalid(ReadInput())));

            foreach (string finalResult in pipeline)
            {
                Console.WriteLine($"Final: {finalResult}");
                Console.WriteLine("---");
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • Pull, not push: The outer foreach in Main() requests a value from CleanOutput, which requests one from TransformData, which requests one from FilterInvalid, which requests one from ReadInput, all the way down the chain before anything is printed.
  • Rejected items never reach later stages: When FilterInvalid rejects “abc”, it does not yield anything, so it silently loops around to pull the next raw item from ReadInput itself, without TransformData or CleanOutput ever being involved for that particular value.
  • One item at a time end to end: Notice “12” fully completes its journey through all four stages, printing “Final: Result: 24”, before “abc” is even read by ReadInput, confirming the pipeline processes one item completely before starting the next.

Exercise 25: Stream Data Asynchronously with IAsyncEnumerable

Practice Problem: Upgrade your iterator skills to modern C#. Write an async iterator FetchDataAsync() that uses await Task.Delay() to simulate network latency, combined with yield return to stream data asynchronously.

Purpose: This exercise helps you practice IAsyncEnumerable<T>, which combines everything from regular iterators with async/await, letting a method yield values over time instead of only one at a time synchronously.

Given Input: Three simulated records, each delayed by 500 milliseconds before being yielded.

Expected Output:

Received Record A at 10:15:32
Received Record B at 10:15:33
Received Record C at 10:15:33

Note: this requires C# 8 or later along with .NET Core 3.0 or later, since IAsyncEnumerable<T> and await foreach did not exist in earlier versions. The exact timestamps will differ every time the program runs, but each line should appear roughly 500 milliseconds after the previous one.

▼ Hint
  • Mark the method as static async IAsyncEnumerable<string> FetchDataAsync(), combining the async keyword with an IAsyncEnumerable<T> return type.
  • Inside the loop, await Task.Delay(500) before each yield return to simulate network latency between items.
  • Consume it with await foreach (string item in FetchDataAsync()) instead of a regular foreach, since the sequence itself is now asynchronous.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace IteratorExercises
{
    class Program
    {
        static async IAsyncEnumerable<string> FetchDataAsync()
        {
            string[] items = { "Record A", "Record B", "Record C" };

            foreach (string item in items)
            {
                await Task.Delay(500);
                yield return item;
            }
        }

        static async Task Main(string[] args)
        {
            await foreach (string item in FetchDataAsync())
            {
                Console.WriteLine($"Received {item} at {DateTime.Now:HH:mm:ss}");
            }
        }
    }
}Code language: C# (cs)

Explanation:

  • async IAsyncEnumerable<string> FetchDataAsync(): Marks this as an asynchronous iterator, allowing await and yield return to be used together inside the same method body.
  • await Task.Delay(500): Pauses the method asynchronously before producing each item, simulating the latency of a real network request without blocking any thread while waiting.
  • await foreach (string item in FetchDataAsync()): The asynchronous counterpart to a regular foreach, awaiting each item as it becomes available instead of requiring the entire sequence to be ready up front.

Filed Under: C# Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

C# Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises
Java Exercises
C# Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: C# Exercises
TweetF  sharein  shareP  Pin

  C# Exercises

  • C# Exercise for Beginners
  • C# Loops Exercise
  • C# Array Exercise
  • C# String Exercise
  • C# OOP Exercise
  • C# Structs, Records and Enums Exercise
  • C# Collections Exercise
  • C# List Exercise
  • C# Dictionary Exercise
  • C# LINQ Exercise
  • C# Exception Handling Exercise
  • C# File Handling Exercise
  • C# Date and Time Exercise
  • C# Generics Exercise
  • C# Lambda Expressions Exercise
  • C# Delegates and Events Exercise
  • C# Extension Methods Exercise
  • C# Regex Exercise
  • C# Pattern Matching Exercise
  • C# Iterators and Yield Exercise
  • C# Indexers and Operator Overloading Exercise
  • C# Random Data Generation Exercise

All Coding Exercises

Python Exercises C Exercises C++ Exercises Java Exercises C# Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises
  • Java Exercises
  • C# Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com