Jeff Garoutte

c# .net and anything else that happens across my desk

Recent posts

Tags

Categories

Navigation

Pages

    Archive

    Blogroll

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    Enum - the good, the bad, the ugly and how to kill them

    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? 

    1. MyObject.Status.CanSave;
    2. MyObject.Status.PromoteStatus();
    3. MyObject.Status.DemoteStatus();
    4. MyObject.Status.CanUserChangeStatus(HttpContext.Current.User);
    5. The ability to add custom statuses from assemblies that add new functionality that was not envisioned or provisioned for in the original. assembly/assemblies.
    6. All the logic is contained within the classes status class itself and not spread out in the objects that use them.
    7. 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...

    1. ado.net.
    2. membership and roles.
    3. 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.

    kick it on DotNetKicks.com

    Posted: Jun 17 2008, 09:05 by jeff | Comments (8) RSS comment feed |
    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5
    Filed under: General

    Related posts

    Comments

    Schneider us said:

    SchneiderI Like enums. I don't feel a need to check the range, if someone calls it with a bad value, they deserve the error, also I don't see it happening often. Most of you complaints are problems with your application design not Enumerations.

    Enums should have an initial value that makes sense.

    Enum StatusEnum {
    Unset = 0,
    Active = 1,
    Inactive = 2,
    New = 3,
    Deleted = 4
    }

    Commonly used Enums Should be in a shared library, which is shared by all applications. Also the Enum can be placed in the namespace and used everywhere withing that namesspace.

    Thanks,
    Schneider

    # June 17 2008, 15:31

    Jeff us said:

    JeffWhile everyone wont agree with this pattern, not every language includes an enum in it's definition.

    Yes, the value of the individual item in an enum can be set and that should not be over looked.

    But at the end of the day, I have found this pattern to be able to do everything an enum can, plus some. Is it more code? Yes. Is it for everyone? No. Does it allow for greater flexibility over an enum? Yes, I can add a propety to the base status that says "CanBeEdited" that can easily but put into all my properties...

    public bool FirstName { get { return _firstName;} set { if (this.MyStatus.CanBeEdited) _firstName=value; else ... } }
    (please forgive the lack of line breaks)
    instead of
    public bool FirstName { get { return _firstName;} set { switch(this.MyStatus) { case StatusEnum.New: case StatusEnum.Active: _firstName=value; break; default: ... break; } }

    What happens when I add a new status or want UnableToReproduce to allow edits as well? With an enum every property needs to be touched. With objects, I change the value of the CanBeEdited property of the status.

    That logic could be refactored into a function that returns a bool within the object that contains the MyStatus property or into a helper class. But if you are going to have a helper class what have you saved over using objects? Nothing that I can see other than having an enum and a helper class. If the readonly logic is put into the object that has the property, now there is a method specific to the status repeated in the object tree classes.

    Dont get me wrong, having an invaild Id in the data store can/will still cause an Exception using this method. But if my application allows for a 3rd party "add-on" and they need to add a status that wasnt not accounted for in the original design...they can. With an enum....not so much.

    # June 18 2008, 03:07

    uğur pelister tr said:

    uğur pelistereverything's ok but why do you cast into int for retrieving the underlying value of enums?????
    the correct way is value.ToString("d") which stand for decimal format.

    # June 18 2008, 11:22

    Pawel Walczak pl said:

    Pawel WalczakBut if my application allows for a 3rd party "add-on" and they need to add a status that wasnt not accounted for in the original design...they can. With an enum....not so much.

    And that's the main purpose of enums: to allow passing values from predefined set only. If You don't need or want such restriction You shouldn't use enum. It seams that You simply abused enum (if it comes to business requirements). You should use some kind of IStatus interface with predefined default set of implementations (which You eventually did).

    In fact the name of the StatusEnum class is missleading. Because you allowed inheritance (protected constructor) the class doesn't act as enum anymore. Everybody can create new values which is against the main purpose of enums concept.

    # June 18 2008, 13:12

    paulo us said:

    pauloin java i would redefine toString() of the enum with a string enum parameter.
    As you can guess there enums are just stateless objects...

    # June 18 2008, 13:42

    Ryan us said:

    RyanYou can use attributes to give your enum items a string description.

    using System.ComponentModel;

    public enum CompanyMatchCriteria
    {
    [Description("Company GUID")]
    Guid = 1,
    [Description("Company Name")]
    Name = 2,
    [Description("Address")]
    Address = 4
    }

    And then you can reflect over them to get this description.

    # June 18 2008, 16:25

    Jeff us said:

    JeffPawel ,

    It seams that You simply abused enum (if it comes to business requirements). You should use some kind of IStatus interface with predefined default set of implementations (which You eventually did).

    By simply sealing the class (one extra keyword in the class definition, sealed) this pattern still functions as enum and allows for polymorphic methods or properties like CanBeEdited.

    In fact the name of the StatusEnum class is missleading. Because you allowed inheritance (protected constructor) the class doesn't act as enum anymore. Everybody can create new values which is against the main purpose of enums concept.

    True, however I had hoped people who want to restirct the set to their own "predefined" set would be able to change "protected" to "internal", "private" or even seal the class and bring it back to a more "true" enum.

    Perhaps I was not as clear as I had hoped in the point that enums are misused, often ill suited to the task at hand, not needed and, as you put it, abused...
    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...

    Really, if a Status property does not have some form business requirement and/or logic behind it, why have it?

    Ryan,
    Yes, that can be done and works nicely to avoid many display issues if reflection is allowed within the application or enviroment. I did think about including that in the article; but the reflection code would just end up in a helper class. I could have (and should have) added the description attribute and mentioned the need for the code in a helper class, but I ran out of caffeine.

    # June 18 2008, 17:59

    Rafael Rosa br said:

    Rafael RosaHi,

    Nice post. I've tried to do something like that some time ago but wasn't very successfull. I tried to adapt your implementation and make some tests but I've been halted when I tried to use it in my Castle ActiveRecord objects. I'm having some trouble trying to persist it to the database, specially because I added some generic features to it.

    Anyway, thanks a lot. If I happen to find a solution I'll let you know.

    Cheers from Brazil,
    Rafael.

    # July 18 2008, 21:19

    Add comment


    (Will show your Gravatar icon)  

      Country flag

    [b][/b] - [i][/i] - [u][/u]- [quote][/quote]