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?
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:”.
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”.
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
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.
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.
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.
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.
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
That’s not what I meant. 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.
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.
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)
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.