Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

The Enumerable Enumerator

Rate me:
Please Sign up or sign in to vote.
4.80/5 (22 votes)
2 Nov 20062 min read 56.3K   321   43   6
Iterate over your enums, and other things to do with enums.

Introduction

The Enumerable Enumerator is something I threw together to address a need to iterate and sequence through enumerations. I find this class useful when working with hardware and dealing with such things as bit fields. I also found application for this class in managing state and sequencing through states. For example, I may have an enumeration of bits:

C#
enum AlarmBits
{
  Door=1,
  Window=2,
  Security=4,
  CoinJam=8,
  BillJam=16,
}

and I simply want to iterate through the enumeration to test each bit.

Or, I may have a set of states:

C#
enum States
{
  Slow,
  Medium,
  Fast
}

and in this case, I want to be able to change state in a forward or backwards direction but not exceed the state limits. As a side benefit, I found that using the methods in this class resulted in more robust application code, as I could change the enumeration without having to go back and update the application code. If I add an alarm bit or a state, I'm only changing the enumeration, not the code that implements iterating through the enumeration or managing a state.

The Code

The class Enumerator consists of the following properties and methods. All the examples use this enum:

C#
enum Test
{
  A=1,
  B=2,
  C=3,
  D=4,
}

You will note that all the methods are static because they operate on the enumeration type as opposed to an instance of the enumeration. The use of generics improves type checking (for example, the current value and the minimum value must be of the same type) and avoids downcasting of the returned value in the application code.

First

Returns the first value in the enumerator. As with all these methods, the enumerations are sorted by value, which may not be the same as the ordinal value.

C#
public static T First
{
  get { return ((T[])Enum.GetValues(typeof(T)))[0]; }
}

Example:

C#
Console.WriteLine("First = " + Enumerator<Test>.First);

The output is:

First = A

Last

Returns the last enumeration value.

C#
public static T Last
{
  get 
  {
    T[] vals = (T[])Enum.GetValues(typeof(T));
    return vals[vals.Length-1]; 
  }
}

Example:

C#
Console.WriteLine("Last = " + Enumerator<Test>.Last);

The output is:

Last = D

PreviousOrFirst

This method returns the previous enumeration value, or returns the first value if the enumeration cannot be decremented further. I find this method, and the corresponding NextOrLast method, useful to work with enumerators that represent state.

C#
public static T PreviousOrFirst(T val)
{
  T[] vals = (T[])Enum.GetValues(typeof(T));
  int idx = Array.FindIndex<T>(vals, delegate(T v) { return v.Equals(val); });
  
  return idx == 0 ? vals[0] : vals[idx - 1];
}

Example:

C#
Console.WriteLine("Previous Or First of A = " + 
    Enumerator<Test>.PreviousOrFirst(Test.A));
Console.WriteLine("Previous Or First of B = " + 
    Enumerator<Test>.PreviousOrFirst(Test.B));

Outputs:

Previous Or First of A = A
Previous Or First of B = A

NextOrLast

This method is the compliment of the PreviousOrFirst method.

C#
public static T NextOrLast(T val)
{
  T[] vals = (T[])Enum.GetValues(typeof(T));
  int idx = Array.FindIndex<T>(vals, delegate(T v) { return v.Equals(val); });

  return idx == vals.Length-1 ? vals[idx] : vals[idx + 1];
}

Example:

C#
Console.WriteLine("Next Or Last of C = " + 
  Enumerator<Test>.NextOrLast(Test.C));
Console.WriteLine("Next Or Last of D = " + 
  Enumerator<Test>.NextOrLast(Test.D));

The output is:

Next Or Last of C = D
Next Or Last of D = D

PreviousBounded

It seemed reasonable to also supply a couple methods that are bounded by a supplied enum value rather than the first or last enum value.

C#
public static T PreviousBounded(T val, T min)
{
  T[] vals = (T[])Enum.GetValues(typeof(T));
  int curIdx = Array.FindIndex<T>(vals, delegate(T v) {return v.Equals(val);});
  int minIdx = Array.FindIndex<T>(vals, delegate(T v) {return v.Equals(min);});
  return curIdx <= minIdx ? vals[minIdx] : vals[curIdx - 1];
}

Example:

C#
Console.WriteLine("Enumerator low limit (C->B) = " + 
  Enumerator<Test>.PreviousBounded(Test.C, Test.B));
Console.WriteLine("Enumerator low limit (B->B) = " + 
  Enumerator<Test>.PreviousBounded(Test.B, Test.B));
Console.WriteLine("Enumerator low limit (A->B) = " + 
  Enumerator<Test>.PreviousBounded(Test.A, Test.B));

The output is:

Enumerator low limit (C->B) = B
Enumerator low limit (B->B) = B
Enumerator low limit (A->B) = B

NextBounded

public static T NextBounded(T val, T max)
{
  T[] vals = (T[])Enum.GetValues(typeof(T));
  int curIdx = Array.FindIndex<T>(vals, delegate(T v) { return v.Equals(val); });
  int maxIdx = Array.FindIndex<T>(vals, delegate(T v) { return v.Equals(max); });

  return curIdx >= maxIdx ? vals[maxIdx] : vals[curIdx + 1];
}

Example:

C#
Console.WriteLine("Enumerator high limit (B->C) = " + 
  Enumerator<Test>.NextBounded(Test.B, Test.C));
Console.WriteLine("Enumerator high limit (C->C) = " + 
  Enumerator<Test>.NextBounded(Test.C, Test.C));
Console.WriteLine("Enumerator high limit (D->C) = " + 
  Enumerator<Test>.NextBounded(Test.D, Test.C));

The output is:

Enumerator high limit (B->C) = C
Enumerator high limit (C->C) = C
Enumerator high limit (D->C) = C

Enumerating

The original goal was simply to be able to iterate over the enumeration (changed as per the suggestions in the comments):

C#
public static IEnumerable<T> Items()
{
  return (T[])Enum.GetValues(typeof(T));
}

Example:

C#
foreach (Test t in Enumerator<Test>.Items())
{
  Console.WriteLine(t + " = " + Convert.ToInt32(t));
}

The output is:

A = 1
B = 2
C = 3
D = 4

Conclusion

So, it's a simple class but it was fun to write, provides some useful functionality, and helps to decouple the enumeration implementation from the application logic, which is something I always find useful.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
Generalpet peeve Pin
wurakeem10-Nov-06 8:40
wurakeem10-Nov-06 8:40 
GeneralRe: pet peeve Pin
Marc Clifton10-Nov-06 9:28
mvaMarc Clifton10-Nov-06 9:28 
GeneralSuggestion Pin
Steve Hansen2-Nov-06 6:31
Steve Hansen2-Nov-06 6:31 
GeneralRe: Suggestion Pin
Christian Klauser2-Nov-06 6:49
Christian Klauser2-Nov-06 6:49 
GeneralRe: Suggestion Pin
Marc Clifton2-Nov-06 8:23
mvaMarc Clifton2-Nov-06 8:23 
GeneralRe: Suggestion Pin
Marc Clifton2-Nov-06 8:20
mvaMarc Clifton2-Nov-06 8:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.