Existe uma maneira de verificar se int é enum legal em c #?

167

Eu li algumas postagens de SO e parece que a operação mais básica está faltando.

public enum LoggingLevel
{
    Off = 0,
    Error = 1,
    Warning = 2,
    Info = 3,
    Debug = 4,
    Trace = 5
};

if (s == "LogLevel")
{
    _log.LogLevel = (LoggingLevel)Convert.ToInt32("78");
    _log.LogLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), "78");
    _log.WriteDebug(_log.LogLevel.ToString());
}

Isso não causa exceções, é um prazer armazenar 78. Existe uma maneira de validar um valor entrando em uma enumeração?

char m
fonte
2
Possível duplicata de Validar valores de enumeração
Erik

Respostas:

271

Confira Enum.IsDefined

Uso:

if(Enum.IsDefined(typeof(MyEnum), value))
    MyEnum a = (MyEnum)value; 

Este é o exemplo dessa página:

using System;    
[Flags] public enum PetType
{
   None = 0, Dog = 1, Cat = 2, Rodent = 4, Bird = 8, Reptile = 16, Other = 32
};

public class Example
{
   public static void Main()
   {
      object value;     
      // Call IsDefined with underlying integral value of member.
      value = 1;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with invalid underlying integral value.
      value = 64;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with string containing member name.
      value = "Rodent";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with a variable of type PetType.
      value = PetType.Dog;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = PetType.Dog | PetType.Cat;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with uppercase member name.      
      value = "None";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = "NONE";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with combined value
      value = PetType.Dog | PetType.Bird;
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = value.ToString();
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
   }
}

O exemplo exibe a seguinte saída:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False
SwDevMan81
fonte
@matti: Converta "78" em qualquer representação numérica LoggingLevelusada como armazenamento, depois apresente isso como um LoggingLevelvalor enum.
Thecoop
9
Parece que IsDefinednão está funcionando para membros enum bitwised.
Saeed Neamati
29

As soluções acima não lidam com [Flags]situações.

Minha solução abaixo pode ter alguns problemas de desempenho (tenho certeza de que é possível otimizar de várias maneiras), mas essencialmente sempre provará se um valor de enum é válido ou não .

Baseia-se em três suposições:

  • Valores enum em C # só podem ser int, absolutamente nada mais
  • Os nomes de enumeração em C # devem começar com um caractere alfabético
  • Nenhum nome de enum válido pode estar com um sinal de menos: -

Chamar ToString()um enum retorna o intvalor se nenhum enum (sinalizador ou não) for correspondido. Se um valor de enum permitido for correspondido, ele imprimirá o nome da (s) correspondência (s).

Assim:

[Flags]
enum WithFlags
{
    First = 1,
    Second = 2,
    Third = 4,
    Fourth = 8
}

((WithFlags)2).ToString() ==> "Second"
((WithFlags)(2 + 4)).ToString() ==> "Second, Third"
((WithFlags)20).ToString() ==> "20"

Com essas duas regras em mente, podemos supor que, se o .NET Framework fizer seu trabalho corretamente, qualquer chamada para o ToString()método de uma enum válida resultará em algo que tenha um caractere alfabético como seu primeiro caractere:

public static bool IsValid<TEnum>(this TEnum enumValue)
    where TEnum : struct
{
    var firstChar = enumValue.ToString()[0];
    return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}

Pode-se chamá-lo de "hack", mas as vantagens são que, dependendo da implementação Enume dos padrões C # da Microsoft , você não depende de seu próprio código ou cheques com potencial de bugs. Em situações em que o desempenho não é excepcionalmente crítico, isso economiza muitas switchdeclarações desagradáveis ou outras verificações!

Editar

Agradecemos a @ChaseMedallion por apontar que minha implementação original não suportava valores negativos. Isso foi corrigido e foram fornecidos testes.

E os testes para fazer backup:

[TestClass]
public class EnumExtensionsTests
{
    [Flags]
    enum WithFlags
    {
        First = 1,
        Second = 2,
        Third = 4,
        Fourth = 8
    }

    enum WithoutFlags
    {
        First = 1,
        Second = 22,
        Third = 55,
        Fourth = 13,
        Fifth = 127
    }

    enum WithoutNumbers
    {
        First, // 1
        Second, // 2
        Third, // 3
        Fourth // 4
    }

    enum WithoutFirstNumberAssigned
    {
        First = 7,
        Second, // 8
        Third, // 9
        Fourth // 10
    }


    enum WithNagativeNumbers
    {
        First = -7,
        Second = -8,
        Third = -9,
        Fourth = -10
    }

    [TestMethod]
    public void IsValidEnumTests()
    {
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4 | 2)).IsValid());
        Assert.IsTrue(((WithFlags)(2)).IsValid());
        Assert.IsTrue(((WithFlags)(3)).IsValid());
        Assert.IsTrue(((WithFlags)(1 + 2 + 4 + 8)).IsValid());

        Assert.IsFalse(((WithFlags)(16)).IsValid());
        Assert.IsFalse(((WithFlags)(17)).IsValid());
        Assert.IsFalse(((WithFlags)(18)).IsValid());
        Assert.IsFalse(((WithFlags)(0)).IsValid());

        Assert.IsTrue(((WithoutFlags)1).IsValid());
        Assert.IsTrue(((WithoutFlags)22).IsValid());
        Assert.IsTrue(((WithoutFlags)(53 | 6)).IsValid());   // Will end up being Third
        Assert.IsTrue(((WithoutFlags)(22 | 25 | 99)).IsValid()); // Will end up being Fifth
        Assert.IsTrue(((WithoutFlags)55).IsValid());
        Assert.IsTrue(((WithoutFlags)127).IsValid());

        Assert.IsFalse(((WithoutFlags)48).IsValid());
        Assert.IsFalse(((WithoutFlags)50).IsValid());
        Assert.IsFalse(((WithoutFlags)(1 | 22)).IsValid());
        Assert.IsFalse(((WithoutFlags)(9 | 27 | 4)).IsValid());

        Assert.IsTrue(((WithoutNumbers)0).IsValid());
        Assert.IsTrue(((WithoutNumbers)1).IsValid());
        Assert.IsTrue(((WithoutNumbers)2).IsValid());
        Assert.IsTrue(((WithoutNumbers)3).IsValid());
        Assert.IsTrue(((WithoutNumbers)(1 | 2)).IsValid()); // Will end up being Third
        Assert.IsTrue(((WithoutNumbers)(1 + 2)).IsValid()); // Will end up being Third

        Assert.IsFalse(((WithoutNumbers)4).IsValid());
        Assert.IsFalse(((WithoutNumbers)5).IsValid());
        Assert.IsFalse(((WithoutNumbers)25).IsValid());
        Assert.IsFalse(((WithoutNumbers)(1 + 2 + 3)).IsValid());

        Assert.IsTrue(((WithoutFirstNumberAssigned)7).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)8).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)9).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)10).IsValid());

        Assert.IsFalse(((WithoutFirstNumberAssigned)11).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)6).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(7 | 9)).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(8 + 10)).IsValid());

        Assert.IsTrue(((WithNagativeNumbers)(-7)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-8)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-9)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-10)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(-11)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(7)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(8)).IsValid());
    }
}
Joshcomley
fonte
1
Obrigado por isso, tive um problema semelhante ao lidar com combinações válidas de sinalizadores. Como alternativa para verificar o primeiro caractere da enum, você também pode tentar int.TryParse (enumValue.ToString ()) ... Se falhar, você terá um conjunto válido de sinalizadores. No entanto, isso pode ser mais lento que a sua solução.
precisa saber é o seguinte
Esta implementação não consegue valores corretamente validar negativos, desde o check é para caracteres não-dígito
ChaseMedallion
Boa pegada!! Eu vou atualizar a minha resposta para acomodar tal, obrigado @ChaseMedallion
joshcomley
Eu gosto desta solução da melhor maneira, os truques de matemática apresentados funcionam apenas se [Flags]tiverem valores inteiros sensíveis.
MrLore
17

A resposta canônica seria Enum.IsDefined, mas isso é a: um pouco lento se usado em um circuito fechado eb: não é útil para [Flags]enumerações.

Pessoalmente, eu pararia de me preocupar com isso e switch, de maneira adequada, lembrando:

  • se estiver tudo bem em não reconhecer tudo (e simplesmente não fazer nada), não adicione um default:(ou tenha um vazio default:explicando o porquê)
  • se houver um comportamento padrão sensível, coloque-o no default:
  • caso contrário, lide com os que você conhece e crie uma exceção para o resto:

Igual a:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}
Marc Gravell
fonte
não está familiarizado com enums [Flags] e o desempenho não é um problema; portanto, sua resposta parece ser a razão pela qual as enums foram inventadas;) olhando para seus "pontos" ou como eles são chamados, então você precisa ter um ponto lá . Aposto que você não os recebeu por nada, mas pense na situação de ler o arquivo de configuração, onde há 257 valores em uma enumeração de enumeração. Muito menos dezenas de outras enumerações. Haveria muitas linhas de casos ...
char m
@matti - isso soa um exemplo extremo; de qualquer maneira, a desserialização é uma área especializada - a maioria dos mecanismos de serialização oferece validação de enum gratuitamente.
Marc Gravell
@matti - em uma nota lateral; Eu diria que trata as respostas com base em seus méritos individuais. Às vezes, entendo as coisas completamente erradas, e alguém com "representante 17" poderia igualmente dar uma resposta perfeita .
Marc Gravell
A resposta da troca é rápida, mas não é genérica.
Eldritch Conundrum
8

Usar:

Enum.IsDefined ( typeof ( Enum ), EnumValue );
n535
fonte
4

Para lidar com [Flags]você, você também pode usar esta solução no C # Cookbook :

Primeiro, adicione um novo ALLvalor à sua enumeração:

[Flags]
enum Language
{
    CSharp = 1, VBNET = 2, VB6 = 4, 
    All = (CSharp | VBNET | VB6)
}

Em seguida, verifique se o valor está em ALL:

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}
Mario Levrero
fonte
2

Uma maneira de fazer isso seria confiar na conversão de elenco e enum para string. Ao converter int para um tipo de Enum, o int é convertido em um valor de enum correspondente ou o enum resultante apenas contém int como um valor se o valor de enum não estiver definido para o int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();

Não testado para casos extremos.

maulik13
fonte
1

Como os outros disseram, Enum.IsDefinedretorna falsemesmo se você tiver uma combinação válida de sinalizadores de bits para uma enumeração decorada com o FlagsAttribute.

Infelizmente, a única maneira de criar um método retornando true para sinalizadores de bit válidos é um pouco longa:

public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    // For enums decorated with the FlagsAttribute, allow sets of flags.
    if (!valid && enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true)
    {
        long mask = 0;
        foreach (object definedValue in Enum.GetValues(enumType))
            mask |= Convert.ToInt64(definedValue);
        long longValue = Convert.ToInt64(value);
        valid = (mask & longValue) == longValue;
    }
    return valid;
}

Convém armazenar em cache os resultados de GetCustomAttributeum dicionário:

private static readonly Dictionary<Type, bool> _flagEnums = new Dictionary<Type, bool>();
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    if (!valid)
    {
        // For enums decorated with the FlagsAttribute, allow sets of flags.
        if (!_flagEnums.TryGetValue(enumType, out bool isFlag))
        {
            isFlag = enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true;
            _flagEnums.Add(enumType, isFlag);
        }
        if (isFlag)
        {
            long mask = 0;
            foreach (object definedValue in Enum.GetValues(enumType))
                mask |= Convert.ToInt64(definedValue);
            long longValue = Convert.ToInt64(value);
            valid = (mask & longValue) == longValue;
        }
    }
    return valid;
}

Observe que o código acima usa a nova Enumrestrição na Tqual está disponível apenas desde o C # 7.3. Você precisa passar um object valueem versões mais antigas e acessá GetType()-lo.

Raio
fonte