Brendan Enrick's Blog

Daily Software Development.


Implementing IEnumerable and IEnumerator

Working with a foreach loop is the primary reason to implement the IEnumerable and IEnumerator interfaces. You’ll want one of each of these to work with the loop.

I am going to do an example DateRange class which will implement IEnumerable<DateTime> and will allow us to iterate through a non-existent collection of DateTime objects.

Note: I am aware of the fact that I could achieve the same result with a for loop. I find the foreach loop more readable.

First we need to create a basic DateRange class. A range can be defined as a StartDate and an EndDate, so I’ll start there.

public class DateRange
{
    public DateRange(DateTime startDate, DateTime endDate)
    {
        StartDate = startDate;
        EndDate = endDate;
    }
 
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

So this DateRange could be useful on its own, but we want to be able to iterate this collection using a foreach. So to start we need to implement the IEnumerable<DateTime> interface.

public class DateRange : IEnumerable<DateTime>
{
    public DateRange(DateTime startDate, DateTime endDate)
    {
        StartDate = startDate;
        EndDate = endDate;
    }
 
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
 
    public IEnumerator<DateTime> GetEnumerator()
    {
        return new DateRangeEnumerator(this);
    }
 
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Notice here that we now need to get the IEnumerator<DateTime> object in the GetEnumerator() method. I jumped the gun a bit and I’ve called a class that doesn’t exist yet. I’ll make another class and implement the required methods for the IEnumerator interface.

public class DateRangeEnumerator : IEnumerator<DateTime>
{
    private int _index = -1;
    private readonly DateRange _dateRange;
 
    public DateRangeEnumerator(DateRange dateRange)
    {
        _dateRange = dateRange;
    }
 
    public void Dispose()
    {
    }
 
    public bool MoveNext()
    {
        _index++;
        if (_index > (_dateRange.EndDate - _dateRange.StartDate).Days)
            return false;
        return true;
    }
 
    public void Reset()
    {
        _index = -1;
    }
 
    public DateTime Current
    {
        get { return _dateRange.StartDate.AddDays(_index); }
    }
 
    object IEnumerator.Current
    {
        get { return Current; }
    }
}

These are the handful of methods we implement for the IEnumerator<DateTime> interface. These are all about moving to the next object and getting the current object. Resetting and Disposal of the object are less important, so make sure you read MoveNext and Current.

Keep in mind here that I could have used a collection for this, but I didn’t because I don’t need one. The calculation to get the items was easy enough.

var dateRange = new DateRange(DateTime.Today.AddDays(-6), DateTime.Today);
foreach (DateTime date in dateRange)
{
    Console.WriteLine(date.ToShortDateString());
}

Output:

10/20/2009
10/21/2009
10/22/2009
10/23/2009
10/24/2009
10/25/2009
10/26/2009

kick it on DotNetKicks.com

Trackbacks & Pingbacks

Arjan’s World » LINKBLOG for Oct 27, 2009 — 27 Oct 2009 12:14 PM

Pingback from Arjan’s World » LINKBLOG for Oct 27, 2009


Twitter Trackbacks for Implementing IEnumerable and IEnumerator : Brendan Enrick's Blog [enrick.com] on Topsy.com — 28 Oct 2009 2:58 AM

Pingback from Twitter Trackbacks for Implementing IEnumerable and IEnumerator : Brendan Enrick's Blog [enrick.com] on Topsy.com


Trackback link for this post:
http://brendan.enrick.com/trackback.ashx?id=201

Comments

 avatar

hwiechers said on 27 Oct 2009 at 10:44 AM

Is there a reason that you didn't use iterators?

Also another bonus to implementing IEnumerable is that you can use Linq on the items.

benrick avatar

Brendan Enrick said on 27 Oct 2009 at 1:46 PM

Linq is one of the main reasons for doing it this way. I also wanted to show that implementations of interfaces are not always what they seem.

This interface is commonly thought of as a collection of some sort, but it really doesn't need to be. This shows that interfaces are very loose and allow us to be creative.

 avatar

Damien said on 28 Oct 2009 at 7:22 AM

This could easily be a useful class if it, for instance, skipped weekends/holidays. Try writing that cleanly in a for loop...

 avatar

Steve said on 28 Oct 2009 at 7:46 AM

Check out yield, you can write stuff like:

public IEnumerable<DateTime> GetEnumerator()

{

int days = (EndDate - StartDate).Days;

for (int i = 0; i < days; i++)

yield return StartDate.AddDays(i);

}

benrick avatar

Brendan Enrick said on 28 Oct 2009 at 9:11 AM

Thanks Damien. You're absolutely right. It is nice to be able to encapsulate logic such as that into a nice, testable class.

benrick avatar

Brendan Enrick said on 28 Oct 2009 at 9:16 AM

Yes, Steve, yield is another great way to get an IEnumerable. Thanks for the comment!

As I was saying to hwiechers. I was really just trying to write a post specifically about implementing a couple of interfaces, so using yield to simulate an IEnumerable really wouldn't have achieved my goal.

 avatar

Razispio said on 03 Nov 2009 at 11:12 AM

Well if you went to the extent of creating your IEnumerator class (instead of using yield), you might as well make it a struct (value type) so that there is no allocation when used in a foreach loop. BCL's List<T> and other collections do that.

Leave a Comment

Please join the discussion and share your thoughts.