Domanda

Ho bisogno di convalidare un intero sapere se è un valido valore di enumerazione.

Qual è il modo migliore per farlo in C#?

È stato utile?

Soluzione

Devi amare queste persone che si suppone che i dati non solo nasce sempre da una UI, ma un'interfaccia utente all'interno del vostro controllo!

IsDefined è bene per la maggior parte degli scenari, si potrebbe iniziare 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;
}

(Ovviamente è solo la goccia il ‘questo’ se pensi che non sia adatto int estensione)

Altri suggerimenti

IMHO il post contrassegnato come risposta non corretta.
Parametro e la convalida dei dati è una delle cose che è stato perforato in me decenni fa.

PERCHÉ

È necessaria la convalida perché in sostanza ogni valore può essere assegnato a un enum, senza buttare un errore.
Ho trascorso molti giorni la ricerca di C# enum convalida, poiché è una funzione necessaria in molti casi.

DOVE

Lo scopo principale dell'enum convalida per me è la convalida di dati letti da un file:non si sa mai se il file è stato danneggiato o è stato modificato esternamente, o è stato violato il scopo.
E con enum validazione dei dati dell'applicazione incollato dagli appunti:non si sa mai se l'utente ha modificato il contenuto degli appunti.

Detto questo, ho passato giorni a ricerca e la sperimentazione di molti metodi, come il profilo della performance di ogni metodo che ho potuto trovare, o di design.

Effettuare chiamate in qualsiasi cosa nel Sistema.Enum è così lento che è stata una notevole riduzione delle prestazioni in funzioni che conteneva centinaia o migliaia di oggetti che avevano uno o più enumerazioni in loro proprietà che doveva essere convalidato per limiti.

Linea di fondo, stare lontano da tutto nel Sistema.Enum classe durante la convalida di valori enum, è terribilmente lento.

RISULTATO

Il metodo che uso attualmente per enum convalida probabilmente disegnare occhi roteanti da molti programmatori qui, ma è imho meno male per il mio specifico, la progettazione dell'applicazione.

Posso definire uno o due costanti che sono alto e (facoltativamente) limite inferiore di enum, e li usa in un paio di if() istruzioni per la convalida.
Uno svantaggio è che si deve essere sicuri di aggiornamento costanti, se si modifica il enum.
Anche questo metodo funziona solo se il enum in "auto" stile in cui ogni enum elemento è incrementale valore intero come 0,1,2,3,4,....Non funzionerà correttamente con le Bandiere o le enumerazioni sono valori che non sono incrementali.

Si noti inoltre che questo metodo è veloce quasi quanto il regolare se "<"">" regolari int32s (che ha ottenuto un punteggio di 38.000 zecche sui miei test).

Per esempio:

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

PRESTAZIONI

Per coloro che sono interessati, ho profilato le seguenti variazioni su un enum convalida, e qui ci sono i risultati.

La profilazione è stata eseguita su di rilascio di compilazione in un ciclo di di un milione di volte su ogni metodo con un numero intero casuale compreso il valore di input.Ogni test è stato eseguito più di 10 volte e la media.Il segno di spunta risultati includono il tempo totale di esecuzione che includono la generazione di numeri casuali etc.ma chi sarà la costante di tutto il test.1 tick = ' di 10ns.

Si noti che il codice non è completo di codice di test, è solo la base enum metodo di convalida.C'erano anche un sacco di ulteriori varianti di questi che sono stati testati, e tutti con risultati simili a quelli mostrati qui che in panchina 1.800.000 di zecche.

Elencati più lento al più veloce arrotondati, con risultati, speriamo senza errori di battitura.

Limiti determinati nel Metodo = 13,600,000 zecche

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 di zecche
Nota:questa versione del codice non morsetto Min/Max, ma ritorna di Default se fuori dai limiti.

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

Sistema.Enum Convertire Int32 con il cast = 1.800.000 di zecche

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

se() Min/Max Costanti = 43,000 zecche = il vincitore da 42x e 316x più veloce.

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

-eol-

Come altri hanno detto, Enum.IsDefined è lento, qualcosa si deve essere consapevoli di se è in un loop.

Quando si fa i confronti multipli, un metodo più veloce è quello di mettere prima i valori in un HashSet.Poi basta utilizzare Contains per verificare se il valore è valido, in questo modo:

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 in particolare mette in guardia contro Enum.IsDefined nel suo post Il Pericolo di una eccessiva Semplificazione.

Il modo migliore per sbarazzarsi di questo requisito (che è, la necessità di convalidare le enumerazioni) è quello di rimuovere i modi in cui gli utenti possono ottenere in modo sbagliato, ad esempio, una casella di input di qualche tipo.Utilizzare le enumerazioni con drop down, per esempio, per far valere valido solo le enumerazioni.

Questa risposta è in risposta a deegee la risposta che solleva i problemi di prestazioni del Sistema.Enum, quindi non dovrebbe essere preso come il mio preferito di una risposta generica, più di indirizzamento enum convalida in stretto scenari di performance.

Se si dispone di una mission critical problema di prestazioni il cui lento ma funzionale codice viene eseguito in un loop stretto, quindi io personalmente vorrei guardare lo spostamento di tale codice al di fuori del ciclo, se possibile, invece di risolvere, riducendo la funzionalità.Vincolando il codice per supportare solo contigui enumerazioni potrebbe essere un incubo per trovare un bug se, per esempio, qualcuno, in futuro, decide di deprecare alcuni valori enum.Semplicisticamente si potrebbe chiamare Enum.GetValues una volta, giusto all'inizio per evitare di scatenare tutta la riflessione, etc migliaia di volte.Che dovrebbe dare un immediato aumento delle prestazioni.Se hai bisogno di maggiori prestazioni e si sa che un sacco di enumerazioni sono contigui (ma si vuole ancora sostenere 'gappy' enum) si potrebbe andare un passo avanti e fare qualcosa di simile:

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

Dove il loop diventa qualcosa di simile a:

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

Sono sicuro che il EnumValidator classi possono, scritto in modo più efficiente (è solo un rapido hack dimostrare), ma francamente chi se ne frega di cosa succede al di fuori dell'importazione loop?L'unico bit che deve essere super-veloce è all'interno del ciclo.Questo è stato il motivo per prendere la classe astratta percorso, al fine di evitare inutili se-enumContiguous-then-else in loop (la fabbrica di Creare viene essenzialmente in anticipo).Si nota un po ' di ipocrisia, per brevità di questo codice vincola la funzionalità int-enums.Dovrei essere facendo uso di IConvertible piuttosto che usare int direttamente, ma questa risposta è già parlato abbastanza!

Questo è come lo faccio basati su più post online.La ragione per fare questo è quello di assicurarsi che le enumerazioni contrassegnati con Flags attributo può anche essere convalidati.

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

Ho trovato questo link che risponde abbastanza bene.Si avvale di:

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

Ho optato per questa soluzione come un mix di prestazioni, praticità e facilità di gestione.

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 la uso così:

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!

Nel mio caso, chiamando Enum.IsDefined sarebbe solo ottenere me a metà strada, come al solito, per rifiutare il "Sconosciuto" valore di enumerazione.

Questo approccio evita i problemi di prestazioni di Enum.IsDefined e definisce la regola di convalida vicino al enum, che mi piace.

Il metodo di estensione è facilmente adattabile alle mutevoli esigenze e possono essere applicate a int, se desiderato (es. public static bool IsValidDogBreed(this int breed))

Ecco una veloce soluzione generica, utilizzando un staticamente-strutturata HashSet<T>.

Si può definire questo una volta nella vostra cassetta degli attrezzi, e quindi utilizzare per tutti i tuoi enum convalida.

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

Si noti che questo approccio può essere facilmente esteso per enum analisi, con l'uso di un dizionario con chiavi stringa (badando sensibilità numerica e rappresentazioni di stringa).

Per verificare se un valore è un valore valido in un'enumerazione, è solo bisogno di chiamare il metodo statico 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
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top