3

Everything is in the title, I've read this question Enum.Parse() or Switch but there is nothing about performance. My Enum is around 10 members long, I'd like to know which one is faster, switch or Enum.Parse() ?

Community
  • 1
  • 1
Thomas Ayoub
  • 27,208
  • 15
  • 85
  • 130
  • Usually, `Parse` is time cosuming, `Enum.Parse` is not an exception. – Dmitry Bychenko Oct 31 '14 at 15:30
  • 1
    Unless you are going to be doing this many, many thousands of times, I wouldn't worry about it. – DavidG Oct 31 '14 at 15:31
  • 1
    Why don't you benchmark it by yourself? However, for 10 records the performance in both cases no user will find difference. – Royal Bg Oct 31 '14 at 15:31
  • 7
    [Which is Faster?](http://ericlippert.com/2012/12/17/performance-rant/) – Sriram Sakthivel Oct 31 '14 at 15:31
  • For the vast majority of the cases where you might use this, you've already spent more time on this question than you would've saved by choosing one over the other. –  Oct 31 '14 at 15:32
  • @SriramSakthivel your link is full of good things to keep in mind. I don't regreat my question since I've discover this article. Thank you ! – Thomas Ayoub Oct 31 '14 at 15:35
  • If you are having performance problem then it should be easy to test. If you are not having performance problems the go with what is best. Skeet has an excellent comment that with switch you have to update when you change or add an enum. – paparazzo Oct 31 '14 at 15:35
  • 1
    Ought to be at least two orders of magnitude, switch() is very fast with a jump table, takes just a few cycles. Pretty easy to measure this yourself btw. – Hans Passant Oct 31 '14 at 15:36

6 Answers6

12

Nice and fast:

public static class EnumHelpers<TTarget>
{
    static EnumHelpers()
    {
        Dict = Enum.GetNames(typeof(TTarget)).ToDictionary(x => x, x => (TTarget)Enum.Parse(typeof(TTarget), x), StringComparer.OrdinalIgnoreCase);
    }

    private static readonly Dictionary<string, TTarget> Dict;

    public static TTarget Convert(string value)
    {
        return Dict[value];
    }
}
Darragh
  • 1,969
  • 1
  • 17
  • 25
  • nice - my tests shows that its almost 6 times faster (10,000,000 Enum.Parse takes 2 sec).. and if the number of enum entries is lower than around 13 entries, or if the hits to convert access the lower ones more frequently, it could init an array instead (or the sorts - like a hibrid dictionary with its ListDictionary) and it can be up to 20 times as fast (if always hitting the first one) – AndersMad Jan 16 '16 at 11:46
  • If you want make this helper a little more faster, you overwrite GetNames, in which you will cache Fields for type – Smagin Alexey Feb 15 '18 at 20:10
  • @SmaginAlexey GetNames is only called once per type (when the static constructor runs). – Darragh Feb 17 '18 at 18:47
  • @Darragh I saw it in your code. But it's not reason refuse my comment)). If you look inside GetNames you see, that FieldInfos is called – Smagin Alexey Feb 18 '18 at 20:12
9

switch is always faster, since parse uses reflection to get the names of the members.. but unless the application is performance critical.. (thousands of executions per second).. using Enum.Parse makes code easier to maintain

CaldasGSM
  • 2,872
  • 13
  • 25
  • Using `switch` also has potential maintenance advantages: it prevents you having to completely rewrite the code if one of the names gets changed to include a space. The most maintainable approach should be used, but which approach that is depends on the application. –  Oct 31 '14 at 15:44
  • 3
    IIRC reflected members are cached, so very first call to `Parse` it will be expensive rest will be amortized. – Sriram Sakthivel Oct 31 '14 at 15:48
7

I've built a fast "switch" version using expression trees.

public static Func<String, TEnum> GetParseEnumDelegate<TEnum>()
{
  var eValue = Expression.Parameter(typeof(String), "value"); // (String value)
  var tEnum = typeof(TEnum);

  return
    Expression.Lambda<Func<String, TEnum>>(
      Expression.Block(tEnum,
        Expression.Switch(tEnum, eValue,
          Expression.Block(tEnum,
            Expression.Throw(Expression.New(typeof(Exception).GetConstructor(Type.EmptyTypes))),
            Expression.Default(tEnum)
          ),
          null,
          Enum.GetValues(tEnum).Cast<Object>().Select(v => Expression.SwitchCase(
            Expression.Constant(v),
            Expression.Constant(v.ToString())
          )).ToArray()
        )
      ), eValue
    ).Compile();
}
...
var parseEnum = GetParseEnumDelegate<YourEnum>();
YourEnum e = parseEnum("SomeEnumValue");

If you need a non-generic version, you must convert the result of the switch from tEnum to typeof(Object).

public static Func<String, Object> GetParseEnumDelegate(Type tEnum)
{
  var eValue = Expression.Parameter(typeof(String), "value"); // (String value)
  var tReturn = typeof(Object);

  return
    Expression.Lambda<Func<String, Object>>(
      Expression.Block(tReturn,
        Expression.Convert( // We need to box the result (tEnum -> Object)
          Expression.Switch(tEnum, eValue,
            Expression.Block(tEnum,
              Expression.Throw(Expression.New(typeof(Exception).GetConstructor(Type.EmptyTypes))),
              Expression.Default(tEnum)
            ),
            null,
            Enum.GetValues(tEnum).Cast<Object>().Select(v => Expression.SwitchCase(
              Expression.Constant(v),
              Expression.Constant(v.ToString())
            )).ToArray()
          ), tReturn
        )
      ), eValue
    ).Compile();
}

I did several perf tests and this delegate version was almost as fast as the native switch. In my scenario (enum with 10 items) it was 5 times faster than Enum.Parse().

wonderik
  • 71
  • 1
  • 2
1

As @CaldasGSM answered, the biggest problem is the reflection that is happening inside the Enum.Parse method. In addition just the number of IF's used in the Enum.Parse internal implementation is much higher than the number of conditions in your switch statement, not to count the other code inside that method. All of that makes it much inferior to a switch.

If you are processing a small number of items like you said than there is actually no significant difference if you are using Enum.Parse vs switch. For a large number of items it is a different story.

However, i would add that one more problem with Enum.Parse is that you have to handle three exception types using try-catch blocks in cases your parsing doesn't work, which will slow down your code as well.

In addition you should also not overlook the cost of boxing the enum values in the object type when using Enum.Parse, which is also a performance penalty.

To resolve the mentioned issues a much better alternative is to use the newer Enum.TryParse API, which will make it simpler to handle errors, and also prevent the boxing to object, since it uses generics.

Here is an example:

Items item;
if (!Enum.TryParse("First", true, out item))
{
    // Handle error
}
Faris Zacina
  • 13,143
  • 7
  • 53
  • 73
1

In isolation, the switch will be faster.

But code rarely executes in isolation. Typically, the user will have entered a value, or you will have loaded it from a disk file. The whole purpose of Enum is to have a symbolic representation of a string value so that you can work with it more easily. It also allows you to validate the data that's entered.

So, let's say you have an enumeration:

public enum Thing
{
    Foo,
    Bar,
    Fooby,
    Barby
}

And somebody gives you a string that's supposed to represent one of those. You write a switch statement:

switch (s)
{
    case "Foo": break;
    case "Bar": break;
    case "Fooby": break;
    case "Barby": break;
    default : throw new ArgumentException();
}

What happens if the user entered "foo"? Are you going to reject it because the case isn't correct? Very unfriendly behavior. So you modify your code to do a ToLower() on the string, and modify your cases to use all lower case.

It's crazy. Your code will be much easier to understand and maintain if you use Enum.TryParse to convert it to an enum, and then do what needs to be done using the enum value.

Jim Mischel
  • 122,159
  • 16
  • 161
  • 305
0

Use a switch (I prefer using parse anyway because of it's more maintainable).

Anyway, premature optimization is the root of all evil.

1911z
  • 1,028
  • 10
  • 15
  • 4
    I don't get it. You say *Premature optimization is the root of all evil* and yet you suggest to use a switch over `Enum.Parse` ? – Sriram Sakthivel Oct 31 '14 at 15:40
  • 2
    I don't understand the joke either. Sorry! And btw jokes are allowed in comments, and not in answers. Your answer could be humorous, but only joke isn't an answer. – Sriram Sakthivel Oct 31 '14 at 15:43