¡Esta característica finalmente es compatible con C # 7.3!
El siguiente fragmento (de las muestras de dotnet ) demuestra cómo:
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;
}
Asegúrese de configurar su versión de idioma en su proyecto C # a la versión 7.3.
Respuesta original a continuación:
Llego tarde al juego, pero lo tomé como un desafío para ver cómo podría hacerse. No es posible en C # (o VB.NET, pero desplácese hacia abajo para F #), pero es posible en MSIL. Escribí esta pequeña 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
}
}
Lo que genera una función que se vería así, si fuera válido C #:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
Luego con el siguiente código 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
}
Desafortunadamente, esto significa tener esta parte de su código escrita en MSIL en lugar de C #, con el único beneficio adicional de que puede restringir este método System.Enum
. También es una especie de fastidio, porque se compila en un ensamblaje separado. Sin embargo, no significa que tenga que implementarlo de esa manera.
Al eliminar la línea .assembly MyThing{}
e invocar ilasm de la siguiente manera:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
obtienes un netmodule en lugar de un ensamblaje.
Desafortunadamente, VS2010 (y anterior, obviamente) no admite agregar referencias de netmodule, lo que significa que tendría que dejarlo en 2 ensamblajes separados cuando esté depurando. La única forma en que puede agregarlos como parte de su ensamblado sería ejecutar csc.exe usted mismo utilizando el /addmodule:{files}
argumento de la línea de comandos. No sería demasiado doloroso en un script de MSBuild. Por supuesto, si eres valiente o estúpido, puedes ejecutar csc tú mismo manualmente cada vez. Y ciertamente se vuelve más complicado ya que múltiples ensambles necesitan acceso a él.
Entonces, PUEDE hacerse en .Net. ¿Vale la pena el esfuerzo extra? Um, bueno, supongo que te dejaré decidir sobre eso.
Solución F # como alternativa
Crédito adicional: Resulta que enum
es posible una restricción genérica en al menos otro lenguaje .NET además 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
Este es más fácil de mantener ya que es un lenguaje bien conocido con soporte completo de Visual Studio IDE, pero aún necesita un proyecto separado en su solución para ello. Sin embargo, naturalmente produce una IL considerablemente diferente (el código es muy diferente) y se basa en la FSharp.Core
biblioteca, que, al igual que cualquier otra biblioteca externa, debe formar parte de su distribución.
Así es como puede usarlo (básicamente lo mismo que la solución MSIL), y para mostrar que falla correctamente en otras estructuras:
// 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);