¿Por qué enviar int a un valor de enumeración no válido NO produce una excepción?


123

Si tengo una enumeración así:

enum Beer
{
    Bud = 10,
    Stella = 20,
    Unknown
}

¿Por qué no arroja una excepción cuando se convierte un tipo intque está fuera de estos valores Beer?

Por ejemplo, el siguiente código no arroja una excepción, emite '50' a la consola:

int i = 50;
var b = (Beer) i;

Console.WriteLine(b.ToString());

Encuentro esto extraño ... ¿Alguien puede aclarar?


27
Tenga en cuenta que siempre puede verificar si un valor es válido con Enum.IsDefined .
Tim Schmelter

genial No lo sabía, probablemente lo
usaré


1
Votado por hacer que Stella valga el doble que Bud.
Dan Bechard

Respuestas:


81

Tomado de Confusion con el análisis de una enumeración

Esta fue una decisión por parte de las personas que crearon .NET. Una enumeración está respaldado por otro tipo de valor ( int, short, byte, etc.), y lo que en realidad puede tener cualquier valor que es válido para los tipos de valor.

Personalmente no soy fanático de la forma en que funciona, así que hice una serie de métodos de utilidad:

/// <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);
    }
}

De esta manera, puedo decir:

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

... o:

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

Editar

Más allá de la explicación dada anteriormente, debe darse cuenta de que la versión .NET de Enum sigue un patrón más inspirado en C que uno inspirado en Java. Esto hace posible tener enumeraciones de "Indicador de bit" que pueden usar patrones binarios para determinar si un "indicador" particular está activo en un valor de enumeración. Si tuviera que definir todas las combinaciones posibles de banderas (es decir MondayAndTuesday, MondayAndWednesdayAndThursday), estas serían extremadamente tediosas. Por lo tanto, tener la capacidad de usar valores de enumeración indefinidos puede ser realmente útil. Solo requiere un poco de trabajo extra cuando desea un comportamiento a prueba de fallas en los tipos de enumeración que no aprovechan este tipo de trucos.


Agradable ... No soy fanático de la forma en que funciona tampoco, me parece extraño
jcvandan

3
@dormisher: "Es una locura, pero hay un método para no hacerlo". Mira mi edición.
StriplingWarrior

2
@StriplingWarrior, por interés. El equivalente de Java es EnumSet.of (lunes, miércoles y jueves). Dentro del EnumSet es solo un largo. Entonces da una buena API sin mucha pérdida de eficiencia.
Iain


@StriplingWarrior en su método de análisis está intentando llamar a IsDefined como un método de extensión. Sin embargo, el método IsDefined no puede convertirse en un método de extensión debido a la forma en que intenta hacer la clase EnumUtil con restricciones (para hacer una comprobación estática de enum, que me gusta mucho). sin embargo, en efecto, me dice que no se puede hacer un método de extensión en una clase genérica. ¿Algunas ideas?
CJC

55

Las enumeraciones se usan a menudo como banderas:

[Flags]
enum Permission
{
    None = 0x00,
    Read = 0x01,
    Write = 0x02,
}
...

Permission p = Permission.Read | Permission.Write;

El valor de p es el número entero 3, que no es un valor de la enumeración, pero claramente es un valor válido.

Personalmente hubiera preferido ver una solución diferente; Preferiría haber tenido la capacidad de hacer tipos enteros de "matriz de bits" y tipos de "un conjunto de valores distintos" como dos características de lenguaje diferentes en lugar de combinarlos en "enumeración". Pero eso fue lo que se les ocurrió al lenguaje original y a los diseñadores de marcos; Como resultado, debemos permitir que los valores no declarados de la enumeración sean valores legales.


19
Pero 3 "claramente es un valor válido" fortalece el punto del OP. Ese atributo [Flags] podría indicarle al compilador que busque una enumeración definida o un valor válido. Sólo digo'.
LarsTech

15

La respuesta corta: los diseñadores de idiomas decidieron diseñar el lenguaje de esta manera.

La respuesta larga: Section 6.2.2: Explicit enumeration conversionsde la Especificación del lenguaje C # dice:

Una conversión de enumeración explícita entre dos tipos se procesa tratando cualquier tipo de enumeración participante como el tipo subyacente de ese tipo de enumeración y luego realizando una conversión numérica implícita o explícita entre los tipos resultantes. Por ejemplo, dado un tipo de enumeración E con y un tipo subyacente de int, una conversión de E a byte se procesa como una conversión numérica explícita (§6.2.1) de int a byte, y una conversión de byte a E se procesa como Una conversión numérica implícita (§6.1.2) de byte a int.

Básicamente, la enumeración se trata como el tipo subyacente cuando se trata de realizar una operación de conversión. Por defecto, el tipo subyacente de una enumeración es Int32, lo que significa que la conversión se trata exactamente como una conversión a Int32. Esto significa que cualquier intvalor válido es permisible.

Sospecho que esto se hizo principalmente por razones de rendimiento. Al hacer enumun tipo integral simple y permitir cualquier conversión de tipo integral, el CLR no necesita hacer todas las verificaciones adicionales. Esto significa que el uso de un enumrealmente no tiene ninguna pérdida de rendimiento en comparación con el uso de un número entero, lo que a su vez ayuda a fomentar su uso.


¿Te parece extraño? Pensé que uno de los puntos principales de una enumeración es agrupar de manera segura un rango de enteros a los que también podemos atribuir significados individuales. Para mí, permitir que un tipo de enumeración contenga cualquier valor entero derrota su propósito.
jcvandan

@dormisher: Acabo de editar y agregar un poco al final. Hay un costo involucrado en proporcionar la "seguridad" que busca. Dicho esto, con el uso normal , esto realmente no es un problema en absoluto.
Reed Copsey

9

De la documentación :

A una variable de tipo Días se le puede asignar cualquier valor en el rango del tipo subyacente; los valores no se limitan a las constantes nombradas.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.