Custom Enumerables
Implement IEnumerable with iterators.
Custom Enumerables is a free C# Academy lesson on CoddyKit — 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, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
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
Recap
A custom enumerable implements IEnumerable<T> so it works with foreach and LINQ.
- Implement
GetEnumeratoras an iterator withyield return. - Also provide the explicit non-generic
GetEnumerator. - Each iteration gets a fresh enumerator.
- Iteration is lazy by default.
Frequently asked questions
Is the “Custom Enumerables” lesson free?
Yes — the full text of “Custom Enumerables” is free to read here on the web, and the C# Academy course includes 4 lessons in total. 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.
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; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
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.