Domanda

Sto costruendo una funzione per estendere il file Enum.Parse concetto che

  • Consente l'analisi di un valore predefinito nel caso in cui non venga trovato un valore Enum
  • Non fa distinzione 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 errore Il vincolo non può essere una classe speciale System.Enum.

Abbastanza giusto, ma esiste una soluzione alternativa per consentire un enum generico o dovrò imitare il file Parse funzione e passa un tipo come attributo, che impone il brutto requisito di boxing al tuo codice.

MODIFICARE Tutti i suggerimenti riportati di seguito sono stati molto apprezzati, grazie.

Mi sono sistemato (ho lasciato il ciclo per mantenere l'insensibilità alle maiuscole: lo sto usando durante l'analisi 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;
    }
}

MODIFICARE: (16 febbraio 2015) Julien Lebosquain ha recentemente postato una soluzione generica indipendente dai tipi 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.

È stato utile?

Soluzione

Da Enum Tipo attrezzi IConvertible interfaccia, 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 il passaggio dell'implementazione dei tipi di valore IConvertible.Le possibilità sono rare però.

Altri suggerimenti

Questa funzionalità è finalmente supportata in C# 7.3!

Il seguente frammento (da i campioni 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# sulla versione 7.3.


Risposta originale di seguito:

Sono in ritardo per il gioco, ma l'ho presa come una sfida per vedere come si poteva fare.Non è possibile in C# (o VB.NET, ma scorri verso il basso per F#), ma è possibile nel 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 that volevo assomiglia a questo, se fosse C# valido:

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 codice scritta in MSIL invece che in C#, con l'unico vantaggio aggiuntivo che puoi limitare questo metodo System.Enum.È anche un po' una seccatura 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 versioni precedenti, ovviamente) non supporta l'aggiunta di riferimenti a netmodule, il che significa che dovresti lasciarlo in 2 assembly separati durante il debug.L'unico modo per aggiungerli come parte del tuo assembly sarebbe eseguire tu stesso csc.exe utilizzando il file /addmodule:{files} argomento della riga di comando.Non lo sarebbe pure doloroso in uno script MSBuild.Naturalmente, se sei coraggioso o stupido, puoi eseguire manualmente csc ogni volta.E diventa certamente più complicato poiché più gruppi devono accedervi.

Quindi, PUO 'essere fatto in .Net.Vale la pena fare uno sforzo extra?Uhm, beh, immagino che ti lascerò decidere su quello.


Soluzione F# come 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ù semplice da gestire poiché è un linguaggio noto con supporto completo dell'IDE di Visual Studio, ma è comunque necessario un progetto separato nella soluzione.Tuttavia, produce naturalmente IL notevolmente diverso (il code È molto diverso) e si basa su FSharp.Core libreria, che, proprio come qualsiasi altra libreria esterna, deve diventare parte della tua distribuzione.

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

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

Do# ≥ 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

È possibile avere un vero vincolo di enumerazione imposto dal compilatore abusando dell'ereditarietà dei vincoli.Il codice seguente specifica sia a class e un struct vincoli allo stesso tempo:

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

Utilizzo:

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 per s avere il vincolo del tipo di valore e T per avere il vincolo del tipo di riferimento.In effetti questo limita T ai tipi System.Object, System.ValueType, System.enum e qualsiasi tipo di interfaccia.

Modificare

Alla domanda ora è stata data una risposta superba Julien Lebosquain.Vorrei anche estendere la sua risposta con ignoreCase, defaultValue e argomenti facoltativi, durante l'aggiunta TryParse E 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 sono attivi La risposta di Vivek utilizzando i commenti e i "nuovi" sviluppi:

  • utilizzo TEnum per chiarezza per gli utenti
  • aggiungere più vincoli di interfaccia per un ulteriore controllo dei vincoli
  • permettere TryParse maniglia ignoreCase con il parametro esistente (introdotto in VS2010/.NET 4)
  • facoltativamente utilizzare il generico default valore (introdotto in VS2005/.Net 2)
  • utilizzo argomenti facoltativi(introdotto in VS2010/.Net 4) con valori predefiniti, per defaultValue E ignoreCase

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

È possibile definire un costruttore statico per la classe che controllerà che il tipo T sia un'enumerazione e lancerà 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 parse, puoi semplicemente usare Enum.Parse(typeof(T), input, true) per convertire da stringa a enum.L'ultimo parametro true serve per ignorare le maiuscole e minuscole dell'input.

Va inoltre considerato che dal rilascio di C# 7.3 l'utilizzo dei vincoli Enum è supportato immediatamente senza dover eseguire ulteriori controlli e cose.

Quindi andando avanti e dato che hai cambiato la versione linguistica 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, guarda il seguente screenshot:enter image description here

EDIT 1: versione di Visual Studio richiesta e considerando ReSharper

Affinché Visual Studio riconosca la nuova sintassi è necessaria almeno la versione 15.7.Puoi trovarlo menzionato anche nelle note di rilascio di Microsoft, vedi Note sulla versione di Visual Studio 2017 15.7.Grazie @MohamedElshawaf per aver segnalato 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 attivato ReSharper, evidenzia il vincolo Enum come un errore che mi dice Impossibile utilizzare "System.Array", "System.Delegate", "System.Enum", "System.ValueType", "object" come vincolo del parametro di tipo.ReSharper suggerisce come soluzione rapida a Rimuovere il vincolo 'Enum' del parametro di tipo T del metodo

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

Ho modificato il campione con dimarzionista.Questa versione funzionerà solo con Enums e non consentirà il passaggio delle strutture.

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

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

Ho dei requisiti specifici in cui devo utilizzare l'enumerazione con il testo associato al valore enum.Ad esempio, quando utilizzo 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());
}

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

È interessante notare che a quanto pare lo è possibile in altre lingue (Gestito C++, IL direttamente).

Per citare:

...Entrambi i vincoli producono effettivamente IL valido e possono anche essere utilizzati da C# se scritti in un altro linguaggio (è possibile dichiarare tali vincoli in C++ gestito o in IL).

Chi lo sa

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

Origine MSDN

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

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

Al momento in cui scrivo, la rubrica è "In discussione" ai Language Development Meetings.

MODIFICARE

Come da nawfalinformazioni, questo verrà introdotto in C# 7.3.

Mi è sempre piaciuto questo (potresti modificare a seconda dei casi):

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

Ho adorato la soluzione di Christopher Currens che utilizza IL, ma per coloro che non vogliono affrontare il complicato compito di includere MSIL nel processo di creazione, ho scritto una funzione simile in C#.

Tieni presente, tuttavia, che non puoi utilizzare restrizioni generiche come where T : Enum perché Enum è un tipo speciale.Pertanto devo verificare se il tipo generico specificato è 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;
}

Ho incapsulato la soluzione di Vivek in una classe di utilità che puoi riutilizzare.Tieni presente che dovresti comunque definire i vincoli di tipo "dove T :struct, IConvertible" sul tuo 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);
        }
    }
}

Ho creato un metodo di estensione to get integer value from enum dai 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 è l'uso

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

Come affermato in altre risposte prima;sebbene ciò non possa essere espresso nel codice sorgente, in realtà può essere fatto a livello IL.@Christopher Currens risposta mostra come fa l'IL a questo.

Con Fodys Componente aggiuntivo ExtraConstraints.Fody esiste un modo molto semplice, completo di strumenti di costruzione, per raggiungere questo obiettivo.Basta aggiungere 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 affinché il vincolo sia presente.Si noti inoltre la funzionalità aggiuntiva di vincolo dei delegati:

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

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

Per quanto riguarda Enums, potresti anche prendere nota di ciò che è molto interessante Enums.NET.

Se va bene usare il casting diretto in seguito, immagino che tu possa usare il file System.Enum classe base nel tuo metodo, ove 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);

Solo per completezza, quella che segue è 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: lo specifichi invece nelle stringhe che stai tentando di analizzare.

Il problema è che non c'è modo di sapere a quale enumerazione String potrebbe corrispondere, quindi la risposta è risolvere quel problema.

Invece di accettare solo il valore stringa, accetta una String che abbia sia l'enumerazione che il valore nel formato "enumeration.value".Il codice funzionante è riportato di seguito: richiede Java 1.8 o successivo.Ciò renderebbe anche l'XML più preciso poiché vedresti qualcosa come color="Color.red" invece di solo color="red".

Dovresti chiamare il metodo AcceptEnumeratedValue() con una stringa contenente il nome del valore punto del 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;
    }


}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top