103 votes

Comment faire un TryParse pour une valeur Enum ?

Je veux écrire une fonction qui peut valider une valeur donnée (passée en tant que chaîne de caractères) par rapport aux valeurs possibles d'un fichier de type enum . En cas de correspondance, il doit retourner l'instance de l'enum ; sinon, il doit retourner une valeur par défaut.

La fonction ne peut pas utiliser en interne try / catch ce qui exclut l'utilisation de Enum.Parse qui lève une exception lorsqu'il reçoit un argument invalide.

J'aimerais utiliser quelque chose de l'ordre du TryParse pour mettre en œuvre cette fonction :

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 votes

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." Quel est l'intérêt ?

1 votes

Quelle est votre aversion pour la solution try/catch ? Si vous essayez d'éviter les exceptions parce qu'elles sont "coûteuses", faites-vous plaisir. Dans 99% des cas, le coût de lancer/capturer une exception est négligeable par rapport à votre code principal.

1 votes

Le coût du traitement des exceptions n'est pas si élevé. Les implémentations internes de toute cette conversion d'énumération sont pleines de gestion d'exceptions. Je n'aime vraiment pas que les exceptions soient lancées et rattrapées pendant la logique normale de l'application. Il peut parfois être utile de casser toutes les exceptions qui sont lancées (même lorsqu'elles sont attrapées). Lancer des exceptions partout rendra l'utilisation beaucoup plus ennuyeuse :)

111voto

Thorarin Points 21538

Enum.IsDefined permet de faire avancer les choses. Ce n'est peut-être pas aussi efficace qu'un TryParse, mais cela fonctionnera sans gestion des exceptions.

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

À noter : un TryParse a été ajoutée dans .NET 4.0.

1 votes

La meilleure réponse que j'ai vue jusqu'à présent... pas de try/catch, pas de GetNames :)

13 votes

Inconvénients de Enum.IsDefined : blogs.msdn.com/brada/archive/2003/11/29/50903.aspx

6 votes

Il n'y a pas non plus de cas d'ignorance sur IsDefined.

32voto

Comme d'autres l'ont dit, vous devez mettre en place votre propre TryParse . Simon Mourier fournit une implémentation complète qui s'occupe de tout.

Si vous utilisez des enums de champs de bits (c'est-à-dire des drapeaux), vous devez également gérer une chaîne de caractères telle que "MyEnum.Val1|MyEnum.Val2" qui est une combinaison de deux valeurs d'énumération. Si vous appelez simplement Enum.IsDefined avec cette chaîne, il retournera false, même si Enum.Parse le traite correctement.

Mise à jour

Comme l'ont mentionné Lisa et Christian dans les commentaires, Enum.TryParse est maintenant disponible pour C# dans .NET4 et plus.

Docs MSDN

0 votes

Peut-être la moins sexy, mais je suis d'accord pour dire que c'est la meilleure solution jusqu'à ce que votre code soit migré vers .NET 4.

1 votes

Comme mentionné ci-dessous, mais pas vraiment visible : A partir de .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

20voto

Simon Mourier Points 49585

Voici une mise en œuvre personnalisée de EnumTryParse . Contrairement à d'autres implémentations courantes, il prend également en charge les enum marqués par l'attribut Flags attribut.

    /// <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 votes

Vous avez fourni la meilleure implémentation et je l'ai utilisée pour mes propres besoins ; cependant, je me demande pourquoi vous utilisez Activator.CreateInstance(type) pour créer la valeur par défaut de l'enum et non Enum.ToObject(type, 0) . Une simple question de goût ?

1 votes

@Pierre - Hmmm... non, cela semblait juste plus naturel à ce moment-là :-) Peut-être que Enum.ToObject est plus rapide car il utilise en interne un appel interne InternalBoxEnum ? Je n'ai jamais vérifié cela...

2 votes

Comme mentionné ci-dessous, mais pas vraiment visible : A partir de .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

16voto

Richard Points 54016

En fin de compte, vous devez mettre en œuvre ce qui suit 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 supplémentaires :

  • Enum.TryParse est inclus dans .NET 4. Voir ici http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Une autre approche serait d'envelopper directement Enum.Parse en attrapant l'exception levée lorsqu'elle échoue. Cette méthode peut être plus rapide lorsqu'une correspondance est trouvée, mais elle sera probablement plus lente dans le cas contraire. Selon les données que vous traitez, cela peut ou non constituer une amélioration nette.

EDIT : Je viens de voir une meilleure implémentation de ceci, 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

0 votes

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 tous les enums. Par exemple, si le type sous-jacent de l'énumération est int, default(T) retournera toujours 0, ce qui peut ou non être valide pour l'énumération.

0 votes

La mise en œuvre sur le blog de Damieng fait no supportent les énumérations avec le Flags attribut.

4voto

Jon Skeet Points 692016

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X