Switching enum : int?

For the life of me I cannot understand why the C# compiler complains about the following code:

public enum ROOM : int
{
ROOM_900 =0,
ROOM_901 =1
}

int i = 0;
switch (i) {
case ROOM.ROOM_900:

}
The compiler error is regarding the case ROOM.ROOM_900 line of code:
Cannot implicitly convert type ROOM' to int’. An explicit conversion exists (are you missing a cast?)

I don’t understand this because I’ve declared that the ROOM enum are ints. So why does a cast need to be performed?

Can someone explain this to me? What is the point of declaring the enum as ints if the compiler is going to complain about the above code?

Thanks

1 Like

It’s because when you do “enum something : int” it’s setting the underlying storage to an int but the type itself is still “enum” and the compiler only knows it’s “enum”; it doesn’t know that it’s an enum or enum or anything like that. Enum was in C# before generics and type parameters were introduced. So you have to cast it to an int, as in “case (int) ROOM.ROOM_900:”.

2 Likes

Because it’s easy to get wrong. In most cases this automatic casting is not what you want to do. It also makes it difficult to change the enum later.

Implicit casts are for casts that just make sense. No information is lost. Nothing can go wrong. The compiler says “No big deal, I got this”.

Explicit casts are for ones that carry some risk, or have the potential to go wrong. It’s the developer saying “I know exactly what I want here, just do it and let me deal with the consequences”.

2 Likes

I guess I don’t understand why an enum type would ever change.
I understand that you may need to do something like this:
byte b = (byte) ROOM.ROOM_900;

But To me, seems the compiler should be “smart enough” to treat ROOM.ROOM_900 as an int value in a switch statement.

having to do “(int)ROOM.ROOM_900” is like having to do:
int x = 0;
int y = (int) x; //Not necessary to cast

3 Likes

also, this code doesn’t compile:

ROOM.ROOM_900=(byte) ROOM.ROOM_900;

so how can the enum type ever change?

I guess you need some real world examples of the cast failing.

enum EpicFail {untested = 0, fail = 1}
EpicFail epicFail;

epicFail = (EpicFail) 3; // This will fail
int myInt = (int)EpicFail.pass + (int)EpicFail.fail; // This has no meaning in the context of the enum
switch ((int)epicFail){...} // This fails as soon as the enum is changed

Given that casting can fail in so many ways, it makes sense that the casting is explicit. The whole point of enums is to avoid magic numbers that can fail. You aren’t supposed to cast them to their underlying data type in most situations.

1 Like

you have this code:

enum ROOMS: int {
x=0,
y =1
}

void test()
{
ROOMS myRoom;
}

how can you ever change myRoom’s x and y at this point?
it is my understanding that you cant.
The compiler even tells me I cant alter myRoom:
error: Static member `ROOMS.x’ cannot be accessed with an instance reference

if so, then why is the compiler telling me that I need this code:
int z = (int) myRoom.x;
instead of just
int z = myRoom.x

what im not understanding is that if myRoom.x and myRoom.y are always ints, why do I need a cast?
If they are not allways ints, then I understand why you need a cast.
Are they not always ints?

They’re not always ints. You can use longs for example:

enum ROOMS : long
{
x = 999999999998L,
y = 999999999999L
}

and then this would cause an overflow if you could cast back to an int automatically. You can manually cast a long to an int, but if the value is too big, it chops off half the bits and you could end up with two different enum values equating to the same ints, which would break things.

1 Like

OK, you’ve stated that they are longs when you declared the enum.
The compiler knows they are longs.

To change my question to conform to your example: how can you change ROOMS’s x and y to be of type int?
(I dont think you can, because they are always longs)

So using your example, why do I need:
long f = (long) ROOMS.x;
instead of just
long f = ROOMS.x

maybe my understanding of what an ENUM is not right.
I only use the enums to simulate a collection of “const” variables.

Ah I see what you mean. So, you’re right that you can’t actually ever change the values of an enum to a different type, but the problem is that all enums are of type “Enum”, and the compiler doesn’t know what the underlying type is. Like, consider this:

enum ROOMS : long
{
    One = 999999999L,
    etc....
}

void SomeMethod()
{
    var room = ROOMS.One;  // <--here, the compiler could know that room is an enum with underlying type long but...
    AnotherMethod(room);
}

void AnotherMethod(Enum someEnum)
{
    int x = someEnum;  // <-- here, the compiler doesn't know what the underlying type of someEnum is, since the function takes any sort of enum
}

Now, Microsoft could have made it easier by making enums like generics, so you could do “Enum ROOMS” and then if you wanted a function that would only accept enums that were longs, you could do “AnotherMethod(Enum someEnum)”. And if they were just adding enum now they might do it that way. But they added enum way back in C# 1.0 before generics were around and before there were type parameters like that, so it’s like the older non-generic List, in that the compiler doesn’t know what type of object is inside it.

1 Like

Enums are replacements for discrete data. They normally represent type. Something like

enum DamageType {slash, pierce, blunt}

In general there is no need to cast them back to the underlying data type.

switch (damageType) {
    case DamageType.slash :
        ...
}

As indicated there are issues with casting, so it requires an explicit cast. It’s only a single operation to add if you really want to do it. Not sure what the deal is.

1 Like

Yes you can. It’s called code maintenance. If the need ever comes up to change the enum values, you’ll be grateful you didn’t hard code in any actual numbers.

Well, I’ll play devil’s advocate here. For code like the following:

const short SOME_VALUE = 5;
int valueToCheck;
...
switch (valueToCheck)
{
  case SOME_VALUE:
    ...
    break;
}

the compiler would not complain because short is a subset of int. As the compiler knows that ROOMS must be some subset of int as well, it’s a bit inconsistent. Code maintenance is mostly irrelevant, because there’s nothing stopping us from changing the above code to check against a long instead of a short. In one case the compiler is permissive, in another, the compiler is overprotective.

Sure, it’s not a big deal to explicitly convert the enum to an int, but it doesn’t seem like it should be necessary

1 Like

That’s not what I meant. :stuck_out_tongue: Obviously you can change the type of any object if you literally go into the code and change the type. TTTTa was talking about changing the type of a value after it is assigned.

1 Like

You can implicitly convert a smaller type to a larger type (short to int) but you can’t implicitly convert a longer type to a shorter type (int to short, or long to int) or switch unsigned/signed (uint to int). If you change the first line to “const long SOME_VALUE = 5;” it will no longer compile.

1 Like

yes, that is exactly what my issue was with it. thanks

Yeah, and I explicitly mentioned that when I said nothing is stopping us from changing short to long and getting compiler errors. But long is NOT a subset of int – short and ROOMs are, yet short compiles and ROOMs does not – do you see the inconsistency?

You could make another argument and say, “Well, but ROOMs is a non-numeric type that just happens to be stored in an int, whereas short is fundamentally numeric”. But if we instead change SOME_VALUE to a char with value ‘x’, once again the code compiles – and char is not numeric (byte is)

Another sample that may help resolve the issue … ( or not )
This is meant to be run as a console App in a C# IDE.

namespace Enums03
{
    using System;

    public enum Room
    {
        None = -1,
        ROOM_900 = 0, // value 0
        ROOM_901 = 1,
        ROOM_909 = 15,
        ROOM_912 = 20
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            int[] myIntegerLevels = {
                                        0,
                                        1,
                                        2,
                                        3,
                                        15,
                                        20,
                                        21
                                    };
            Room currentRoom;
            //
            foreach (int myIntegerLevel in myIntegerLevels) {
                currentRoom = Enum.IsDefined(typeof (Room), myIntegerLevel)
                                  ? (Room) myIntegerLevel
                                  : Room.None;

                switch (currentRoom) {
                    case Room.None:
                        Console.WriteLine("Level: {0} -- Doing stuff for No Room",
                                          myIntegerLevel);
                        break;
                    case Room.ROOM_900:
                        Console.WriteLine("Level: {0} -- Doing stuff for Room 900",
                                          myIntegerLevel);
                        break;
                    case Room.ROOM_901:
                        Console.WriteLine("Level: {0} -- Doing stuff for Room 901",
                                          myIntegerLevel);
                        break;
                    case Room.ROOM_909:
                        Console.WriteLine("Level: {0} -- Doing stuff for Room 909",
                                          myIntegerLevel);
                        break;
                    case Room.ROOM_912:
                        Console.WriteLine("Level: {0} -- Doing stuff for Room 912",
                                          myIntegerLevel);
                        break;
                }
            }
            Console.ReadKey();
        }
    }
}

1 Like

How does this compile for you? because the compiler in Unity would at least complain about: “case Room.ROOM_900”.

But ROOMS could be a long or a uint, so it’s not necessarily a subset of int. See the example I posted above where you pass the enum to a different method.

What you’re saying would be true if an enum tracked its type at compile time like a generic, like i was saying. If instead of enum : long { blah… } it was Enum { blah… } then the type would be “Enum” and you’d know that it was an enum with underlying type long. But it doesn’t work that way, due to enums being added before generics, and instead the compiler can only knows that the type of all enums is “Enum”, it can’t tell what the underlying type is when you pass it to a function.

1 Like