![]() |
Languages »
C# »
Enumerations
License: The Code Project Open License (CPOL)
Enum Builder: The Curious Case of the Inheritable EnumBy marcustsProves that we can create a class that behaves like an inheritable enum |
C#, .NET (.NET3.0, .NET3.5), Visual-Studio (VS2008)
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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:
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.enum-like values, so the end user never has to (and should not) ever call new.using Marcus_Technical_Services.Enum_Builder;
EnumBuilder. Decide on the access modifier; public is often wise if you intend to use these values elsewhere:
public class EnumBuilder_Grandfather : EnumBuilder
{
}
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. public class EnumBuilder_Grandfather : EnumBuilder
{
public static string GRANDFATHER_HARRY;
public static string GRANDFATHER_BRUCE;
}
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;
}
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 strings 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 exampleFor 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;
}
}
public static string for any other purpose except as stated here, as they will definitely get sucked and used as part of your 'enums'!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.
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.
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!
Meaningful excerpts from the Enum Builder 'engine', the abstract class EnumBuilder:
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>;
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;
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();
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());
}
}
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);
}
}
}
}
}
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);
}
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.
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}
public static strings for the 'enums' because we need those to be processed and managed early, and only once per class.abstract EnumBuilder via static methods, those routines get called before any class ever gets instantiated.Activator.CreateInstance().| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 1 Nov 2009 Editor: Deeksha Shenoy |
Copyright 2009 by marcusts Everything else Copyright © CodeProject, 1999-2010 Web22 | Advertise on the Code Project |