¿Hay alguna manera de verificar si int es una enumeración legal en C #?

167

He leído algunas publicaciones SO y parece que falta la operación más básica.

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

Esto no causa excepciones, es feliz de almacenar 78. ¿Hay alguna manera de validar un valor que entra en una enumeración?

encanto
fuente
2
Posible duplicado de Validar valores de enumeración
Erik

Respuestas:

271

Echa un vistazo a Enum.IsDefined

Uso:

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

Este es el ejemplo de esa 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));
   }
}

El ejemplo muestra el siguiente resultado:

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False
SwDevMan81
fuente
@matti: Convierta "78" en cualquier representación numérica que LoggingLeveluse como almacenamiento, luego preséntelo como un LoggingLevelvalor de enumeración.
thecoop
9
Parece que IsDefinedno funciona para los miembros de enumeración bitwised.
Saeed Neamati
29

Las soluciones anteriores no abordan [Flags]situaciones.

Mi solución a continuación puede tener algunos problemas de rendimiento (estoy seguro de que uno podría optimizar de varias maneras) pero esencialmente siempre probará si un valor de enumeración es válido o no .

Se basa en tres supuestos:

  • Los valores de enumeración en C # solo pueden ser int, absolutamente nada más
  • Los nombres de enumeración en C # deben comenzar con un carácter alfabético
  • Ningún nombre de enumeración válido puede tener un signo menos: -

Llamar ToString()a una enumeración devuelve el intvalor si no se coincide ninguna enumeración (marca o no). Si un valor de enumeración permitido coincide, imprimirá el nombre de la (s) coincidencia (es).

Entonces:

[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"

Con estas dos reglas en mente, podemos suponer que si .NET Framework hace su trabajo correctamente, cualquier llamada a un ToString()método de enumeración válido dará como resultado algo que tenga un carácter alfabético como primer carácter:

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

Uno podría llamarlo un "hack", pero las ventajas son que al confiar en la propia implementación de Microsoft Enumy en los estándares de C #, no está confiando en su propio código o verificación potencialmente defectuoso. En situaciones donde el rendimiento no es excepcionalmente crítico, ¡esto ahorrará muchas switchdeclaraciones desagradables u otras comprobaciones!

Editar

Gracias a @ChaseMedallion por señalar que mi implementación original no admitía valores negativos. Esto ha sido remediado y se proporcionaron pruebas.

Y las pruebas para respaldarlo:

[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
fuente
1
Gracias por esto, tuve un problema similar al tratar con combinaciones de banderas válidas. Como alternativa a verificar el primer carácter de la enumeración, también puede intentar int. TryParse (enumValue.ToString ()) ... Si falla, tiene un conjunto válido de indicadores. Sin embargo, esto podría ser más lento que su solución.
MadHenchbot
Esta implementación no puede validar correctamente los valores negativos, ya que la verificación es para caracteres que no son dígitos
ChaseMedallion
¡¡Buena atrapada!! Actualizaré mi respuesta para dar cabida a eso, gracias @ChaseMedallion
joshcomley
Me gusta más esta solución, los trucos matemáticos presentados solo funcionan si [Flags]tienen valores enteros razonables.
MrLore
17

La respuesta canónica sería Enum.IsDefined, pero eso es a: un poco lento si se usa en un ciclo cerrado, yb: no es útil para las [Flags]enumeraciones.

Personalmente, dejaría de preocuparme por eso, y de manera switchapropiada, recordando:

  • Si está bien no reconocer todo (y simplemente no hacer nada), entonces no agregue un default:(o tenga un vacío default:explicando por qué)
  • si hay un comportamiento predeterminado razonable, póngalo en el default:
  • de lo contrario, maneja los que conoces y lanza una excepción para el resto:

Al igual que:

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}
Marc Gravell
fuente
no está familiarizado con las enumeraciones [Banderas] y el rendimiento no es un problema, por lo que su respuesta parece ser la razón por la cual se inventaron las enumeraciones en primer lugar;) mirando sus "puntos" o como se llamen, por lo que debe tener un punto allí . Apuesto a que no los obtuvo por nada, pero piense en la situación de leer el archivo de configuración donde hay 257 valores en una definición de enumeración. Y mucho menos docenas de otras enumeraciones. Habría muchas filas de casos ...
char m
@matti: eso suena un ejemplo extremo; La deserialización es un área especializada de todos modos: la mayoría de los motores de serialización ofrecen validación de enumeración de forma gratuita.
Marc Gravell
@matti - en una nota al margen; Yo diría que trata las respuestas en función de sus méritos individuales. A veces me equivoco completamente, y alguien con "representante 17" podría igualmente dar una respuesta perfecta .
Marc Gravell
La respuesta de cambio es rápida, pero no es genérica.
Eldritch Conundrum
8

Utilizar:

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

Para tratar con [Flags]usted, también puede usar esta solución de C # Cookbook :

Primero, agregue un nuevo ALLvalor a su enumeración:

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

Luego, verifique si el valor está en ALL:

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

Una forma de hacerlo sería confiar en la conversión de conversión de enum a cadena. Al convertir int en un tipo Enum, int se convierte en un valor enum correspondiente o la enum resultante solo contiene int como valor si el valor enum no está definido para int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

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

No probado para ningún caso de borde.

maulik13
fuente
1

Como dijeron los demás, Enum.IsDefinedregresa falseincluso si tiene una combinación válida de banderas de bits para una enumeración decorada con el FlagsAttribute.

Lamentablemente, la única forma de crear un método que devuelva verdadero para marcas de bits válidas es un poco largo:

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

Es posible que desee almacenar en caché los resultados de GetCustomAttributeun diccionario:

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

Tenga en cuenta que el código anterior usa la nueva Enumrestricción Tque solo está disponible desde C # 7.3. Necesita pasar un object valueen versiones anteriores y recurrir GetType()a él.

Rayo
fuente