11

For converting a string to an enum, which of the following ways is better?

  1. This code:

    colorEnum color = (colorEnum)Enum.Parse(typeof(colorEnum), "Green");
    
  2. or this:

    string colorString = ...
    colorEnum color;        
    switch (colorString)
    {
        case "Green":
            color = colorEnum.Green;
            break;
        case "Red":
            color = colorEnum.Red;
            break;
        case "Orange":
            color = colorEnum.Orange;
            break;
        ....
    }
    
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Nicola Pesavento
  • 584
  • 9
  • 22
  • I thought you cannot do switch on strings. – Ramon Snir Sep 21 '11 at 09:01
  • 5
    @Ramon: You're wrong - in C# we've always been able to switch on strings. – Jon Skeet Sep 21 '11 at 09:01
  • "better" how? #1 is less code and less maintenance, #2 is faster and safer, at 2 points each it seems like a draw. – harold Sep 21 '11 at 11:11
  • @harold Exolain how its less maintenance? Every time you change colorEnum you have to change the switch too. Also the switch is only less code if the enum is short. – Tom Squires Sep 21 '11 at 11:55
  • @TomSquires I said #1 is less maintenance and less code. Not #2. – harold Sep 21 '11 at 12:09
  • Ah got you. I dont think 2 will be faster though. Also i dont agree with safer. I would rather have an exception thrown than a typo causing the wrong enum to be given. – Tom Squires Sep 21 '11 at 12:26
  • 1
    @TomSquires it's faster, you can try it - as for safer, the switch does exactly what it looks like it does, enum.parse also parses "-4" and "Green,Red" (resulting in `color.Red|color.Green`) - all kinds of crazy things that you probably don't expect and can cause lots of trouble. As for typo's, you can solve them, you can't solve enum.parse's problems because you didn't write it – harold Sep 21 '11 at 12:36

11 Answers11

8

You should use the Enum.TryParse, if it fails you can handle the error correctly.

sample:

     ColorsEnum colorValue; 
     if (Enum.TryParse(colorString, out colorValue))        
        if (Enum.IsDefined(typeof(Colors), colorValue) | colorValue.ToString().Contains(","))  
           Console.WriteLine("Converted '{0}' to {1}.", colorString, colorValue.ToString());
        else
           Console.WriteLine("{0} is not an underlying value of the Colors enumeration.", colorString);
     else
        Console.WriteLine("{0} is not a member of the Colors enumeration.", colorString);
Peter
  • 26,307
  • 7
  • 58
  • 80
8

(Warning: includes plug for my own open source library...)

Personally I'd use Unconstrained Melody, which ends up with cleaner and more typesafe code:

ColorEnum color = Enums.ParseName<ColorEnum>(text);

You can use TryParseName if you suspect it may be invalid. Obviously this requires an extra library, but hopefully you'll find other things in there helpful too :)

Enum.TryParse from .NET 4 is better than the other built-in options, but:

  • You won't catch non-enum types at compile time, e.g. Enum.TryParse<int>(...) will still compile; Unconstrained Melody really only allows enum types
  • Enum.TryParse will also parse "1" (or whatever the numeric value is when converted to a string) - if you really only expect names, I think it's better to only accept names

I definitely wouldn't switch on the string values - it means if you rename the enum values, you've got to remember to rename the case value as well.

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • Since all of the methods on Enums.cs are constrained the same, could you not express it as a class constraint instead (and be able to name it `Enum`), or does that not work (or is there some reason you're avoiding that)? – Damien_The_Unbeliever Sep 21 '11 at 09:12
  • @Damien: Well, the extension methods couldn't be in a generic class. I *could* potentially have one class for extension methods and one class for non-extension methods... but I'm not sure it would make much difference. – Jon Skeet Sep 21 '11 at 09:16
4

And what about Enum.TryParse<TEnum> ?

string myColorStr = "red";
colorEnum myColor;
if(!Enum.TryParse<colorEnum>(myColorStr, true, out myColor))
{
    throw new InvalidOperationException("Unknown color " + myColorStr);
}
Steve B
  • 34,941
  • 18
  • 92
  • 155
2

Number 1 simply on readability and maintainability. If you extend the enum you need to do no extra work, wheras with 2 you have to add more cases to the switch statement

saj
  • 4,192
  • 2
  • 24
  • 24
2

Because you added the tag 'performance', I'm going to go with the switch.
Yes, you will have to change the cases when you rename/add/remove anything in the enum. Well that's just too bad then. Any variant of Enum.Parse/TryParse uses a lot of weird code and some reflection, just take a look inside the function with ILSpy or such. Then there is also the issue of accepting "-12354" and even a comma-separated list of valid names (resulting in all of them ORed together) even when the enum doesn't have a [Flags] attribute.

As an alternative, you could make a dictionary that translates enum names to values. It should actually be faster than the switch, because a switch on strings also goes through a dictionary but you save the actual switch part.

Obviously both ways cost some more maintenance than enum.parse and variants; whether it's worth it is up to you, since out of all of us only you have enough knowledge of the project to make the performance/coding-time trade off.

harold
  • 53,069
  • 5
  • 75
  • 140
1

Personally, while I am totally fine with the Enum.Parse solution for non-performance scenarios (read: one off run of this function occasionally ... and there are many such scenarios to be sure), I can't countenance the thought of possibly involving some reflection type method when this function needs to be performed in a loop over hundreds/thousands plus enum values at once. Gack!

So the following is a solution that gets some of the best of both worlds.

Just retrieve all values of the enum at startup time or what not, whenever it works best for you (below is one way of doing that), and then construct a Dictionary with them.

    private static Dictionary<string, Color> colorDictionary;
    public static Dictionary<string, Color> ColorDictionary
    {
        get
        {
            if (colorDictionary== null) {
                colorDictionary = new Dictionary<string, Color>();
                var all = Enum.GetValues(typeof(Color)).OfType<Color>();
                foreach (var val in all)
                    dict.Add(val.ToString(), val);
            }
            return colorDictionary;
        }
    }
Nicholas Petersen
  • 7,801
  • 6
  • 50
  • 65
1

1) Is much better. It's cleaner code. You are doing in one line what would take multiple in 2). Also, it's less bug prone. When you add another item to colorEnum, you would need to remember to extend 2) wheras 1) would just work.

You may also want some error handling on the Enum.Parse.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Tom Squires
  • 8,106
  • 9
  • 44
  • 68
1

Other than the fact that the two different code snippets doesn't do the same thing, I'd use this:

colorEnum color;
if (!colorEnum.TryParse(colorString, true, out color)
    color = colorEnum.Green;    // Or whatever default value you wish to have.

If you don't have .NET 4.0 then I'd do something like this:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
    if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
        return defaultValue;

    return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
}

This is an Extension Method to string.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Sani Singh Huttunen
  • 21,744
  • 5
  • 66
  • 73
0

I find the switch variant horrible since you will have to modify the switch everytime you change the enum also.

I like to use the TryParse that belongs to your enum. So you can use it like this

string colorString = .....
colorEnum color;

colorEnum.TryParse(colorString, out color);

Or if you don't care about the case of the string

colorEnum.TryParse(colorString, true, out color);

The returnvalue of TryParse is true if the string was a valid enum, false if not.

Øyvind Bråthen
  • 54,098
  • 26
  • 117
  • 143
0

On a performance point of view, as enums are implemented as static fields, the parse method will probably ends up doing refection on then enum type and try a GetField method which might be faster than the case. On the other end, if 90% of the case, the color is green, the case will be very fast... Note that the CLR sometimes rearrange code internally, changing the order of the case based on statistic (actually, I'm not sure it does that but the doc claims it could).

VdesmedT
  • 8,797
  • 3
  • 31
  • 50
  • 3
    on the contrary, reflection is slow as molasses and switch is blazing fast - switch on strings is implemented as a dictionary to convert strings to integers, and then a normal dense switch, which has nothing to do with statistics because it's a jump table not "linear IF's" which *isn't used anyway*, the compiler makes a "tree of IF's and some switches for the dense parts" - which is of course an implementation detail – harold Sep 21 '11 at 11:20
0

I use the following, it gets you all the type safety while still not falling over when you add new values into the Enum, it's also very fast.

public static colorEnum? GetColorFromString(string colorString)
{
    colorEnum? retVal = null;
    if(Enum.IsDefined(typeof(colorEnum), colorString))
        retVal = (colorEnum)Enum.Parse(typeof(colorEnum), colorString);
    return retVal;
}

My test with 8 items in the enum shows this way to be faster than the switch method.

Or else you can use (the very slow way):

public static colorEnum? GetColorFromString(string colorString)
{
    foreach (colorEnum col in Enum.GetValues(typeof(colorEnum)))
    {
        if (col.ToString().Equals(colorString))
        {
            return col;
        }
    }
    return null;
}
Seph
  • 7,864
  • 10
  • 58
  • 86