views:

863

answers:

8

There is something that I cannot understand in C#. You can cast an out-of-range int into an enum and the compiler does not flinch. Imagine this enum:

enum Colour
{
    Red = 1,
    Green = 2,
    Blue = 3
}

Now, if you write:

Colour eco;
eco = (Colour)17;

The compiler thinks that’s fine. And the runtime, too. Uh?

Why did the C# team decide to make this possible? This decision misses the point of using enums, I think, in scenarios like this:

void DoSomethingWithColour(Colour eco)
{
    //do something to eco.
}

In a strong-typed language like C#, I would like to assume that eco will always hold a legal Colour value. But this is not the case. A programmer could call my method with a value of 17 assigned to eco (as in previous code snippet), so the code in my method must not assume that eco holds a legal Colour value. I need to test for it explicitly and handle the exceptional values as I please. Why is this?

In my humble opinion, it would be much nicer if the compiler issued an error (or even a warning) message when casting an out-of range int into an enum, if the int value is known at compile time. If not, the runtime should throw an exception at the assignment statement.

What do you think? Is there any reason why this is so?

(Note. This is a question I posted ages ago on my blog but got no informative response.)

+14  A: 

Guessing about 'why' is always dangerous, but consider this:

enum Direction { North =1, East = 2, South = 4, West = 8 }
Direction ne = Direction.North | Direction.East;

int value = (int) ne; // value == 3
string text = ne.ToString();  // text == "3"

If the [Flags] attribute is put in front of the enum, that last line changes to

string text = ne.ToString();  // text == "North, East"
Henk Holterman
Your point being? :-)If you want to make SouthEast to be a valid direction, I think you should include it in the enum declaration as SouthEast = 10, for example. Agree?
CesarGon
They are called flag enumerations. Very vaild design.
Matthew Whited
No, do not agree. Bit fields are a perfectly valid use of an enum, so much so that the [Flags] attribute is not even required to use them as such.
Ed Swangren
In this example you could certainly add a SouthEast value, but what if your enum specifies a bunch of options? Are you going to create one value for every possible combination?
Ed Swangren
@CesarGon... no. If I want to set flags on a property, I don't want to have to list every possible combination of options. For directions this might be trivial to do, but in most other cases it would suck.
Brandon
No, don't agree. enums are useful (intended) to be used as bitmasks (sets). The example is weak in that North|South isn't a valid direction, but in general the number of combinations would be far to big.
Henk Holterman
@CesarGon: Imagine your flags enum contained not 4 bits but 16 bits. Would you really like to define all 65536 possible combinations (assuming all combinations are valid)? The point of the [flags] attribute is just that you don't have to do that. (Your points stays valid, though: You could still cast (Direction)16, although 16 is not a valid Direction)
nikie
Yes, flag enums are a valid design, no problem with that. But I am afraid that the consequence is that enum types are providing less value (in terms of type safety) than they could.Maybe language designers should treat enum types as one thing and bitfields (called flag enums in C#) as a different thing, using different constructs in the language. I would like to have the type safety that enum types are supposed to give you, and the flexibility that bitfields are supposed to offer.
CesarGon
You could alway use "static readonly" or "const" if you don't want other values.
Matthew Whited
Sorry folks, I wasn't too clear in my previous comments: I did not mean to say that you should add every single combination of valies to a flag enum; flag enums (bitfields) are meant to be combined as necessary. I was referring to non-flag enums only (see my previous comment please).
CesarGon
@Matthew Whited: Could you please provide an example of "static readonly" or "const" on an enum?
CesarGon
+2  A: 

When you define an enum you are essentially giving names to values (syntatic sugar if you will). When you cast 17 to Colour, you are storing a value for a Colour that has no name. As you probably know in the end it is just an int field anyways.

This is akin to declaring an integer that would accept only values from 1 to 100; the only language I ever saw that supported this level of checking was CHILL.

Otávio Décio
Disagree. Maybe in the olden days of C++ it was syntactic sugar. But in .NET, enums are first-class types. That's the whole point of having enum types!
CesarGon
Can you imagine the overhead for the CLR to check for valid enums? I think in the end they made the decision to let it go unchecked because it was not worth checking it.
Otávio Décio
How about the fact you could no longer use flag enumerations?
Matthew Whited
As I said in other comment, maybe the language and runtime should treat enum types and bitfields (flag enums) as different constructs.
CesarGon
+3  A: 

You don't need to deal with exceptions. The precondition for the method is that callers should use the enum, not cast any willy nilly int to the said enum. That would be madness. Isn't the point of enums not to use the ints?

Any dev who would cast 17 to the Colour enum would need 17 kicks up their backside as far as I'm concerned.

Wim Hollebrandse
Agreed about the kicks. :-DBut defensive programming is a good practice. And when you're doing public interfaces for your class library, you *know* you should check input parameters.
CesarGon
So you set a default value on a switch and either throw an exception or break out to the debugger. There are plenty of vaild cases for these integer values from enumerations.
Matthew Whited
@CesarGon: So check them yourself, in the example you give all you need to check is that the value is between 1 and 3. CmdrTallen has a comment showing how to check in general.
tloach
Exactly, that is what I am doing. I check for preconditions myself at method entry.My point is, this shouldn't be necessary. My argument is: when you specify a parameter as int, you don't need to check that the received value is an int (the compiler/runtime guarantee that). When you specify a parameter as a class FooBar, same thing: you don't need to check. However, when you specify a parameter as a Colour enum, you still have to check that it is a Colour enum. I find this treatment of enums a bit inconsistent.
CesarGon
+3  A: 

That is one of the many reasons why you should never be assigning integer values to your enums. If they have important values that need to be used in other parts of the code turn the enum into an object.

Bryan Rowe
There are also plenty of reasons to include the values. You could be interoperating with an external system and just using the enumeration to make your code cleaner.
Matthew Whited
I dont think there are 'plenty' of reasons. I understand the argument of interfacing with external systems, sure. But there is no way you can argue that an enum with values assigned is going to result in cleaner code -- more like smellier code. You can follow a simple value object pattern and create easy to understand objects... and, you know, follow the whole OOP thing?
Bryan Rowe
When transferring data to and from the DB you need to coerce enums from int to enum and back.
Guy
+2  A: 

It would throw an error by using the Enum.Parse();

Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17");

Might be worth using to get a runtime error thrown, if you like.

CmdrTallen
+1  A: 

Short version:

Don't do this.

Trying to change enums into ints with only allowing valid values (maybe a default fallback) requires helper methods. At that point you have don't have an enum--you reallly have a class.

Doubly so if the ints are improtant--like Bryan Rowe said.

Broam
+8  A: 

Not sure about why, but I recently found this "feature" incredibly useful. I wrote something like this the other day

// a simple enum
public enum TransmissionStatus
{
    Success = 0,
    Failure = 1,
    Error = 2,
}
// a consumer of enum
public class MyClass 
{
    public void ProcessTransmissionStatus (TransmissionStatus status)
    {
        ...
        // an exhaustive switch statement, but only if
        // enum remains the same
        switch (status)
        {
            case TransmissionStatus.Success: ... break;
            case TransmissionStatus.Failure: ... break;
            case TransmissionStatus.Error: ... break;
            // should never be called, unless enum is 
            // extended - which is entirely possible!
            // remember, code defensively! future proof!
            default:
                throw new NotSupportedException ();
                break;
        }
        ...
    }
}

question is, how do I test that last case clause? It is completely reasonable to assume someone may extend TransmissionStatus and not update its consumers, like poor little MyClass above. Yet, I would still like to verify its behaviour in this scenario. One way is to use casting, such as

[Test]
[ExpectedException (typeof (NotSupportedException))]
public void Test_ProcessTransmissionStatus_ExtendedEnum ()
{
    MyClass myClass = new MyClass ();
    myClass.ProcessTransmissionStatus ((TransmissionStatus)(10));
}
johnny g
CesarGon
So ... presuming enums were completely type safe with value validation, how would you go about testing the preceding conditional? Would you rather add an "Unsupported" value into every enum you ever write for the express purpose of testing default clauses? If you ask me, that smells like a much bigger stink ;)
johnny g
In fact, an Unsupported value in an enum is explicitly clear. I think that would be a much better way to do the testing, yes. :-)
CesarGon
wow, good luck :)
johnny g