21

There is no data loss by doing this, so what's the reason for having to explicitly cast enums to ints?

Would it not be more intuitive if it was implicit, say when you have a higher level method like:

PerformOperation ( OperationType.Silent type )

where PerformOperation calls a wrapped C++ method that's exposed as such:

_unmanaged_perform_operation ( int operation_type )
Joan Venge
  • 315,713
  • 212
  • 479
  • 689

7 Answers7

48

There are two primary and inconsistent uses of enums:

enum Medals
{ Gold, Silver, Bronze }

[Flags]
enum FilePermissionFlags
{
    CanRead = 0x01,
    CanWrite = 0x02,
    CanDelete = 0x04
}

In the first case, it makes no sense to treat these things as numbers. The fact that they are stored as integers is an implementation detail. You can't logically add, subtract, multiply or divide Gold, Silver and Bronze.

In the second case, it also makes no sense to treat these things as numbers. You can't add, subtract, multiply or divide them. The only sensible operations are bitwise operations.

Enums are lousy numbers, so you should not be able to treat them as numbers accidentally.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    How about implicitly converting an enum to its underlying value type when it is explicitly specified. i.e. "enum FilePermissionFlags : int" could implicitly convert to int. – Lazlo Jan 18 '11 at 21:42
  • 8
    @Lazlo: And how do you tell what was in the source code when the enum was loaded out of metadata? That's a very strange thing to switch behaviour on. Really what would have been better in my opinion was truly enumerated types for things like "Medals", that are not convertible to any kind of integer, and a special kind of integer that is a "flag uint" that has the bitwise operations on it. The fact that ints are conflated to be both small numbers *and* small arrays of bits only seems normal because we grew up with it; in fact it is really gross. – Eric Lippert Jan 18 '11 at 22:00
  • @Eric Lippert: Is there no flagging possible for such a case? You work with the compiler on a daily basis, I presume. Regarding the bit flags, I agree. – Lazlo Jan 18 '11 at 22:25
  • @Eric Lippert: I find it useful to use enumerations (which I explicitly assign integer values, so that things won't break if I reorder them or whatever) in order to work with SQL. So, I can have a C# function which takes in a `Medals` parameter and my SQL can take a (for example) `new SqlParameter("@myMedal", (int)Medals.Gold)`. Is that the wrong way to do it? – Brian Jan 19 '11 at 14:37
  • IMHO, it's an annoying, overprotective restriction. There are a few like this built in the c# compiler (i.e. you cannot fall through case label like in c++). But as I said, it's only MHO. – HuBeZa Mar 15 '11 at 10:01
  • 3
    @Eric: As you say, Enums are rather broken really (the IsDefined hack is another example). They seem to be treated as a special concept, but without the necessary infrastruture in the compiler to have them behave sensibly. – nicodemus13 Oct 15 '11 at 21:14
  • 3
    @nicodemus13: I agree completely. I wish we had made two kinds of enums: collections of flags, and distinct values. – Eric Lippert Oct 15 '11 at 21:18
  • 2
    @Eric Lippert: Ah well, next time! Minor annoyances in a great language. – nicodemus13 Oct 16 '11 at 10:49
  • @EricLippert: The fact that the types used for cardinal numbers can also be used within an unchecked numeric context for modulo arithmetic is likewise ugly; I wish C# had defined separate types for those purposes even if within the type system both were stored as `Int32` [personally I think there should have been a separate `Wrap32`, which would not be *convertible* to `Int32` but would include `AsSigned` and `AsUnsigned`; that would make the effects of modular arithmetic much clearer. – supercat Feb 10 '15 at 22:29
  • @supercat: I agree. There are any number of ways that we could add richer type safety to integers. Consider for example all the ways in which you can encode type information in Hungarian notation, which was essentially invented as a manually-checked type system on integers and pointers. Really it should be the compiler that is verifying that an index plus an index is a bug, an index plus an offset is an index, and so on. – Eric Lippert Feb 11 '15 at 00:00
  • 1
    @EricLippert: You reminded me of something I've lamented: the fall of Apps Hungarian. For each mutable type, there are at least five different kinds of fields. In Java, for example, where `Point` is a mutable class, a class which needs to adjust its location might have to use `myLoc = new Point(myLoc.x+1, myLoc.y);`, or it might have to use `myLoc.x +=1;`, or both might work, depending upon which of the find kinds of reference `myLoc` represents. Apps Hungarian may not have been as nice as real type-system-based distinctions, but it would IMHO have been better than nothing. – supercat Feb 11 '15 at 00:19
  • 1
    @supercat: Indeed, one of my seminal moments in learning how to code was rewriting the VBScript string library -- which was conditionally compiled for 16 bit and 32 bit operating systems using ASCII, Unicode and DBCS -- so that every variable had a correct Hungarian prefix indicating whether it was a count of bytes or characters, whether it was a pointer to a wide char or a narrow char, and so on. That alone found so many obscure bugs. It's a nice technique. – Eric Lippert Feb 11 '15 at 00:26
  • I see no reason to force prog-r to write every time (int)Edge.Left just because somebody... ah... can use it incorrectly! In my case I have generic Int field which *cannot* be made enum, but have to transfer some Enum value (RPC task). It's just one of use cases, but which make me mad - I have to convert (hidden)Int to the same Int. So what you achieve with that restriction?? That nobody can write "edge = Edge.left + 1"? It's obvious mistake, what can be *easy* spot by compiler, but in "warning" form, not a restriction by language! Completely useless restriction leading to bloated code, thanks – Vincent Oct 23 '17 at 18:17
5

Because enums do not have to be int based:

The enum keyword is used to declare an enumeration, a distinct type consisting of a set of named constants called the enumerator list. Every enumeration type has an underlying type, which can be any integral type except char.

So you can do something like this:

enum Something :long  { valueX = 0, valueY = 2147483648L }
Philip Rieck
  • 32,368
  • 11
  • 87
  • 99
  • 1
    You are right, but the compiler could figure out if the enum is int based and allow this, or enforce and int based enum by other means? – Joan Venge Jan 18 '11 at 19:47
  • Alternatively it could try to always implicitly cast to the type of the first member. – Joan Venge Jan 18 '11 at 19:48
  • 2
    Sure the compiler *could* - but what is the meaning of `valueY - valueX`? What if the names were `Blue` and `Checkered`? If you are using enums as integers so often that an explicit cast is a burden, perhaps you should question your design. – Philip Rieck Jan 18 '11 at 19:54
  • I am just trying to find the reasoning which I got it now. But I don't think there is anything wrong with my design because like I said the wrapped framework is beyond my control. – Joan Venge Jan 18 '11 at 20:00
2

I think that the answer to "Why enums require an explicit cast to int type?" is that strongly-typed enums as per C# are a useful defence against common programming errors.

Imagine I had a painting management application, based on two enums:

enum Textures { Gloss, Satin, Matt };
enum Colors { White, Green, Red, Blue, Brown };

And a room-painting method like this:

private void PaintRoom (Textures texture, Colors color)

In a language with strong enum typing like in C#, a command like this:

 // nonsensical attempt to have texture=White and color=Matt
PaintRoom(Colors.White, Textures.Matt);

...would yeild a compile-time error (can't put color into where a texture is expected and vice versa). But in languages where the enums aren't strongly-typed and/or implicit conversion can occur (including C and C++), both our Colors and our Textures can be treated as int, so we can happily issue a PaintRoom(Colors.White, Textures.Matt) command and it will compile fine, but we end up with a room painted Gloss Red (0, 2) instead of the Matt White (2, 0) that we intended and the code on a quick glance seems to say.

So strongly-typed enums are mostly a good thing, to stop people accidentally putting enums into places they were not meant for. And where someone really wants their enum as an int, C# forces the coder to make their intention clear by insisting on an explicit conversion.

Jinlye
  • 1,774
  • 18
  • 19
2

Why do you say no data loss? Not all enums are ints, after all. They must be integer-typed, but that can mean byte, ulong, etc.

As a corner-case the literal 0 is implicit, but; what would be your use-case here?

It is pretty rare I need to do this - usually data import etc. An occasional no-op cast makes perfect sense to me, and avoids accidental mistakes.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks, I am not sure what you mean in your second paragraph. 0 can be implicitly cast to an enum? – Joan Venge Jan 18 '11 at 19:50
  • 2
    @Joan: The spec says that any *literal zero* is convertible to any enum, so that you are guaranteed to be able to initialize it to its default value even if there is no zero value defined in the enum. The compiler actually allows any *constant zero*. See http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx and http://blogs.msdn.com/b/ericlippert/archive/2006/03/29/the-root-of-all-evil-part-two.aspx for the horrible details. – Eric Lippert Jan 18 '11 at 19:52
  • @Eric: Thanks, I got it now. I am also not clear about Marc's first paragraph. So enums are int based whether the members are strings or any other types? Like the members are stored as ints? – Joan Venge Jan 18 '11 at 19:55
  • 1
    @Joan - consider `enum Foo : sbyte {..}` - each member here is sbyte, not int. – Marc Gravell Jan 18 '11 at 19:57
2

Would it not be more intuitive if it was implicit, say when you have a higher level method like:

I actually think not. In this case, you're trying to use an Enum in an edge case.

However, if enums were implicitly converted to integer values, this would dramatically reduce their effectiveness. By forcing an int conversion explicitly, the compiler is treating enum as a special type - one of many options, not as an integer. This more clearly demonstrates the intent of the enum, and reduces the chance of programmer mistakes (ie: assigning values that aren't defined in the enum to an enum variable, etc).

I personally am glad that enum in C# is more than (effectively) a constant int value.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

That is the way C# works...

If Enum had inherited from int, then this should be possible. Enum doesn't inherit from int, and therefore, a cast is required.

The only way to implicit cast classes, is if they inherit.

Jan Sverre
  • 4,617
  • 1
  • 22
  • 28
0

As others have said, it is impossible to change the required cast from EnumType to int. However, there are several workarounds. For instance, I have several enums that refer to similar things, and wanted to be able to assign any of them to a variable. So I went with:

public ValueType State {
    get {
        return state;
    }
    set {
        state = (int)value;
    }
}

and thus, I could say both playerObject.animator.State = Player.STANDING and monsterObject.animator.State = Monster.DROOLING, implicitly as far as the calling class could tell.

Also, yes, there is a legitimate reason why I have to treat enum values as numbers.

Resigned June 2023
  • 4,638
  • 3
  • 38
  • 49