Click here to Skip to main content
5,786,882 members and growing! (24,442 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

Posted: 30 Aug 2008
Updated: 30 Aug 2008
Views: 5,402
Bookmarked: 14 times
Note: This is an unedited reader contribution
Announcements
Loading...



Search    
Advanced Search
Sitemap
3 votes for this Article.
Popularity: 1.91 Rating: 4.00 out of 5
0 votes, 0.0%
1
1 vote, 33.3%
2
0 votes, 0.0%
3
0 votes, 0.0%
4
2 votes, 66.7%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

I like to use the enumerators features of .Net framework, specially the foreach keyword. Code looks really clean and easy to understand and mantain. But enumerators lacks of several desirable features like:

  • Circularity
  • Reversibility
  • Constraining
  • Stepping

I have created a helper class that enhance enumeration of existing collection enumerators. Since there are lots of collections in .Net's System.Collections and System.Collections.Generic namespaces, with proper enumerators, and even developer can create its 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. Them are defined in the System.Collections namespaces, so you never have to worry about using an extra namespace into 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 list = new SortedListstring />();

      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 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 do 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 a circular-stepped enumerator than 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, just you 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




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: Philips Healthcare Informatics
Location: Peru Peru

Other popular Collections articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 12 of 12 (Total in Forum: 12) (Refresh)FirstPrevNext
GeneralFew questionsmemberMember 159112721:25 2 Sep '08  
GeneralRe: Few questionsmemberJaime Olivares5:50 5 Sep '08  
GeneralFunction Extensionsmembereyanson5:55 31 Aug '08  
GeneralRe: Function ExtensionsmemberJaime Olivares12:12 1 Sep '08  
GeneralRe: Function ExtensionsmemberQwertie15:36 9 Sep '08  
GeneralRe: Function ExtensionsmemberJaime Olivares15:40 9 Sep '08  
GeneralEnumerators must be disposedmemberDaniel Grunwald23:02 30 Aug '08  
GeneralRe: Enumerators must be disposedmemberJaime Olivares12:17 1 Sep '08  
GeneralRe: Enumerators must be disposedmemberDaniel Grunwald13:36 1 Sep '08  
GeneralUnachievable goalmemberPIEBALDconsult17:19 30 Aug '08  
GeneralRe: Unachievable goalmemberJaime Olivares17:24 30 Aug '08  
GeneralRe: Unachievable goalmemberJaime 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:
Copyright 2008 by Jaime Olivares
Everything else Copyright © CodeProject, 1999-2009
Web16 | Advertise on the Code Project