The blessed enumeration
I remember when I first learned about enumerations; they opened a whole world of easy coding and I think they have their place….CommandType anyone? But really, let's kill them.
How often have you seen this…
enum StatusEnum { Active, Inactive, New, Deleted }
That makes it easy to create a Status property on a class and set it….
private StatusEnum _status; public StatusEnum MyStatus { get { return _status; } set { _status = value; } }
Well now what happens when I want to display the status…
protected void Page_Load(object sender, EventArgs e) { DoSomething(); } public void DoSomething() { MyStatus = StatusEnum.Deleted; Response.Write(MyStatus); }
It prints "Deleted". Hey this, is easy. Ok well lets add some more status types.
public enum StatusEnum { Active, Inactive, New, Deleted, "Unable to reproduce" } public enum StatusEnum { Active, Inactive, New, Deleted, Unable to reproduce }
Both throw errors because you can not have quotes or spaces in an enum. So that leaves us with
public enum StatusEnum { Active, Inactive, New, Deleted, Unabletoreproduce }
but that's hard to read so lets use
public enum StatusEnum { Active, Inactive, New, Deleted, UnableToReproduce }
So we change DoSomething to use the new status
public void DoSomething() { MyStatus = StatusEnum.UnableToReproduce; Response.Write(MyStatus); }
And it prints out UnableToReproduce as expected…
Well wait, that's ugly isn't it?
Well lets go down to the data layer and we can figure out the display/formatting issues later.
Well unless we want to store the enum as a string we have to type cast it for storage.
(Int32)MyStatus
Easy as can be, and pulling it back out from the data store
IDataReader reader = null; int32 position=reader.GetOrdinal("MyStatus"); MyStatus = (StatusEnum)reader.GetInt32(position);
This is so easy, Enums are a blessing…
Now what happens when the MyStatus column has a null, -1, 20 or 500 for a value? Errrrrr…Hey that why we have error handling, right?
What if, for use in reports, we are going to store the status as a string? Well we don't have to do anything special to get the string value into the database, after all MyStatus printed the string name just fine. Getting out it isn't too bad…
MyStatus = (StatusEnum)Enum.Parse(typeof(StatusEnum), reader.GetString(position));
But if this is being done for a report you still have to address the display issues, remember UnableToReproduce?
What happens if you want to stop something that has a "new" status from being moved to a deleted status? You put a switch in the set of the property? What do you do when you want to add the status to a second object? Now you have to copy that code to the new object and you have two spots to maintain the same logic OR you refractor and have some sort of "status logic" object that manages the rules of what status can replace a given status.
Great, but what happens if we kill the enum and use objects instead?
public class StatusEnum { static StatusEnum() { Active = new StatusEnum(0, "Active"); Inactive = new StatusEnum(1, "Inactive"); New = new StatusEnum(2, "New"); Deleted = new StatusEnum(3, "Deleted"); UnableToReproduce = new StatusEnum(4, "Unable To Reproduce"); } public static StatusEnum New { get; protected set; } public static StatusEnum Deleted { get; protected set; } public static StatusEnum UnableToReproduce { get; protected set; } public static StatusEnum Inactive { get; protected set; } public static StatusEnum Active { get; protected set; } private static System.Collections.Generic.Dictionary<Int32, StatusEnum> _statuses = new System.Collections.Generic.Dictionary<int32, StatusEnum>(); public static StatusEnum GetByName(String name) { StatusEnum result = null; KeyValuePair<Int32, StatusEnum > pair = __statuses.FirstOrDefault(s => s.Value.Name == name); if (pair.Value != null) result = pair.Value; return result; } public static StatusEnum GetById(Int32 id) { StatusEnum result = null; if (_statuses.ContainsKey(id)) result = _statuses[id]; return result; } protected StatusEnum(int32 id, String name) { _id = id; _name = name; _statuses.Add(_id, this); } private int32 _id; public int32 Id { get { return _id; } set { _id = value; } } private String _name; public String Name { get { return _name; } set { _name = value; } } }
What does this give us? This gives us the ability to get the status by id or name with out type casting. The static variables give us the same functionality as the enum ( StatsEnum.New ) and the display name of UnableToReproduce is ready for display, But now we could add a status from a database by hooking into static constructor. Also if we had an assembly that we needed a new status from we could use reflection to check the projects assemblies for classes that inherit StatusEnum (the biggest drawn back with doing this is getting all of the program assemblies, see Reflecting your assemblies in an ASP.Net web site for more information) . You can create subclasses of a status.
What does a subclass give you?
- MyObject.Status.CanSave;
- MyObject.Status.PromoteStatus();
- MyObject.Status.DemoteStatus();
- MyObject.Status.CanUserChangeStatus(HttpContext.Current.User);
- The ability to add custom statuses from assemblies that add new functionality that was not envisioned or provisioned for in the original. assembly/assemblies.
- All the logic is contained within the classes status class itself and not spread out in the objects that use them.
- Polymorphic methods can be used to implement status based logic.
Enums have their place, mostly near the edges of your objects where you are going into com objects or items that are pre-built using them like...
- ado.net.
- membership and roles.
- That 3rd party DLL that you curse under your breath.
I have had to use enums that were part of a pre-built system; but I have not had to create a new enum since I started using this pattern. More importantly, I have not dreaded having to add a new status or element to an enum; all the logic has been in the class itself so there is no hunt for all the spots that use it.