Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Enum Builder: The Curious Case of the Inheritable Enum

0.00/5 (No votes)
1 Nov 2009 1  
Proves that we can create a class that behaves like an inheritable enum
Father and son inherit from 'grandfather', so begin numbering after him.  Uncle and nephew form their own parallel branch, also numnbering after the grandfather.  Enum Builder can inherit and branch endlessly.

Introduction

Programmers often express their frustration over the limitations of System.Enum. The worst of these is the failure of the Enum to expand and scale within the natural growth of a program's inheritance tree. Enum Builder eliminates this constraint.

This project also illustrates some powerful features of .NET:

  • It allows the end users to create their own 'enum' classes with a few lines of code, without even declaring a constructor, and yet they can enjoy powerful functionality. This is achieved through a strong interface and a robust abstract class.
  • It uses reflection and static fields to inject values into inheriting classes. This would seem intuitively impossible.
  • It behaves exactly like an Enum by implementing an indexer.
  • It implements the Singleton design pattern, though there are limitations due to the simple end class design.
  • It lazily auto-instantiates classes as needed to build proper enum-like values, so the end user never has to (and should not) ever call new.

Using the Code

  1. Copy Enum_Builder.dll from the folder Enum_Builder\bin\Release to your project folder.
  2. Add a reference to the builder. Use the Browse option and choose the Enum_Builder.dll you just copied.
  3. In any class where you wish to use an Enum Builder, import the namespace (the underscores indicate a namespace vs. a class):
  4. using Marcus_Technical_Services.Enum_Builder;
  5. Declare a class that inherits EnumBuilder. Decide on the access modifier; public is often wise if you intend to use these values elsewhere:
     public class EnumBuilder_Grandfather : EnumBuilder
     {
     }
  6. Add your 'enum' values as public static strings. Do not assign any values to these strings; Enum Builder will over-write them. In the example below, GRANDFATHER_HARRY will become 0, GRANDFATHER_BRUCE will get 1, etc.
  7.  public class EnumBuilder_Grandfather : EnumBuilder
     {
      public static string GRANDFATHER_HARRY;
      public static string GRANDFATHER_BRUCE;
     }
  8. In this or any other class, you can inherit your own newly created Enum Builder by creating another two:
  9.  public class EnumBuilder_Father : EnumBuilder_Grandfather
     {
      public static string FATHER_BOB;
      public static string FATHER_CHARLES;
      public static string FATHER_WILLIAM;
      public static string FATHER_ALFRED;
     }
    
     public class EnumBuilder_Son : EnumBuilder_Father
     {
      public static string SON_JAMES;
      public static string SON_DYLAN;
      public static string SON_AVERY;
     }
  10. At run-time, these values will begin after those of their base class, so FATHER_BOB will become 2, SON_JAMES will get 6, etc.
  11. Enum Builder also supports branching inheritance, so if you declare another set of classes that inherit from EnumBuilder_Grandfather, they will get the same numbering scheme as the father and son, since they are parallel:
  12.  public abstract class EnumBuilder_Uncle : EnumBuilder_Grandfather
     {
      public static string UNCLE_ABE;
      public static string UNCLE_JONAH;
      public static string UNCLE_TIM;
      public static string UNCLE_ALBERT;
     }
    
     public abstract class EnumBuilder_Nephew : EnumBuilder_Uncle
     {
      public static string NEPHEW_SIGGY;
      public static string NEPHEW_MAURY;
      public static string NEPHEW_ANTHONY;
     }

    You can make any inheriting classes abstract. This is another potent feature of Enum Builder. The public static strings inside these last two classes will increment as usual, but they will not be public referenceable because they cannot be instantiated.

  13. Enum Builders can also be used in more exotic ways, such as with this internal example
  14. For testing, I inherited from nephew from the inheritance tree above. To challenge Enum Builder, I also nested this class inside of another class.

     public abstract class EnumBuilder_InternalUsage_Abs
     {
      public abstract class EnumBuilder_Internal : EnumBuilder_Nephew
      {
       public static string JULIE;
       public static string MARY;
      }
     }

    Next I inherited the abstract class above and inserted an inherited reference to the nested class above. The question is, will MARGARET increment beyond JULIE and MARY? (The answer is in the screen shot at the top of this article).

     public class EnumBuilder_InternalUsageTest : EnumBuilder_InternalUsage_Abs
     {
      public class EnumBuilderInternal : EnumBuilder_InternalUsage_Abs.EnumBuilder_Internal
      {
       
       public static string MARGARET;
       public static string PEGGY;
       public static string ALICE;
       public static string BEATRICE;
      }
     }
  15. Per the rules of transcendental meditation (sit perfectly still. If you must wiggle, wiggle): Do not muck with these classes. If you must disobey this precaution, do not add public static string for any other purpose except as stated here, as they will definitely get sucked and used as part of your 'enums'!
  16. There are a number of ways to use these newly created Enum Builders:
    • The Wrong Way

    • I include this because I know you are going to do it even if I ask you not to:

       EnumBuilder_Grandfather testGrandfather = 
      		new EnumBuilder_Grandfather();
       Console.WriteLine();
       Console.WriteLine("Testing GRANDFATHER:");
       Console.WriteLine();
       Console.WriteLine("       Trying Harry: ->" + 
      	testGrandfather[EnumBuilder_Grandfather.GRANDFATHER_HARRY] + "<-");
       Console.WriteLine("       Trying Bruce: ->" + 
      	testGrandfather[EnumBuilder_Grandfather.GRANDFATHER_BRUCE] + "<-");
       testGrandfather = null;

      Amazingly, this works, but it also creates a new instance of your Enum Builder repeatedly and without productive purpose. Note that you get the enum value by indexing ("[]") with the name of your custom class EnumBuilder_Grandfather and a dot, just as you would a 'normal' enum.

    • The Right Way -- Sort Of

    • You can create a local variable and assign it using the static instantiator from the abstract class EnumBuilder:

       EnumBuilder_Father testfather = EnumBuilder.Instance<EnumBuilder_Father>();
       Console.WriteLine();
       Console.WriteLine("     Testing FATHER:");
       Console.WriteLine();
       Console.WriteLine("         Trying Bob: ->" + 
      		testfather[EnumBuilder_Father.FATHER_BOB] + "<-");
       Console.WriteLine("     Trying Charles: ->" + 
      		testfather[EnumBuilder_Father.FATHER_CHARLES] + "<-");
       Console.WriteLine("     Trying William: ->" + 
      		testfather[EnumBuilder_Father.FATHER_WILLIAM] + "<-");
       Console.WriteLine("      Trying Alfred: ->" + 
      		testfather[EnumBuilder_Father.FATHER_ALFRED] + "<-");
       testfather = null;

      This is fine, but it does burn up a local variable that you must then dispose. Frankly, we don't need it.

    • The Real Right Way

    • Just refer to the static instantiator; it builds one instance of your custom Enum Builder class and thereafter reuses it (that's all in the internal plumbing):

       Console.WriteLine("        Testing SON:");
       Console.WriteLine("       Trying James: ->" + 
      	EnumBuilder.Instance<EnumBuilder_Son>()[EnumBuilder_Son.SON_JAMES] + "<-");
       Console.WriteLine("       Trying Dylan: ->" + 
      	EnumBuilder.Instance<EnumBuilder_Son>()[EnumBuilder_Son.SON_DYLAN] + "<-");
       Console.WriteLine("       Trying Avery: ->" + 
      	EnumBuilder.Instance<EnumBuilder_Son>()[EnumBuilder_Son.SON_AVERY] + "<-");
      Perfecto!

How It Works

Meaningful excerpts from the Enum Builder 'engine', the abstract class EnumBuilder:

Aliases

To simplify our referencing of odd-looking types. Currently we cannot refer to an alias from another alias, hence some clunky moments below.

 using TypeDictionary     = List<Type>;
 using TypeDictionaries   = Dictionary<Type, List<Type>>;
 using FieldDictionary    = List<string>;
 using FieldDictionaries  = Dictionary<Type, List<string>>;
 using Objects            = Dictionary<Type,Object>;
 using EnumBuilder_IFaces = Dictionary<Type,EnumBuilder_IFace>;

Delegates

Defines how to create a function pointer to an instance of EnumBuilder_IFace.

 private delegate EnumBuilder_IFace       NewEnumBuilderDelegate(Type myType);

A variable pointing to the "new" instance creator, and is the actual delegate of the type above.

 private static   NewEnumBuilderDelegate  returnNewEnumBuilderDelegate   = EnumBuilder.NewEnumBuilder;

Variables

For each inheriting type, store:

A list of the types starting above EnumBuilder and including itself.

private  static                      TypeDictionaries    typeStacks       = new TypeDictionaries();

A list of all fields in all relevant types, maintaining the exact order in which they were declared (bottom to top).

private  static                      FieldDictionaries   fieldStacks      = new FieldDictionaries();

A list of lockers to manage contention when we try to instantiate classes.

private  static  volatile  readonly  Objects             syncRootLocks    = new Objects();

A list of instances so we can (try to) enforce the Singleton pattern for each inheriting class.

private  static                      EnumBuilder_IFaces  singleInstances  = new EnumBuilder_IFaces();

Properties (Implemented from Enum_Builder_IFace)

The main indexer to get an enum-style integer value from a string. Enum Builder has already calculated the integer values of all public static strings entered by the programmer. This indexer searches the static Field_Stack to find out whether the string passed does indeed exist, and is so, returns the rote order (0 ->).

 public int this[string enumName]
 {
  get
  {
   if (NoFieldStackForThisType())
   {
    return DEFAULT_ENUM_NOT_FOUND_NUM;
   }

   if (String.IsNullOrEmpty(enumName))
   {
    return IndexError(enumName, NULL_OR_EMPTY_WARNING);
   }

   string upperEnumName = enumName.ToUpper();

   int foundIndex = Field_Stack(this.GetType()).IndexOf(upperEnumName);

   if (foundIndex >= 0)
   {
    return foundIndex;
   }
   else
   {
    return IndexError(enumName, DOES_NOT_EXIST_WARNING);
   }
  }
 }

Emulates System.Enum GetNames: returns a list of the public static strings declared in this and all inherited classes.

 public FieldDictionary GetNames
 {
  get
  {
   if (NoFieldStackForThisType())
   {
    // Not exactly an error; we should not call Enum_Builder
    //    with a bunch of empty classes, theoretically...
    return null;
   }

   return Field_Stack(this.GetType());
  }
 }

Methods (Static)

To enforce the Singleton design pattern, we expose a static method with constraints on who can call it. We only allow classes that implement our interface and that can be instantiated (they can call new).

 public static T Instance<t>() where T : class, EnumBuilder_IFace, new()
 {
  return (T)Instance_Internal(typeof(T), returnNewEnumBuilderDelegate);
 }

A handy delegate to point to the method that will create an instance. We use the Activator, which is best for classes with no constructor parameters.

 private static EnumBuilder_IFace NewEnumBuilder(Type myType)
 {
  return (EnumBuilder_IFace)Activator.CreateInstance(myType);
 }

Finds or creates a Singleton instance of any of our inherited classes.

 private static EnumBuilder_IFace Instance_Internal
		(Type myType, NewEnumBuilderDelegate myCreator)
 {

  // If we have the instance, return it
  if (singleInstances.ContainsKey(myType))
  {
   return singleInstances[myType];
  }

  // ELSE create the instance

  // Find or lazily create a sync locker object
  if (!(syncRootLocks.ContainsKey(myType)))
  {
   syncRootLocks.Add(myType, new Object());
  }

  lock (syncRootLocks)
  {
   // Redundant, but needed in highly contentious situations
   if (!(singleInstances.ContainsKey(myType)))
   {
    singleInstances.Add(myType, myCreator(myType));

    // Success
    return singleInstances[myType];
   }
  }

  // The FAIL case
  return null;
 }

The store-house and accessor for the 'enum' values that we equate with public static strings. Every inheriting type gets one of these (see the fieldStacks list as declared earlier). If Field_Stack is null, it will auto-instantiate itself for the current class, and once created, will not do so again.

 private static FieldDictionary Field_Stack(Type myType)
 {
  // Build the type stack if it is null, then build the field stack.
  if (!(typeStacks.ContainsKey(myType)))
  {
   // Create a list of integers for "myType".  It will never be used for another type.
   //	   We cannot do this inside "ReapTypeParents", as it is recursive.
   typeStacks.Add(myType, new TypeDictionary());

   // Create the list of types below us -- 
   //  The first parameter is what we are storing
   //  The second parameter is what we are targeting
   ReapTypeParents(myType, myType);

   // Create the list of public static string fields so we can index them
   ReapFields(myType);
  }

  // Returns the field stack for this type
  return fieldStacks[myType];
 }

Gets the list of types below a given type, and insert them "backwards" so we can reap them and create a list of public static string fields in the proper order.

 private static void ReapTypeParents(Type myType, Type typeToReap)
 {
  // If we hit the base abstract class "Enum_Builder", exit
  //    Should NEVER get to null before we hit the base abstract class
  if ((typeToReap == null) || (typeToReap == ENUM_BUILDER_ABSTRACT_TYPE))
  {
   return;
  }

  // Insert immediately in the first list position;
  //    on initial entry, this will be the original calling type
  //  on successive recursive passes,
  //  deeper classes will push their way up front at position 0.

  // NOTE that we can never add two of the same type,
  //  as we are traversing an inheritance tree from top to bottom.
  typeStacks[myType].Insert(0, typeToReap);

  // Call ourselves recursively; we have more types to seek
  ReapTypeParents(myType, typeToReap.BaseType);
 }

Occurs once per type. Builds a stack of all declared public static strings, starting from the lowest inherited type up to the current type. The order of these strings is what allows Enum Builder to return that string's enum-equivalent integer value.

 private static void ReapTypeFields(Type myType)
 {
  // Clear or (if needed) create the fields
  if (fieldStacks.ContainsKey(myType))
  {
   fieldStacks[myType].Clear();
  }
  else
  {
   fieldStacks.Add(myType, new FieldDictionary());
  }

  // If the type stack is empty, leave
  if (!(typeStacks.ContainsKey(myType)))
  {
   return;
  }

  FieldInfo[] staticFields;
  string thisStaticFieldName;

  foreach (var thisType in typeStacks[myType])
  {
   staticFields = thisType.GetFields(BindingFlags.Static | BindingFlags.Public);

   // Iterate through the found fields
   foreach (var thisStaticField in staticFields)
   {
    // Get the instance name; convert to upper for safety
    thisStaticFieldName = thisStaticField.Name.ToUpper();

    // Confirm that this is a string
    if (thisStaticField.FieldType == STRING_FIELD_TYPE)
    {
     // Push the name back to the field so it can be referred to 
     // programmatically (otherwise, this field value is null!)
     // We are not required to use the instance, 
     // since the fields being injected are static, so we pass NULL
     thisStaticField.SetValue(null, thisStaticFieldName);

     // If the static field name exists in the field stack, consider an error; 
     // this is fairly serious.
     // The programmer should not re-use public static string names 
     // in the inheritance tree.
     if (fieldStacks[myType].Contains(thisStaticFieldName))
     {
      Debug.WriteLine("WARNING: 
	Enum Builder has found more than one copy of the public static string
                       field ->" + thisStaticFieldName + "<-.  
			Please change the field name or it will be IGNORED.");
     }
     else
     {
      // Add the field; it is in the correct order already
      fieldStacks[myType].Add(thisStaticFieldName);
     }
    }
   }
  }
 }

Constructors

The instance constructor has only one job: make sure that Field_Stack has been referred to so it can auto-instantiate itself. This is a classic corner case that I will discuss at the bottom of the article.

 protected EnumBuilder()
 {
  if (NoFieldStackForThisType()) {}
 }

The static constructor sets the name of EnumBuilder's base abstract class so we can determine when we hit it (it is the "bottom" of all reaps).

 static EnumBuilder()
 {
  ENUM_BUILDER_ABSTRACT_TYPE = System.Type.GetType(ENUM_BUILDER_ABSTRACT_NAME);
 }

Design limitations

Enum Builder numbers from 0 onward. I could add the functionality to start at any number, but this might prove more distracting than valuable. With so much inheritance going on, I am not sure it will end up being as clear as one might think. Virtually all uses of System.Enum simply require an index of 0 onward.

Challenges, Issues and Other Interesting Deviations

I thought creating Enum Builder would be easy: I'll just declare a static interface, set up static inheritance, inject static field values into static storage, and return the results. There would be no need for class instantiation, since it is not apparently necessary in the requirements.

Au contraire! I threw away a dozen versions of this project before arriving at the bizarre hybrid you see before you. In .NET, you cannot currently declare a static interface. OK, so I dropped that and began from an abstract class.

Arret! In .NET, you cannot declare a static indexer. This is true even if the class contains only static elements (sigh). I wasn't willing to ruin the programmer's experience over such a limitation.

I also didn't want to force the programmer to call new. Why use new if you just need to get an index of a static field? I almost got there, too, but for the dilemma of the order of operations in a .NET indexer. When you call:

 EnumBuilder.Instance<myLocalEnumBuilder>()[myLocalEnumBuilder..SOME_ENUM]

Everything looks normal: Enum Builder receives the generic type myLocalEnumBuilder, reaps its inherited types and their fields, and stands ready with a 100% successful set-up for the request. Then you place a watch on the incoming indexer variable, myLocalEnumBuilder.SOME_ENUM. It is null. Merde!

It would seem that you cannot act soon enough to both build a set of static values and also make them available to a caller in time for them to pipe in as a method parameter. Or can you?

During much nervous futzing, and for no particular reason, I wandered down to the constructor and referred to the static Field_Stack (If != null, blah, blah). And wah-lah! My parameter had value. Here's why:

{curtain parts, revealing flustered, aging magician, out of breath}

  • We have to accept the fact that all end classes for using an Enum Builder will be non-abstract. We can use any number of abstract classes in between, just not at the end. No big deal.
  • We do need to stick with public static strings for the 'enums' because we need those to be processed and managed early, and only once per class.
  • Since all of the work gets done in the abstract EnumBuilder via static methods, those routines get called before any class ever gets instantiated.
  • The indexer needs an instance, but it can create its own, as it does in the sample call above. Hint: Look at the call to Activator.CreateInstance().
  • By the time any class creates its instance, it already has the values it needs to accurately pass a parameter. So the parameter is always valid using the current approach.

History

  • November 1st, 2009: Initial release, Version 1.0

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