views:

265

answers:

7

My app has a lot of different lookup values, these values don't ever change, e.g. US States. Rather than putting them into database tables, I'd like to use enums.

But, I do realize doing it this way involves having a few enums and a lot of casting from "int" and "string" to and from my enums.

Alternative, I see someone mentioned using a Dictionary<> as a lookup tables, but enum implementation seems to be cleaner.

So, I'd like to ask if keeping and passing around a lot of enums and casting them be a problem to performance or should I use the lookup tables approach, which performs better?

Edit: The casting is needed as ID to be stored in other database tables.

+5  A: 

You're not going to notice a big difference in performance between the two, but I'd still recommend using a Dictionary because it will give you a little more flexibility in the future.

For one thing, an Enum in C# can't automatically have a class associated with it like in Java, so if you want to associate additional information with a state (Full Name, Capital City, Postal abbreviation, etc.), creating a UnitedState class will make it easier to package all of that information into one collection.

Also, even though you think this value will never change, it's not perfectly immutable. You could conceivably have a new requirement to include Territories, for example. Or maybe you'll need to allow Canadian users to see the names of Canadian Provinces instead. If you treat this collection like any other collection of data (using a repository to retrieve values from it), you will later have the option to change your repository implementation to pull values from a different source (Database, Web Service, Session, etc.). Enums are much less versatile.

Edit

Regarding the performance argument: Keep in mind that you're not just casting an Enum to an int: you're also running ToString() on that enum, which adds considerable processing time. Consider the following test:

const int C = 10000;
int[] ids = new int[C];
string[] names = new string[C];
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i< C; i++)
{
    var id = (i % 50) + 1;
    names[i] = ((States)id).ToString();
}
sw.Stop();
Console.WriteLine("Enum: " + sw.Elapsed.TotalMilliseconds);
var namesById = Enum.GetValues(typeof(States)).Cast<States>()
                .ToDictionary(s => (int) s, s => s.ToString());
sw.Restart();
for (int i = 0; i< C; i++)
{
    var id = (i % 50) + 1;
    names[i] = namesById[id];
}
sw.Stop();
Console.WriteLine("Dictionary: " + sw.Elapsed.TotalMilliseconds);

Results:

Enum: 26.4875
Dictionary: 0.7684

So if performance really is your primary concern, a Dictionary is definitely the way to go. However, we're talking about such fast times here that there are half a dozen other concerns I'd address before I would even care about the speed issue.

Enums in C# were not designed to provide mappings between values and strings. They were designed to provide strongly-typed constant values that you can pass around in code. The two main advantages of this are:

  1. You have an extra compiler-checked clue to help you avoid passing arguments in the wrong order, etc.
  2. Rather than putting "magical" number values (e.g. "42") in your code, you can say "States.Oklahoma", which renders your code more readable.

Unlike Java, C# does not automatically check cast values to ensure that they are valid (myState = (States)321), so you don't get any runtime data checks on inputs without doing them manually. If you don't have code that refers to the states explicitly ("States.Oklahoma"), then you don't get any value from #2 above. That leaves us with #1 as the only real reason to use enums. If this is a good enough reason for you, then I would suggest using enums instead of ints as your key values. Then, when you need a string or some other value related to the state, perform a Dictionary lookup.

Here's how I'd do it:

public enum StateKey{
    AL = 1,AK,AS,AZ,AR,CA,CO,CT,DE,DC,FM,FL,GA,GU,
    HI,ID,IL,IN,IA,KS,KY,LA,ME,MH,MD,MA,MI,MN,MS,
    MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,MP,OH,OK,OR,PW,
    PA,PR,RI,SC,SD,TN,TX,UT,VT,VI,VA,WA,WV,WI,WY,
}

public class State
{
    public StateKey Key {get;set;}
    public int IntKey {get {return (int)Key;}}
    public string PostalAbbreviation {get;set;}

}

public interface IStateRepository
{
    State GetByKey(StateKey key);
}

public class StateRepository : IStateRepository
{
    private static Dictionary<StateKey, State> _statesByKey;
    static StateRepository()
    {
        _statesByKey = Enum.GetValues(typeof(StateKey))
        .Cast<StateKey>()
        .ToDictionary(k => k, k => new State {Key = k, PostalAbbreviation = k.ToString()});
    }
    public State GetByKey(StateKey key)
    {
        return _statesByKey[key];
    }
}

public class Foo
{
    IStateRepository _repository;
    // Dependency Injection makes this class unit-testable
    public Foo(IStateRepository repository) 
    {
        _repository = repository;
    }
    // If you haven't learned the wonders of DI, do this:
    public Foo()
    {
        _repository = new StateRepository();
    }

    public void DoSomethingWithAState(StateKey key)
    {
        Console.WriteLine(_repository.GetByKey(key).PostalAbbreviation);
    }
}

This way:

  1. you get to pass around strongly-typed values that represent a state,
  2. your lookup gets fail-fast behavior if it is given invalid input,
  3. you can easily change where the actual state data resides in the future,
  4. you can easily add state-related data to the State class in the future,
  5. you can easily add new states, territories, districts, provinces, or whatever else in the future.
  6. getting a name from an int is still about 15 times faster than when using Enum.ToString().

[grunt]

StriplingWarrior
+1, I completely agree with the "not perfectly immutable" part. You'll hate it if your ten year old application is based on a political model from the past and you don't get support for it anymore...
Stefan Steinegger
Disagree that there's not a big performance difference; enums in C# are just ints, so it's much quicker to cast between them and ints. Your points are good, but I'd be hard-pressed to recommend sacrificing performance for possibly-unnecessary flexibility.
TMN
@TMN: Even if enums would be quicker, we're talking about such high speeds that it wouldn't be noticeable in the vast majority of applications. I would be hard-pressed to recommend sacrificing flexibility for a minuscule improvement in performance. However, as it turns out, a Dictionary lookup will actually be a lot faster for the sort of thing he's talking about doing. See my updated answer.
StriplingWarrior
PS--Make sure the enum values are all explicitly declared. Otherwise it's too easy for someone to accidentally insert or reorder a value, and mess up your database relationship.
StriplingWarrior
+13  A: 

Casting from int to an enum is extremely cheap... it'll be faster than a dictionary lookup. Basically it's a no-op, just copying the bits into a location with a different notional type.

Parsing a string into an enum value will be somewhat slower.

I doubt that this is going to be a bottleneck for you however you do it though, to be honest... without knowing more about what you're doing, it's somewhat hard to recommendation beyond the normal "write the simplest, mode readable and maintainable code which will work, then check that it performs well enough."

Jon Skeet
A: 

Enums will greatly outperform almost anything, especially dictionary's. Enums only use single byte. But why would you be casting? Seems like you should be using the enums everywhere.

MrFox
Enums will usually use 4 bytes - their underlying type defaults to `int` unless you explicitly declare otherwise.
LukeH
The "greatly outperform" part depends largely on what you're doing with the enum once you get it. If you use it in a switch statement, enums will be faster. If you call ToString on it, a pre-populated Dictionary will be faster. See my answer.
StriplingWarrior
A: 

Avoid enum as you can: enums should be replaced singletons deriving from base class or implementing an interface.

The practice of using enum comes from an old style programming of C.

You start to use an enum for the US States, then you will need the number of inhabitants, the capitol..., and you will need a lot of big switches to get all of this infos.

onof
Enums in C# aren't objects. They don't really derive from a base class or implement an interface. They are just type-safe constants. They can be useful when used as such, but they are dangerous if misunderstood.
StriplingWarrior
I know that enums aren't object. This is the reason i say they are useless. Anyway i made clear my answer.
onof
+2  A: 

You could use TypeSafeEnum s

Here's a base class

Public MustInherit Class AbstractTypeSafeEnum
    Private Shared ReadOnly syncroot As New Object
    Private Shared masterValue As Integer = 0

    Protected ReadOnly _name As String
    Protected ReadOnly _value As Integer

    Protected Sub New(ByVal name As String)
        Me._name = name
        SyncLock syncroot
            masterValue += 1
            Me._value = masterValue
        End SyncLock
    End Sub

    Public ReadOnly Property value() As Integer
        Get
            Return _value
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return _name
    End Function

    Public Shared Operator =(ByVal ats1 As AbstractTypeSafeEnum, ByVal ats2 As AbstractTypeSafeEnum) As Boolean
        Return (ats1._value = ats2._value) And Type.Equals(ats1.GetType, ats2.GetType)
    End Operator

    Public Shared Operator <>(ByVal ats1 As AbstractTypeSafeEnum, ByVal ats2 As AbstractTypeSafeEnum) As Boolean
        Return Not (ats1 = ats2)
    End Operator

End Class

And here's an Enum :

Public NotInheritable Class EnumProcType
    Inherits AbstractTypeSafeEnum

    Public Shared ReadOnly CREATE As New EnumProcType("Création")
    Public Shared ReadOnly MODIF As New EnumProcType("Modification")
    Public Shared ReadOnly DELETE As New EnumProcType("Suppression")

    Private Sub New(ByVal name As String)
        MyBase.New(name)
    End Sub

End Class

And it gets easier to add Internationalization.

Sorry about the fact that it's in VB and french though.

Cheers !

Alex Rouillard
Because the integer values are mapped in the database, it would be good to make it possible to specify explicit int values, so they don't change if they get reordered.
StriplingWarrior
Well, I do think it wouldn't be too hard to change the code to allow that.
Alex Rouillard
A: 

I personally like enums... IMO they are easier to code with

StuBob
A: 

Alternatively you can use constants

Guru
How would using constants be advantageous here?
StriplingWarrior