Créer une méthode générique contraignant T à une énumération


1190

Je construis une fonction pour étendre le Enum.Parseconcept

  • Permet d'analyser une valeur par défaut au cas où aucune valeur Enum ne serait 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.

D'accord, mais existe-t-il une solution de contournement pour autoriser une énumération générique, ou vais-je devoir imiter la Parsefonction et passer un type en tant qu'attribut, ce qui force l'exigence de boxe laide à votre code.

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

Je me suis installé (j'ai quitté 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é une solution générique de type sécurisé imposée par le compilateur dans MSIL ou F # ci-dessous, qui vaut bien un coup d'œil, et un vote positif. Je supprimerai cette modification si la solution bouillonne plus haut sur la page.


10
Vous devriez peut-être utiliser ToUpperInvariant () au lieu de ToLower () ...
Max Galkin

31
@Shimmy: Dès que vous transmettez un type de valeur à la méthode d'extension, vous travaillez sur une copie de celui-ci, vous ne pouvez donc pas changer son état.
Garo Yeriazarian

4
Je sais que c'est un vieux fil, je ne sais pas s'ils ont changé les choses, mais les méthodes d'extension fonctionnent très bien pour les types de valeur, bien sûr, elles pourraient ne pas toujours avoir autant de sens, mais j'ai utilisé "Secondes statiques TimeSpan publiques (cet int x) { return TimeSpan.FromSeconds (x);} "pour activer la syntaxe de" Wait.For (5.Seconds ()) ... "
Jens

6
Réalisez que cela ne faisait pas partie de la question, mais vous pouvez améliorer votre logique de boucle foreach en utilisant String.Equals avec StringComparison.InvariantCultureIgnoreCase
Firestrand

Réponses:


1006

Puisque EnumType implémente l' IConvertibleinterface, 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");
   }

   //...
}

Cela permettra toujours le passage des types de valeurs implémentés IConvertible. Les chances sont cependant rares.


2
Les génériques sont disponibles depuis .NET 2.0. Par conséquent, ils sont également disponibles dans vb 2005.
Vivek

46
Eh bien, rendez-le encore plus contraint, si vous choisissez de suivre ce chemin ... utilisez "classe TestClass <T> où T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde

106
Une autre suggestion est de définir le type générique avec l'identifiant TEnum. Ainsi: public TEnum GetEnumFromString <TEnum> (valeur de chaîne) où TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa

11
Vous ne gagnez pas grand-chose en incluant les autres interfaces car presque tous les types de valeurs intégrés implémentent toutes ces interfaces. Cela est particulièrement vrai pour les contraintes sur une méthode d'extension générique, ce qui est extrêmement pratique pour fonctionner sur des énumérations, à l'exception du fait que ces méthodes d'extension sont comme un virus qui infecte tous vos objets. IConvertable au moins le réduit un peu.
russbishop

2
@SamIam: Lorsque vous avez posté, ce fil était quoi, âgé de 6 ans et demi, et vous aviez raison, aucune vérification au moment de la compilation dans aucune des réponses. Puis seulement 3 jours plus tard, après 6 ans, vous avez réalisé votre souhait - voir le post de Julien Lebosquain ci-dessous.
David I. McIntosh

663

Cette fonctionnalité est enfin prise en charge en C # 7.3!

L'extrait de code suivant (à partir des exemples dotnet ) montre comment:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Veillez à définir votre version de langue dans votre projet C # sur la version 7.3.


Réponse originale ci-dessous:

Je suis en retard dans le match, mais je l'ai pris comme un défi pour voir comment cela pourrait être fait. Ce n'est pas possible en C # (ou VB.NET, mais faites défiler vers le bas pour F #), mais c'est possible dans MSIL. J'ai écrit cette petite chose ....

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Ce qui génère une fonction qui serait ressembler à cela, si elle était valide C #:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Puis avec le code C # suivant:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

Malheureusement, cela signifie que cette partie de votre code est écrite en MSIL au lieu de C #, le seul avantage supplémentaire étant que vous êtes en mesure de contraindre cette méthode System.Enum. C'est aussi une sorte de déception, car il est compilé dans un assemblage séparé. Cependant, cela ne signifie pas que vous devez le déployer de cette façon.

En supprimant la ligne .assembly MyThing{}et en invoquant ilasm comme suit:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

vous obtenez un netmodule au lieu d'un assemblage.

Malheureusement, VS2010 (et plus tôt, évidemment) ne prend pas en charge l'ajout de références de netmodule, ce qui signifie que vous devez le laisser dans 2 assemblys distincts lorsque vous déboguez. La seule façon de les ajouter dans le cadre de votre assembly serait d'exécuter vous-même csc.exe à l'aide de l' /addmodule:{files}argument de ligne de commande. Ce ne serait pas trop douloureux dans un script MSBuild. Bien sûr, si vous êtes courageux ou stupide, vous pouvez exécuter manuellement csc à chaque fois. Et cela devient certainement plus compliqué car plusieurs assemblages doivent y avoir accès.

Donc, cela PEUT être fait en .Net. Vaut-il l'effort supplémentaire? Euh, eh bien, je suppose que je vais vous laisser décider.


Solution F # comme alternative

Crédit supplémentaire: Il s'avère qu'une restriction générique sur enumest possible dans au moins un autre langage .NET en plus de MSIL: F #.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

Celui-ci est plus facile à maintenir car il s'agit d'un langage bien connu avec une prise en charge complète de Visual Studio IDE, mais vous avez toujours besoin d'un projet distinct dans votre solution pour cela. Cependant, il produit naturellement un IL considérablement différent (le code est très différent) et il dépend de la FSharp.Corebibliothèque, qui, comme toute autre bibliothèque externe, doit faire partie de votre distribution.

Voici comment vous pouvez l'utiliser (essentiellement la même que la solution MSIL), et pour montrer qu'il échoue correctement sur des structures autrement synonymes:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);

67
Ouais, très hardcore. J'ai le plus grand respect pour quelqu'un qui peut coder en IL et je sais comment les fonctionnalités sont prises en charge au niveau de langue supérieur - un niveau que beaucoup d'entre nous considèrent toujours comme étant de bas niveau dans les applications, les règles métier, les interfaces utilisateur, les bibliothèques de composants, etc. .
TonyG

13
Ce que j'aimerais vraiment savoir, c'est pourquoi l'équipe C # n'a pas encore commencé à autoriser cela, car il est déjà pris en charge par MSIL.
MgSam

25
@MgSam - De Eric Lippert :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Christopher Currens

5
@LordofScripts: Je pense que la raison est que cela depuis une classe qui limite un Tà System.Enumne serait pas en mesure de faire toutes les choses avec Tque les gens pourraient attendre, les auteurs de C # cernées ils peuvent aussi bien interdire complètement. Je considère la décision malheureuse, car C # ayant simplement ignoré tout traitement spécial des System.Enumcontraintes, il aurait été possible d'écrire une HasAnyFlags<T>(this T it, T other)méthode d'extension qui était des ordres de grandeur plus rapide que Enum.HasFlag(Enum)et qui vérifiait ses arguments par type.
supercat

9
Je ne pense pas avoir jamais eu de projet où je ne me suis pas retrouvé ici. C # 6 contient 110% de sucre syntaxique et CECI n'y est pas entré? Coupez la merde.
Michael Blackburn

214

C # ≥ 7,3

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

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

C # ≤ 7,2

Vous pouvez avoir une véritable contrainte d'énumération imposée par le compilateur en abusant de l'héritage des contraintes. Le code suivant spécifie à la fois les contraintes a classet a struct:

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>
{
}

Usage:

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

Remarque: cela 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 valide que S ait la contrainte de type valeur et T la contrainte de type référence. En fait, cela limite T aux types System.Object, System.ValueType, System.Enum et à tout type d'interface.


7
@ DavidI.McIntosh EnumClassUtils<System.Enum>est suffisant pour restreindre T à tout System.Enumtype dérivé. structon le Parserestreint ensuite à un type d'énumération réel. Vous devez vous limiter Enumà un moment donné. Pour ce faire, votre classe doit être imbriquée. Voir gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain

7
Juste pour être clair, mon commentaire "pas agréable" n'était pas un commentaire sur votre solution - c'est vraiment un beau hack. Juste "pas agréable" que MS nous oblige à utiliser un tel hack alambiqué.
David I. McIntosh

2
Existe-t-il un moyen de travailler cela pour être également utilisable pour les méthodes d'extension?
Mord Zuber

3
Qu'est-ce que la where TClass : classcontrainte gagne ici?
tsemer

2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm

30

Éditer

Julien Lebosquain a superbement répondu à la question . Je voudrais également étendre sa réponse avec ignoreCase, defaultValueet les arguments facultatifs, tout en ajoutant TryParseet 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);

Vieux

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

  • utiliser TEnumpour plus de clarté pour les utilisateurs
  • ajouter plus de contraintes d'interface pour une vérification supplémentaire des contraintes
  • laisser TryParsegérer ignoreCaseavec le paramètre existant (introduit dans VS2010 / .Net 4)
  • utiliser éventuellement la defaultvaleur générique (introduite dans VS2005 / .Net 2)
  • utiliser des arguments facultatifs (introduits dans VS2010 / .Net 4) avec des valeurs par défaut, pour defaultValueetignoreCase

résultant en:

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

18

Vous pouvez définir un constructeur statique pour la classe qui vérifiera que le type T est une énumération et lèvera 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), input, true) pour convertir une chaîne en énumération. Le dernier vrai paramètre est pour ignorer la casse de l'entrée.


1
C'est une bonne option pour les classes génériques - mais bien sûr, cela n'aide pas pour les méthodes génériques.
McGarnagle

En outre, cela n'est pas non plus appliqué au moment de la compilation, vous ne sauriez que vous avez fourni un non Enum Tlorsque le constructeur a exécuté. Bien que ce soit bien plus agréable que d'attendre un constructeur d'instance.
2018

15

Il convient également de tenir compte du fait que depuis la sortie de C # 7.3 à l'aide des contraintes Enum est prise en charge hors de la boîte sans avoir à faire de vérification supplémentaire et d'autres choses.

Donc, à l'avenir et étant donné que 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...
    }

Dans le cas où vous ne savez pas comment changer la version linguistique en C # 7.3, voir la capture d'écran suivante: entrez la description de l'image ici

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

Pour que Visual Studio reconnaisse la nouvelle syntaxe, vous avez besoin au moins de 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 valide.

Veuillez également noter que dans mon cas, ReSharper 2018.1 au moment de la rédaction de cet EDIT ne prend pas encore en charge C # 7.3. L'activation de ReSharper met en surbrillance la contrainte Enum comme une erreur m'indiquant Ne peut pas utiliser 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' comme contrainte de paramètre de type . ReSharper suggère comme solution rapide pour supprimer la contrainte «Enum» du type de paramètre T de la méthode

Cependant, si vous désactivez temporairement ReSharper sous Outils -> Options -> ReSharper Ultimate -> Général, vous verrez que la syntaxe est parfaitement correcte étant donné que vous utilisez VS 15.7 ou supérieur et C # 7.3 ou supérieur.


1
Quelle version VS utilisez-vous?
mshwf

1
@MohamedElshawaf Je pense que c'est la version 15.7 qui contient le support de C # 7.3
Patrick Roberts

1
Je pense qu'il vaut mieux écrire where T : struct, Enum, pour éviter de se passer System.Enumcomme paramètre de type.
Mariusz Pawelski

Comme @MariuszPawelski j'écris struct, Enum. Ma justification est expliquée dans la réponse et les commentaires ici .
Stephen Kennedy

Les informations ReSharper m'ont vraiment aidé. Notez que la dernière version d'aperçu prend en charge cette fonctionnalité.
DalSoft

11

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

13
Je ne retournerais pas la valeur par défaut en cas d'échec; Je laisserais l'exception se propager (tout comme avec Enum.Parse). Utilisez plutôt TryParse pour renvoyer un booléen et renvoyez le résultat à l'aide d'un paramètre out.
Mark Simpson

1
OP veut qu'il soit insensible à la casse, ce n'est pas le cas.
Konrad Morawski

9

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

1
C'est mieux que la réponse acceptée car elle vous permet d'appeler defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)même si vous ne savez pas de quel type d'énumération il s'agit, seulement que l'objet est une énumération.
styfle

1
La vérification préalable avec IsDefinedruinera cependant l'insensibilité à la casse. Contrairement à Parse, IsDefinedn'a aucun ignoreCaseargument et MSDN indique qu'il ne correspond qu'à la casse exacte .
Nyerguds

5

J'ai une exigence spécifique où j'ai besoin d'utiliser enum avec du texte associé à la valeur enum. Par exemple, lorsque j'utilise enum pour spécifier le type d'erreur, il fallait 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());
}

4

J'espère que cela vous sera utile:

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

1
Si vous avez besoin d'une insensibilité à la casse, remplacez simplement return (TValue)Enum.Parse(typeof (TValue), value);parreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos

3

Chose intéressante, apparemment, cela est possible dans d'autres langages (Managed C ++, IL directement).

Citer:

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

Qui sait


2
Les extensions gérées pour C ++ ne prennent PAS en charge les génériques, je pense que vous voulez dire C ++ / CLI.
Ben Voigt

3

C'est mon point de vue. 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


2
Cela n'a pas vraiment de sens. S'il TEnums'agit en fait d'un type Enum mais qu'il texts'agit d'une chaîne vide, vous obtenez un ArgumentExceptiondicton "TEnum doit être un type Enum" même s'il l'est.
Nick

3

Les réponses existantes sont vraies à partir de C # <= 7.2. Cependant, il existe une demande de fonctionnalité en langage C # (liée à une demande de fonctionnalité corefx ) pour permettre ce qui suit;

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

Au moment de la rédaction du présent article, la fonctionnalité était "En discussion" lors des réunions de développement linguistique.

ÉDITER

Selon les informations de nawfal , cela est introduit dans C # 7.3 .


1
Discussion intéressante là-bas, merci. Cependant, rien n'est encore
gravé

1
@johnc, très vrai , mais vaut une note et il est une caractéristique fréquemment posées. Il y a de
fortes


1

J'ai toujours aimé cela (vous pouvez le modifier selon vos besoins):

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

1

J'ai adoré la solution de Christopher Currens utilisant IL, mais pour ceux qui ne veulent pas gérer des affaires délicates d'inclure MSIL dans leur processus de construction, j'ai écrit une fonction similaire en C #.

Veuillez noter cependant que vous ne pouvez pas utiliser de restriction générique, where T : Enumcar 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:

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

1

J'ai encapsulé la solution de Vivek dans une classe utilitaire que vous pouvez réutiliser. Veuillez noter que vous devez toujours définir des contraintes de type "où 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);
        }
    }
}

1

J'ai créé une méthode d'extension, to get integer value from enum regardez 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);
}

c'est l'usage

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

Bien que cela fonctionne probablement, il n'a presque aucun rapport avec la question.
quetzalcoatl

1

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

Avec Fody s Add-In ExtraConstraints.Fody, il existe un moyen très simple, complet avec des outils de construction, pour y parvenir. Ajoutez simplement leurs packages nuget ( Fody, ExtraConstraints.Fody) à votre projet et ajoutez les contraintes comme suit (extrait du fichier Lisez-moi des contraintes supplémentaires):

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

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

et Fody ajoutera l'IL nécessaire pour que la contrainte soit présente. Notez également la fonction 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 voudrez peut-être également prendre note du très intéressant Enums.NET .


1

Ceci est ma mise en œuvre. Fondamentalement, vous pouvez configurer n'importe quel attribut et cela fonctionne.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }

0

Si vous pouvez utiliser la conversion directe par la suite, je suppose que vous pouvez utiliser la System.Enumclasse de base dans votre méthode, si nécessaire. Il vous suffit de remplacer soigneusement les paramètres de type. Ainsi, 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);

l'utilisation de Enum.ToObject()donnerait un résultat plus flexible. Ajouté à cela, vous pouvez faire des comparaisons de chaînes sans respecter la casse, ce qui annulerait la nécessité d'appelerToLower()
DiskJunky

-6

Juste pour être complet, ce qui suit est une solution Java. Je suis certain que la même chose pourrait être faite en C # également. Cela évite d'avoir à spécifier le type n'importe où dans le code - à la place, vous le spécifiez dans les chaînes que vous essayez d'analyser.

Le problème est qu'il n'y a aucun moyen de savoir à quelle énumération la chaîne peut correspondre - la réponse est donc de résoudre ce problème.

Au lieu d'accepter uniquement la valeur de chaîne, acceptez une chaîne qui possède à 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 version ultérieure. Cela rendrait également le XML plus précis, car vous verriez quelque chose comme color = "Color.red" au lieu de simplement color = "red".

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

La méthode renvoie 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;
    }


}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.