Question

Je construis une fonction pour étendre le Enum.Parse concept qui

  • Permet à une valeur par défaut d'être analysée si aucune valeur Enum n'est trouvée
  • est insensible à la casse

J'ai donc écrit ce qui suit:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Je reçois une contrainte d'erreur ne peut pas être une classe spéciale System.Enum.

Très bien, mais existe-t-il une solution permettant d'autoriser une énumération générique, ou vais-je devoir imiter la fonction Parse et passer un type en tant qu'attribut, ce qui force l'exigence de la boxe laide à votre code

MODIFIER Toutes les suggestions ci-dessous ont été grandement appréciées, merci.

Je me suis installé (j'ai laissé la boucle pour maintenir l'insensibilité à la casse - je l'utilise lors de l'analyse de XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

EDIT: (16 février 2015) Julien Lebosquain a récemment publié la solution générique de sécurité du type du compilateur dans MSIL. ou F # ci-dessous, qui vaut bien un coup d'oeil et un vote positif. Je vais supprimer cette modification si la solution bouillonne plus haut dans la page.

Était-ce utile?

La solution

Puisque Enum Type implémente IConvertible l'interface, une meilleure implémentation devrait ressembler à ceci:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Ceci permettra toujours de passer des types de valeur implémentant <=>. Les chances sont rares cependant.

Autres conseils

C # & # 8805; 7,3

À partir de C # 7.3 (disponible avec Visual Studio 2017 & # 8805; v15.7), ce code est maintenant complètement valide:

public static TEnum Parse<TEnum>(string value)
where TEnum : struct, Enum { ... }

C # & # 8804; 7.2

Vous pouvez avoir une contrainte d'enum imposée par le compilateur réel en abusant de l'héritage de contrainte. Le code suivant spécifie à la fois une class et une struct contrainte:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

    public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Utilisation:

EnumUtils.Parse<SomeEnum>("value");

Remarque: ceci est spécifiquement indiqué dans la spécification du langage C # 5.0:

  

Si le paramètre de type S dépend du paramètre de type T alors:   [...] Il est valable pour   S pour avoir la contrainte de type valeur et T pour avoir le type référence   contrainte. Effectivement, cela limite T aux types System.Object,   System.ValueType, System.Enum et tout type d'interface.

Modifier

Julien Lebosquain a superbement répondu à cette question. J'aimerais également étendre sa réponse avec ignoreCase, defaultValue et des arguments optionnels, tout en ajoutant TryParse et ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Exemples d'utilisation:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

Ancien

Mes anciennes améliorations de la réponse de Vivek en utilisant les commentaires et les "nouveaux" développements:

  • utiliser TEnum par souci de clarté pour les utilisateurs
  • ajouter plus de contraintes d'interface pour un contrôle de contrainte supplémentaire
  • laisser default gérer <=> avec le paramètre existant (introduit dans VS2010 / .Net 4)
  • utilisez éventuellement la <=> valeur générique (introduite dans VS2005 /.Net 2)
  • utiliser les arguments facultatifs (introduits dans VS2010 / .Net 4) avec des valeurs par défaut, pour <=> et <=>

ayant pour résultat:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}

Vous pouvez définir un constructeur statique pour la classe qui vérifiera que le type T est une énumération et lève une exception s'il ne l'est pas. C’est la méthode mentionnée par Jeffery Richter dans son livre CLR via C #.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Ensuite, dans la méthode d’analyse, vous pouvez simplement utiliser Enum.Parse (typeof (T), entrée, true) pour convertir une chaîne en enum. Le dernier paramètre vrai sert à ignorer la casse de l'entrée.

Il convient également de noter que, depuis la publication de C # 7.3 à l'aide de contraintes Enum, la prise en charge immédiate est prise en charge sans qu'il soit nécessaire d'effectuer des vérifications supplémentaires.

Donc, si vous avez changé la version linguistique de votre projet en C # 7.3, le code suivant fonctionnera parfaitement:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

Si vous ne savez pas comment modifier la version linguistique en C # 7.3, reportez-vous à la capture d'écran suivante: entrer la description de l'image ici

EDIT 1 - Version requise de Visual Studio et prise en compte de ReSharper

Pour que Visual Studio puisse reconnaître la nouvelle syntaxe, vous devez avoir au minimum la version 15.7. Vous pouvez trouver cela également mentionné dans les notes de publication de Microsoft, voir Notes de publication de Visual Studio 2017 15.7 . Merci à @MohamedElshawaf d'avoir signalé cette question valable.

Veuillez noter que dans mon cas, ReSharper 2018.1 au moment de la rédaction de cette EDIT ne prend pas encore en charge le format C # 7.3. Lorsque ReSharper est activé, la contrainte Enum est soulignée comme une erreur indiquant que ne peut pas utiliser 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'objet' comme type paramètre de contrainte . ReSharper suggère une solution rapide pour Supprimer la contrainte 'Enum' du type paramètre T de la méthode

Cependant, si vous désactivez ReSharper temporairement sous Outils - > Options - & Gt; ReSharper Ultimate - & Gt; Général , vous verrez que la syntaxe est parfaite, car vous utilisez VS 15.7 ou supérieur et C # 7.3 ou supérieur.

J'ai modifié l'échantillon par dimarzionist. Cette version ne fonctionnera qu'avec Enums et ne laissera pas passer les structures.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}

J'ai essayé d'améliorer un peu le code:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}

J'ai des exigences spécifiques dans lesquelles je devais utiliser enum avec du texte associé à la valeur enum. Par exemple, lorsque j'utilise enum pour spécifier le type d'erreur, il est nécessaire de décrire les détails de l'erreur.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}

J'espère que cela vous aidera:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}

Il est intéressant de noter que ceci est apparemment possible dans d’autres langues (géré C ++, IL directement).

Pour citer:

  

... Les deux contraintes produisent en réalité un IL valide et peuvent également être utilisées par C # si elles sont écrites dans un autre langage (vous pouvez déclarer ces contraintes en C ++ géré ou en IL).

Qui sait

C’est ce que j’ai pensé. Combiné à partir des réponses et MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

Source MSDN

Les réponses existantes sont vraies à partir de C # < = 7.2. Cependant, il existe une demande de fonctionnalité en langage C # (liée à un demande de fonctionnalité corefx ) pour autoriser les éléments suivants;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

Au moment de la rédaction de cet article, la fonctionnalité est & "En discussion &"; lors des réunions de développement linguistique.

MODIFIER

Selon les informations de nawfal , elles sont introduites dans C # 7.3 .

J'ai toujours aimé cela (vous pouvez le modifier si nécessaire):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}

J’ai adoré la solution de Christopher Currens utilisant l’IL, mais pour ceux qui ne veulent pas s’occuper de questions complexes consistant à inclure MSIL dans leur processus de construction, j’ai écrit une fonction similaire en C #.

Notez cependant que vous ne pouvez pas utiliser de restriction générique comme where T : Enum car Enum est un type spécial. Par conséquent, je dois vérifier si le type générique donné est vraiment enum.

Ma fonction est la suivante:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}

J'ai encapsulé la solution de Vivek dans une classe d'utilitaires que vous pouvez réutiliser. Veuillez noter que vous devez toujours définir les contraintes de type & "; T: struct, IConvertible &"; sur votre type.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}

J'ai créé une extension Méthode to get integer value from enum jetez un oeil à la mise en œuvre de la méthode

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

il s'agit d'une utilisation

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way

Comme indiqué dans d'autres réponses auparavant; bien que cela ne puisse pas être exprimé en code source, cela peut être fait au niveau IL. @Christopher Currens answer montre comment le IL parvient à cela.

Avec le Fody , le complément ExtraConstraints.Fody , il existe un moyen très simple, avec un outil de construction, pour y parvenir. Ajoutez simplement leurs packages de nugets (Fody, ExtraConstraints.Fody) à votre projet et ajoutez les contraintes suivantes (extrait du fichier Readme d’ExpertContraintes):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

et Fody ajouteront le IL nécessaire à la présence de la contrainte. Notez également la fonctionnalité supplémentaire de contraindre les délégués:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

En ce qui concerne Enums, vous pouvez également prendre note du très intéressant Enums.NET . .

Si vous pouvez utiliser la diffusion directe par la suite, vous pouvez utiliser la System.Enum classe de base dans votre méthode, si nécessaire. Il vous suffit de remplacer les paramètres de type avec précaution. Donc, l’implémentation de la méthode serait comme:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Ensuite, vous pouvez l'utiliser comme:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);

Juste pour être complet, voici une solution Java. Je suis certain que la même chose pourrait être faite en C #. Cela évite d'avoir à spécifier le type n'importe où dans le code, mais plutôt dans les chaînes que vous essayez d'analyser.

Le problème est qu’il n’ya aucun moyen de savoir à quelle énumération la chaîne peut correspondre - la solution consiste donc à résoudre ce problème.

Au lieu d’accepter uniquement la valeur de chaîne, acceptez une chaîne comportant à la fois l’énumération et la valeur sous la forme & "enumeration.value &"; Le code de travail est ci-dessous - nécessite Java 1.8 ou une version ultérieure. Cela rendrait également le XML plus précis, car vous verriez quelque chose comme: color = & Quot; Color.red & Quot; au lieu de color = & "; rouge &";.

Vous appelez la méthode acceptEnumeratedValue () avec une chaîne contenant le nom enum, nom de la valeur de point.

La méthode retourne la valeur énumérée formelle.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top