C# Academy · Lesson

Custom Enumerables

Implement IEnumerable with iterators.

Lesson 4 of 413 steps

Custom Enumerables is a free C# Academy lesson on CoddyKit. This is lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, and your progress syncs across the web and the CoddyKit app. The C# Academy course includes 4 lessons in total.

Building Your Own Enumerable

By implementing IEnumerable<T> on a class, you make it usable in a foreach. Combined with iterator methods, this lets you create custom collections and ranges with very little code.

Implementing GetEnumerator With yield

The simplest way to implement IEnumerable<T> is to write GetEnumerator as an iterator using yield return. The compiler builds the enumerator for you.

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

public class IntRange : IEnumerable<int>
{
    private readonly int _start, _count;
    public IntRange(int start, int count) { _start = start; _count = count; }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _count; i++)
            yield return _start + i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new IntRange(5, 4))
            Console.WriteLine(n);
    }
}

Why Two GetEnumerator Methods?

IEnumerable<T> inherits the non-generic IEnumerable, so you implement both. The explicit non-generic one simply delegates to the generic version.

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

public class Letters : IEnumerable<char>
{
    public IEnumerator<char> GetEnumerator()
    {
        yield return 'x';
        yield return 'y';
        yield return 'z';
    }
    // Required because IEnumerable<T> extends IEnumerable
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var c in new Letters())
            Console.WriteLine(c);
    }
}

Encapsulating Custom Logic

A custom enumerable can hide complex generation rules behind a clean type. Here a class yields a Fibonacci sequence of a chosen length.

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

public class Fibonacci : IEnumerable<int>
{
    private readonly int _count;
    public Fibonacci(int count) { _count = count; }

    public IEnumerator<int> GetEnumerator()
    {
        int a = 0, b = 1;
        for (int i = 0; i < _count; i++)
        {
            yield return a;
            int next = a + b; a = b; b = next;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Fibonacci(8))
            Console.WriteLine(n);
    }
}

Wrapping an Internal Collection

A custom enumerable often wraps internal storage, exposing iteration while controlling how items are added or filtered.

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

public class EvenBag : IEnumerable<int>
{
    private readonly List<int> _items = new List<int>();
    public void Add(int n) { if (n % 2 == 0) _items.Add(n); }

    public IEnumerator<int> GetEnumerator() => _items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var bag = new EvenBag();
        bag.Add(1); bag.Add(2); bag.Add(3); bag.Add(4);
        foreach (var n in bag) Console.WriteLine(n);
    }
}

Custom Enumerables Work With LINQ

Any type implementing IEnumerable<T> instantly gains all of LINQ. Your custom collection can be filtered, projected, and aggregated for free.

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

public class IntRange : IEnumerable<int>
{
    private readonly int _start, _count;
    public IntRange(int start, int count) { _start = start; _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _count; i++) yield return _start + i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var range = new IntRange(1, 10);
        Console.WriteLine(range.Where(n => n % 3 == 0).Sum());
    }
}

Each foreach Gets a Fresh Enumerator

Because GetEnumerator is called each time you iterate, a custom enumerable can be enumerated repeatedly, each pass starting fresh.

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

public class IntRange : IEnumerable<int>
{
    private readonly int _count;
    public IntRange(int count) { _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 1; i <= _count; i++) yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var r = new IntRange(3);
        foreach (var n in r) Console.Write(n);
        Console.WriteLine();
        foreach (var n in r) Console.Write(n); // fresh again
        Console.WriteLine();
    }
}

Generic Custom Enumerable

Make the enumerable generic to hold any element type. Here a simple ring exposes its items in order starting from a chosen offset.

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

public class Ring<T> : IEnumerable<T>
{
    private readonly T[] _items;
    private readonly int _start;
    public Ring(T[] items, int start) { _items = items; _start = start; }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < _items.Length; i++)
            yield return _items[(_start + i) % _items.Length];
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var ring = new Ring<string>(new[] { "a", "b", "c", "d" }, 2);
        foreach (var s in ring) Console.WriteLine(s);
    }
}

Lazy Custom Enumerables

Since GetEnumerator is itself an iterator, your custom enumerable is lazy by default: items are produced only as the consumer pulls them.

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

public class Squares : IEnumerable<int>
{
    private readonly int _count;
    public Squares(int count) { _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 1; i <= _count; i++)
        {
            Console.WriteLine("computing " + i);
            yield return i * i;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Squares(3))
            Console.WriteLine("got " + n);
    }
}

When to Build a Custom Enumerable

Reach for a custom enumerable when iteration order or generation logic is a core part of a type, such as a tree traversal, a paged data source, or a mathematical sequence. Otherwise a plain iterator method is enough.

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

public class Countdown : IEnumerable<int>
{
    private readonly int _from;
    public Countdown(int from) { _from = from; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = _from; i >= 0; i--) yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Countdown(5)) Console.WriteLine(n);
    }
}

Try It Yourself

Build a custom enumerable that produces a geometric sequence, then use LINQ on it. Your type plugs straight into the whole ecosystem.

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

public class Geometric : IEnumerable<int>
{
    private readonly int _start, _ratio, _count;
    public Geometric(int start, int ratio, int count) { _start = start; _ratio = ratio; _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        int value = _start;
        for (int i = 0; i < _count; i++) { yield return value; value *= _ratio; }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var seq = new Geometric(1, 2, 6);
        Console.WriteLine(string.Join(", ", seq));
        Console.WriteLine("sum: " + seq.Sum());
    }
}

Quick Check

Recall the IEnumerable contract.

Recap

A custom enumerable implements IEnumerable<T> so it works with foreach and LINQ.

  • Implement GetEnumerator as an iterator with yield return.
  • Also provide the explicit non-generic GetEnumerator.
  • Each iteration gets a fresh enumerator.
  • Iteration is lazy by default.
Free to start

Learn C# with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
93
Lessons
346

Frequently Asked Questions

Is the “Custom Enumerables” lesson free?

Yes — the full text of “Custom Enumerables” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO. The C# Academy course includes 4 lessons in total.

What will I learn in “Custom Enumerables”?

Implement IEnumerable with iterators. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.

How long does the “Custom Enumerables” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Iterator Methods with yield return
  2. yield break and Early Termination
  3. Lazy Evaluation Semantics
  4. Custom Enumerables
← Back to C# Academy