Pergunta

Preciso validar um número inteiro para saber se é um valor enum válido.

Qual é a melhor maneira de fazer isso em C#?

Foi útil?

Solução

Você tem que amar essas pessoas que presumem que os dados nem sempre vêm de uma IU, mas de uma IU sob seu controle!

IsDefined é adequado para a maioria dos cenários, você pode começar com:

public static bool TryParseEnum<TEnum>(this int enumValue, out TEnum retVal)
{
 retVal = default(TEnum);
 bool success = Enum.IsDefined(typeof(TEnum), enumValue);
 if (success)
 {
  retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue);
 }
 return success;
}

(Obviamente, basta descartar 'isto' se você não acha que é uma extensão int adequada)

Outras dicas

IMHO, a postagem marcada como resposta está incorreta.
A validação de parâmetros e dados é uma das coisas que me foram ensinadas décadas atrás.

POR QUE

A validação é necessária porque essencialmente qualquer valor inteiro pode ser atribuído a um enum sem gerar um erro.
Passei muitos dias pesquisando a validação de enum C# porque é uma função necessária em muitos casos.

ONDE

O principal objetivo da validação de enum para mim é validar os dados lidos de um arquivo:você nunca sabe se o arquivo foi corrompido, modificado externamente ou hackeado propositalmente.
E com a validação enum dos dados do aplicativo colados da área de transferência:você nunca sabe se o usuário editou o conteúdo da área de transferência.

Dito isso, passei dias pesquisando e testando muitos métodos, incluindo traçar o perfil do desempenho de cada método que pude encontrar ou projetar.

Fazer chamadas para qualquer coisa em System.Enum é tão lento que era uma penalidade perceptível no desempenho em funções que continham centenas ou milhares de objetos que tinham uma ou mais enumerações em suas propriedades que precisavam ser validadas para limites.

Resumindo, fique longe de tudo na classe System.Enum ao validar valores enum, é terrivelmente lento.

RESULTADO

O método que uso atualmente para validação de enum provavelmente atrairá olhares revirados de muitos programadores aqui, mas é o menos prejudicial para o design de meu aplicativo específico.

Eu defino uma ou duas constantes que são os limites superior e (opcionalmente) inferior da enumeração e as uso em um par de instruções if() para validação.
Uma desvantagem é que você deve atualizar as constantes se alterar a enumeração.
Este método também funciona apenas se o enum for um estilo "auto", onde cada elemento enum é um valor inteiro incremental, como 0,1,2,3,4,....Não funcionará corretamente com sinalizadores ou enums que possuem valores que não são incrementais.

Observe também que esse método é quase tão rápido quanto o normal if "<" ">" em int32s regulares (que marcou 38.000 ticks em meus testes).

Por exemplo:

public const MyEnum MYENUM_MINIMUM = MyEnum.One;
public const MyEnum MYENUM_MAXIMUM = MyEnum.Four;

public enum MyEnum
{
    One,
    Two,
    Three,
    Four
};

public static MyEnum Validate(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

DESEMPENHO

Para aqueles que estão interessados, criei o perfil das seguintes variações em uma validação de enum e aqui estão os resultados.

O perfil foi realizado na compilação do lançamento em um loop de um milhão de vezes em cada método com um valor de entrada inteiro aleatório.Cada teste foi executado mais de 10 vezes e calculada a média.Os resultados do tick incluem o tempo total de execução, que incluirá a geração de números aleatórios, etc.mas esses serão constantes durante os testes.1 tick = 10ns.

Observe que o código aqui não é o código de teste completo, é apenas o método básico de validação de enum.Também houve muitas variações adicionais que foram testadas, e todas elas com resultados semelhantes aos mostrados aqui, que testaram 1.800.000 ticks.

Listado do mais lento ao mais rápido com resultados arredondados, espero que não haja erros de digitação.

Limites determinados no Método = 13.600.000 tiques

public static T Clamp<T>(T value)
{
    int minimum = Enum.GetValues(typeof(T)).GetLowerBound(0);
    int maximum = Enum.GetValues(typeof(T)).GetUpperBound(0);

    if (Convert.ToInt32(value) < minimum) { return (T)Enum.ToObject(typeof(T), minimum); }
    if (Convert.ToInt32(value) > maximum) { return (T)Enum.ToObject(typeof(T), maximum); }
    return value;
}

Enum.IsDefined = 1.800.000 tiques
Observação:esta versão do código não se limita a Mín/Máx, mas retorna Padrão se estiver fora dos limites.

public static T ValidateItem<T>(T eEnumItem)
{
    if (Enum.IsDefined(typeof(T), eEnumItem) == true)
        return eEnumItem;
    else
        return default(T);
}

System.Enum Convert Int32 com conversões = 1.800.000 tiques

public static Enum Clamp(this Enum value, Enum minimum, Enum maximum)
{
    if (Convert.ToInt32(value) < Convert.ToInt32(minimum)) { return minimum; }
    if (Convert.ToInt32(value) > Convert.ToInt32(maximum)) { return maximum; }
    return value;
}

if() Constantes Mín/Máx. = 43.000 ticks = o vencedor 42x e 316x mais rápido.

public static MyEnum Clamp(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

-eol-

Como outros mencionaram, Enum.IsDefined é lento, algo que você deve estar ciente se estiver em loop.

Ao fazer comparações múltiplas, um método mais rápido é primeiro colocar os valores em um HashSet.Depois é só usar Contains para verificar se o valor é válido, assim:

int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet<int> validVals = new HashSet<int>((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
    // is valid
}

Brad Abrams adverte especificamente contra Enum.IsDefined em sua postagem O perigo da simplificação excessiva.

A melhor maneira de se livrar desse requisito (ou seja, a necessidade de validar enums) é remover maneiras pelas quais os usuários podem errar, por exemplo, algum tipo de caixa de entrada.Use enums com menus suspensos, por exemplo, para impor apenas enums válidos.

Esta resposta é uma resposta à resposta de deegee, que levanta os problemas de desempenho do System.Enum, portanto, não deve ser considerada minha resposta genérica preferida, abordando mais a validação de enum em cenários de desempenho restrito.

Se você tiver um problema de desempenho de missão crítica em que um código lento, mas funcional, está sendo executado em um loop apertado, eu pessoalmente consideraria mover esse código para fora do loop, se possível, em vez de resolvê-lo reduzindo a funcionalidade.Restringir o código para suportar apenas enums contíguos pode ser um pesadelo para encontrar um bug se, por exemplo, alguém no futuro decidir descontinuar alguns valores de enum.De forma simplista, você poderia chamar Enum.GetValues ​​uma vez, logo no início, para evitar o acionamento de toda a reflexão, etc., milhares de vezes.Isso deve proporcionar um aumento imediato de desempenho.Se você precisa de mais desempenho e sabe que muitas de suas enums são contíguas (mas ainda deseja oferecer suporte a enums 'gappy'), você pode ir além e fazer algo como:

public abstract class EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    protected static bool IsContiguous
    {
        get
        {
            int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();

            int lowest = enumVals.OrderBy(i => i).First();
            int highest = enumVals.OrderByDescending(i => i).First();

            return !Enumerable.Range(lowest, highest).Except(enumVals).Any();
        }
    }

    public static EnumValidator<TEnum> Create()
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("Please use an enum!");
        }

        return IsContiguous ? (EnumValidator<TEnum>)new ContiguousEnumValidator<TEnum>() : new JumbledEnumValidator<TEnum>();
    }

    public abstract bool IsValid(int value);
}

public class JumbledEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int[] _values;

    public JumbledEnumValidator()
    {
        _values = Enum.GetValues(typeof (TEnum)).Cast<int>().ToArray();
    }

    public override bool IsValid(int value)
    {
        return _values.Contains(value);
    }
}

public class ContiguousEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int _highest;
    private readonly int _lowest;

    public ContiguousEnumValidator()
    {
        List<int> enumVals = Enum.GetValues(typeof (TEnum)).Cast<int>().ToList();

        _lowest = enumVals.OrderBy(i => i).First();
        _highest = enumVals.OrderByDescending(i => i).First();
    }

    public override bool IsValid(int value)
    {
        return value >= _lowest && value <= _highest;
    }
}

Onde seu loop se torna algo como:

//Pre import-loop
EnumValidator< MyEnum > enumValidator = EnumValidator< MyEnum >.Create();
while(import)   //Tight RT loop.
{
    bool isValid = enumValidator.IsValid(theValue);
}

Tenho certeza de que as classes EnumValidator poderiam ser escritas com mais eficiência (é apenas um truque rápido para demonstrar), mas, francamente, quem se importa com o que acontece fora do ciclo de importação?A única parte que precisa ser super rápida está dentro do loop.Esse foi o motivo para seguir a rota da classe abstrata, para evitar um if-enumContiguous-then-else desnecessário no loop (a fábrica Create essencialmente faz isso antecipadamente).Você notará um pouco de hipocrisia, por uma questão de brevidade, este código restringe a funcionalidade a int-enums.Eu deveria usar IConvertible em vez de usar int diretamente, mas esta resposta já é bastante prolixa!

É assim que faço com base em várias postagens online.A razão para fazer isso é garantir que as enumerações marcadas com Flags atributo também pode ser validado com sucesso.

public static TEnum ParseEnum<TEnum>(string valueString, string parameterName = null)
{
    var parsed = (TEnum)Enum.Parse(typeof(TEnum), valueString, true);
    decimal d;
    if (!decimal.TryParse(parsed.ToString(), out d))
    {
        return parsed;
    }

    if (!string.IsNullOrEmpty(parameterName))
    {
        throw new ArgumentException(string.Format("Bad parameter value. Name: {0}, value: {1}", parameterName, valueString), parameterName);
    }
    else
    {
        throw new ArgumentException("Bad value. Value: " + valueString);
    }
}

Eu achei isto link isso responde muito bem.Ele usa:

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)

Decidi por esta solução como uma mistura de desempenho, conveniência e facilidade de manutenção.

public enum DogBreed
{
    Unknown = 0,
    Beagle = 1,
    Labrador = 2,
    PeruvianIncaOrchid = 3,
}
public static class DogBreedExtensions
{
    public static bool IsValidDogBreed(this DogBreed breed)
    {
        var v = (int)breed;
        return v >= 1 && v <= 3;
    }
}

E use-o assim:

var goodInput = 2;
var goodDog = (DogBreed)goodInput;
goodDog.IsValidDogBreed(); // true.

var badInput = 7;
var badDog = (DogBreed)badInput; // no problem, bad data here we come.
badDog.IsValidDogBreed(); // false, you're in the doghouse!

No meu caso, chamar Enum.IsDefined só me levaria a meio caminho, já que normalmente quero rejeitar o valor enum "Desconhecido".

Essa abordagem evita os problemas de desempenho do Enum.IsDefined e define a regra de validação próxima ao enum, que eu gosto.

O método de extensão é facilmente adaptável às mudanças de requisitos e pode ser aplicado a int se desejado - (ou seja, public static bool IsValidDogBreed(this int breed))

Aqui está uma solução genérica rápida, usando um sistema construído estaticamente HashSet<T>.

Você pode definir isso uma vez em sua caixa de ferramentas e usá-lo para toda a validação de enum.

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// Throws if the type parameter is not an enum type.
    /// </summary>
    public static bool IsDefined<T>(T enumValue)
    {
        if (typeof(T).BaseType != typeof(System.Enum)) throw new ArgumentException($"{nameof(T)} must be an enum type.");

        return EnumValueCache<T>.DefinedValues.Contains(enumValue);
    }

    /// <summary>
    /// Statically caches each defined value for each enum type for which this class is accessed.
    /// Uses the fact that static things exist separately for each distinct type parameter.
    /// </summary>
    internal static class EnumValueCache<T>
    {
        public static HashSet<T> DefinedValues { get; }

        static EnumValueCache()
        {
            if (typeof(T).BaseType != typeof(System.Enum)) throw new Exception($"{nameof(T)} must be an enum type.");

            DefinedValues = new HashSet<T>((T[])System.Enum.GetValues(typeof(T)));
        }
    }
}

Observe que essa abordagem também é facilmente estendida à análise de enum, usando um dicionário com chaves de string (considerando a insensibilidade a maiúsculas e minúsculas e representações numéricas de string).

Para validar se um valor é válido em uma enumeração, você só precisa chamar o método estático Enum.IsDefined.

int value = 99;//Your int value
if (Enum.IsDefined(typeof(your_enum_type), value))
{
   //Todo when value is valid
}else{
   //Todo when value is not valid
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top