C# Enum Craziness: Sometimes What You Expect Isn’t The Case

I learned something new today about enums that I find really weird. Lets start with the following test enum:

public enum Action
{
    Run = 2,
    Walk = 4,
    Crawl = 8
}

and then some code to do something with that enum:

static void Main(string[] args)
{
    Console.WriteLine((Action)2);
    Console.WriteLine((Action)4);
    Console.WriteLine((Action)8);
    Console.WriteLine((Action)10);
}

What do you think will happen here? Will it even compile?

When I saw this code snippet I said to myself, “the first three lines look ok but the last line won’t work because 10 isn’t a valid value for this enum. Well, I was wrong. This program actually outputs:

Run
Walk
Crawl
10

Huh?!? How could this be? 10 isn’t a valid value according to my enum definition!

This Should Never Happen…Right?

Ok, lets try something different. How about a method?

public static void Execute(Action action)
{
    switch (action)
    {
        case Action.Run:
            Console.WriteLine("Running");
            break;
        case Action.Walk:
            Console.WriteLine("Walking");
            break;
        case Action.Crawl:
            Console.WriteLine("Crawling");
            break;
        default:
            Console.WriteLine("This will never happen! {0}", action);
            break;
    }
}

Surely you can’t pass anything into this method other than one of the 3 values defined in my enum. So lets run some code:

static void Main(string[] args)
{
    Execute(Action.Run);
    Execute(Action.Walk);
    Execute((Action)55);
}

What do you think this does? Well, it outputs:

Running
Walking
This will never happen! 55

Just about this time you must be thinking: “This has to be a bug!”. Well, it is not. It is by design. here is the excerpt from the C# design spec:

14.5 Enum values and operations

Each enum type defines a distinct type; an explicit enumeration conversion (Section 6.2.2) is required to convert between an enum type and an integral type, or between two enum types. The set of values that an enum type can take on is not limited by its enum members. In particular, any value of the underlying type of an enum can be cast to the enum type, and is a distinct valid value of that enum type.

Wow, that is not at all what I expected when it comes to limiting the possible values of enums. So what are enums good for then? Are they just for code readability? Between this little revelation and my previous hack to associate string values to enums, I am loosing faith in enums.

Can You Handle it?

So what is the best way to check for this in your methods? Should you throw an exception if you receive an enum value you were not expecting? Should you just ignore it? Well, lets see what the .Net base class libraries do.

First, lets start with the System.IO.File class. What happens if I run the following code?

File.Open(@"C:\temp\test.txt", (FileMode)500);

Well, it throws a System.ArgumentOutOfRangeException with the message “Enum value was out of legal range.”. Ok, makes sense – I did pass in something out of range.

Lets try reflection. What does the following code snippet output?

PropertyInfo[] info = typeof(StringBuilder)
.GetProperties((BindingFlags)303); Console.WriteLine(info.Length);

Well, it just outputs ‘0’ which means in this case it is just being ignored and an empty array is returned.

What about System.String? Lets try this code snippet:

"TestString".Equals("TESTSTRING", (StringComparison)245);

Well, it turns out this throws an exception but instead of being a System.ArgumentOutOfRangeException like System.IO did, it throws a System.ArgumentException with the message “The string comparison type passed in is currently not supported.” Ok, so this is kind of the same but still a little inconsistent if you ask me.

Is Anything Safe These Days?

So what is a developer to do? Obviously you need to be aware of this when you are receiving enum types from publicly facing code. It seems there is no clear guidance on this that I can find. The C# design spec explains the behavior but doesn’t really give any guidance on why or how this should be handled. So the only other place to turn for guidance is one of my favorite .Net books Framework Design Guidelines by Brad Abrams and Krzysztof Cwalina. On their section on enums, I can’t find any guidance on how to handle out of range enum values. I do, however, find guidance that we should be using enums:

DO use an enum to strongly type parameters, properties, and return values that represent sets of values

They also suggest that enums should be favored over static constants:

DO favor using an enum over static constants

And Jeffrey Richter (and while I am pointing out my favorite .Net books, I have to add Mr. Richter’s book CLR via C# which contains priceless information on the CLR that you can’t find anywhere else) adds the following commentary:

An enum is a structure with a set of static constants. The reason to follow this guideline is because you will get some additional compiler and reflection support if you define an enum versus manually defining a structure with static constants.

So I guess we do continue to use enums and just know that we can’t always trust their values to be valid. Do you think the C# design spec should be amended to include a recommended behavior for out of range enum values? Perhaps Brad and Krzysztof can include something in their second edition of Framework Design Guidelines.

kick it on DotNetKicks.com

Advertisements

12 Responses to C# Enum Craziness: Sometimes What You Expect Isn’t The Case

  1. I believe the reason why this is possible is to support flags. In other words,

    [flags()]
    public enum MyFlag { unknown = 2, red = 4, blue = 8 }

    public MyFlag WhoGotDaFlag = MyFlag.red | MyFlag.blue;

    Test that out and see what the int value of WhoGotDaFlag is. I bet it isn’t 2, 4 or 8.

    OR-ing together enum values is an extremely important feature of the CLR, and is used throughout the framework. I doubt it’ll be changed.

  2. Luke Foust says:

    I agree that OR-ing enums together is a feature (although it is rather un-intuitive). I with the compiler could make a distinction between enums which are marked with the [Flags] attribute and those which aren’t meant to be OR-ed together.

  3. I second their thoughts that this was meant for quick bit-masking. Take a look at the tutorial I wrote on flags with ASP.NET http://tucholski.blogspot.com/2007/10/dynamic-tab-navigation-wmaster-pages.html

  4. James Curran says:

    [Flags] is part of it. The other part is that if the CLR had to verify every int to enum cast against EVERY possible enum value, your code would come to a screeching halt. Consider the code to handle that:

    enum MyFlag myflag;
    myflag = (int) someInt;

    would become (effectively)

    enum MyFlag myflag;
    bool OK= false;
    for(int v in Enum.GetValues(typeof(MyFlag))
    {
    if (v == someInt)
    {
    OK= true;
    break;
    }
    if (!OK)
    throw new InvalidCastException();

    myflag = (int) someInt;

    Do you really want to go through all that every time you assign an int to an enum? And remember, this cannot be done as compile time, even if the int being assigned is a constant, as the enum may be defined in a different assembly, and may change between compile & run.

  5. Another reason why this behavior can be useful is when it comes to extending enumerations. I came across this recently when working on a Linq provider where there is an ExpressionVisitor class that walks the expression tree. There is a framework enumeration that defines all of the possible expression types, but I can just as easy extend this visitor class to add several of my custom expression types. The ExpressionVisitor uses the enumeration from the framework and enumerations cannot be inherited from, but I can define my own enumeration and specify values that do not overlap with the framework enumeration. I can then pass in values from my new enumeration with no problems. Otherwise I would have to redefine the entire enumeration and add my custom values, then convert to the original enumeration when passing values to the base class. Whether or not this would be a better solution is up for debate. 🙂

  6. JohnB says:

    Interesting, I get the same results with VB too.

  7. […] C# Enum Craziness: Sometimes What You Expect Isn’t The Case « Spontaneous Publicity – Luke Foust talks about some of the stranger behaviour of the enum […]

  8. leppie says:

    Enum.IsDefined() should be used.

  9. NinjaCross says:

    It’s strange that no recommendations and specific hints are given in the official c# specs.
    Anyway, I absolutely agree with the explanation made by James Curran.
    Thanks Luke for pointing to this very important aspect of Enums

  10. […] researching something to do with the new System.Addin namespace in C# 3.5, I was reminded of some enum craziness I’d […]

  11. Vipin Misys says:

    Good Article.Does it mean that we can’t trust ENUM anymore 🙂

    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: