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
- Copy Enum_Builder.dll from the folder Enum_Builder\bin\Release to your project folder.
- Add a reference to the builder. Use the Browse option and choose the Enum_Builder.dll you just copied.
- In any class where you wish to use an Enum Builder, import the namespace (the underscores indicate a namespace vs. a class):
using Marcus_Technical_Services.Enum_Builder;
- 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
{
}
- Add your '
enum
' values as public static string
s. Do not assign any values to these string
s; Enum Builder will over-write them. In the example below, GRANDFATHER_HARRY will become 0, GRANDFATHER_BRUCE will get 1, etc.
public class EnumBuilder_Grandfather : EnumBuilder
{
public static string GRANDFATHER_HARRY;
public static string GRANDFATHER_BRUCE;
}
- In this or any other class, you can inherit your own newly created Enum Builder by creating another two:
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;
}
- 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.
- 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:
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 string
s inside these last two classes will increment as usual, but they will not be public
referenceable because they cannot be instantiated.
Enum
Builders can also be used in more exotic ways, such as with this internal example
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;
}
}
- 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 'enum
s'!
- There are a number of ways to use these newly created Enum Builders:
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 string
s 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 string
s declared in this and all inherited classes.
public FieldDictionary GetNames
{
get
{
if (NoFieldStackForThisType())
{
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 (singleInstances.ContainsKey(myType))
{
return singleInstances[myType];
}
if (!(syncRootLocks.ContainsKey(myType)))
{
syncRootLocks.Add(myType, new Object());
}
lock (syncRootLocks)
{
if (!(singleInstances.ContainsKey(myType)))
{
singleInstances.Add(myType, myCreator(myType));
return singleInstances[myType];
}
}
return null;
}
The store-house and accessor for the 'enum
' values that we equate with public static string
s. 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)
{
if (!(typeStacks.ContainsKey(myType)))
{
typeStacks.Add(myType, new TypeDictionary());
ReapTypeParents(myType, myType);
ReapFields(myType);
}
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 ((typeToReap == null) || (typeToReap == ENUM_BUILDER_ABSTRACT_TYPE))
{
return;
}
typeStacks[myType].Insert(0, typeToReap);
ReapTypeParents(myType, typeToReap.BaseType);
}
Occurs once per type. Builds a stack of all declared public static string
s, starting from the lowest inherited type up to the current type. The order of these string
s is what allows Enum Builder to return that string
's enum
-equivalent integer value.
private static void ReapTypeFields(Type myType)
{
if (fieldStacks.ContainsKey(myType))
{
fieldStacks[myType].Clear();
}
else
{
fieldStacks.Add(myType, new FieldDictionary());
}
if (!(typeStacks.ContainsKey(myType)))
{
return;
}
FieldInfo[] staticFields;
string thisStaticFieldName;
foreach (var thisType in typeStacks[myType])
{
staticFields = thisType.GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var thisStaticField in staticFields)
{
thisStaticFieldName = thisStaticField.Name.ToUpper();
if (thisStaticField.FieldType == STRING_FIELD_TYPE)
{
thisStaticField.SetValue(null, thisStaticFieldName);
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
{
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 string
s for the 'enum
s' 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