Custom Enumerables
Implement IEnumerable with iterators.
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
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.
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
- Iterator Methods with yield return
- yield break and Early Termination
- Lazy Evaluation Semantics
- Custom Enumerables