Comment TryParse pour la valeur Enum?

94

Je veux écrire une fonction qui peut valider une valeur donnée (passée sous forme de chaîne) par rapport aux valeurs possibles d'un enum. Dans le cas d'une correspondance, il doit renvoyer l'instance enum; sinon, il doit renvoyer une valeur par défaut.

La fonction ne peut pas utiliser en interne try/ catch, ce qui exclut l'utilisation Enum.Parse, qui lève une exception quand un argument non valide est donné.

Je voudrais utiliser quelque chose comme une TryParsefonction pour implémenter ceci:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}
Manish Basantani
la source
8
Je ne comprends pas cette question; vous dites "Je veux résoudre ce problème, mais je ne veux utiliser aucune des méthodes qui me donneraient une solution." À quoi ça sert?
Domenic le
1
Quelle est votre aversion pour essayer / attraper une solution? Si vous essayez d'éviter les exceptions parce qu'elles sont «coûteuses», accordez-vous une pause. Dans 99% des cas, l'exception de coût pour lancer / attraper est négligeable par rapport à votre code principal.
SolutionYogi
1
Le coût de la gestion des exceptions n'est pas si grave. Enfer, les implémentations internes de toute cette conversion d'énumération sont pleines de gestion des exceptions. Cependant, je n'aime vraiment pas que les exceptions soient lancées et interceptées pendant la logique d'application normale. Il peut parfois être utile d'interrompre toutes les exceptions levées (même lorsqu'elles sont interceptées). Lancer des exceptions partout rendra cela beaucoup plus ennuyeux à utiliser :)
Thorarin
3
@Domenic: Je cherche juste une meilleure solution que ce que je connais déjà. Souhaitez-vous jamais aller à une enquête ferroviaire pour demander un itinéraire ou un train que vous connaissez déjà :).
Manish Basantani le
2
@Amby, le coût de la simple saisie d'un bloc try / catch est négligeable. Le coût de LANCER une exception n'est pas, mais alors c'est censé être exceptionnel, non? Aussi, ne dites pas "nous ne savons jamais" ... profilez le code et découvrez-le. Ne perdez pas votre temps à vous demander si quelque chose est lent, DÉCOUVREZ!
akmad

Réponses:

31

Comme d'autres l'ont dit, vous devez mettre en œuvre le vôtre TryParse. Simon Mourier fournit une mise en œuvre complète qui s'occupe de tout.

Si vous utilisez des énumérations de champs de bits (c'est-à-dire des indicateurs), vous devez également gérer une chaîne comme "MyEnum.Val1|MyEnum.Val2"qui est une combinaison de deux valeurs d'énumération. Si vous appelez simplement Enum.IsDefinedavec cette chaîne, il retournera false, même s'il le Enum.Parsegère correctement.

Mettre à jour

Comme mentionné par Lisa et Christian dans les commentaires, Enum.TryParseest maintenant disponible pour C # dans .NET4 et plus.

Documents MSDN

Victor Arndt Mueller
la source
Peut-être le moins sexy, mais je suis d'accord que c'est certainement le meilleur jusqu'à ce que votre code soit migré vers .NET 4.
Lisa
1
Comme mentionné ci-dessous, mais pas vraiment visible: Depuis .Net 4 Enum.TryParse est disponible et fonctionne sans codage supplémentaire. Plus d'informations sont disponibles sur MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
106

Enum.IsDefined fera avancer les choses. Ce n'est peut-être pas aussi efficace qu'un TryParse, mais cela fonctionnera sans traitement d'exception.

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

A noter: une TryParseméthode a été ajoutée dans .NET 4.0.

Thorarin
la source
1
Meilleure réponse que j'ai vue jusqu'à présent ... pas d'essai / attrapé, pas de GetNames :)
Thomas Levesque
13
Inconvénients de Enum.IsDefined: blogs.msdn.com/brada/archive/2003/11/29/50903.aspx
Nader Shirazie
6
il n'y a pas non plus d'ignorer la casse sur IsDefined
Anthony Johnston
2
@ Anthony: si vous souhaitez prendre en charge l'insensibilité à la casse, vous en aurez besoin GetNames. En interne, toutes ces méthodes (y compris Parse) utilisent GetHashEntry, ce qui fait la réflexion réelle - une fois. Du bon côté, .NET 4.0 a un TryParse, et c'est générique aussi :)
Thorarin
+1 Cela m'a sauvé la journée! Je rétroporte un tas de code de .NET 4 vers .NET 3.5 et vous m'avez sauvé :)
daitangio
20

Voici une implémentation personnalisée de EnumTryParse. Contrairement à d'autres implémentations courantes, il prend également en charge les énumérations marquées avec l' Flagsattribut.

    /// <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;
    }
Simon Mourier
la source
1
vous avez fourni la meilleure implémentation et je l'ai utilisée à mes propres fins; cependant, je me demande pourquoi vous utilisez Activator.CreateInstance(type)pour créer la valeur d'énumération par défaut et non Enum.ToObject(type, 0). Juste une question de goût?
Pierre Arnaud
1
@Pierre - Hmmm ... non, cela semblait juste plus naturel à ce moment-là :-) Peut-être Enum.ToObject est-il plus rapide puisqu'il utilise en interne un appel interne InternalBoxEnum? Je n'ai jamais vérifié ça ...
Simon Mourier
2
Comme mentionné ci-dessous, mais pas vraiment visible: Depuis .Net 4 Enum.TryParse est disponible et fonctionne sans codage supplémentaire. Plus d'informations sont disponibles sur MSDN: msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian
16

En fin de compte, vous devez mettre en œuvre ceci autour de 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;
}

Notes complémentaires:

  • Enum.TryParseest inclus dans .NET 4. Voir ici http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Une autre approche consisterait à encapsuler directement la Enum.Parsecapture de l'exception levée en cas d'échec. Cela pourrait être plus rapide lorsqu'une correspondance est trouvée, mais sera probablement plus lent sinon. En fonction des données que vous traitez, cela peut ou non être une nette amélioration.

EDIT: Je viens de voir une meilleure implémentation à ce sujet, qui met en cache les informations nécessaires: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5

Richard
la source
J'allais suggérer d'utiliser default (T) pour définir la valeur par défaut. Il s'avère que cela ne fonctionnerait pas pour toutes les énumérations. Par exemple, si le type sous-jacent de l'énumération était int default (T) retournera toujours 0, ce qui peut ou non être valide pour l'énumération.
Daniel Ballinger
L'implémentation sur le blog de Damieng ne prend pas en charge les énumérations avec l' Flagsattribut.
Uwe Keim
9

Basé sur .NET 4.5

Exemple de code ci-dessous

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

Référence: http://www.dotnetperls.com/enum-parse

Hugo Hilário
la source
4

J'ai une implémentation optimisée que vous pouvez utiliser dans UnconstrainedMelody . En fait, il ne s'agit que de mettre en cache la liste des noms, mais cela se fait d'une manière agréable, fortement typée et contrainte de manière générique :)

Jon Skeet
la source
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)) {

}
Everson Rafael
la source
2

Il n'y a actuellement aucun Enum.TryParse prêt à l'emploi. Cela a été demandé sur Connect ( toujours pas Enum.TryParse ) et a obtenu une réponse indiquant une éventuelle inclusion dans le prochain framework après .NET 3.5. Vous devrez mettre en œuvre les solutions de contournement suggérées pour le moment.

Ahmad Mageed
la source
1

Le seul moyen d'éviter la gestion des exceptions est d'utiliser la méthode GetNames (), et nous savons tous que les exceptions ne doivent pas être utilisées abusivement pour la logique d'application commune :)

Philippe Leybaert
la source
1
Ce n'est pas le seul moyen. Enum.IsDefined (..) empêchera la levée d'exceptions dans le code utilisateur.
Thorarin
1

La mise en cache d'une fonction / dictionnaire généré dynamiquement est-elle autorisée?

Comme vous ne connaissez pas (semblez-vous) le type d'énumération à l'avance, la première exécution pourrait générer quelque chose dont les exécutions ultérieures pourraient tirer parti.

Vous pouvez même mettre en cache le résultat de Enum.GetNames ()

Essayez-vous d'optimiser le processeur ou la mémoire? En avez-vous vraiment besoin?

Nader Shirazie
la source
L'idée est d'optimiser le processeur. J'accepte que je puisse le faire au prix de la mémoire. Mais ce n'est pas la solution que je recherche. Merci.
Manish Basantani le
0

Comme d'autres l'ont déjà dit, si vous n'utilisez pas Try & Catch, vous devez utiliser IsDefined ou GetNames ... Voici quelques exemples ... ils sont fondamentalement tous les mêmes, le premier gérant des énumérations nullables. Je préfère le 2ème car c'est une extension sur des cordes, pas des enums ... mais vous pouvez les mélanger comme vous le souhaitez!

  • 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

la source
0

Il n'y a pas de TryParse car le type d'Enum n'est pas connu avant l'exécution. Un TryParse qui suit la même méthodologie que la méthode Date.TryParse lèverait une erreur de conversion implicite sur le paramètre ByRef.

Je suggère de faire quelque chose comme ça:

//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);
    }
}
ben
la source
Pour les Tryméthodes dont les résultats peuvent être des types valeur, ou dont le résultat nullpeut être légitime (par Dictionary.TryGetValue, which has both such traits), the normal pattern is for a exemple, la méthode Try` pour renvoyer boolet transmettre le résultat en tant que outparamètre. Pour celles qui renvoient des types de classe où nulln'est pas un résultat valide, il n'y a aucune difficulté à utiliser un nullretour pour indiquer l'échec.
supercat
-1

Jetez un œil à la classe Enum (struct?) Elle-même. Il existe une méthode Parse à ce sujet, mais je ne suis pas sûr d'un tryparse.

Spence
la source
Je connais la méthode Enum.Parse (typeof (TEnum), strEnumValue). Il lève ArgumentException si strEnumValue n'est pas valide. Looking for TryParse ........
Manish Basantani
-2

Cette méthode convertira un type d'énumération:

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

Il vérifie le type sous-jacent et obtient le nom par rapport à lui à analyser. Si tout échoue, la valeur par défaut sera renvoyée.

Naveed Ahmed
la source
3
qu'est-ce que cela fait "Enum.GetName (typeof (HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" Probablement une certaine dépendance sur votre code local.
Manish Basantani