我正在构建一个函数来扩展这个Enum.Parse概念
Enum.Parse
所以我写了以下内容:
public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum { if (string.IsNullOrEmpty(value)) return defaultValue; foreach (T item in Enum.GetValues(typeof(T))) { if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item; } return defaultValue; }
我得到一个错误约束不能是特殊类System.Enum。
System.Enum
很公平,但是是否有允许通用枚举的解决方法,或者我将不得不模仿该Parse函数并将类型作为属性传递,这将丑陋的装箱要求强加给您的代码。
Parse
编辑 以下所有建议都非常感谢,谢谢。
已经确定(我已经离开循环以保持不区分大小写 - 我在解析 XML 时使用它)
public static class EnumUtils { public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible { if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type"); if (string.IsNullOrEmpty(value)) return defaultValue; foreach (T item in Enum.GetValues(typeof(T))) { if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item; } return defaultValue; } }
编辑: (2015 年 2 月 16 日)Christopher Currens在下面的 MSIL 或 F#中发布了一个编译器强制类型安全的通用解决方案,非常值得一看,并点赞。如果解决方案在页面上进一步冒泡,我将删除此编辑。
编辑 2: (2021 年 4 月 13 日)由于这个问题现在已经得到解决和支持,自 C# 7.3 以来,我已经更改了接受的答案,尽管全面阅读顶级答案对于学术和历史兴趣是值得的 :)
以下片段(来自dotnet 示例)演示了如何:
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; }
确保将 C# 项目中的语言版本设置为 7.3 版。
原答案如下:
我玩游戏迟到了,但我把它当作一个挑战,看看它是如何完成的。这在 C#(或 VB.NET,但向下滚动到 F#)中是不可能的,但在 MSIL 中 是可能的。 我写了这个小……东西
// 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 } }
如果它是有效的 C#,它 会 生成一个看起来像这样的函数:
T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum
然后使用以下 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 }
不幸的是,这意味着您的这部分代码是用 MSIL 而不是 C# 编写的,唯一的额外好处是您可以通过System.Enum. 这也有点令人失望,因为它被编译成一个单独的程序集。但是,这并不意味着您必须以这种方式部署它。
通过删除该行.assembly MyThing{}并调用 ilasm 如下:
.assembly MyThing{}
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
你得到一个网络模块而不是一个程序集。
不幸的是,VS2010(以及更早的版本,显然)不支持添加 netmodule 引用,这意味着您在调试时必须将其保留在 2 个单独的程序集中。将它们添加为程序集的唯一方法是使用/addmodule:{files}命令行参数自己运行 csc.exe。在 MSBuild 脚本中不会 太痛苦。 当然,如果你是勇敢或愚蠢的,你可以每次手动运行 csc 。而且它肯定会变得更加复杂,因为多个程序集需要访问它。
/addmodule:{files}
所以,它可以在.Net中完成。值得付出额外的努力吗?嗯,好吧,我想我会让你决定那个。
额外信用:事实证明,enum除了 MSIL:F# 之外,至少还有一种其他 .NET 语言可以对 进行通用限制。
enum
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
这个更容易维护,因为它是一种具有完整 Visual Studio IDE 支持的知名语言,但您仍然需要在解决方案中为它创建一个单独的项目。但是,它自然会产生相当不同的 IL(代码 非常 不同),并且它依赖于FSharp.Core库,就像任何其他外部库一样,它需要成为您的发行版的一部分。
FSharp.Core
以下是如何使用它(基本上与 MSIL 解决方案相同),并表明它在其他同义结构上正确失败:
// 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);