Diese Funktion wird endlich in C # 7.3 unterstützt!
Das folgende Snippet (aus den Dotnet-Beispielen ) 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 Sie Ihre Sprachversion in Ihrem C # -Projekt auf Version 7.3 einstellen.
Originalantwort unten:
Ich bin zu spät zum Spiel, aber ich habe es als Herausforderung angesehen, zu sehen, wie es gemacht werden kann. Dies ist in C # (oder VB.NET nicht möglich, aber für F # nach unten scrollen), in MSIL jedoch möglich . Ich habe dieses kleine ... Ding 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
}
}
Was eine Funktion erzeugt, die so aussehen würde , wenn sie gültig wäre C #:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
Dann mit folgendem 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 dieser Teil Ihres Codes in MSIL anstelle von C # geschrieben wird. Der einzige zusätzliche Vorteil besteht darin, dass Sie diese Methode einschränken können System.Enum
. Es ist auch eine Art Mist, weil es in einer separaten Assembly zusammengestellt wird. Dies bedeutet jedoch nicht, dass Sie es auf diese Weise bereitstellen müssen.
Durch Entfernen der Zeile .assembly MyThing{}
und Aufrufen von ilasm wie folgt:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
Sie erhalten ein Netzmodul anstelle einer Baugruppe.
Leider unterstützt VS2010 (und natürlich früher) das Hinzufügen von Netzmodulreferenzen nicht, was bedeutet, dass Sie es beim Debuggen in zwei separaten Assemblys belassen müssen. Die einzige Möglichkeit, sie als Teil Ihrer Assembly hinzuzufügen, besteht darin, csc.exe selbst mit dem /addmodule:{files}
Befehlszeilenargument auszuführen . Es wäre nicht auch soIn einem MSBuild-Skript schmerzhaft. Wenn Sie mutig oder dumm sind, können Sie csc natürlich jedes Mal manuell ausführen. Und es wird sicherlich komplizierter, da mehrere Baugruppen Zugriff darauf benötigen.
So kann es in .Net gemacht werden. Lohnt sich der zusätzliche Aufwand? Ähm, ich denke, ich werde dich darüber entscheiden lassen.
F # Lösung als Alternative
Zusätzliches Guthaben: Es stellt sich heraus, dass eine generische Einschränkung enum
in mindestens einer anderen .NET-Sprache neben MSIL: F # möglich ist.
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 Sprache ist einfacher zu warten, da es sich um eine bekannte Sprache mit vollständiger Unterstützung für Visual Studio IDE handelt. Sie benötigen jedoch noch ein separates Projekt in Ihrer Lösung. Es erzeugt jedoch natürlich erheblich unterschiedliche IL (der Code ist sehr unterschiedlich) und es stützt sich auf dieFSharp.Core
Bibliothek, die wie jede andere externe Bibliothek Teil Ihrer Distribution werden muss.
So können Sie es verwenden (im Grunde das gleiche wie die MSIL-Lösung) und zeigen, dass es bei ansonsten synonymen Strukturen korrekt 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);