Cette fonctionnalité est enfin prise en charge dans C# 7.3 !
L'extrait suivant (tiré de les échantillons 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 à ce que la version du langage dans votre projet C# soit la version 7.3.
Réponse originale ci-dessous :
Je suis en retard dans le jeu, mais j'ai pris cela comme un défi pour voir comment cela pouvait être fait. Ce n'est pas possible en C# (ou VB.NET, mais faites défiler vers le bas pour F#), mais est possible dans MSIL. J'ai écrit ce petit....thing
// 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 ressemblerait à ceci, s'il s'agissait d'un code C# valide :
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 plutôt qu'en C#, le seul avantage étant que vous pouvez contraindre cette méthode en System.Enum
. C'est aussi un peu dommage, parce qu'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 les versions antérieures, évidemment) ne prend pas en charge l'ajout de références netmodule, ce qui signifie que vous devez le laisser dans deux assemblages distincts lorsque vous déboguez. La seule façon de les ajouter à votre assemblage serait d'exécuter csc.exe vous-même en utilisant la commande /addmodule:{files}
en ligne de commande. Il ne serait pas aussi douloureux dans un script de MSBuild. Bien sûr, si vous êtes courageux ou stupide, vous pouvez exécuter csc vous-même manuellement à chaque fois. Et cela devient certainement plus compliqué lorsque plusieurs assemblages ont besoin d'y accéder.
Il est donc possible de le faire en .Net. Cela vaut-il la peine de faire un effort supplémentaire ? Hum, eh bien, je suppose que je vous laisse décider de cela.
Solution F# comme alternative
Crédit supplémentaire : Il s'avère qu'une restriction générique sur les enum
est possible dans au moins un autre langage .NET que 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
Ce dernier est plus facile à maintenir car il s'agit d'un langage bien connu avec un support complet de l'IDE Visual Studio, mais vous avez toujours besoin d'un projet séparé dans votre solution pour ce langage. Cependant, il produit naturellement un IL considérablement différent (le code es très différente) et elle s'appuie sur la FSharp.Core
qui, comme toute autre bibliothèque externe, doit être intégrée à votre distribution.
Voici comment vous pouvez l'utiliser (en gros, la même chose que la solution MSIL), et pour montrer qu'elle échoue correctement sur des structs 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);
12 votes
Peut-être que vous devrait utiliser ToUpperInvariant() au lieu de ToLower()...
0 votes
Pourquoi les méthodes d'extension sont-elles réservées aux types de référence ?
34 votes
@Shimmy : Dès que vous passez un type de valeur à la méthode d'extension, vous travaillez sur une copie de ce type de valeur, et vous ne pouvez donc pas modifier son état.
5 votes
Je sais que c'est un vieux fil, je ne sais pas s'ils ont changé les choses, mais les méthodes d'extension fonctionnent bien pour les types de valeur, bien qu'elles n'aient pas toujours autant de sens, mais j'ai utilisé "public static TimeSpan Seconds(this int x) { return TimeSpan.FromSeconds(x) ; }" pour permettre la syntaxe de "Wait.For(5.Seconds())..."
7 votes
Je réalise que cela ne faisait pas partie de la question, mais vous pourriez améliorer la logique de votre boucle foreach en utilisant String.Equals avec StringComparison.InvariantCultureIgnoreCase.
2 votes
Duplication possible de Quelqu'un connaît-il une bonne solution pour pallier l'absence d'une contrainte générique de type enum ?
3 votes
Pourquoi utiliser cette
foreach
-Boucle ?Enum.Parse
contient unignoreCase
-(je suppose que depuis .Net 2.0 ).0 votes
Je suis allé de l'avant et j'ai ajouté un répondre avec le système existant
ignoreCase
-et un paramètre génériquedefault
comme arguments optionnels, et quelques autres améliorations suggérées par d'autres.1 votes
Il convient de noter que Enum.Parse peut traiter un enum avec l'attribut [Flags], à condition que les valeurs de la chaîne soient séparées par des virgules.
0 votes
Veuillez publier votre solution dans un message de réponse. N'incluez pas de réponses dans les questions.
0 votes
Comment la chaîne de valeurs est-elle créée ? Si elle a été créée à l'aide de la fonction
Enum.ToString()
et le type Enum est marqué par l'attribut[Flags]
l'attributdefaultValue
sera toujours renvoyée par la méthode avecvalue = (enum.type1 | enum.type2).ToString() == type1, type2
. Boîtier d'angle.1 votes
Consultez cette réponse écrite par moi stackoverflow.com/a/38410351/4009642
0 votes
@bigworld12 L'exigence initiale qui m'a poussé à faire cela est perdue depuis longtemps dans la nuit des temps, mais la solution est néanmoins très complète :)
1 votes
La mise en œuvre de cette fonctionnalité est prévue pour C# 7 ! Votez pour ! github.com/dotnet/roslyn/issues/262
3 votes
Très vieux sujet, mais il y a eu une énorme amélioration depuis C# 7.3. L'utilisation des contraintes Enum est désormais entièrement supportée. Voir ma réponse plus longue tout en bas.
1 votes
NB : cette fonctionnalité est prise en charge à partir de la version 7.3 de C#.
1 votes
Bien que cela fonctionne en c# 7.3 et plus, il est quelque peu stupide que le fait d'avoir une propriété nullable d'un type
T
avec la contraintewhere T: Enum
le compilateur pleure encore comme un bébé que T doit être d'un type non annulable Il en résulte que dans toute la hiérarchie des génériques transmis, il faut spécifierstruct
également.