Crea un metodo generico che limiti T a un Enum


1189

Sto costruendo una funzione per estendere questo Enum.Parseconcetto

  • Consente di analizzare un valore predefinito nel caso in cui non venga trovato un valore Enum
  • Non distingue tra maiuscole e minuscole

Quindi ho scritto quanto segue:

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

Ricevo un vincolo di errore non può essere una classe speciale System.Enum.

Abbastanza giusto, ma esiste una soluzione alternativa per consentire un Enum generico, o dovrò imitare la Parsefunzione e passare un tipo come attributo, che impone il brutto requisito di boxe al tuo codice.

MODIFICA Tutti i suggerimenti seguenti sono stati molto apprezzati, grazie.

Ho optato per (ho lasciato il ciclo per mantenere la distinzione tra maiuscole e minuscole - lo sto usando quando analizzo 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 febbraio 2015) Julien Lebosquain ha recentemente pubblicato una soluzione generica sicura per il tipo applicata dal compilatore in MSIL o F # di seguito, che vale la pena dare un'occhiata e un voto positivo. Rimuoverò questa modifica se la soluzione bolle più in alto nella pagina.


10
Forse dovresti usare ToUpperInvariant () invece di ToLower () ...
Max Galkin,

31
@Shimmy: non appena si passa un tipo di valore al metodo di estensione, si sta lavorando su una copia di esso, quindi non è possibile modificarne lo stato.
Garo Yeriazarian,

4
Sappi che è un vecchio thread, non so se hanno cambiato le cose, ma i metodi di estensione funzionano bene per i tipi di valore, certo che potrebbero non avere sempre lo stesso senso, ma ho usato "Second TimeSpan Seconds statico pubblico (this int x) { return TimeSpan.FromSeconds (x);} "per abilitare la sintassi di" Wait.For (5.Seconds ()) ... "
Jens

6
Comprendi che questo non faceva parte della domanda, ma potresti migliorare la logica del tuo ciclo foreach usando String.Equals con StringComparison.InvariantCultureIgnoreCase
Firestrand

Risposte:


1006

Poiché EnumType implementa l' IConvertibleinterfaccia, un'implementazione migliore dovrebbe essere qualcosa del genere:

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

   //...
}

Ciò consentirà comunque l'implementazione dei tipi di valore IConvertible. Le possibilità sono rare però.


2
I generici sono disponibili da .NET 2.0. Quindi sono disponibili anche in vb 2005.
Vivek,

46
Bene, rendilo ancora più vincolato, quindi, se scegli di seguire questa strada ... usa "class TestClass <T> dove T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde,

106
Un altro suggerimento è quello di definire il tipo generico con l'identificatore TEnum. Pertanto: public TEnum GetEnumFromString <TEnum> (valore stringa) dove TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa

11
Non guadagni molto includendo le altre interfacce perché quasi tutti i tipi di valore integrati implementano tutte quelle interfacce. Ciò è particolarmente vero per i vincoli su un metodo di estensione generico, che è estremamente utile per operare su enum, tranne per il fatto che tali metodi di estensione sono come un virus che infetta tutti i tuoi oggetti. IConvertable almeno lo restringe un po '.
russbishop

2
@SamIam: Quando hai pubblicato, questo thread aveva 6 anni e mezzo e tu avevi ragione, nessun tempo di compilazione per il check-in in nessuna delle risposte. Quindi solo 3 giorni dopo, dopo 6 anni, hai ottenuto il tuo desiderio - vedi il post di Julien Lebosquain in basso.
David I. McIntosh,

663

Questa funzione è finalmente supportata in C # 7.3!

Il frammento seguente (dagli esempi dotnet ) dimostra come:

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

Assicurati di impostare la versione della lingua nel tuo progetto C # alla versione 7.3.


Risposta originale di seguito:

Sono in ritardo al gioco, ma l'ho preso come una sfida per vedere come si potesse fare. Non è possibile in C # (o VB.NET, ma scorrere verso il basso per F #), ma è possibile in MSIL. Ho scritto questa piccola cosa ...

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

Il che genera una funzione che sarebbe simile a questa, se fosse valida C #:

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

Quindi con il seguente codice C #:

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
}

Sfortunatamente, questo significa avere questa parte del tuo codice scritta in MSIL anziché in C #, con l'unico vantaggio aggiunto che puoi limitare questo metodo System.Enum. È anche una specie di peccato, perché viene compilato in un assembly separato. Tuttavia, ciò non significa che devi distribuirlo in questo modo.

Rimuovendo la linea .assembly MyThing{}e invocando ilasm come segue:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

ottieni un netmodule invece di un assembly.

Sfortunatamente, VS2010 (e prima, ovviamente) non supporta l'aggiunta di riferimenti netmodule, il che significa che dovresti lasciarlo in 2 assiemi separati durante il debug. L'unico modo per aggiungerli come parte dell'assembly sarebbe eseguire csc.exe da soli usando l' /addmodule:{files}argomento della riga di comando. Non sarebbe troppo doloroso in uno script di MSBuild. Naturalmente, se sei coraggioso o stupido, puoi eseguire csc da solo ogni volta. E certamente diventa più complicato in quanto più assembly hanno bisogno di accedervi.

Quindi, PU CAN essere fatto in .Net. Vale la pena lo sforzo extra? Beh, immagino che ti lascerò decidere su quello.


F # Soluzione in alternativa

Credito extra: si scopre che una restrizione generica su enumè possibile in almeno un altro linguaggio .NET oltre a 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

Questo è più facile da mantenere poiché è un linguaggio ben noto con supporto IDE di Visual Studio completo, ma è comunque necessario un progetto separato nella soluzione. Tuttavia, produce naturalmente IL notevolmente diverso (il codice è molto diverso) e si basa sulla FSharp.Corelibreria, che, come qualsiasi altra libreria esterna, deve entrare a far parte della tua distribuzione.

Ecco come puoi usarlo (sostanzialmente uguale alla soluzione MSIL) e per dimostrare che fallisce correttamente su strutture altrimenti:

// 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
Sì, molto hardcore. Ho il massimo rispetto per qualcuno che è in grado di programmare in IL e so come sono supportate le funzionalità al livello linguistico superiore, un livello che molti di noi considerano ancora di basso livello in applicazioni, regole aziendali, UI, librerie di componenti, ecc. .
TonyG

13
Quello che mi piacerebbe davvero sapere è perché il team di C # non ha ancora iniziato a consentirlo, poiché è già supportato da MSIL.
MgSam,

25
@MgSam - Da 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: Penso che il motivo è che dal momento che una classe che vincola una Tper System.Enumnon sarebbe in grado di fare tutte le cose con Tche la gente potrebbe aspettarsi, di C # autori capito che possono così vietare del tutto. Ritengo la decisione sfortunata, dal momento che C # aveva semplicemente ignorato qualsiasi gestione speciale dei System.Enumvincoli, sarebbe stato possibile scrivere un HasAnyFlags<T>(this T it, T other)metodo di estensione che era ordini di grandezza più veloci di Enum.HasFlag(Enum)e che controllava i suoi argomenti.
supercat

9
Non credo di aver mai avuto un progetto in cui non sono finito qui. C # 6 è lo zucchero sintattico al 110% e QUESTO non è entrato? Taglia la merda.
Michael Blackburn,

214

C # ≥ 7.3

A partire da C # 7.3 (disponibile con Visual Studio 2017 ≥ v15.7), questo codice è ora completamente valido:

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

C # ≤ 7.2

Puoi avere un vero vincolo enum applicato dal compilatore abusando dell'ereditarietà del vincolo. Il codice seguente specifica contemporaneamente i vincoli a classe structa:

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

Uso:

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

Nota: questo è specificamente indicato nelle specifiche del linguaggio C # 5.0:

Se il parametro di tipo S dipende dal parametro di tipo T, allora: [...] È valido che S abbia il vincolo del tipo di valore e T abbia il vincolo del tipo di riferimento. In effetti ciò limita T ai tipi System.Object, System.ValueType, System.Enum e qualsiasi tipo di interfaccia.


7
@ DavidI.McIntosh EnumClassUtils<System.Enum>è sufficiente per limitare T a qualsiasi System.Enumtipo derivato. structon Parsequindi lo limita ulteriormente a un tipo enum vero. È necessario limitare Enuma un certo punto. Per fare ciò, la tua classe deve essere nidificata. Vedi gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain,

7
Giusto per essere chiari, il mio commento "non piacevole" non era un commento sulla tua soluzione - è davvero un bel trucco. Solo "non piacevole" che la SM ci costringe a usare un tale hack contorto.
David I. McIntosh,

2
C'è un modo di lavorare per essere utilizzabile anche per i metodi di estensione?
Mord Zuber,

3
Cosa where TClass : classguadagna il vincolo qui?
Tsemer,

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

30

modificare

Julien Lebosquain risponde in modo superbo alla domanda . Vorrei anche estendere la sua risposta con ignoreCase, defaultValuee gli argomenti opzionali, mentre l'aggiunta di TryParsee 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
}

Esempi di utilizzo:

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

Vecchio

I miei vecchi miglioramenti sulla risposta di Vivek usando i commenti e i "nuovi" sviluppi:

  • usare TEnumper chiarezza per gli utenti
  • aggiungere ulteriori vincoli di interfaccia per un ulteriore controllo dei vincoli
  • lasciare che TryParsela maniglia ignoreCasecon il parametro esistente (introdotto nel VS2010 / .Net 4)
  • utilizzare facoltativamente il defaultvalore generico (introdotto in VS2005 / .Net 2)
  • utilizzare argomenti opzionali (introdotti in VS2010 / .Net 4) con valori predefiniti, per defaultValueeignoreCase

con il risultato di:

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

È possibile definire un costruttore statico per la classe che controllerà che il tipo T sia un enum e genererà un'eccezione in caso contrario. Questo è il metodo menzionato da Jeffery Richter nel suo libro CLR via C #.

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

Quindi nel metodo di analisi, puoi semplicemente usare Enum.Parse (typeof (T), input, true) per convertire da stringa in enum. L'ultimo vero parametro serve per ignorare il caso dell'input.


1
Questa è una buona opzione per le classi generiche, ma ovviamente non aiuta per i metodi generici.
McGarnagle,

Inoltre, anche questo non viene applicato al momento della compilazione, sapresti che hai fornito un non solo Enum Tquando il costruttore è stato eseguito. Anche se questo è molto più bello che aspettare un costruttore di istanze.
giovedì

15

Va anche considerato che, dal momento che il rilascio di C # 7.3 utilizzando i vincoli Enum è supportato e pronto all'uso senza dover fare ulteriori controlli e cose.

Quindi andando avanti e dato che hai cambiato la versione in lingua del tuo progetto in C # 7.3 il seguente codice funzionerà perfettamente:

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

Nel caso in cui non sai come cambiare la versione della lingua in C # 7.3, vedi la seguente schermata: inserisci qui la descrizione dell'immagine

EDIT 1 - Versione richiesta di Visual Studio e considerando ReSharper

Perché Visual Studio riconosca la nuova sintassi è necessario almeno la versione 15.7. Puoi trovare quello menzionato anche nelle note di rilascio di Microsoft, vedi le Note di rilascio di Visual Studio 2017 15.7 . Grazie @MohamedElshawaf per aver sottolineato questa domanda valida.

Si noti inoltre che nel mio caso ReSharper 2018.1 al momento della stesura di questo EDIT non supporta ancora C # 7.3. Avendo ReSharper attivato, evidenzia il vincolo Enum come errore che mi dice Impossibile utilizzare 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' come vincolo del parametro di tipo . ReSharper suggerisce una soluzione rapida per rimuovere il vincolo 'Enum' del parametro tipo T del metodo

Tuttavia, se ReSharper viene temporaneamente disattivato in Strumenti -> Opzioni -> ReSharper Ultimate -> Generale , vedrai che la sintassi è perfettamente corretta dato che usi VS 15.7 o versioni successive e C # 7.3 o versioni successive.


1
Quale versione VS stai usando?
mshwf,

1
@MohamedElshawaf Credo che sia la versione 15.7 che contiene il supporto per C # 7.3
Patrick Roberts,

1
Penso che sia meglio scrivere where T : struct, Enum, per evitare di passare System.Enumse stesso come parametro di tipo.
Mariusz Pawelski

Come @MariuszPawelski scrivo struct, Enum. La mia logica è spiegata nella risposta e nei commenti qui .
Stephen Kennedy,

Le informazioni su ReSharper mi hanno davvero aiutato. Nota l'ultima versione di anteprima supporta questa funzione.
DalSoft,

11

Ho modificato il campione da dimarzionista. Questa versione funziona solo con Enum e non consente alle strutture di passare.

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
Non restituirei il valore predefinito in caso di errore; Lascerei propagare l'eccezione (proprio come fa con Enum.Parse). Invece, usa TryParse restituendo un valore bool e restituisci il risultato usando un parametro out.
Mark Simpson,

1
OP vuole che non faccia distinzione tra maiuscole e minuscole, questo non lo è.
Konrad Morawski,

9

Ho provato a migliorare un po 'il codice:

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
È meglio della risposta accettata perché ti consente di chiamare defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)anche se non sai che tipo di enum è, solo che l'oggetto è un enum.
Styfle

1
Il controllo anticipato con, tuttavia IsDefined, rovinerà l'insensibilità del caso. Diversamente Parse, IsDefinednon ha ignoreCaseargomenti e MSDN afferma che corrisponde solo al caso esatto .
Nyerguds,

5

Ho un requisito specifico in cui ho richiesto di usare enum con testo associato al valore enum. Ad esempio, quando uso enum per specificare il tipo di errore, è necessario descrivere i dettagli dell'errore.

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

Spero che questo sia 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
Se hai bisogno di insensibilità al caso, sostituisci semplicemente return (TValue)Enum.Parse(typeof (TValue), value);conreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos il

3

È interessante notare che, a quanto pare, ciò è possibile in altri linguaggi (Managed C ++, IL direttamente).

Per citare:

... Entrambi i vincoli producono effettivamente IL valido e possono anche essere consumati da C # se scritti in un'altra lingua (puoi dichiararli in C ++ gestito o in IL).

Chissà


2
Le estensioni gestite per C ++ non hanno QUALSIASI supporto per generici, penso che intendi C ++ / CLI.
Ben Voigt,

3

Questa è la mia opinione. Combinato dalle risposte e 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));
    }
}

Fonte MSDN


2
Questo non ha davvero senso. Se in TEnumrealtà è un tipo Enum ma textè una stringa vuota, viene visualizzato il ArgumentExceptionmessaggio "TEnum deve essere un tipo Enum" anche se lo è.
Nick,

3

Le risposte esistenti sono vere a partire da C # <= 7.2. Tuttavia, esiste una richiesta di funzionalità in linguaggio C # (legata a una richiesta di funzionalità corefx ) per consentire quanto segue;

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

Al momento in cui scrivo, la funzione è "In discussione" durante le riunioni di sviluppo linguistico.

MODIFICARE

Secondo le informazioni di nawfal , questo è stato introdotto in C # 7.3 .


1
Discussione interessante lì, grazie. Tuttavia, nulla (ancora)
johnc

1
@johnc, molto vero ma merita una nota ed è una funzione spesso richiesta. Ci sono
buone

1
Questo è in arrivo in C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/… . :)
nawfal,

1

Mi è sempre piaciuto (è possibile modificarlo come appropriato):

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

Ho adorato la soluzione di Christopher Currens utilizzando IL, ma per coloro che non vogliono affrontare affari complicati di includere MSIL nel loro processo di creazione, ho scritto funzioni simili in C #.

Si noti tuttavia che non è possibile utilizzare restrizioni generiche come where T : Enumperché Enum è di tipo speciale. Quindi devo verificare se il tipo generico dato è davvero enum.

La mia funzione è:

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

Ho incapsulato la soluzione di Vivek in una classe di utilità che puoi riutilizzare. Si noti che è comunque necessario definire i vincoli di tipo "dove T: struct, IConvertible" sul proprio tipo.

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

Ho creato un metodo di estensione to get integer value from enum dare un'occhiata all'implementazione del metodo

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

questo è uso

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

Anche se probabilmente funziona, non ha quasi alcuna rilevanza per la domanda.
quetzalcoatl

1

Come affermato in altre risposte prima; mentre questo non può essere espresso in codice sorgente, può effettivamente essere fatto a livello di IL. La risposta di @Christopher Currens mostra come l'IL faccia questo.

Con gli extraConstraints aggiuntivi di Fody . Per ottenere questo, c'è un modo molto semplice, completo di strumenti di costruzione. Aggiungi i loro pacchetti nuget ( Fody, ExtraConstraints.Fody) al tuo progetto e aggiungi i vincoli come segue (Estratto dal file Leggimi di ExtraConstraints):

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

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

e Fody aggiungerà l'IL necessario per la presenza del vincolo. Si noti inoltre la funzionalità aggiuntiva dei delegati vincolanti:

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

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

Per quanto riguarda Enums, potresti anche voler prendere nota dell'interessantissimo Enums.NET .


1

Questa è la mia implementazione. Fondamentalmente, è possibile impostare qualsiasi attributo e funziona.

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

Se in seguito va bene usare il casting diretto, immagino che tu possa usare la System.Enumclasse base nel tuo metodo, ovunque sia necessario. Devi solo sostituire attentamente i parametri del tipo. Quindi l'implementazione del metodo sarebbe come:

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

Quindi puoi usarlo come:

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

l'uso di Enum.ToObject()produrrebbe un risultato più flessibile. Aggiunto a ciò, potresti fare i confronti di stringhe senza distinzione tra maiuscole e minuscole che negherebbero la necessità di chiamareToLower()
DiskJunky

-6

Solo per completezza, la seguente è una soluzione Java. Sono certo che lo stesso potrebbe essere fatto anche in C #. Evita di dover specificare il tipo in qualsiasi punto del codice, invece lo si specifica nelle stringhe che si sta tentando di analizzare.

Il problema è che non esiste alcun modo per sapere a quale enumerazione potrebbe corrispondere la stringa, quindi la risposta è risolvere il problema.

Invece di accettare solo il valore di stringa, accetta una stringa che abbia sia l'enumerazione che il valore nel formato "enumeration.value". Il codice di lavoro è inferiore: richiede Java 1.8 o successivo. Ciò renderebbe anche l'XML più preciso come in te vedresti qualcosa come color = "Color.red" anziché solo color = "rosso".

Si chiamerebbe il metodo acceptEnumeratedValue () con una stringa contenente il nome del valore punto nome enum.

Il metodo restituisce il valore enumerato formale.

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


}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.