Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion with parsing an Enum

Tags:

c#

enums

parsing

I am converting a numeric value that is of string type into a corresponding Enum. While I was testing my code, I discovered interesting behavior that has me confused.

Using the code example below, can somebody shed light on why an exception isn't thrown if/when the "s" variable has a value that doesn't match one of the Enum values? Also, how is that the sEnum var can be set to a value that doesn't exist within the definition of the Stooge enum?

class Program
{
    enum Stooge
    {
        Unspecified,
        Moe,
        Larry,
        Curly,
        Shemp
    }

    static void Main(string[] args)
    {
        while (true)
        {
            Console.WriteLine("Enter a number...");

            string s = Console.ReadLine();
            Stooge sEnum = (Stooge)(int.Parse(s)); //Why doesn't this line throw if s != 0, 1, 2, 3, or 4?

            Console.WriteLine("\r\nYou entered: {0}\r\nEnum String Value: {1}\r\nEnum Int Value: {2}\r\n", s, sEnum.ToString(), (int)sEnum);
        }
    }
}
like image 738
Jed Avatar asked Feb 03 '11 22:02

Jed


People also ask

Can you parse an enum?

The Parse method in Enum converts the string representation of the name or numeric value of enum constants to an equivalent enumerated object.

Is enum parse case sensitive?

If value is the string representation of the name of an enumeration value, the comparison of value with enumeration names is case-sensitive.

How to store enum in database c#?

No need to convert (int)Enum or (Enum)int in code. Just use the enum and ef code first will save the int for you. p.s.: "[EnumDataType(typeof(PhoneTypes))]" attribute is not required, just an extra if you want additional functionality. Save this answer.

How do I convert a string to enum in typescript?

To convert a string to an enum: Use keyof typeof to cast the string to the type of the enum. Use bracket notation to access the corresponding value of the string in the enum.


2 Answers

This was a decision on the part of the people who created .NET. An enum is backed by another value type (int, short, byte, etc), and so it can actually have any value that is valid for those value types.

I personally am not a fan of the way this works, so I made a series of utility methods:

/// <summary>
/// Utility methods for enum values. This static type will fail to initialize 
/// (throwing a <see cref="TypeInitializationException"/>) if
/// you try to provide a value that is not an enum.
/// </summary>
/// <typeparam name="T">An enum type. </typeparam>
public static class EnumUtil<T>
    where T : struct, IConvertible // Try to get as much of a static check as we can.
{
    // The .NET framework doesn't provide a compile-checked
    // way to ensure that a type is an enum, so we have to check when the type
    // is statically invoked.
    static EnumUtil()
    {
        // Throw Exception on static initialization if the given type isn't an enum.
        Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
    }

    /// <summary>
    /// In the .NET Framework, objects can be cast to enum values which are not
    /// defined for their type. This method provides a simple fail-fast check
    /// that the enum value is defined, and creates a cast at the same time.
    /// Cast the given value as the given enum type.
    /// Throw an exception if the value is not defined for the given enum type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumValue"></param>
    /// <exception cref="InvalidCastException">
    /// If the given value is not a defined value of the enum type.
    /// </exception>
    /// <returns></returns>
    public static T DefinedCast(object enumValue)

    {
        if (!System.Enum.IsDefined(typeof(T), enumValue))
            throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                           typeof (T).FullName);
        return (T) enumValue;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="enumValue"></param>
    /// <returns></returns>
    public static T Parse(string enumValue)
    {
        var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
        //Require that the parsed value is defined
        Require.That(parsedValue.IsDefined(), 
            () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                enumValue, typeof(T).FullName)));
        return parsedValue;
    }

    public static bool IsDefined(T enumValue)
    {
        return System.Enum.IsDefined(typeof (T), enumValue);
    }

}

public static class EnumExtensions
{
    public static bool IsDefined<T>(this T enumValue)
        where T : struct, IConvertible
    {
        return EnumUtil<T>.IsDefined(enumValue);
    }
}

This way, I can say:

if(!sEnum.IsDefined()) throw new Exception(...);

... or:

EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.

Update

As Brandon Kramer pointed out in the comments, C# 7.3 introduced some new generic types that allow the where T : struct, IConvertible above to be replaced with where T : Enum, to get better compile-time checking of the Enum-ness of the type being passed in. That way you can get rid of the guard statement in EnumUtil's static constructor.

like image 62
StriplingWarrior Avatar answered Sep 19 '22 10:09

StriplingWarrior


An enum is just technically an int (or whatever you have defined the enum's underlying type to be). you can check for a corresponding value in the enum, though with a call to Enum.IsDefined. More info here: Cast int to enum in C#

like image 41
Gabriel Magana Avatar answered Sep 22 '22 10:09

Gabriel Magana