Cette fonctionnalité est enfin prise en charge en C # 7.3!
L'extrait de code suivant (à partir des exemples dotnet ) montre comment:
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;
}
Veillez à définir votre version de langue dans votre projet C # sur la version 7.3.
Réponse originale ci-dessous:
Je suis en retard dans le match, mais je l'ai pris comme un défi pour voir comment cela pourrait être fait. Ce n'est pas possible en C # (ou VB.NET, mais faites défiler vers le bas pour F #), mais c'est possible dans MSIL. J'ai écrit cette petite chose ....
// 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
}
}
Ce qui génère une fonction qui serait ressembler à cela, si elle était valide C #:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
Puis avec le code C # suivant:
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
}
Malheureusement, cela signifie que cette partie de votre code est écrite en MSIL au lieu de C #, le seul avantage supplémentaire étant que vous êtes en mesure de contraindre cette méthode System.Enum
. C'est aussi une sorte de déception, car il est compilé dans un assemblage séparé. Cependant, cela ne signifie pas que vous devez le déployer de cette façon.
En supprimant la ligne .assembly MyThing{}
et en invoquant ilasm comme suit:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
vous obtenez un netmodule au lieu d'un assemblage.
Malheureusement, VS2010 (et plus tôt, évidemment) ne prend pas en charge l'ajout de références de netmodule, ce qui signifie que vous devez le laisser dans 2 assemblys distincts lorsque vous déboguez. La seule façon de les ajouter dans le cadre de votre assembly serait d'exécuter vous-même csc.exe à l'aide de l' /addmodule:{files}
argument de ligne de commande. Ce ne serait pas trop douloureux dans un script MSBuild. Bien sûr, si vous êtes courageux ou stupide, vous pouvez exécuter manuellement csc à chaque fois. Et cela devient certainement plus compliqué car plusieurs assemblages doivent y avoir accès.
Donc, cela PEUT être fait en .Net. Vaut-il l'effort supplémentaire? Euh, eh bien, je suppose que je vais vous laisser décider.
Solution F # comme alternative
Crédit supplémentaire: Il s'avère qu'une restriction générique sur enum
est possible dans au moins un autre langage .NET en plus 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
Celui-ci est plus facile à maintenir car il s'agit d'un langage bien connu avec une prise en charge complète de Visual Studio IDE, mais vous avez toujours besoin d'un projet distinct dans votre solution pour cela. Cependant, il produit naturellement un IL considérablement différent (le code est très différent) et il dépend de la FSharp.Core
bibliothèque, qui, comme toute autre bibliothèque externe, doit faire partie de votre distribution.
Voici comment vous pouvez l'utiliser (essentiellement la même que la solution MSIL), et pour montrer qu'il échoue correctement sur des structures autrement synonymes:
// 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);