views:

1234

answers:

7

If I have the following enum:

public enum ReturnValue{
    Success = 0,
    FailReason1 = 1,
    FailReason2 = 2
    //Etc...
}

Can I avoid casting when I return, like this:

public static int main(string[] args){
    return (int)ReturnValue.Success;
}

If not, why isn't an enum value treated as an int by default?

A: 

Enums and ints are simply not implicitly castable as per the spec (except for the literal 0, which is allowed for comparison tests / assignments / etc). The explicit cast is all that is needed, though.

Marc Gravell
(spec 13.1.3 and 13.2.2 of ECMA 334v4)
Marc Gravell
A: 

No, you can't avoid casting; as to why there's no implicit conversion, I don't know, but there's not.

mquander
+14  A: 

enums are supposed to be type safe. I think they didn't make them implicitly castable to discourage other uses. Although the framework allows you to assign a constant value to them, you should reconsider your intent. If you primarily use the enum for storing constant values, consider using a static class:

public static class ReturnValue
{
    public const int Success = 0;
    public const int FailReason1 = 1;
    public const int FailReason2 = 2;
    //Etc...
}

That lets you do this.

public static int main(string[] args){
    return ReturnValue.Success;
}

EDIT

When you do want to provide values to an enum is when you want to combine them. See the below example:

[Flags] // indicates bitwise operations occur on this enum
public enum DaysOfWeek : byte // byte type to limit size
{
    Sunday = 1,
    Monday = 2,
    Tuesday = 4,
    Wednesday = 8,
    Thursday = 16,
    Friday = 32,
    Saturday = 64,
    Weekend = Sunday | Saturday,
    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday
}

This enum can then be consumed by using bitwise math. See the below example for some applications.

public static class DaysOfWeekEvaluator
{
    public static bool IsWeekends(DaysOfWeek days)
    {
        return (days & DaysOfWeek.Weekend) == DaysOfWeek.Weekend;
    }

    public static bool IsAllWeekdays(DaysOfWeek days)
    {
        return (days & DaysOfWeek.Weekdays) == DaysOfWeek.Weekdays;
    }

    public static bool HasWeekdays(DaysOfWeek days)
    {
        return ((int) (days & DaysOfWeek.Weekdays)) > 0;
    }

    public static bool HasWeekendDays(DaysOfWeek days)
    {
        return ((int) (days & DaysOfWeek.Weekend)) > 0;
    }
}
Michael Meadows
Works like a charm, Thanks! Strange that you can define an enum: "public enum MyEnum : int{ ... }" and not have its values implicitly castable. There's no concept of a "generic enum" is there? - public enum<int> MyEnum - or is this completely ridiculous?
Pwninstein
You can delcare type on your enum "public enum ReturnValue : int" which enforces that the values are of type int. It still doesn't provide implicit casting. Editing to show when it _is_ a good idea to use values in an enum.
Michael Meadows
+1  A: 

There is no implicit cast because the enum does not have to use int as the underlying type. If your enum used a uint as the underlying type, for instance, there is no implicit cast from uint to int.

tvanfosson
A: 

Strangely enough, this is not specific to the .NET Framework, but just to C#. As the other commenters have already pointed out, in C# this is basically a specification of the language. The same is not true in VB.NET.

Check out the MSDN reference page for Enums in VB.NET. Note that you can specify the data type of an enumeration at Enum declaration time.

That means, if you really don't want to litter your code with casts to (int), you could write your enumeration in VB.NET, declare it as an integer, then use that Enum from C#.

Remember how they told us computers would make our lives so much simpler? :)

Mike
You'll run into the same problem here, since you're consuming the enum in C#, and remember, C# can't implicitly convert the enum! You're back to square one ;)
Matthew Scharley
A: 

You can ascribe this behaviour to the basic intention behind creating Enumerations... to create a set of named constants that can only have specified (or default) values depending on the underlying type.

There are two separate issues to consider, as related to your question:

  1. An Enum value cannot be treated as an int by default because then you would be able to provide any integer and there would be no compile time check to validate that the provided integer does in fact exist as a value in the Enumeration.

  2. Casting becomes necessary since you are trying to convert from the governing type (of type YourCustomEnum which derives from the System.Enum class) to the underlying type, i.e., int or byte, etc.

Cerebrus
A: 

The c# enum is useless.

You can avoid casting from your type AND constrain the values that can be explicitly cast to your type by making a sealed class, and providing implicit/explicit conversion operators.

  • Provide an implicit operator for converting from your type to a generic int so you don't have to cast.
  • Provide an explicit operator for converting from an int to your type, which throws an error if the integer fails to meet the constraint, such as (int x) => (x >= 0 && x <= 2).

If using this technique, create a generic immutable base class such as ConstrainedNumber<T>, which has a constructor that accepts a T value and delegate for the constraint: delegate bool NumberConstraint<T>(T value). The constructor should run the value through the constraint delegate, and throw an exception if it fails to meet the constraint. The base class should also take care of the implicit conversion operation to T, and should handle equality by overloading object.Equals(object) and object.GetHashCode(), defining == and != operators for the type ConstrainedNumber<T>, and implementing IEquatable<T> and IEquatable<ConstrainedNumber<T>>. I also recommend defining an copy constructor for the base class, and all derived types. Cloning can then be implemented cleanly in the base class by retrieving the copy constructor via reflection, but this is entirely optional. You can figure out the ConstrainedNumber<T> implementation yourself, unless I've already posted it on stackoverflow somewhere.

You can provide named static readonly values in your derived ConstrainedNumber, so that you can access them just like an enum.

public sealed class ReturnValue: ConstrainedNumber<int>
{
    public static readonly NumberConstraint<int> constraint = (int x) => (x >= 0 && x < 3);

    public static readonly ReturnValue Success = new ReturnValue(0);
    public static readonly ReturnValue FailReason1 = new ReturnValue(1);
    public static readonly ReturnValue FailReason2 = new ReturnValue(2);

    private ReturnValue( int value ): base( value, constraint ) {}
    private ReturnValue( ReturnValue original ): base (original) {} //may be used to support IClonable implementation in base class
    public static explicit operator ReturnValue( int value )
    {
        switch(value) //switching to return an existing instance is more efficient than creating a new one and re-checking the constraint when there is a limited number of allowed values; if the constraint was more generic, such as an even number, then you would instead return a new instance here, and make your constructors public.
        {
            case 0: return Success;
            case 1: return FailReason1;
            case 2: return FailReason2;
        }
        throw new ArgumentException( "Value fails to meet the constraint defined for " + typeof(ReturnValue).FullName + ".", "value" );
    }

}

You could use this technique for any constraint. For example, a class called EvenNumber may have a constraint that returns true if the given number is even. In that case, you'd just make your constructors public, and simplify your static conversion operator to just return a new EvenNumber, instead of switching to return one of the limited existing instances.

It could be used like this:

EvenNumber x = (EvenNumber)2;
EvenNumber y = (EvenNumber)3; //throws exception "Value fails to meet the constraint defined for {namespace}.EvenNumber."  A c# enum would stupidly allow such a cast, creating an invalid EvenNumber, breaking the object-oriented model
int z = x; //implicit conversion, no cast necessary;
Triynko