1
public enum Foo : byte 
{
    BAR = 0x00,
    BAZ = 0x01,
    DERP = 0xFF
}

public void AppendAsHex(StringBuilder sb, byte b)
{
    sb.AppendFormat("{0:X}", b);
}

Why does this demand an explicit cast?

Foo theDerp = Foo.DERP;
AppendAsHex(sb, (byte)theDerp); // Fine
AppendAsHex(sb, theDerp); // Compile Error

No loss of precision can occur. The method declares it only wants a byte, disregarding any enum goodness.

EDIT

This works fine if we trade the enum for a byte and make the function take another numeric type, eg:

public void AppendAsHex(StringBuilder sb, uint u)
{
    sb.AppendFormat("{0:X}", u);
}

byte b = 21;

AppendAsHex(sb, b); // Fine

So, the compiler will promote a numeric type to a larger numeric type without any fuss, but demands a cast to do the same with an enum:byte to byte.

Clearly an enum:byte is not technically of Type byte, but surely the compiler could see it's of type System.Enum and check the type of the values contained in the enum?

While it makes perfect sense if using complex types the compiler may not be able to size up, in this case the compiler is fully aware of everything. I don't see how, if primitives can be promoted, the compiler would refuse to promote/cast something explicitly declared as a primitive.

It seems inconsistent to me and I'd like to understand it better.

Alex McMillan
  • 17,096
  • 12
  • 55
  • 88

3 Answers3

2

Very simply because C# is strongly typed. An enum value is not of type byte even if you set it's translated values as byte, so you must cast it as byte before you can use it with a function that is expecting type byte. It's no different than casting another type.

Also, if your focus is on keeping things clean, you could consider rewriting (or overloading) your method slightly so that the cast is invisible to everything outside of it. It doesn't change the solution, but assuming you will be reusing the method in more than one place, it is less code:

public void AppendAsHex(StringBuilder sb, Foo b)
{
    AppendAsHex(sb, (byte)b);
}
public void AppendAsHex(StringBuilder sb, byte b)
{
    sb.AppendFormat("{0:X}", b);
}

At which point, this would work

Foo theDerp = Foo.DERP;
AppendAsHex(sb, theDerp);
Lawrence Johnson
  • 3,924
  • 2
  • 17
  • 30
  • Thanks, I do understand that... and it makes perfect sense if using complex types the compiler doesn't know about... but in this case, the compiler is fully aware of everything. I don't see how, if primitives can be promoted, the compiler would refuse to promote/cast something explicitly declared as a primitive. – Alex McMillan Jul 01 '16 at 02:38
  • I think this is an active decision by the .NET to help guide people. If the signature requires a int/byte and I try to shove an Enum in, I would want it pointing out that this is not valid. However giving me to opportunity to cast it allows me to explicitly say I know what i am doing. Not all Enums represent numeric values (all though they may be backed by a numeric identifier), just like a phone number is can be a number but shouldn't be treated as having the properties of a number (add, subtract, order). – Lee Campbell Jul 01 '16 at 02:48
  • 1
    I would only want it "pointing out that this is not valid" if the underlying type of the enum was different. What's the benefit of being able to specify the underlying type of an enum if you can't use its values in that way? I hate muddying my code with (what I feel are) unnecessary casts. – Alex McMillan Jul 01 '16 at 02:56
  • I don't disagree with how you feel about it, but it's not going to change how it works. – Lawrence Johnson Jul 01 '16 at 03:06
  • @AlexMcMillan I added a little extra to the answer for an alternate way to write the code so that when you reuse the method you don't have to cast every time. Doesn't change the answer, but something to consider. – Lawrence Johnson Jul 01 '16 at 05:12
  • 1
    @LawrenceJohnson thanks. It was actually the overloads that prompted me to ask this question. I actually want to pass my function any of a bunch of numeric types (as well as `enum`s based on numeric types). I hoped to just define my function as accepting the largest type and have everything implicitly cast/promote to that type, but the `enum`s wouldn't. – Alex McMillan Jul 01 '16 at 05:52
  • Whenever I write code that references a lot of enums, I also detest the way I have to cast them. In those cases I create a readonly struct of named integer constants that performs the same task without the casting. Far less annoying... – Alan Feb 27 '23 at 01:53
2

The underlying type specifies how much storage is allocated for each enumerator. However, an explicit cast is necessary to convert from enum type to an integral type.

https://msdn.microsoft.com/en-us/library/sbbt4032(v=vs.140).aspx

So, despite the use of : to declare the underlying type, the actual base type of any enum is System.Enum. That's why it still needs explicit cast.

currarpickt
  • 2,290
  • 4
  • 24
  • 39
0

Check out the answer here.

An enum cannot inherit from anything but System.Enum so all the ': byte' is doing is changing how the values of an enum are represented.

Community
  • 1
  • 1
Iverelo
  • 146
  • 1
  • 8
  • 1
    What has inheritance got to do with it? Why can't the compiler look up the underlying type, realise it's the exact type expected, and perform the cast? Is there some downside like accidentally passing a `byte` when you meant to pass a `byte`? That just doesn't make sense. – Alex McMillan Jul 01 '16 at 02:44
  • From [this answer](http://stackoverflow.com/questions/577946/can-i-avoid-casting-an-enum-value-when-i-try-to-use-or-return-it/577953#577953) it's per the spec. i.e. by design. You can find the spec [here](http://www.ecma-international.org/publications/standards/Ecma-334.htm). We can only guess as for the reasons behind the design. – Tone Jul 01 '16 at 02:48
  • Thanks for your help. I wish there was a logical reason for it. – Alex McMillan Jul 01 '16 at 02:50
  • The oddest thing is this "Foo theDerp = Foo.DERP;" What does that mean? How can you assign a value of enum to the enum itself? – Iverelo Jul 01 '16 at 02:56