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.Core
libreria, 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);