Wednesday, January 16, 2008

Instead of Enum

Since I tend to ramble I'll start this out with the code.



using System;
using System.Collections.Generic;
using System.Reflection;

public class MyEnum
{
#region Fields
private int id;

// Other values
private string name;
private bool archived_flag;
#endregion

#region Static Pre-defined instances (this is the enum)
public static readonly MyEnum Empty = MyEnum.Factory(-999, string.Empty);
public static readonly MyEnum First = MyEnum.Factory(1, "First Item");
public static readonly MyEnum Second = MyEnum.Factory(2, "Second Item");
public static readonly MyEnum Third = MyEnum.Factory(3, "Third Item");
public static readonly MyEnum Fourth = MyEnum.Factory(4, "Fourth Item");
#endregion

#region Constructors
private MyEnum()
{

}
#endregion

#region Properties
public string ID
{
get
{
return this.id;
}
}
public string Name
{
get
{
return this.name;
}
}

#endregion

private static MyEnum Factory(int id, string name)
{
MyEnum me = new MyEnum();
me.id = id;
me.name = name;

return me;
}

public static MyEnum GetByID(int id)
{
if (lAll == null) { MyEnum.GetAll(); }
foreach (MyEnum me in lAll)
{
if (me.id == id)
{
return me;
}
}
return MyEnum.Empty;
}

public static List lAll = null;

public static List GetAll()
{

if (lAll == null)
{
lAll = new List();

Type myType = typeof(MyEnum);
FieldInfo[] fields = myType.GetFields();

foreach (FieldInfo fi in fields)
{
if (fi.FieldType == myType && fi.IsStatic && fi.IsPublic)
{
MyEnum value = fi.GetValue(null) as MyEnum;
if (value != null)
{
lAll.Add(value);
}
}
}
}

return lAll;

}

#region Operators
public override bool Equals(object obj)
{
if (obj == null) return false;

MyEnum co = obj as MyEnum;
if ((object)co == null) // Cast to avoid calling the override == operator
{
return false;
}

return this.Equals(co);
}

public override int GetHashCode()
{
return base.GetHashCode();
}

public bool Equals(MyEnum me)
{
return (object)me == null ? false : this.ID.Equals(me.ID);
}
public static bool operator ==(MyEnum a, MyEnum b)
{
// If the same instance or both null then return true
if (System.Object.ReferenceEquals(a, b))
{
return true;
}

// If one is null return false
if ((object)a == null (object)b == null) // Cast to avoid calling the override == operator
{
return false;
}

return a.Equals(b);
}
public static bool operator !=(MyEnum a, MyEnum b)
{
return !(a == b); // This calls the override operator above
}
#endregion

public override string ToString()
{
return this.Name;
}

}


I use this pattern as a replacement for almost all my former enums. Using classes like the one above you get a simple type with a set of pre-defined, valid, values (like an enum). It's type-safe. It handles equals(), == and !=. It also allows you to add properties and methods to each value which provides much more programming power.

5 comments:

  1. Did you test the overhead on this method? I would be curious to know how efficient this would be over an enum type.

    ReplyDelete
  2. I have not tested the overhead. But, since an enum is a simple mapping to an underlying type the compiler can do optimizations that it can't do when using this method.

    But, then again, if you are really (really) serious about overhead or performance then you'd probably want to choose a language that runs closer to the metal than C#.

    ReplyDelete
  3. You have just gone the Java way before there were enums in the language. Java still creates enums using classes though, so you can actually provide enum constructors for example, but it's discouraged imho.

    So you've really reverted back to something non-standard that you now have to add to every single code-base you're working on, and you're losing the properties of the C# enum like being able to store it as a byte, being able to do bitwise operands like:
    using (IDataReader reader = new SqlDataReader(new SqlCommand("SELECT * FROM foobar;"),
    CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection))
    {
    }

    to take one example. Instead you could use something like attributes:
    http://www.eggheadcafe.com/tutorials/aspnet/abbb43b9-a667-4930-9588-542844a32d71/using-attributes-with-enu.aspx

    Overhead performance on reading static readonly class instances?

    Plus you're disabling garbage collection and adding more code to maintain. Then you also have to create a new enum type for every enum type which is a pain!!

    ReplyDelete
  4. Henke

    Thanks for the feedback. Performance and memory managment is a weak point of this solutions. But I didn't explain very well in my post (and never came back to update it) that I am not trying to replace enum but I'm trying to give static lists an enum like quality.

    I continue to use enums for Flags or Options but most of the time I (used to) end up using enums for simple static lists. Like "gender" or "status". I would often create a database table to hold this kind of static list (a genders table). I'd query the table to generate lists or populate properties on other objects. But I really wanted that list to be like an enum so I could compare and assign it to a property. But enums didn't provide a way to add all the properties that I might need. I could cast an enum back to its base type but that is pain and it's only one piece of data.

    So I came up with my solutions. Now I can setup something like "gender" as a static list enum-ish:

    Gender.Male = Gender.Factory(1, "M", "Male", "Blue");
    Gender.Female = Gender.Factory(2, "F", "Female", "Pink");

    Gender has the properties of ID, Abbreviation, Description, and Color. I can assign this to my other object's properties:

    person.Gender = Gender.Male (enum-ish)

    I can compare it when working on lists of objects (enum-ish)

    foreach (Person p in people) {
    if (p.Gender == Gender.Female) {

    }
    }

    I can "query" the Gender list (database-ish):

    Gender.GetAll();

    and use data-binding on controls (definitely not enum-ish).

    I can use the Gender ID property to store in the database. When I load up an object I can read the ID value from the database and "query" my static list. No need to join tables or add extra properties to my object which really belong to my static list. No need to cast my ID int back to an enum.

    person.Gender = Gender.GetByID(db.read("gender_id"));

    I don't have to define a Gender class that the database can rehidrate. I don't have to instantiate a new Gender object everytime another object has a gender property.

    person.Gender = new Gender();

    So it's better than holding in the database and instantiating into its own class. My list of thousands of people all point to the same 2 static Gender instances.

    It's better than enum (for this usage) because I need the additional properties and I want the strong typing.

    db.write((int)enumGender.Male); -- ick

    Enumerating the enum was a pain and I had to have all these helper classes and even then I didn't have any other properties. Using my static list I can populate list boxes or drop downs and I already have the display property and ID property built in to the objects.

    myList.DataTextField = "Description";
    myList.DataValueField = "ID";
    myList.DataSource = Gender.GetAll();
    myList.DataBind();

    This solution has been great for me. The static list fits in neatly with my other objects. It emulates the way I used to do things with the database without having to hit the database and I get the strong typed lists so it feels like an enum.

    ReplyDelete