抽象方法中的通用枚举参数。
抽象方法中的通用枚举参数。
我正在编写一个函数来扩展Enum.Parse
的概念,它:
- 允许在找不到枚举值时解析默认值
- 大小写不敏感
所以我写了下面的代码:
public static T GetEnumFromString(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
。
很好,但是否有解决方法允许通用枚举,或者我必须模拟Parse
函数并将类型作为属性传递,这将强制将不优美的装箱要求添加到您的代码中。
编辑 下面所有的建议都非常感激,谢谢。
这是我最终选择的解决方案(我保留了循环以维护大小写不敏感性 - 当解析XML时我将使用它):
public static class EnumUtils { public static T ParseEnum(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开始已经解决和支持了此问题,我已更改接受的答案,但完全阅读前几个答案对于学术和历史兴趣还是值得的:)
这个功能终于在C# 7.3中得到了支持!
以下代码片段(摘自dotnet样例)展示了如何做到:
public static DictionaryEnumNamedValues () where T : System.Enum { var result = new Dictionary (); 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(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(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:
ilasm.exe /DLL /OUTPUT=MyThing.netmodule
你会得到一个netmodule,而不是一个程序集。
不幸的是,VS2010(以及更早版本)不支持添加netmodule引用,这意味着在调试时必须保留两个单独的程序集。你唯一的添加它们作为你程序集一部分的方法是使用命令行参数/addmodule:{files}
自己运行csc.exe。在MSBuild脚本中,这不会太痛苦。当然,如果你勇敢或愚蠢,你可以每次手动运行csc。当多个程序集需要访问它时,它肯定会变得更加复杂。
因此,在.NET中可以这样做。是否值得花费额外的精力?这个问题由你决定。
F#解决方案作为替代方案
额外的学分:除MSIL外,至少还有一种其他.NET语言支持对枚举进行通用限制: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
使用起来更加简单,因为它是一种众所周知的语言,具有完整的Visual Studio IDE支持,但是仍然需要在解决方案中为它创建一个单独的项目。但是,它自然产生了完全不同的IL(代码非常不同),并且依赖于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);