Por que converter int com valor de enum inválido NÃO gera exceção?


123

Se eu tiver um enum assim:

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

Por que não lança uma exceção ao converter um intque está fora desses valores para um tipo de Beer?

Por exemplo, o código a seguir não gera uma exceção, ele gera '50' para o console:

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

Console.WriteLine(b.ToString());

Acho isso estranho ... alguém pode esclarecer?


27
Observe que você sempre pode verificar se um valor é válido com Enum.IsDefined .
Tim Schmelter

legal eu não sabia que, provavelmente irá usá-lo no problema que eu estou atualmente tentando resolver
jcvandan


1
Votado por fazer Stella valer o dobro do que Bud.
precisa

Respostas:


81

Retirado da confusão com a análise de um enum

Esta foi uma decisão da parte das pessoas que criaram o .NET. Uma enumeração é apoiado por um outro tipo de valor ( int, short, byte, etc), e por isso pode realmente ter qualquer valor que é válido para os tipos de valor.

Pessoalmente, não sou fã da maneira como isso funciona, então criei uma série de métodos utilitários:

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

Dessa forma, posso dizer:

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

... ou:

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

Editar

Além da explicação fornecida acima, você deve perceber que a versão .NET do Enum segue um padrão mais inspirado em C do que o inspirado em Java. Isso possibilita a enumeração de "Bit Flag", que pode usar padrões binários para determinar se um "flag" específico está ativo em um valor de enum. Se você tivesse que definir todas as combinações possíveis de bandeiras (ou seja MondayAndTuesday, MondayAndWednesdayAndThursday), estes seriam extremamente entediante. Portanto, ter a capacidade de usar valores de enumer indefinidos pode ser realmente útil. Requer apenas um pouco de trabalho extra quando você deseja um comportamento à prova de falhas em tipos de enumeração que não aproveitam esse tipo de truque.


Bom ... Eu não sou um fã da maneira como ele funciona tanto, parece bizarro para mim
jcvandan

3
@dormisher: "É uma loucura, ainda há um método a não ser." Veja minha edição.
usar o seguinte

2
@StriplingWarrior, por interesse. O equivalente em Java é EnumSet.of (segunda, quarta, quinta-feira). Dentro do EnumSet há apenas um único comprimento. Portanto, fornece uma API agradável sem muita perda de eficiência.
Iain


@StriplingWarrior no seu método de análise que você está tentando chamar IsDefined como um método de extensão. No entanto, o método IsDefined não pode ser transformado em um método de extensão devido à maneira como você tenta criar a classe EnumUtil com restrições (para fazer a verificação estática do enum, que eu gosto muito). no entanto, na verdade, está me dizendo que não é possível criar um método de extensão em uma classe genérica. Alguma ideia?
CJC

55

As enums são frequentemente usadas como sinalizadores:

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

Permission p = Permission.Read | Permission.Write;

O valor de p é o número inteiro 3, que não é um valor da enumeração, mas claramente é um valor válido.

Eu pessoalmente prefiro ter visto uma solução diferente; Eu preferiria ter a capacidade de criar tipos inteiros de "matriz de bits" e tipos de "um conjunto de valores distintos" como dois recursos de linguagem diferentes, em vez de confundir ambos em "enum". Mas é isso que os designers originais de linguagem e estrutura criaram; como resultado, temos que permitir que valores não declarados da enum sejam valores legais.


19
Mas 3 "claramente é um valor válido" fortalece o argumento do OP. Esse atributo [Flags] poderia dizer ao compilador para procurar uma enumeração definida ou um valor válido. Apenas dizendo'.
LarsTech

15

A resposta curta: os designers de idiomas decidiram projetar o idioma dessa maneira.

A resposta longa: Section 6.2.2: Explicit enumeration conversionsda especificação de idioma C # diz:

Uma conversão de enumeração explícita entre dois tipos é processada tratando qualquer tipo de enum participante como o tipo subjacente desse tipo de enum e executando uma conversão numérica implícita ou explícita entre os tipos resultantes. Por exemplo, dado um tipo de enumeração E com e tipo subjacente de int, uma conversão de E para byte é processada como uma conversão numérica explícita (§6.2.1) de int para byte e uma conversão de byte para E é processada como uma conversão numérica implícita (§6.1.2) de byte para int.

Basicamente, a enumeração é tratada como o tipo subjacente quando se trata de executar uma operação de conversão. Por padrão, o tipo subjacente de uma enum é Int32, o que significa que a conversão é tratada exatamente como uma conversão para Int32. Isso significa que qualquer intvalor válido é permitido.

Eu suspeito que isso foi feito principalmente por razões de desempenho. Ao criar enumum tipo integral simples e permitir qualquer conversão de tipo integral, o CLR não precisa fazer todas as verificações extras. Isso significa que o uso de um enumrealmente não apresenta perda de desempenho em comparação ao uso de um número inteiro, o que, por sua vez, ajuda a incentivar seu uso.


Você acha isso estranho? Eu pensei que um dos pontos principais de um enum é agrupar com segurança um intervalo de números inteiros que também podemos atribuir significados individuais. Para mim, permitir que um tipo de enum mantenha qualquer valor inteiro anula seu objetivo.
jcvandan

@dormisher: Acabei de editar e adicionar um pouco no final. Há um custo envolvido no fornecimento da "segurança" que você procura. Dito isto, com o uso normal , isso realmente não é um problema.
Reed Copsey

9

A partir da documentação :

Uma variável do tipo Dias pode ser atribuída a qualquer valor no intervalo do tipo subjacente; os valores não estão limitados às constantes nomeadas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.