Casting ints to enums in C#

前端 未结 12 2117
北荒
北荒 2020-12-05 02:30

There is something that I cannot understand in C#. You can cast an out-of-range int into an enum and the compiler does not flinch. Imagine this

相关标签:
12条回答
  • 2020-12-05 02:41

    That is unexpected... what we really want is to control the casting... for instance:

    Colour eco;
    if(Enum.TryParse("17", out eco)) //Parse successfully??
    {
      var isValid = Enum.GetValues(typeof (Colour)).Cast<Colour>().Contains(eco);
      if(isValid)
      {
         //It is really a valid Enum Colour. Here is safe!
      }
    }
    
    0 讨论(0)
  • 2020-12-05 02:43

    When you define an enum you are essentially giving names to values (syntatic sugar if you will). When you cast 17 to Colour, you are storing a value for a Colour that has no name. As you probably know in the end it is just an int field anyways.

    This is akin to declaring an integer that would accept only values from 1 to 100; the only language I ever saw that supported this level of checking was CHILL.

    0 讨论(0)
  • 2020-12-05 02:48

    That is one of the many reasons why you should never be assigning integer values to your enums. If they have important values that need to be used in other parts of the code turn the enum into an object.

    0 讨论(0)
  • 2020-12-05 02:48

    Thought I'd share the code I ended up using to validate Enums, as so far we don't seem to have anything here that works...

    public static class EnumHelper<T>
    {
        public static bool IsValidValue(int value)
        {
            try
            {
                Parse(value.ToString());
            }
            catch
            {
                return false;
            }
    
            return true;
        }
    
        public static T Parse(string value)
        {
            var values = GetValues();
            int valueAsInt;
            var isInteger = Int32.TryParse(value, out valueAsInt);
            if(!values.Select(v => v.ToString()).Contains(value)
                && (!isInteger || !values.Select(v => Convert.ToInt32(v)).Contains(valueAsInt)))
            {
                throw new ArgumentException("Value '" + value + "' is not a valid value for " + typeof(T));
            }
    
            return (T)Enum.Parse(typeof(T), value);
        }
    
        public static bool TryParse(string value, out T p)
        {
            try
            {
                p = Parse(value);
                return true;
            }
            catch (Exception)
            {
                p = default(T);
                return false;
            }
    
        }
    
        public static IEnumerable<T> GetValues()
        {
            return Enum.GetValues(typeof (T)).Cast<T>();
        }
    }
    
    0 讨论(0)
  • 2020-12-05 02:48

    Old question, but this confused me recently, and Google brought me here. I found an article with further searching that finally made it click for me, and thought I'd come back and share.

    The gist is that Enum is a struct, which means it's a value type (source). But in essence it acts as a derived type of the underlying type (int, byte, long, etc.). So if you can think of an Enum type as really just the same thing as its underlying type with some added functionality/syntactic sugar (as Otávio said) then you'll be aware of this "problem" and be able to protect against it.

    Speaking of that, here is the heart of an extension method I wrote to easily convert/parse things to an Enum:

    if (value != null)
    {
        TEnum result;
        if (Enum.TryParse(value.ToString(), true, out result))
        {
            // since an out-of-range int can be cast to TEnum, double-check that result is valid
            if (Enum.IsDefined(typeof(TEnum), result.ToString()))
            {
                return result;
            }
        }
    }
    
    // deal with null and defaults...
    

    The variable value there is of type object, as this extension has "overloads" that accept int, int?, string, Enum, and Enum?. They're all boxed and sent to the private method that does the parsing. By using both TryParse and IsDefined in that order, I can parse all the types just mentioned, including mixed case strings, and it's pretty robust.

    Usage is like so (assumes NUnit):

    [Test]
    public void MultipleInputTypeSample()
    {
        int source;
        SampleEnum result;
    
        // valid int value
        source = 0;
        result = source.ParseToEnum<SampleEnum>();
        Assert.That(result, Is.EqualTo(SampleEnum.Value1));
    
        // out of range int value
        source = 15;
        Assert.Throws<ArgumentException>(() => source.ParseToEnum<SampleEnum>());
    
        // out of range int with default provided
        source = 30;
        result = source.ParseToEnum<SampleEnum>(SampleEnum.Value2);
        Assert.That(result, Is.EqualTo(SampleEnum.Value2));
    }
    
    private enum SampleEnum
    {
        Value1,
        Value2
    }
    

    Hope that helps somebody.

    Disclaimer: we do not use flags/bitmask enums... this has not been tested with that usage scenario and probably wouldn't work.

    0 讨论(0)
  • 2020-12-05 02:48

    It would throw an error by using the Enum.Parse();

    Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17");
    

    Might be worth using to get a runtime error thrown, if you like.

    0 讨论(0)
提交回复
热议问题