Enumeration on a custom class

How to make a custom class enumerable (work with foreach)

Home DailyDrop

Daily Knowledge Drop

To enable enumeration on a class (ability to foreach on the class), the only requirement is for a method named GetEnumerator to exist on the class.

There is no requirement for the class to implement any interface (IEnumerable, IEnumerator etc), just the presence of the method is enough.


Without GetEnumerator

Last week we looked at adding indexing to a class - adding enumeration is similar, so we'll use similar examples.

In the examples below, we are using a ProductPrice entity. This class keeps some basic details of a product, as well as an array of prices, one price for each month of the year. So Prices[0] is the price for January, Prices[1] is the price for February and so on.

public class ProductPrice
{
    public int ProductId { get; set; }

    public string ProductName { get; set; }

    public decimal[] Prices { get; set; }

    public ProductPrice(int productId, string productName, decimal[] prices)
    {
        ProductId = productId;
        ProductName = productName;
        Prices = prices;
    }
}

To iterate through each price for the product, we can do this via the Prices property exposed on the class.

var pp = new ProductPrice(112, "Green t-shirt", 
    new decimal[] { 0, 0, 0, 100, 100, 80, 80, 50, 50, 100, 100, 60 });

foreach (var price in pp.Prices)
{
    Console.WriteLine(price);
}

This code will function just fine - however the ProductPrice class can be extended to make it more intuitive and easier to work with.


With GetEnumerator

The main purpose of the ProductPrice class is to store price information related to a product. So let's update the class so that we can access a price directly, without going via the Price property.

ProductPrice class update:

public class ProductPrice
{
    public int ProductId { get; set; }

    public string ProductName { get; set; }

    // Now private
    private decimal[] Prices { get; }

    public ProductPrice(int productId, string productName, decimal[] prices)
    {
        ProductId = productId;
        ProductName = productName;
        Prices = prices;
    }

    // New method called GetEnumerator added. 
    // The return type of the method is IEnumerator<T>, 
    // where T is the specific type being returned
    public IEnumerator<decimal> GetEnumerator()
    {
        //  The items in the underlying Prices array are 
        // `yield returned` one at a time
        foreach (var price in Prices)
            yield return price;
    }
}

If unfamiliar with the yield keyword, in short it indicates that the method in which it appears is an iterator, and will return one item at a time, one for each iteration.

The ProductPrice class can now be used as follows:

var pp = new ProductPrice(112, "Green t-shirt", 
    new decimal[] { 0, 0, 0, 100, 100, 80, 80, 50, 50, 100, 100, 60 });

// No need to go via Prices
foreach (var price in pp)
{
    Console.WriteLine(price);
}

Field enumeration

Another use for GetEnumerator method is to be able to enumerate through each fields/property on a class.

In the below example, we have an Address class which has a number of string fields:

public class Address
{
    public string Number { get; set; }

    public string Line1 { get; set; }

    public string Line2 { get; set; }

    public string Suburb { get; set; }

    public string Town { get; set; }

    public string Province { get; set; }

    public Address(string number, string line1, string line2, 
        string suburb, string town, string province)
    {
        Number = number;
        Line1 = line1;
        Line2 = line2;
        Suburb = suburb;
        Town = town;
        Province = province;
    }

    public IEnumerator<string> GetEnumerator()
    {
        yield return Number;
        yield return Line1;
        yield return Line2;
        yield return Suburb;
        yield return Town;
        yield return Province;
    }
}

The GetEnumerator method has been added even though this class doesn't have an internal array of items to iterate over. Instead we will iterate over each property of the class.
Each property of the class is yield returned one at a time (so each time the iterator is called, the next item will be returned)

This allows for accessing a property via an index. This can be leveraged to iteration through the properties on the class:

var address = new Address("11", "Main Road", "XYZ Estate", 
    "Suburb1", "Town2", "Province3");

foreach (var field in address)
{
    Console.WriteLine(field);
}

The output:

11
Main Road
XYZ Estate
Suburb1
Town2
Province3

Notes

Similarly to working with indexers, adding your own enumeration will generally not be required for everyday use. However its advantageous to know that the option exists, and is as simple as adding a GetEnumerator method to the class in question.


Daily Drop 23: 03-03-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net iteration enumeration getenumerator foreach