Validar valores de Enum

Necesito validar un número entero para saber si es un valor enum válido.

¿Cuál es la mejor manera de hacer esto en C #?

¡Tienes que amar a estas personas que asumen que los datos no siempre provienen de una IU, sino de una IU bajo tu control!

IsDefined está bien para la mayoría de los escenarios, puedes comenzar con:

 public static bool TryParseEnum(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, solo deje caer el ‘esto’ si no cree que sea una extensión int adecuada)

En mi humilde opinión, la publicación marcada como la respuesta es incorrecta.
La validación de parámetros y datos es una de las cosas que me han enseñado décadas atrás.

POR QUÉ

La validación es necesaria porque, esencialmente, cualquier valor entero puede asignarse a una enumeración sin arrojar un error.
Pasé muchos días investigando la validación de C # enum porque es una función necesaria en muchos casos.

DÓNDE

El principal objective en la validación enum para mí es validar los datos leídos de un archivo: nunca se sabe si el archivo se ha dañado, si se ha modificado externamente o si se ha pirateado a propósito.
Y con la validación enum de los datos de la aplicación pegados desde el portapapeles: nunca se sabe si el usuario ha editado el contenido del portapapeles.

Dicho esto, pasé días investigando y probando muchos métodos, incluido el perfil del rendimiento de cada método que pude encontrar o diseñar.

Hacer llamadas en cualquier elemento de System.Enum es tan lento que se observó una penalización de rendimiento notable en funciones que contenían cientos o miles de objetos que tenían una o más enumeraciones en sus propiedades que tenían que validarse para los límites.

En pocas palabras, aléjate de todo en la clase System.Enum al validar los valores enum, es terriblemente lento.

RESULTADO

El método que actualmente uso para la validación enum probablemente atraiga la atención de muchos progtwigdores aquí, pero es el menos malo para mi diseño de aplicación específico.

Defino una o dos constantes que son los límites superiores y (opcionalmente) inferiores de la enumeración, y los uso en un par de declaraciones if () para la validación.
Un inconveniente es que debe asegurarse de actualizar las constantes si cambia la enumeración.
Este método también solo funciona si la enumeración es un estilo “automático” donde cada elemento enum es un valor entero incremental como 0,1,2,3,4, …. No funcionará correctamente con indicadores o enumeraciones que tener valores que no son incrementales

También tenga en cuenta que este método es casi tan rápido como regular si “<" ">” en int32s regulares (que obtuvo 38,000 ticks en mis pruebas).

Por ejemplo:

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

ACTUACIÓN

Para aquellos que están interesados, perfilé las siguientes variaciones en una validación enum, y aquí están los resultados.

El perfilado se realizó en la comstackción de publicación en un bucle de un millón de veces en cada método con un valor de entrada entero al azar. Cada prueba se ejecutó más de 10 veces y se promedió. Los resultados de tic incluyen el tiempo total para ejecutar, que incluirá la generación de números aleatorios, etc. pero estos serán constantes en todas las pruebas. 1 tick = 10ns.

Tenga en cuenta que el código aquí no es el código de prueba completo, es solo el método básico de validación enum. También hubo una gran cantidad de variaciones adicionales en estos que fueron probados, y todos ellos con resultados similares a los que se muestran aquí que en el banco de 1,800,000 garrapatas.

Enumerado más lento al más rápido con resultados redondeados, con suerte no hay errores tipográficos.

Límites determinados en Method = 13,600,000 ticks

 public static T Clamp(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 ticks
Nota: esta versión de código no se ajusta a Min / Max, pero devuelve Default si está fuera de límites.

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

System.Enum Convert Int32 con moldes = 1,800,000 tics

 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 tics = el ganador por 42x y 316x más 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-

Brad Abrams específicamente advierte contra Enum.IsDefined en su publicación The Danger of Oversimplification .

La mejor manera de deshacerse de este requisito (es decir, la necesidad de validar enumeraciones) es eliminar las formas en que los usuarios pueden equivocarse, por ejemplo, un cuadro de entrada de algún tipo. Use enumeraciones con menús desplegables, por ejemplo, para aplicar solo enumeraciones válidas.

Como otros han mencionado, Enum.IsDefined es lento, algo que debes tener en cuenta si está en un bucle.

Al hacer comparaciones múltiples, un método más rápido es poner primero los valores en un HashSet . Luego simplemente use Contains para verificar si el valor es válido, así:

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

Esta respuesta responde a la respuesta de deegee que plantea los problemas de rendimiento de System.Enum, por lo que no debe tomarse como mi respuesta genérica preferida, ya que aborda más la validación enum en escenarios de rendimiento estrictos.

Si tiene un problema de rendimiento crítico para la misión donde el código lento pero funcional se ejecuta en un ciclo cerrado, yo personalmente consideraría mover ese código fuera del ciclo si es posible en lugar de resolver reduciendo la funcionalidad. Restringir el código solo para admitir enumeraciones contiguas podría ser una pesadilla para encontrar un error si, por ejemplo, alguien en el futuro decide depreciar algunos valores enum. Simplificando, puedes llamar a Enum.GetValues ​​una vez, al principio para evitar que se activen todas las reflexiones, etc. miles de veces. Eso debería darle un aumento de rendimiento inmediato. Si necesita más rendimiento y sabe que muchas de sus enumeraciones son contiguas (pero aún desea admitir enumeraciones de ‘gappy’), puede ir un paso más allá y hacer algo como:

 public abstract class EnumValidator where TEnum : struct, IConvertible { protected static bool IsContiguous { get { int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast().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 Create() { if (!typeof(TEnum).IsEnum) { throw new ArgumentException("Please use an enum!"); } return IsContiguous ? (EnumValidator)new ContiguousEnumValidator() : new JumbledEnumValidator(); } public abstract bool IsValid(int value); } public class JumbledEnumValidator : EnumValidator where TEnum : struct, IConvertible { private readonly int[] _values; public JumbledEnumValidator() { _values = Enum.GetValues(typeof (TEnum)).Cast().ToArray(); } public override bool IsValid(int value) { return _values.Contains(value); } } public class ContiguousEnumValidator : EnumValidator where TEnum : struct, IConvertible { private readonly int _highest; private readonly int _lowest; public ContiguousEnumValidator() { List enumVals = Enum.GetValues(typeof (TEnum)).Cast().ToList(); _lowest = enumVals.OrderBy(i => i).First(); _highest = enumVals.OrderByDescending(i => i).First(); } public override bool IsValid(int value) { return value >= _lowest && value <= _highest; } } 

Donde tu loop se convierte en algo así como:

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

Estoy seguro de que las clases de EnumValidator podrían escribirse de manera más eficiente (es un truco rápido para demostrarlo) pero, francamente, ¿a quién le importa lo que sucede fuera del ciclo de importación? El único bit que necesita ser superrápido está dentro del ciclo. Esta fue la razón por la que tomamos la ruta de la clase abstracta, para evitar un if-enumContiguous-then-else innecesario en el ciclo (la creación de fábrica lo hace esencialmente por adelantado). Notarás un poco de hipocresía, por brevedad este código limita la funcionalidad a int-enums. Debería hacer uso de IConvertible en lugar de usar int directamente, ¡pero esta respuesta ya es lo suficientemente prolija!

Así es como lo hago basándome en múltiples publicaciones en línea. La razón para hacer esto es asegurarse de que las enumeraciones marcadas con el atributo Flags también se puedan validar con éxito.

 public static TEnum ParseEnum(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); } } 

Encontré este enlace que lo responde bastante bien. Usa:

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

Para validar si un valor es un valor válido en una enumeración, solo necesita llamar al 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 }