Diese Funktion wird endlich in C# 7.3 unterstützt!
Der folgende Ausschnitt (aus die Dotnet-Beispiele ) zeigt, wie:
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;
}
Stellen Sie sicher, dass die Sprachversion in Ihrem C#-Projekt auf Version 7.3 eingestellt ist.
Originalantwort unten:
Ich bin zwar spät dran, aber ich habe es als Herausforderung angenommen, um zu sehen, wie es gemacht werden kann. Es ist nicht möglich in C# (oder VB.NET, aber scrollen Sie nach unten für F#), aber ist möglich in MSIL. Ich habe dieses kleine .... geschrieben.
// 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
}
}
Das erzeugt eine Funktion, die würde so aussehen, wenn es gültiges C# wäre:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
Dann mit dem folgenden C#-Code:
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
}
Leider bedeutet dies, dass Sie diesen Teil Ihres Codes in MSIL statt in C# schreiben müssen, wobei der einzige zusätzliche Vorteil darin besteht, dass Sie diese Methode durch System.Enum
. Es ist auch eine Art von Schade, weil es in eine separate Baugruppe kompiliert wird. Das bedeutet jedoch nicht, dass Sie es auf diese Weise bereitstellen müssen.
Durch Entfernen der Zeile .assembly MyThing{}
und ruft ilasm wie folgt auf:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
erhalten Sie ein Netzmodul anstelle einer Baugruppe.
Leider unterstützt VS2010 (und früher, offensichtlich) nicht das Hinzufügen von Netmodule Referenzen, was bedeutet, dass Sie es in 2 separaten Assemblys verlassen müssen, wenn Sie debuggen. Der einzige Weg, wie Sie sie als Teil Ihrer Assembly hinzufügen können, wäre, csc.exe selbst auszuführen, indem Sie die /addmodule:{files}
Befehlszeilenargument. Es wäre nicht zu schmerzhaft in einem MSBuild-Skript. Wenn Sie mutig oder dumm sind, können Sie csc natürlich auch jedes Mal manuell ausführen. Und es wird sicherlich komplizierter, wenn mehrere Assemblies darauf zugreifen müssen.
Es KANN also in .Net gemacht werden. Ist es den zusätzlichen Aufwand wert? Ähm, nun, ich denke, das müssen Sie selbst entscheiden.
F# Lösung als Alternative
Extra Credit: Es hat sich herausgestellt, dass eine generische Beschränkung auf enum
ist neben MSIL auch in mindestens einer anderen .NET-Sprache möglich: 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
Diese ist einfacher zu pflegen, da es sich um eine bekannte Sprache mit vollständiger Unterstützung der Visual Studio IDE handelt, aber Sie benötigen trotzdem ein separates Projekt in Ihrer Lösung dafür. Allerdings erzeugt sie natürlich deutlich anderes IL (der Code es sehr unterschiedlich) und stützt sich auf die FSharp.Core
Bibliothek, die, wie jede andere externe Bibliothek, Teil Ihrer Distribution werden muss.
Hier ist, wie Sie es verwenden können (im Grunde das gleiche wie die MSIL-Lösung), und zu zeigen, dass es korrekt auf sonst synonyme Strukturen fehlschlägt:
// 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 Stimmen
Vielleicht sind Sie verwenden sollten ToUpperInvariant() anstelle von ToLower()...
0 Stimmen
Warum gibt es Erweiterungsmethoden nur für Referenztypen?
34 Stimmen
@Shimmy: Sobald Sie einen Werttyp an die Erweiterungsmethode übergeben, arbeiten Sie an einer Kopie davon, so dass Sie seinen Zustand nicht ändern können.
5 Stimmen
Ich weiß, dass es ein alter Thread ist und weiß nicht, ob sie etwas geändert haben, aber Erweiterungsmethoden funktionieren gut für Werttypen, sicher, dass sie vielleicht nicht immer so viel Sinn machen, aber ich habe "public static TimeSpan Seconds(this int x) { return TimeSpan.FromSeconds(x); }" verwendet, um die Syntax von "Wait.For(5.Seconds())..." zu aktivieren.
7 Stimmen
Dies war zwar nicht Teil der Frage, aber Sie könnten Ihre foreach-Schleifenlogik verbessern, indem Sie String.Equals mit StringComparison.InvariantCultureIgnoreCase verwenden
2 Stimmen
Mögliche Duplikate von Kennt jemand eine gute Abhilfe für das Fehlen einer generischen Enum-Beschränkung?
3 Stimmen
Warum sollte man das
foreach
-Schleife?Enum.Parse
enthält eineignoreCase
-Parameter (ich nehme an, da .Net 2.0 ).0 Stimmen
Ich fügte noch eine Antwort mit den bestehenden
ignoreCase
-Parameter und einen generischendefault
Wert als optionale Argumente, und einige weitere Verbesserungen, die von anderen vorgeschlagen wurden.1 Stimmen
Es ist erwähnenswert, dass Enum.Parse eine Aufzählung mit dem Attribut [Flags] verarbeiten kann, sofern die Werte in der Zeichenkette durch Kommas getrennt sind.
0 Stimmen
Bitte posten Sie Ihre Lösung als Antwortbeitrag. Fügen Sie keine Antworten in Fragen ein.
0 Stimmen
Wie wird der Wertstring erstellt? Wenn diese mit der Funktion
Enum.ToString()
Methode, und der Enum-Typ ist mit dem[Flags]
Attribut, dasdefaultValue
wird immer von der Methode mitvalue = (enum.type1 | enum.type2).ToString() == type1, type2
. Eckgehäuse.1 Stimmen
Prüfen Sie diese von mir geschriebene Antwort stackoverflow.com/a/38410351/4009642
0 Stimmen
@bigworld12 Die ursprüngliche Anforderung an mich, dies zu tun, ist längst in den Nebeln der Zeit verloren, aber sehr umfassende Lösung dennoch :)
1 Stimmen
Die Implementierung dieser Funktionalität ist für C# 7 in Planung! Stimmen Sie ab! github.com/dotnet/roslyn/issues/262
3 Stimmen
Sehr altes Thema, aber es gibt eine große Verbesserung seit C# 7.3. Die Verwendung von Enum-Constraints wird nun vollständig unterstützt. Siehe meine längere Antwort den ganzen Weg nach unten am Ende.
1 Stimmen
NB: Diese Funktion wird ab C# 7.3 unterstützt.
1 Stimmen
Trotz der Tatsache, dass dies in c# 7.3 und aufwärts funktioniert, ist es etwas dumm, dass mit nullable Eigenschaft eines Typs
T
mit der Nebenbedingungwhere T: Enum
das Kompilat weint immer noch wie ein Baby, das T muss von einem nicht-nullbaren Typ sein , was dazu führt, dass man in der gesamten Hierarchie der übergebenen Generika angeben mussstruct
auch eine Einschränkung.0 Stimmen
Ab .NET Core 2.0 (ab 2017) verfügt die Klassenbibliothek über generische Überladungen, die es Ihnen ermöglichen, Dinge zu schreiben wie
var val = Enum.Parse<DayOfWeek>("ThuRSday", ignoreCase: true);
.