Come TryParse per il valore Enum?


94

Voglio scrivere una funzione che possa convalidare un dato valore (passato come stringa) rispetto a possibili valori di un file enum. Nel caso di una corrispondenza, dovrebbe restituire l'istanza enum; in caso contrario, dovrebbe restituire un valore predefinito.

La funzione non può utilizzare internamente try/ catch, che esclude using Enum.Parse, che genera un'eccezione quando viene fornito un argomento non valido.

Mi piacerebbe usare qualcosa sulla falsariga di una TryParsefunzione per implementare questo:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}

8
Non capisco questa domanda; stai dicendo "Voglio risolvere questo problema, ma non voglio usare nessuno dei metodi che mi darebbero una soluzione." Qual e il punto?
Domenic

1
Qual è la tua avversione per provare / prendere una soluzione? Se stai cercando di evitare le eccezioni perché sono "costose", concediti una pausa. Nel 99% dei casi, l'eccezione del costo di lancio / cattura è trascurabile rispetto al codice principale.
SoluzioneYogi

1
Il costo della gestione delle eccezioni non è così grave. Diavolo, le implementazioni interne di tutta questa conversione di enumerazione sono piene di gestione delle eccezioni. Tuttavia, non mi piace che le eccezioni vengano lanciate e catturate durante la normale logica dell'applicazione. A volte può essere utile interrompere tutte le eccezioni lanciate (anche quando vengono rilevate). Lanciare eccezioni ovunque lo renderà molto più fastidioso da usare :)
Thorarin

3
@Domenic: Sto solo cercando una soluzione migliore di quella che già so. Andresti mai a una richiesta ferroviaria per chiedere un percorso o un treno che già conosci :).
Manish Basantani

2
@Amby, il costo per inserire semplicemente un blocco try / catch è trascurabile. Il costo di GETTARE un'eccezione non lo è, ma allora dovrebbe essere eccezionale, no? Inoltre, non dire "non lo sappiamo mai" ... profila il codice e scoprilo. Non perdere tempo chiedendoti se qualcosa è lento, SCOPRILO!
akmad

Risposte:


31

Come altri hanno già detto, devi implementare il tuo TryParse. Simon Mourier fornisce un'implementazione completa che si prende cura di tutto.

Se stai usando enumerazioni bitfield (cioè flag), devi anche gestire una stringa come la "MyEnum.Val1|MyEnum.Val2"combinazione di due valori enum. Se chiami solo Enum.IsDefinedcon questa stringa, restituirà false, anche se la Enum.Parsegestisce correttamente.

Aggiornare

Come menzionato da Lisa e Christian nei commenti, Enum.TryParseè ora disponibile per C # in .NET4 e versioni successive.

Documenti MSDN


Forse il meno sexy, ma sono d'accordo che questo è sicuramente il migliore fino a quando il tuo codice non viene migrato a .NET 4.
Lisa

1
Come menzionato di seguito, ma non realmente visibile: a partire da .Net 4 Enum.TryParse è disponibile e funziona senza codifica aggiuntiva. Ulteriori informazioni sono disponibili su MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

106

Enum.IsDefined farà le cose. Potrebbe non essere efficiente come probabilmente sarebbe un TryParse, ma funzionerà senza la gestione delle eccezioni.

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
    if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
        return defaultValue;

    return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
}

Da segnalare: un TryParsemetodo è stato aggiunto in .NET 4.0.


1
La migliore risposta che ho visto finora ... no try / catch, no GetNames :)
Thomas Levesque


6
inoltre non è possibile ignorare il caso su IsDefined
Anthony Johnston

2
@ Anthony: se vuoi supportare l'insensibilità tra maiuscole e minuscole, ti servirà GetNames. Internamente, tutti questi metodi (incluso Parse) usano GetHashEntry, che fa la riflessione effettiva - una volta. Il lato positivo è che .NET 4.0 ha un TryParse, ed è anche generico :)
Thorarin

+1 Mi ha salvato la giornata! Sto eseguendo il backport di un mucchio di codice da .NET 4 a .NET 3.5 e mi hai salvato :)
daitangio

20

Ecco un'implementazione personalizzata di EnumTryParse. A differenza di altre implementazioni comuni, supporta anche l'enumerazione contrassegnata con l' Flagsattributo.

    /// <summary>
    /// Converts the string representation of an enum to its Enum equivalent value. A return value indicates whether the operation succeeded.
    /// This method does not rely on Enum.Parse and therefore will never raise any first or second chance exception.
    /// </summary>
    /// <param name="type">The enum target type. May not be null.</param>
    /// <param name="input">The input text. May be null.</param>
    /// <param name="value">When this method returns, contains Enum equivalent value to the enum contained in input, if the conversion succeeded.</param>
    /// <returns>
    /// true if s was converted successfully; otherwise, false.
    /// </returns>
    public static bool EnumTryParse(Type type, string input, out object value)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        if (!type.IsEnum)
            throw new ArgumentException(null, "type");

        if (input == null)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        input = input.Trim();
        if (input.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        string[] names = Enum.GetNames(type);
        if (names.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        Type underlyingType = Enum.GetUnderlyingType(type);
        Array values = Enum.GetValues(type);
        // some enums like System.CodeDom.MemberAttributes *are* flags but are not declared with Flags...
        if ((!type.IsDefined(typeof(FlagsAttribute), true)) && (input.IndexOfAny(_enumSeperators) < 0))
            return EnumToObject(type, underlyingType, names, values, input, out value);

        // multi value enum
        string[] tokens = input.Split(_enumSeperators, StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        ulong ul = 0;
        foreach (string tok in tokens)
        {
            string token = tok.Trim(); // NOTE: we don't consider empty tokens as errors
            if (token.Length == 0)
                continue;

            object tokenValue;
            if (!EnumToObject(type, underlyingType, names, values, token, out tokenValue))
            {
                value = Activator.CreateInstance(type);
                return false;
            }

            ulong tokenUl;
            switch (Convert.GetTypeCode(tokenValue))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                    tokenUl = (ulong)Convert.ToInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;

                //case TypeCode.Byte:
                //case TypeCode.UInt16:
                //case TypeCode.UInt32:
                //case TypeCode.UInt64:
                default:
                    tokenUl = Convert.ToUInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;
            }

            ul |= tokenUl;
        }
        value = Enum.ToObject(type, ul);
        return true;
    }

    private static char[] _enumSeperators = new char[] { ',', ';', '+', '|', ' ' };

    private static object EnumToObject(Type underlyingType, string input)
    {
        if (underlyingType == typeof(int))
        {
            int s;
            if (int.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(uint))
        {
            uint s;
            if (uint.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ulong))
        {
            ulong s;
            if (ulong.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(long))
        {
            long s;
            if (long.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(short))
        {
            short s;
            if (short.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ushort))
        {
            ushort s;
            if (ushort.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(byte))
        {
            byte s;
            if (byte.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(sbyte))
        {
            sbyte s;
            if (sbyte.TryParse(input, out s))
                return s;
        }

        return null;
    }

    private static bool EnumToObject(Type type, Type underlyingType, string[] names, Array values, string input, out object value)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (string.Compare(names[i], input, StringComparison.OrdinalIgnoreCase) == 0)
            {
                value = values.GetValue(i);
                return true;
            }
        }

        if ((char.IsDigit(input[0]) || (input[0] == '-')) || (input[0] == '+'))
        {
            object obj = EnumToObject(underlyingType, input);
            if (obj == null)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
            value = obj;
            return true;
        }

        value = Activator.CreateInstance(type);
        return false;
    }

1
hai fornito la migliore implementazione e io l'ho usata per i miei scopi; tuttavia, mi chiedo perché usi Activator.CreateInstance(type)per creare il valore enum predefinito e non Enum.ToObject(type, 0). Solo una questione di gusti?
Pierre Arnaud

1
@Pierre - Hmmm ... no, sembrava più naturale in quel momento :-) Forse Enum.ToObject è più veloce dato che internamente usa una chiamata interna InternalBoxEnum? Non l'ho mai verificato ...
Simon Mourier

2
Come menzionato di seguito, ma non realmente visibile: a partire da .Net 4 Enum.TryParse è disponibile e funziona senza codifica aggiuntiva. Ulteriori informazioni sono disponibili su MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

16

Alla fine devi implementare questo intorno Enum.GetNames:

public bool TryParseEnum<T>(string str, bool caseSensitive, out T value) where T : struct {
    // Can't make this a type constraint...
    if (!typeof(T).IsEnum) {
        throw new ArgumentException("Type parameter must be an enum");
    }
    var names = Enum.GetNames(typeof(T));
    value = (Enum.GetValues(typeof(T)) as T[])[0];  // For want of a better default
    foreach (var name in names) {
        if (String.Equals(name, str, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) {
            value = (T)Enum.Parse(typeof(T), name);
            return true;
        }
    }
    return false;
}

Note aggiuntive:

  • Enum.TryParseè incluso in .NET 4. Vedere qui http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Un altro approccio sarebbe quello di eseguire il wrapping direttamente Enum.Parsecatturando l'eccezione generata quando fallisce. Potrebbe essere più veloce quando viene trovata una corrispondenza, ma in caso contrario sarà probabilmente più lento. A seconda dei dati che stai elaborando, questo può o meno essere un netto miglioramento.

EDIT: ho appena visto un'implementazione migliore su questo, che memorizza nella cache le informazioni necessarie: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5


Stavo per suggerire di utilizzare default (T) per impostare il valore predefinito. Si scopre che questo non funzionerebbe per tutte le enumerazioni. Ad esempio, se il tipo sottostante per l'enumerazione era int, il valore predefinito (T) restituirà sempre 0, che può essere valido o meno per l'enumerazione.
Daniel Ballinger,

L'implementazione nel blog di Damieng non supporta le enumerazioni con l' Flagsattributo.
Uwe Keim

9

Basato su .NET 4.5

Codice di esempio di seguito

using System;

enum Importance
{
    None,
    Low,
    Medium,
    Critical
}

class Program
{
    static void Main()
    {
    // The input value.
    string value = "Medium";

    // An unitialized variable.
    Importance importance;

    // Call Enum.TryParse method.
    if (Enum.TryParse(value, out importance))
    {
        // We now have an enum type.
        Console.WriteLine(importance == Importance.Medium);
    }
    }
}

Riferimento: http://www.dotnetperls.com/enum-parse


4

Ho un'implementazione ottimizzata che potresti usare in UnconstrainedMelody . Effettivamente sta solo memorizzando nella cache l'elenco dei nomi, ma lo sta facendo in un modo carino, fortemente tipizzato, genericamente vincolato :)


4
enum EnumStatus
{
    NAO_INFORMADO = 0,
    ENCONTRADO = 1,
    BLOQUEADA_PELO_ENTREGADOR = 2,
    DISPOSITIVO_DESABILITADO = 3,
    ERRO_INTERNO = 4,
    AGARDANDO = 5
}

...

if (Enum.TryParse<EnumStatus>(item.status, out status)) {

}

2

Al momento non esiste un Enum.TryParse predefinito. Questo è stato richiesto su Connect ( Still no Enum.TryParse ) e ha ottenuto una risposta che indicava la possibile inclusione nel framework successivo dopo .NET 3.5. Per ora dovrai implementare le soluzioni alternative suggerite.


1

L'unico modo per evitare la gestione delle eccezioni è utilizzare il metodo GetNames () e sappiamo tutti che le eccezioni non dovrebbero essere abusate per la logica dell'applicazione comune :)


1
Non è l' unico modo. Enum.IsDefined (..) impedirà la generazione di eccezioni nel codice utente.
Thorarin

1

La memorizzazione nella cache di una funzione / dizionario generato dinamicamente è consentita?

Poiché non si (sembra) conoscere il tipo di enumerazione in anticipo, la prima esecuzione potrebbe generare qualcosa di cui le successive esecuzioni potrebbero trarre vantaggio.

Puoi persino memorizzare nella cache il risultato di Enum.GetNames ()

Stai cercando di ottimizzare per CPU o memoria? Ne hai davvero bisogno?


L'idea è ottimizzare la CPU. D'accordo che posso farlo a costo della memoria. Ma non è la soluzione che sto cercando. Grazie.
Manish Basantani

0

Come altri hanno già detto, se non usi Try & Catch, devi usare IsDefined o GetNames ... Ecco alcuni esempi ... fondamentalmente sono tutti uguali, il primo che gestisce enumerazioni nullable. Preferisco il secondo perché è un'estensione sulle corde, non sulle enumerazioni ... ma puoi mescolarle come vuoi!

  • www.objectreference.net/post/Enum-TryParse-Extension-Method.aspx
  • flatlinerdoa.spaces.live.com/blog/cns!17124D03A9A052B0!605.entry
  • mironabramson.com/blog/post/2008/03/Another-version-for-the-missing-method-EnumTryParse.aspx
  • lazyloading.blogspot.com/2008/04/enumtryparse-with-net-35-extension.html

0

Non esiste un TryParse perché il tipo di Enum non è noto fino al runtime. Un TryParse che segue la stessa metodologia del metodo Date.TryParse genererebbe un errore di conversione implicito sul parametro ByRef.

Suggerisco di fare qualcosa del genere:

//1 line call to get value
MyEnums enumValue = (Sections)EnumValue(typeof(Sections), myEnumTextValue, MyEnums.SomeEnumDefault);

//Put this somewhere where you can reuse
public static object EnumValue(System.Type enumType, string value, object NotDefinedReplacement)
{
    if (Enum.IsDefined(enumType, value)) {
        return Enum.Parse(enumType, value);
    } else {
        return Enum.Parse(enumType, NotDefinedReplacement);
    }
}

Per Trymetodi i cui risultati possono essere tipi valore, o dove nullpuò essere un risultato legittimo (es Dictionary.TryGetValue, which has both such traits), the normal pattern is for a . Metodo Try` da restituire boole passare il risultato come outparametro. Per quelli che restituiscono tipi di classe in cui nullnonèun risultato valido, non ci sono difficoltà a usare un nullritorno per indicare il fallimento.
supercat

-1

Dai un'occhiata alla stessa classe Enum (struct?). Esiste un metodo Parse su questo ma non sono sicuro di un tryparse.


Conosco il metodo Enum.Parse (typeof (TEnum), strEnumValue). Genera ArgumentException se strEnumValue non è valido. Alla ricerca di TryParse ........
Manish Basantani

-2

Questo metodo convertirà un tipo di enum:

  public static TEnum ToEnum<TEnum>(object EnumValue, TEnum defaultValue)
    {
        if (!Enum.IsDefined(typeof(TEnum), EnumValue))
        {
            Type enumType = Enum.GetUnderlyingType(typeof(TEnum));
            if ( EnumValue.GetType() == enumType )
            {
                string name = Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue);
                if( name != null)
                    return (TEnum)Enum.Parse(typeof(TEnum), name);
                return defaultValue;
            }
        }
        return (TEnum)Enum.Parse(typeof(TEnum), EnumValue.ToString());
    } 

Controlla il tipo sottostante e ottiene il nome per analizzarlo. Se tutto fallisce, restituirà il valore predefinito.


3
cosa sta facendo "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Probabilmente una dipendenza dal tuo codice locale.
Manish Basantani
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.