Channels ▼
RSS

Parallel

Fixing the enum in C#


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

Custom Configuring Java Apps: Extending log4j

Type-Safe File-Based Configuration


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video