Click here to Skip to main content
6,593,923 members and growing! (12,275 online)
Email Password   helpLost your password?
General Programming » Collections » General     Beginner License: The Code Project Open License (CPOL)

Custom Enumerators

By Jaime Olivares

Modify the behaviour of any enumerator to make it circular, constrained or stepped. Also reversible enumerator.
C#, Windows, Win Mobile, .NET CF, .NET, Mobile, Visual Studio, Architect, Dev, Design
Version:2 (See All)
Posted:30 Aug 2008
Views:11,040
Bookmarked:21 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
4 votes for this article.
Popularity: 2.41 Rating: 4.00 out of 5

1
1 vote, 25.0%
2

3
1 vote, 25.0%
4
2 votes, 50.0%
5

Introduction

I like to use the enumerators features of .NET Framework, specially the foreach keyword. The code looks really clean and is easy to understand and maintain. But enumerators lack several desirable features like:

  • Circularity
  • Reversibility
  • Constraining
  • Stepping

I have created a helper class that enhances enumeration of existing collection enumerators. Since there are lots of collections in .NET's System.Collections and System.Collections.Generic namespaces, with proper enumerators, even a developer can create his/her own collection classes with custom enumerators, the solution must point to modify the behaviour of those enumerators instead of creating custom enumerators for each kind of collection.

The Helper Class

The helper class wraps a set of static methods that can be used without instantiating an object. They are defined in the System.Collections namespaces, so you never have to worry about using an extra namespace in your source file.

namespace System.Collections
{
    public class Enumerators
    {
        public static IEnumerable CircularEnum(IEnumerable _enumerator) ...
        public static IEnumerable ReverseEnum(IEnumerable _enumerator) ...
        public static IEnumerable SteppedEnum(IEnumerable _enumerator, int _step) ...
        public static IEnumerable ConstrainedEnum
		(IEnumerable _enumerator, int _start) ...
        public static IEnumerable ConstrainedEnum
		(IEnumerable _enumerator, int _start, int _count) ...
    }
}

The Sample Collection

As can be read in the previous code, all enumerator methods will receive another enumerator object (any derived from IEnumerable interface). For testing purposes, I have included a SortedList generic collection, that can be enumerated in key/value pairs or individually by key or value lists.

SortedList<int,> list = new SortedList<int,string>();

list.Add(1, "one");
list.Add(2, "two");
list.Add(3, "three");
list.Add(4, "four");
list.Add(5, "five");
list.Add(6, "six");
list.Add(7, "seven");
list.Add(8, "eight");
list.Add(9, "nine");
list.Add(10, "ten");

Circular Enumerator

The simplest enumerator is the circular, processed by the CircularEnum method; it will invoke the original enumerator inside an infinite while loop, as follows:

public static IEnumerable CircularEnum(IEnumerable _enumerator)
{
    while (true)
    {
         IEnumerator enu = _enumerator.GetEnumerator();
         while (enu.MoveNext())
         {
             yield return enu.Current;
         }
    }
}

There should be some kind of control to stop the infinite loop under certain condition. In the sample code, there is a constant defined as:

const int max = 15;  // Max number of iterations to stop circular enumerator

Having defined a stop behaviour, the circular enumerator can be used as:

Console.WriteLine("Dictionary circular enumeration:");
int i = 0;
foreach (KeyValuePair<int, string> pair in Enumerators.CircularEnum(list))
{
    Console.WriteLine("   " + pair.ToString());
    if (++i >= max)
        break;   // stop circular enumerator, will be infinite if not
}
Console.WriteLine("   (stopped)\r\n");

OUTPUT

Dictionary circular enumeration:
   [1, one]
   [2, two]
   [3, three]
   [4, four]
   [5, five]
   [6, six]
   [7, seven]
   [8, eight]
   [9, nine]
   [10, ten]
   [1, one]
   [2, two]
   [3, three]
   [4, four]
   [5, five]
   (stopped)

Constrained Enumerator

Sometimes it is needed to traverse just a portion of an enumerated collection, something like the Array.Copy() method. The ConstrainedEnum method has two versions to allow specify a starting element and optionally an element count. First element has zero index, and count can be zero or greater.

public static IEnumerable ConstrainedEnum(IEnumerable _enumerator, int _start)
{
    if (_start < 0)
        throw new ArgumentException
	("Invalid step value, must be positive or zero.");

    IEnumerator enu = _enumerator.GetEnumerator();
    while (enu.MoveNext())
    {  
        if (--_start < 0)
            yield return enu.Current;
    }
}

public static IEnumerable ConstrainedEnum
	(IEnumerable _enumerator, int _start, int _count)
{
    if (_start < 0)
        throw new ArgumentException
	("Invalid step value, must be positive or zero.");
    if (_count < 0)
        throw new ArgumentException
	("Invalid count value, must be positive or zero.");

    if (_count > 0)
    {
        IEnumerator enu = _enumerator.GetEnumerator();
        if (enu.MoveNext())
        {
            while (--_start > 0)
            {
                if (!enu.MoveNext())
                    break;
            }
            if (_start <= 0)
            {
                while (--_count >= 0)
                {
                    if (enu.MoveNext())
                        yield return enu.Current;
                    else
                        break;
                }
            }
        }
    }
}

The sample code uses the second version of the ConstrainedEnum method:

Console.WriteLine("Constrained enumeration (2,5):");
foreach (KeyValuePair<int,> pair in Enumerators.ConstrainedEnum(list, 2, 5))
{
    Console.WriteLine("   " + pair.ToString());
}
Console.WriteLine("   (finished)\r\n");

OUTPUT

Constrained enumeration (2,5):
   [3, three]
   [4, four]
   [5, five]
   [6, six]
   [7, seven]
   (finished)

Stepped Enumerator

This method (SteppedEnum) allows to traverse a collection skipping some elements. Using a step value 1 will behave like a regular enumerator, step value 2 will skip one element in every iteration, etc.

public static IEnumerable SteppedEnum(IEnumerable _enumerator, int _step)
{
    if (_step < 1)
        throw new ArgumentException
	("Invalid step value, must be greater than zero.");

    IEnumerator enu = _enumerator.GetEnumerator();
    while (enu.MoveNext())
    {
        yield return enu.Current;

        for (int i = _step; i > 1; i--)
            if (!enu.MoveNext())
                break;
    }
}

The sample code will enumerate a collection skipping two elements in each iteration:

Console.WriteLine("Stepped enumeration (3):");
foreach (int value in Enumerators.SteppedEnum(list.Keys, 3))
{
    Console.WriteLine("   " + value.ToString());
}
Console.WriteLine("   (finished)\r\n");

OUTPUT

Stepped enumeration (3):
   1
   4
   7
   10
   (finished)

Bonus: Reverse Enumerator

.NET enumerators (derived from IEnumerable) are not designed to be traversed backwards, then, in theory, there cannot be a reverse enumerator. The ReverseEnum method does some reflection processing to find an indexer property in the collection represented by the passed enumerator object. Reverse enumeration process will be achieved by accessing each element in the collection by using its index. The method will throw an exception if the collection doesn't have a Count and an Item properties, so the collection should be derived from IList or IList<> interfaces.

public static IEnumerable ReverseEnum(IEnumerable _enumerator)
{
    System.Reflection.PropertyInfo countprop = 
	_enumerator.GetType().GetProperty("Count", typeof(int));
    if (countprop == null)
        throw new ArgumentException
	("Collection doesn't have a Count property, cannot enumerate.");

    int count = (int)countprop.GetValue(_enumerator, null);
    if (count<1)
        throw new ArgumentException("Collection is empty");

    System.Reflection.PropertyInfo indexer = 
	_enumerator.GetType().GetProperty("Item", new Type[] { typeof(int) });
    if (indexer == null)
        throw new ArgumentException
          ("Collection doesn't have a proper indexed property, cannot enumerate.");

    for (int i = count - 1; i >= 0; i--)
        yield return indexer.GetValue(_enumerator, new object[] { i });
}

For this method, the sample code will traverse the Values collection only, not the key/value pairs:

Console.WriteLine("Reverse enumeration:");
foreach (string value in Enumerators.ReverseEnum(list.Values))
{
    Console.WriteLine("   " + value.ToString());
}
Console.WriteLine("   (finished)\r\n");

OUTPUT

Reverse enumeration:
   ten
   nine
   eight
   seven
   six
   five
   four
   three
   two
   one
   (finished)

Combining Enumerators

The enumerators can be combined (chained) in any way, but you should be aware about the effect produced. It is not the same as circular-stepped enumerator or a stepped-circular enumerator. The sample code combines all the four kinds of enumerators in a single foreach statement:

Console.WriteLine
    ("Combined Constrained(2,6)-Stepped(3)-Circular-Reverse enumeration:");
i = 0;
foreach (int value in Enumerators.ConstrainedEnum
    (Enumerators.SteppedEnum(Enumerators.CircularEnum
    (Enumerators.ReverseEnum(list.Keys)), 3), 2, 6))
{
    Console.WriteLine("   " + value.ToString());
    if (++i >= max * 2)
        break;   // stop circular enumerator, will be infinite if not
}
Console.WriteLine("   (finished)");

OUTPUT

Combined Constrained(2,6)-Stepped(3)-Circular-Reverse enumeration:
   4
   1
   8
   5
   2
   9
   (finished)

Using the Source Code

The provided source code is built using Visual C# 2008, so trying to compile the solution in a lower version may not be possible. Anyway, you can simply create a new console project and attach the Program.cs and Enumerators.cs source files. To use the Enumerators class into your own project, you just need the last one.

History

  • August 30, 2008: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jaime Olivares


Member


Computer Electronics professional and senior Windows C++ and C# developer with experience in many other programming languages, platforms and application areas including communications, simulation systems, GIS, graphics and mobile issues.
Also have experience in electronic interfaces development, specially for military applications.
Currently intensively working with Visual C# 2008.
Top-100 contributor at Experts-Exchange forum.
Occupation: Software Developer (Senior)
Company: Royal Philips Electronics - Healthcare Informatics
Location: Peru Peru

Other popular Collections articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 12 of 12 (Total in Forum: 12) (Refresh)FirstPrevNext
GeneralFew questions PinmemberMember 159112721:25 2 Sep '08  
GeneralRe: Few questions PinmemberJaime Olivares5:50 5 Sep '08  
GeneralFunction Extensions Pinmembereyanson5:55 31 Aug '08  
GeneralRe: Function Extensions PinmemberJaime Olivares12:12 1 Sep '08  
GeneralRe: Function Extensions PinmemberQwertie15:36 9 Sep '08  
GeneralRe: Function Extensions PinmemberJaime Olivares15:40 9 Sep '08  
GeneralEnumerators must be disposed PinmemberDaniel Grunwald23:02 30 Aug '08  
GeneralRe: Enumerators must be disposed PinmemberJaime Olivares12:17 1 Sep '08  
GeneralRe: Enumerators must be disposed PinmemberDaniel Grunwald13:36 1 Sep '08  
GeneralUnachievable goal PinmemberPIEBALDconsult17:19 30 Aug '08  
GeneralRe: Unachievable goal PinmemberJaime Olivares17:24 30 Aug '08  
GeneralRe: Unachievable goal PinmemberJaime Olivares17:26 30 Aug '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 30 Aug 2008
Editor: Deeksha Shenoy
Copyright 2008 by Jaime Olivares
Everything else Copyright © CodeProject, 1999-2009
Web20 | Advertise on the Code Project