Convertir une chaîne en une énumération en C #

896

Quelle est la meilleure façon de convertir une chaîne en une valeur d'énumération en C #?

J'ai une balise de sélection HTML contenant les valeurs d'une énumération. Lorsque la page est publiée, je veux récupérer la valeur (qui sera sous la forme d'une chaîne) et la convertir en valeur d'énumération.

Dans un monde idéal, je pourrais faire quelque chose comme ça:

StatusEnum MyStatus = StatusEnum.Parse("Active");

mais ce n'est pas un code valide.

Ben Mills
la source

Réponses:

1511

Dans .NET Core et .NET> 4, il existe une méthode d'analyse générique :

Enum.TryParse("Active", out StatusEnum myStatus);

Cela inclut également les nouvelles outvariables en ligne de C # 7 , donc cela fait le try-parse, la conversion au type d'énumération explicite et initialise + remplit la myStatusvariable.

Si vous avez accès à C # 7 et au dernier .NET, c'est la meilleure façon.

Réponse originale

Dans .NET, c'est plutôt moche (jusqu'à 4 ou plus):

StatusEnum MyStatus = (StatusEnum) Enum.Parse(typeof(StatusEnum), "Active", true);

J'ai tendance à simplifier cela avec:

public static T ParseEnum<T>(string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

Ensuite, je peux faire:

StatusEnum MyStatus = EnumUtil.ParseEnum<StatusEnum>("Active");

Une option suggérée dans les commentaires est d'ajouter une extension, ce qui est assez simple:

public static T ToEnum<T>(this string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

StatusEnum MyStatus = "Active".ToEnum<StatusEnum>();

Enfin, vous voudrez peut-être avoir une énumération par défaut à utiliser si la chaîne ne peut pas être analysée:

public static T ToEnum<T>(this string value, T defaultValue) 
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    T result;
    return Enum.TryParse<T>(value, true, out result) ? result : defaultValue;
}

Ce qui en fait l'appel:

StatusEnum MyStatus = "Active".ToEnum(StatusEnum.None);

Cependant, je serais prudent d'ajouter une méthode d'extension comme celle-ci stringcar (sans contrôle d'espace de noms), elle apparaîtra sur toutes les instances, stringqu'elles contiennent ou non une énumération (ce 1234.ToString().ToEnum(StatusEnum.None)serait donc valide mais absurde). Il est souvent préférable d'éviter d'encombrer les classes principales de Microsoft avec des méthodes supplémentaires qui ne s'appliquent que dans des contextes très spécifiques, à moins que toute votre équipe de développement ne comprenne très bien ce que font ces extensions.

Keith
la source
17
Si la performance est importante (ce qui est toujours le cas), réponse chk donnée par Mckenzieg1 ci-dessous: stackoverflow.com/questions/16100/…
Nash
28
@avinashr a raison sur la réponse de @ McKenzieG1, mais ce n'est PAS TOUJOURS important. Par exemple, ce serait une micro-optimisation inutile de s'inquiéter de l'analyse enum si vous faisiez un appel DB pour chaque analyse.
Keith
4
@HM Je ne pense pas qu'une extension soit appropriée ici - c'est un peu un cas spécial et une extension s'appliquerait à chaque chaîne. Si vous vouliez vraiment le faire, ce serait un changement trivial.
Keith
7
Que diriez-vous d'Enum.TryParse?
Elaine
15
très agréable. vous avez besoin d'une structure where T: dans votre dernier exemple.
bbrik
331

Utilisez Enum.TryParse<T>(String, T)(≥ .NET 4.0):

StatusEnum myStatus;
Enum.TryParse("Active", out myStatus);

Il peut être encore simplifié avec l' inline de type de paramètre de C # 7.0 :

Enum.TryParse("Active", out StatusEnum myStatus);
Erwin Mayer
la source
45
Ajoutez le paramètre booléen moyen pour la sensibilité à la casse et c'est de loin la solution la plus sûre et la plus élégante.
DanM7
18
Allez, combien d'entre vous ont mis en œuvre cette réponse sélectionnée de 2008 pour ne faire défiler vers le bas et trouver que c'est la meilleure réponse (moderne).
TEK
@TEK Je préfère en fait la réponse de 2008.
Zero3
Je ne comprends pas. Parselève des exceptions explicatives pour ce qui n'a pas fonctionné avec la conversion (la valeur était null, vide ou aucune constante d'énumération correspondante), ce qui est bien mieux que TryParsela valeur de retour booléenne (qui supprime l'erreur concrète)
yair
2
Enum.TryParse <T> (String, T) est défectueux lors de l'analyse des chaînes entières. Par exemple, ce code analysera avec succès une chaîne absurde comme une énumération absurde: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net
196

Notez que les performances de Enum.Parse()sont horribles, car elles sont implémentées via la réflexion. (Il en va de même pour Enum.ToString, ce qui va dans l'autre sens.)

Si vous devez convertir des chaînes en Enums dans un code sensible aux performances, votre meilleur pari est de créer un Dictionary<String,YourEnum>démarrage et de l'utiliser pour effectuer vos conversions.

McKenzieG1
la source
10
J'ai mesuré 3 ms pour convertir une chaîne en Enum lors de la première exécution, sur un ordinateur de bureau. (Juste pour illustrer le niveau de timidité).
Matthieu Charbonnier du
12
Wow 3ms est des ordres de grandeur terribles
John Stock
1
pouvez-vous ajouter un exemple de code à ce sujet, nous avons donc une idée sur la façon de remplacer et d'utiliser
transformateur
Si votre application est utilisée par 1 million de personnes => cela représente jusqu'à 50 heures de vie humaine que vous consommez :) Sur une seule page. : P
Cătălin Rădoi
95

Vous recherchez Enum.Parse .

SomeEnum enum = (SomeEnum)Enum.Parse(typeof(SomeEnum), "EnumValue");
DavidWhitney
la source
31

Vous pouvez maintenant utiliser des méthodes d'extension :

public static T ToEnum<T>(this string value, bool ignoreCase = true)
{
    return (T) Enum.Parse(typeof (T), value, ignoreCase);
}

Et vous pouvez les appeler par le code ci-dessous (voici FilterTypeun type enum):

FilterType filterType = type.ToEnum<FilterType>();
Foyzul Karim
la source
1
J'ai mis à jour cela pour prendre la valeur en tant qu'objet et le convertir en chaîne à l'intérieur de cette méthode. De cette façon, je peux prendre une valeur int .ToEnum au lieu de chaînes uniquement.
RealSollyM
2
@SollyM Je dirais que c'est une cause d'idée horrible, alors cette méthode d'extension s'appliquera à tous les types d'objets. Deux méthodes d'extension, une pour la chaîne et une pour l'int, seraient plus propres et beaucoup plus sûres à mon avis.
Svish
@Svish, c'est vrai. La seule raison pour laquelle j'ai fait cela est que notre code est utilisé uniquement en interne et que je voulais éviter d'écrire 2 extensions. Et puisque la seule fois où nous convertissons en Enum est avec string ou int, je ne pense pas que ce soit un problème autrement.
RealSollyM
3
@SollyM Internal ou non, je suis toujours celui qui gère et utilise mon code: PI serait ennuyé si j'obtenais un ToEnum dans chaque menu intellisense, et comme vous le dites, puisque la seule fois où vous convertissez en enum est à partir d'une chaîne ou int, vous pouvez être sûr que vous n'aurez besoin que de ces deux méthodes. Et deux méthodes ne sont pas beaucoup plus qu'une, surtout quand elles sont aussi petites et du type utilitaire: P
Svish
20
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

Donc, si vous aviez une énumération nommée humeur, cela ressemblerait à ceci:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

   // ...
   Mood m = (Mood) Enum.Parse(typeof(Mood), "Happy", true);
   Console.WriteLine("My mood is: {0}", m.ToString());
brendan
la source
18

IL FAUT SE MÉFIER:

enum Example
{
    One = 1,
    Two = 2,
    Three = 3
}

Enum.(Try)Parse() accepte plusieurs arguments séparés par des virgules et les combine avec le binaire «ou»| . Vous ne pouvez pas désactiver cela et à mon avis, vous ne le voulez presque jamais.

var x = Enum.Parse("One,Two"); // x is now Three

Même s'il Threen'était pas défini, xobtiendrait toujours une valeur int 3. C'est encore pire: Enum.Parse () peut vous donner une valeur qui n'est même pas définie pour l'énumération!

Je ne voudrais pas ressentir les conséquences des utilisateurs, volontairement ou non, déclenchant ce comportement.

De plus, comme mentionné par d'autres, les performances sont loin d'être idéales pour les énumérations volumineuses, à savoir linéaires dans le nombre de valeurs possibles.

Je suggère ce qui suit:

    public static bool TryParse<T>(string value, out T result)
        where T : struct
    {
        var cacheKey = "Enum_" + typeof(T).FullName;

        // [Use MemoryCache to retrieve or create&store a dictionary for this enum, permanently or temporarily.
        // [Implementation off-topic.]
        var enumDictionary = CacheHelper.GetCacheItem(cacheKey, CreateEnumDictionary<T>, EnumCacheExpiration);

        return enumDictionary.TryGetValue(value.Trim(), out result);
    }

    private static Dictionary<string, T> CreateEnumDictionary<T>()
    {
        return Enum.GetValues(typeof(T))
            .Cast<T>()
            .ToDictionary(value => value.ToString(), value => value, StringComparer.OrdinalIgnoreCase);
    }
Timo
la source
4
En fait, c'est très utile de le savoir Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Signifie que vous pouvez configurer vos valeurs d'énumération en tant que puissances de 2 et que vous avez un moyen très simple d'analyser plusieurs drapeaux booléens, par exemple. "UseSSL, NoRetries, Sync". En fait, c'est probablement pour cela qu'il a été conçu.
pcdev
16

Enum.Parse est votre ami:

StatusEnum MyStatus = (StatusEnum)Enum.Parse(typeof(StatusEnum), "Active");
tags2k
la source
13

Vous pouvez étendre la réponse acceptée avec une valeur par défaut pour éviter les exceptions:

public static T ParseEnum<T>(string value, T defaultValue) where T : struct
{
    try
    {
        T enumValue;
        if (!Enum.TryParse(value, true, out enumValue))
        {
            return defaultValue;
        }
        return enumValue;
    }
    catch (Exception)
    {
        return defaultValue;
    }
}

Ensuite, vous l'appelez comme:

StatusEnum MyStatus = EnumUtil.ParseEnum("Active", StatusEnum.None);

Si la valeur par défaut n'est pas une énumération, Enum.TryParse échouera et lèvera une exception qui est interceptée.

Après des années d'utilisation de cette fonction dans notre code à de nombreux endroits, il est peut-être bon d'ajouter les informations selon lesquelles cette opération coûte des performances!

Nelly
la source
Je n'aime pas les valeurs par défaut. Cela peut conduire à des résultats imprévisibles.
Daniël Tulp
5
quand cela lèvera-t-il jamais une exception?
andleer
@andleer si la valeur d'énumération ne correspond pas au même type d'énumération que la valeur par défaut
Nelly
@Nelly Ancien code ici mais le defaultValueet le type de retour de méthode sont tous les deux de type T. Si les types sont différents, vous recevrez une erreur de temps de compilation: «ne peut pas convertir de« ConsoleApp1.Size »en« ConsoleApp1.Color »» ou quels que soient vos types.
andleer
@andleer, je suis désolé que ma dernière réponse ne soit pas correcte. Il est possible que cette méthode lève une exception Syste.ArgumentException dans le cas où quelqu'un appelle cette fonction avec une valeur par défaut qui n'est pas de type enum. Avec c # 7.0, je ne pouvais pas faire une clause where de T: Enum. C'est pourquoi j'ai attrapé cette possibilité avec une prise d'essai.
Nelly
8

Nous ne pouvions pas supposer une entrée parfaitement valide, et nous sommes allés avec cette variation de la réponse de @ Keith:

public static TEnum ParseEnum<TEnum>(string value) where TEnum : struct
{
    TEnum tmp; 
    if (!Enum.TryParse<TEnum>(value, true, out tmp))
    {
        tmp = new TEnum();
    }
    return tmp;
}
écart
la source
7
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str) 
{ 
    return (T) Enum.Parse(typeof(T), str);
}
Mark Cidade
la source
5

Analyse la chaîne en TEnum sans try / catch et sans méthode TryParse () à partir de .NET 4.5

/// <summary>
/// Parses string to TEnum without try/catch and .NET 4.5 TryParse()
/// </summary>
public static bool TryParseToEnum<TEnum>(string probablyEnumAsString_, out TEnum enumValue_) where TEnum : struct
{
    enumValue_ = (TEnum)Enum.GetValues(typeof(TEnum)).GetValue(0);
    if(!Enum.IsDefined(typeof(TEnum), probablyEnumAsString_))
        return false;

    enumValue_ = (TEnum) Enum.Parse(typeof(TEnum), probablyEnumAsString_);
    return true;
}
jite.gs
la source
1
S'il est nécessaire de faire une description si le code contient déjà une description? Ok, je l'ai fait :)
jite.gs
3

Code super simple utilisant TryParse:

var value = "Active";

StatusEnum status;
if (!Enum.TryParse<StatusEnum>(value, out status))
    status = StatusEnum.Unknown;
Brian Rice
la source
2

J'aime la solution de la méthode d'extension ..

namespace System
{
    public static class StringExtensions
    {

        public static bool TryParseAsEnum<T>(this string value, out T output) where T : struct
        {
            T result;

            var isEnum = Enum.TryParse(value, out result);

            output = isEnum ? result : default(T);

            return isEnum;
        }
    }
}

Voici ci-dessous mon implémentation avec tests.

using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
using static System.Console;

private enum Countries
    {
        NorthAmerica,
        Europe,
        Rusia,
        Brasil,
        China,
        Asia,
        Australia
    }

   [TestMethod]
        public void StringExtensions_On_TryParseAsEnum()
        {
            var countryName = "Rusia";

            Countries country;
            var isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsTrue(isCountry);
            AreEqual(Countries.Rusia, country);

            countryName = "Don't exist";

            isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsFalse(isCountry);
            AreEqual(Countries.NorthAmerica, country); // the 1rst one in the enumeration
        }
alhpe
la source
1
public static T ParseEnum<T>(string value)            //function declaration  
{
    return (T) Enum.Parse(typeof(T), value);
}

Importance imp = EnumUtil.ParseEnum<Importance>("Active");   //function call

==================== Un programme complet ====================

using System;

class Program
{
    enum PetType
    {
    None,
    Cat = 1,
    Dog = 2
    }

    static void Main()
    {

    // Possible user input:
    string value = "Dog";

    // Try to convert the string to an enum:
    PetType pet = (PetType)Enum.Parse(typeof(PetType), value);

    // See if the conversion succeeded:
    if (pet == PetType.Dog)
    {
        Console.WriteLine("Equals dog.");
    }
    }
}
-------------
Output

Equals dog.
Rae Lee
la source
1

J'ai utilisé la classe (version fortement typée d'Enum avec analyse et améliorations des performances). Je l'ai trouvé sur GitHub, et cela devrait également fonctionner pour .NET 3.5. Il a une surcharge de mémoire car il met en mémoire tampon un dictionnaire.

StatusEnum MyStatus = Enum<StatusEnum>.Parse("Active");

Le blogpost est Enums - Meilleure syntaxe, performances améliorées et TryParse dans NET 3.5 .

Et code: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/System/EnumT.cs

Patrik Lindström
la source
1

Pour les performances, cela pourrait aider:

    private static Dictionary<Type, Dictionary<string, object>> dicEnum = new Dictionary<Type, Dictionary<string, object>>();
    public static T ToEnum<T>(this string value, T defaultValue)
    {
        var t = typeof(T);
        Dictionary<string, object> dic;
        if (!dicEnum.ContainsKey(t))
        {
            dic = new Dictionary<string, object>();
            dicEnum.Add(t, dic);
            foreach (var en in Enum.GetValues(t))
                dic.Add(en.ToString(), en);
        }
        else
            dic = dicEnum[t];
        if (!dic.ContainsKey(value))
            return defaultValue;
        else
            return (T)dic[value];
    }
Koray
la source
1

J'ai trouvé qu'ici le cas des valeurs enum qui ont une valeur EnumMember n'a pas été considéré. Alors c'est parti:

using System.Runtime.Serialization;

public static TEnum ToEnum<TEnum>(this string value, TEnum defaultValue) where TEnum : struct
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    TEnum result;
    var enumType = typeof(TEnum);
    foreach (var enumName in Enum.GetNames(enumType))
    {
        var fieldInfo = enumType.GetField(enumName);
        var enumMemberAttribute = ((EnumMemberAttribute[]) fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), true)).FirstOrDefault();
        if (enumMemberAttribute?.Value == value)
        {
            return Enum.TryParse(enumName, true, out result) ? result : defaultValue;
        }
    }

    return Enum.TryParse(value, true, out result) ? result : defaultValue;
}

Et exemple de cette énumération:

public enum OracleInstanceStatus
{
    Unknown = -1,
    Started = 1,
    Mounted = 2,
    Open = 3,
    [EnumMember(Value = "OPEN MIGRATE")]
    OpenMigrate = 4
}
isxaker
la source
1

Vous devez utiliser Enum.Parse pour obtenir la valeur d'objet à partir d'Enum, après quoi vous devez changer la valeur d'objet en valeur d'énumération spécifique. La conversion en valeur énumérée peut être effectuée à l'aide de Convert.ChangeType. Veuillez consulter l'extrait de code suivant

public T ConvertStringValueToEnum<T>(string valueToParse){
    return Convert.ChangeType(Enum.Parse(typeof(T), valueToParse, true), typeof(T));
}
Bartosz Gawron
la source
1

Essayez cet exemple:

 public static T GetEnum<T>(string model)
    {
        var newModel = GetStringForEnum(model);

        if (!Enum.IsDefined(typeof(T), newModel))
        {
            return (T)Enum.Parse(typeof(T), "None", true);
        }

        return (T)Enum.Parse(typeof(T), newModel.Result, true);
    }

    private static Task<string> GetStringForEnum(string model)
    {
        return Task.Run(() =>
        {
            Regex rgx = new Regex("[^a-zA-Z0-9 -]");
            var nonAlphanumericData = rgx.Matches(model);
            if (nonAlphanumericData.Count < 1)
            {
                return model;
            }
            foreach (var item in nonAlphanumericData)
            {
                model = model.Replace((string)item, "");
            }
            return model;
        });
    }

Dans cet exemple, vous pouvez envoyer chaque chaîne et définir votre Enum. Si vous Enumaviez des données que vous vouliez, renvoyez-les comme Enumtype.

AmirReza-Farahlagha
la source
1
Vous écrasez newModelsur chaque ligne, donc si elle contient des tirets, elle ne sera pas remplacée. De plus, vous n'avez pas à vérifier si la chaîne contient quelque chose, vous pouvez simplement appeler de Replacetoute façon:var newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen
@LarsKristensen Oui, nous pouvons créer une méthode qui supprime les caractères non alphanumériques.
AmirReza-Farahlagha
1

Je ne sais pas quand cela a été ajouté, mais sur la classe Enum, il y a maintenant un

Parse<TEnum>(stringValue)

Utilisé comme tel avec l'exemple en question:

var MyStatus = Enum.Parse<StatusEnum >("Active")

ou en ignorant le boîtier par:

var MyStatus = Enum.Parse<StatusEnum >("active", true)

Voici les méthodes décompilées utilisées:

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value) where TEnum : struct
    {
      return Enum.Parse<TEnum>(value, false);
    }

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value, bool ignoreCase) where TEnum : struct
    {
      TEnum result;
      Enum.TryParse<TEnum>(value, ignoreCase, true, out result);
      return result;
    }
JCisar
la source
0
        <Extension()>
    Public Function ToEnum(Of TEnum)(ByVal value As String, ByVal defaultValue As TEnum) As TEnum
        If String.IsNullOrEmpty(value) Then
            Return defaultValue
        End If

        Return [Enum].Parse(GetType(TEnum), value, True)
    End Function
AHMED RABEE
la source
0
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
    return defaultValue;

return Enum.Parse(typeof(TEnum), value, true);}
AHMED RABEE
la source
0

Si le nom de la propriété est différent de ce que vous voulez l'appeler (c'est-à-dire les différences de langue), vous pouvez faire comme ceci:

MyType.cs

using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[JsonConverter(typeof(StringEnumConverter))]
public enum MyType
{
    [EnumMember(Value = "person")]
    Person,
    [EnumMember(Value = "annan_deltagare")]
    OtherPerson,
    [EnumMember(Value = "regel")]
    Rule,
}

EnumExtensions.cs

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public static class EnumExtensions
{
    public static TEnum ToEnum<TEnum>(this string value) where TEnum : Enum
    {
        var jsonString = $"'{value.ToLower()}'";
        return JsonConvert.DeserializeObject<TEnum>(jsonString, new StringEnumConverter());
    }

    public static bool EqualsTo<TEnum>(this string strA, TEnum enumB) where TEnum : Enum
    {
        TEnum enumA;
        try
        {
            enumA = strA.ToEnum<TEnum>();
        }
        catch
        {
            return false;
        }
        return enumA.Equals(enumB);
    }
}

Program.cs

public class Program
{
    static public void Main(String[] args) 
    { 
        var myString = "annan_deltagare";
        var myType = myString.ToEnum<MyType>();
        var isEqual = myString.EqualsTo(MyType.OtherPerson);
        //Output: true
    }     
}
Joel Wiklund
la source