with - t where c#




Crea un metodo generico che vincola T a un Enum (14)

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

È possibile avere un vincolo enum forzato del compilatore reale abusando dell'ereditarietà dei vincoli. Il seguente codice specifica contemporaneamente una class e un vincolo di 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>
{
}

Uso:

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

Nota: questo è espressamente 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 di avere il vincolo del tipo di riferimento. In pratica ciò limita T ai tipi System.Object, System.ValueType, System.Enum e qualsiasi tipo di interfaccia.

Sto costruendo una funzione per estendere il concetto Enum.Parse

  • Consente di analizzare un valore predefinito nel caso in cui non venga trovato un valore Enum
  • Il case è insensibile

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 che un vincolo di errore non può essere una classe speciale System.Enum .

Giusto, ma c'è una soluzione alternativa per consentire un Enum generico, o dovrò dover simulare la funzione Parse e passare un tipo come attributo, che costringe il brutto requisito di boxing al codice.

EDIT Tutti i suggerimenti sottostanti sono stati molto apprezzati, grazie.

Si sono stabiliti (ho lasciato il ciclo per mantenere l'insensibilità del case - lo sto usando durante l'analisi di 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 pubblicato recentemente una soluzione generica di tipo safe-safe implementata nel compilatore in MSIL o F # qui sotto, che vale la pena dare un'occhiata e un upvote. Rimuoverò questa modifica se la soluzione bolle ulteriormente sulla pagina.


Questa funzione è finalmente supportata in C # 7.3!

Il seguente frammento (dai campioni dotnet ) dimostra che usa:

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 qui sotto:

Sono in ritardo rispetto al gioco, ma ho considerato la sfida come vedere come potrebbe essere fatto. 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
  }
} 

Che genera una funzione simile a questa, 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 che questa parte del tuo codice è scritta in MSIL anziché in C #, con l'unico vantaggio che sei in grado di vincolare questo metodo con System.Enum . È anche un po 'faticoso, perché viene compilato in un assembly separato. Tuttavia, ciò non significa che devi implementarlo in questo modo.

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

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

ottieni un modulo net invece di un assembly.

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

Quindi, può essere fatto in. Net. Vale la pena lo sforzo extra? Um, beh, immagino che ti lascerò decidere su quello.

F # Soluzione 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ù facile da gestire poiché è una lingua ben conosciuta con il completo supporto IDE di Visual Studio, ma per la soluzione è necessario un progetto separato. Tuttavia, produce in modo molto diverso IL (il codice è molto diverso) e si basa sulla libreria FSharp.Core , che, come qualsiasi altra libreria esterna, deve diventare parte della distribuzione.

Ecco come puoi usarlo (fondamentalmente lo stesso della soluzione MSIL), e per mostrare 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);

È possibile definire un costruttore statico per la classe che verificherà che il tipo T è un enum e genera un'eccezione se non lo è. 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 string a enum. L'ultimo vero parametro è per ignorare il caso dell'input.


Come affermato in altre risposte prima; mentre questo non può essere espresso nel codice sorgente, può effettivamente essere fatto su IL Level. @Christopher La answer Currens mostra come l'IL fa a quello.

Con Fody add-in di Fody . Per ExtraConstraints.Fody questo ExtraConstraints.Fody c'è un modo molto semplice, completo di strumenti di costruzione. Basta aggiungere i pacchetti nuget ( Fody , ExtraConstraints.Fody ) al progetto e aggiungere 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 perché il vincolo sia presente. Nota anche la funzione aggiuntiva dei delegati vincolanti:

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

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

Per quanto riguarda Enum, potresti anche voler prendere nota del molto interessante Enums.NET .


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 è l'uso

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

Ho dei requisiti specifici in cui ho richiesto di usare enum con testo associato al valore enum. Ad esempio quando uso enum per specificare il tipo di errore richiesto per 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());
}

Ho modificato il campione con il dimarzionista. Questa versione funzionerà solo con Enums e non lascerà passare le 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 il codice un po ':

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

Mi è sempre piaciuto (potresti modificare 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>();
}

Poiché Enum Type implementa l'interfaccia IConvertible , una migliore implementazione 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 di tipi di valore che implementano IConvertible . Le probabilità sono rare però.


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

Va anche considerato che dal momento che il rilascio di C # 7.3 usando i vincoli Enum è supportato immediatamente senza dover fare controlli e cose aggiuntive.

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 si sappia come cambiare la versione della lingua in C # 7.3, vedere lo screenshot seguente:

MODIFICA 1 - Versione di Visual Studio richiesta e considerando ReSharper

Per consentire a Visual Studio di riconoscere la nuova sintassi, è necessaria almeno la versione 15.7. Puoi trovare quello menzionato anche nelle note di rilascio di Microsoft, vedi Note di rilascio di Visual Studio 2017 15.7 . Grazie a @MohamedElshawaf per aver indicato questa domanda valida.

Si noti inoltre che nel mio caso ReSharper 2018.1 al momento di scrivere questo EDIT non supporta ancora C # 7.3. Avendo ReSharper attivato mette in evidenza il vincolo Enum come errore che mi dice Non puoi usare 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' come tipo parameter constraint . ReSharper suggerisce come una soluzione rapida per rimuovere il vincolo "Enum" del parametro di tipo T del metodo

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


In Java, dovresti usare ...

    SomeClass<T extends enum> {
}

Piuttosto semplice, quello.


Se è corretto utilizzare il cast diretto, suppongo che tu possa usare la System.Enumclasse base nel tuo metodo, ovunque sia necessario. Hai solo bisogno di sostituire con attenzione 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);






generic-constraints