Pregunta

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

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

¿Fue útil?

Solución

¡Debes amar a estas personas que asumen que los datos no solo siempre provienen de una interfaz de usuario, sino de una interfaz de usuario que está bajo tu control!

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

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, simplemente elimine "esto" si no cree que sea una extensión int adecuada)

Otros consejos

En mi humilde opinión, la publicación marcada como respuesta es incorrecta.
La validación de parámetros y datos es una de las cosas que me inculcaron hace décadas.

POR QUÉ

Se requiere validación porque esencialmente se puede asignar cualquier valor entero a una enumeración sin generar un error.
Pasé muchos días investigando la validación de enumeraciones de C# porque es una función necesaria en muchos casos.

DÓNDE

Para mí, el objetivo principal de la validación de enumeraciones es validar los datos leídos de un archivo:Nunca se sabe si el archivo se ha dañado, se ha modificado externamente o se ha pirateado intencionadamente.
Y con validación de enumeración 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.

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

En pocas palabras, manténgase alejado de todo en la clase System.Enum al validar valores de enumeración, es terriblemente lento.

RESULTADO

El método que uso actualmente para la validación de enumeraciones probablemente atraerá la atención de muchos programadores aquí, pero en mi humilde opinión es el menos malo para el diseño de mi aplicación específica.

Defino una o dos constantes que son los límites superior y (opcionalmente) inferior de la enumeración, y las uso en un par de declaraciones if() para validación.
Una desventaja es que debes asegurarte de actualizar las constantes si cambias la enumeración.
Este método también solo funciona si la enumeración es un estilo "automático" donde cada elemento de enumeración es un valor entero incremental como 0,1,2,3,4,....No funcionará correctamente con banderas o enumeraciones que tengan valores que no sean incrementales.

También tenga en cuenta que este método es casi tan rápido como el normal si "<" ">" en int32 normales (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, describí las siguientes variaciones en una validación de enumeración y aquí están los resultados.

La creación de perfiles se realizó en la compilación del lanzamiento en un bucle de un millón de veces en cada método con un valor de entrada entero aleatorio.Cada prueba se realizó más de 10 veces y se promedió.Los resultados del tick incluyen el tiempo total de ejecución, que incluirá la generación de números aleatorios, etc.pero serán constantes a lo largo de las pruebas.1 tic = 10 ns.

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 de enumeración.También se probaron muchas variaciones adicionales, y todas ellas con resultados similares a los que se muestran aquí, que compararon 1.800.000 ticks.

Enumerados del más lento al más rápido con resultados redondeados, con suerte sin errores tipográficos.

Límites determinados en el método = 13.600.000 tics

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 tics
Nota:esta versión del código no se limita a Mín./Máx., pero devuelve Predeterminado si está fuera de los límites.

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

System.Enum Convertir Int32 con conversiones = 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ínimas y máximas = 43.000 ticks = el ganador 42 veces y 316 veces 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-

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

Al realizar comparaciones múltiples, un método más rápido es poner primero los valores en una HashSet.Entonces simplemente usa Contains para comprobar 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<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 advierte específicamente contra Enum.IsDefined en su publicación El peligro de la simplificación excesiva.

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

Esta respuesta es en respuesta a la respuesta de Deegee que plantea problemas de rendimiento de System.Enum, por lo que no debe tomarse como mi respuesta genérica preferida, sino que aborda más bien la validación de enumeraciones en escenarios de rendimiento estrictos.

Si tiene un problema de rendimiento de misión crítica en el que se ejecuta un código lento pero funcional en un bucle cerrado, yo personalmente consideraría sacar ese código del bucle si es posible en lugar de resolverlo reduciendo la funcionalidad.Restringir el código para que solo admita enumeraciones contiguas podría ser una pesadilla para encontrar un error si, por ejemplo, alguien en el futuro decide desaprobar algunos valores de enumeración.De manera simplista, podrías llamar a Enum.GetValues ​​una vez, justo al principio, para evitar activar toda la reflexión, etc., miles de veces.Eso debería brindarle un aumento inmediato del rendimiento.Si necesita más rendimiento y sabe que muchas de sus enumeraciones son contiguas (pero aún desea admitir enumeraciones "entrecortadas"), puede ir un paso más allá y hacer 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;
    }
}

Donde tu bucle se convierte en algo 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 solo un truco rápido para demostrarlo) pero, francamente, ¿a quién le importa lo que suceda fuera del ciclo de importación?Lo único que debe ser súper rápido está dentro del bucle.Esta fue la razón para tomar la ruta de la clase abstracta, para evitar un if-enumContiguous-then-else innecesario en el bucle (la fábrica Create esencialmente hace esto por adelantado).Notarás un poco de hipocresía; por brevedad, este código limita la funcionalidad a int-enums.Debería utilizar IConvertible en lugar de usar int directamente, ¡pero esta respuesta ya tiene bastantes palabras!

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

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

encontré esto enlace eso lo responde bastante bien.Usa:

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

Me decidí por esta solución como una combinación de rendimiento, conveniencia y mantenibilidad.

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

Y úsalo así:

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!

En mi caso, llamar a Enum.IsDefined solo me llevaría a la mitad del camino, ya que normalmente quiero rechazar el valor de enumeración "Desconocido".

Este enfoque evita los problemas de rendimiento de Enum.IsDefined y define la regla de validación cerca de la enumeración, lo cual me gusta.

El método de extensión se adapta fácilmente a los requisitos cambiantes y podría aplicarse a int si se desea (es decir, public static bool IsValidDogBreed(this int breed))

Aquí hay una solución genérica rápida, que utiliza una construcción estática HashSet<T>.

Puede definir esto una vez en su caja de herramientas y luego usarlo para toda la validación de enumeraciones.

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

Tenga en cuenta que este enfoque también se puede extender fácilmente al análisis de enumeraciones, mediante el uso de un diccionario con claves de cadena (teniendo en cuenta la distinción entre mayúsculas y minúsculas y representaciones de cadenas numéricas).

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
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top