Este recurso é finalmente suportado no C # 7.3!
O seguinte snippet (das amostras dotnet ) demonstra como:
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;
}
Certifique-se de definir sua versão do idioma no seu projeto C # para a versão 7.3.
Resposta original abaixo:
Estou atrasado para o jogo, mas aceitei como um desafio ver como isso poderia ser feito. Não é possível em C # (ou VB.NET, mas role para baixo para F #), mas é possível em MSIL. Eu escrevi essa coisinha ....
// 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
}
}
O que gera uma função que se pareceria com isso, se fosse C # válido:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
Em seguida, com o seguinte 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
}
Infelizmente, isso significa ter essa parte do seu código escrita em MSIL em vez de C #, com o único benefício adicional de poder restringir esse método System.Enum
. Também é meio chato, porque é compilado em uma montagem separada. No entanto, isso não significa que você deve implantá-lo dessa maneira.
Removendo a linha .assembly MyThing{}
e chamando o ilasm da seguinte maneira:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
você obtém um módulo de rede em vez de um assembly.
Infelizmente, o VS2010 (e anteriormente, obviamente) não oferece suporte à adição de referências de módulo de rede, o que significa que você teria que deixá-lo em dois assemblies separados quando estiver depurando. A única maneira de adicioná-los como parte do seu assembly seria executar o csc.exe você mesmo, usando o /addmodule:{files}
argumento da linha de comando. Não seria muito doloroso em um script MSBuild. Obviamente, se você é corajoso ou estúpido, pode executar o CSC manualmente manualmente toda vez. E certamente fica mais complicado, pois vários conjuntos precisam acessar.
Então, isso pode ser feito em .Net. Vale a pena o esforço extra? Bem, acho que vou deixar você decidir sobre isso.
Solução F # como alternativa
Crédito extra: Acontece que uma restrição genérica enum
é possível em pelo menos uma outra linguagem .NET além 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
Essa é mais fácil de manter, pois é uma linguagem conhecida com suporte completo ao IDE do Visual Studio, mas você ainda precisa de um projeto separado em sua solução. No entanto, produz naturalmente uma IL consideravelmente diferente (o código é muito diferente) e depende daFSharp.Core
biblioteca, que, como qualquer outra biblioteca externa, precisa se tornar parte da sua distribuição.
Veja como você pode usá-lo (basicamente o mesmo que a solução MSIL) e para mostrar que ele falha corretamente em estruturas sinônimas:
// 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);