Moving on to the initiallization, I first get a list of those subclass fields that are both static and public by specifying
so-called BindingFlags to the GetField method:
FieldInfo[] theFields =
typeof(T).GetFields( BindingFlags.Static
| BindingFlags.Public | BindingFlags.DeclaredOnly);
Now, I spin through the list, pulling out only those elements that are also readonly (there's not BindingFlags for this property,
unfortunately):
foreach( var item in theFields )
{ if( item.IsInitOnly )
//...
Finally, I get the actual derived-class field that the FieldInfo describes:
var theValue = (T) item.GetValue(null);
and, provided that that field is an instance of the subclass type, I add it to the dictionary:
if (theValue.GetType() == typeof (T))
dict.Add(item.Name, theValue);
Of the subclass fields, only those that are exactly the same type as the subclass and are also static, public and readonly are
added to the list. That is, we'll pull out only the enum elements.
Since other fields are just ignored, you're free to use them in your subclass definition without difficulty.
The rest of the class definition is easier, starting with the Name() method (which returns the enum-element name as a string):
public string Name()
{ return fieldName ?? (fieldName = DiscoverFieldName(out myOrdinal));
}
private string DiscoverFieldName(out int ordinalValue)
{
int position = -1;
foreach( var element in enumElements.Value ) // find the requested element
{
++position;
if (element.Value == this)
{ ordinalValue = position;
return element.Key;
}
}
Debug.Assert(fieldName != null);
ordinalValue = -1;
return null;
}
Even though there is no explicit locking, these methods are actually thread safe.
First, the enumElements table is initialized safely by the
Lazy<T> object (when the enumElements.Value property is accessed the first time). Thereafter, it is used in a read-only way by discoverFieldName(). The Name() might be called simultaneously by several threads, but in
all cases, the values returned from discoverFieldName() will be the
same, so locking is unnecessary.
The Name() method also assigns ordinal values (Ordinal() calls Name()).
The relevant code just spins through the dictionary elements and uses the position in the list as the
ordinal value. I'm foolishly leveraging the fact that the reflection APIs return the
field definitions in declaration order, and that the order is preserved when I put the fields int
enumElements to get the correct value. That is, I'm assuming that Dictionary is a linear list.
I probably shouldn't do that, since I'm accessing it through an interface.
The next few methods simply provide simple aliases for other methods:
override public string ToString() { return Name(); }
public String Description() { return Name().Replace("_", " ").ToLower(); }
public override int GetHashCode() { return Ordinal(); }
public override bool Equals(object obj) { return obj == this; }
public int Ordinal() { Name(); return myOrdinal; }
The Values() method returns an IEnumerable<T>, from which you can extract an Enumerator across
the subclass elements (that is, across the values in the enumElements dictionary).
I have to go to the trouble of creating an IEnumerable<T> object because the foreach
statement requires an IEnumerable<T>, not the underlying Enumerator. This one does nothing
but fetch the Enumerator out of the dictionary's Values set:
public static IEnumerable<T> Values()
{ return new EnumerationFactory();
}
private class EnumerationFactory : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator()
{ return enumElements.Value.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{ return enumElements.Value.Values.GetEnumerator();
}
}
Finally, the ValueOf(string) method is a static method that returns the subclass object referenced
by the field named in the string argument:
public static T ValueOf( string name )
{ T value;
if( !enumElements.Value.TryGetValue(name, out value))
throw new ArgumentException( "No enum element named " + name + " was found in " + typeof(T).Name );
return value;
}
}
}
Conclusion
Although C# enums have their problems, it's easy to solve them by using what amounts to the
enum design pattern. A small reusable superclass adds useful methods missing from a simple
roll-your-own enum, so there's no practical reason to use one over the other and the roll-your-own
version lets you use methods, constructors, interfaces, and so on.
Related Articles
Solving the Configuration Problem for Java Apps



