创建将T约束为枚举的泛型方法

我正在构建一个函数来扩展Enum.Parse概念

  • 允许在未找到Enum值的情况下解析默认值
  • 对案件不敏感

所以我写了以下内容:

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

很公平,但是有没有一种解决方法可以允许泛型枚举,或者我必须模仿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日)克里斯多夫货币已经发布了编译器在MSIL或F#中强制执行类型安全的泛型解决方案下面,这是非常值得一看,并赞成。

编辑2:(2021年4月13日)由于这个问题现在已经得到解决和支持,自C#7.3以来,我已经改变了接受的答案,尽管为了学术和历史兴趣,充分阅读顶级答案是值得的:)

459062 次浏览

希望这是有帮助的:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)where TValue : struct // enum{try{if (String.IsNullOrEmpty(value))return defaultValue;return (TValue)Enum.Parse(typeof (TValue), value);}catch(Exception ex){return defaultValue;}}

我通过dimarzionist修改了示例。此版本仅适用于枚举,不允许结构通过。

public static T ParseEnum<T>(string enumString)where T : struct // enum{if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)throw new Exception("Type given must be an Enum");try{
return (T)Enum.Parse(typeof(T), enumString, true);}catch (Exception ex){return default(T);}}

您可以为类定义一个静态构造函数,它将检查类型T是否为枚举,如果不是则抛出异常。这是Jeffery Richter在他的书CLR via C#中提到的方法。

internal sealed class GenericTypeThatRequiresAnEnum<T> {static GenericTypeThatRequiresAnEnum() {if (!typeof(T).IsEnum) {throw new ArgumentException("T must be an enumerated type");}}}

然后在parse方法中,您可以使用Enumm. Parse(typeof(T),输入,true)将字符串转换为枚举。最后一个true参数用于忽略输入的大小写。

由于Enum Type实现了IConvertible接口,更好的实现应该是这样的:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible{if (!typeof(T).IsEnum){throw new ArgumentException("T must be an enumerated type");}
//...}

这仍然允许传递实现IConvertible的值类型。

有趣的是,显然这是可能在其他语言(托管C++,IL直接)。

引用:

这两个约束实际上都会产生有效的IL,如果用另一种语言编写,也可以由C#使用(您可以在托管C++或IL中声明这些约束)。

谁知道呢

我确实有特定的要求,我需要将枚举与与枚举值关联的文本一起使用。例如,当我使用枚举指定错误类型时,它需要描述错误详细信息。

public static class XmlEnumExtension{public static string ReadXmlEnumAttribute(this Enum value){if (value == null) throw new ArgumentNullException("value");var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);return attribs.Length > 0 ? attribs[0].Name : value.ToString();}
public static T ParseXmlEnumAttribute<T>(this string str){foreach (T item in Enum.GetValues(typeof(T))){var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;}return (T)Enum.Parse(typeof(T), str, true);}}
public enum MyEnum{[XmlEnum("First Value")]One,[XmlEnum("Second Value")]Two,Three}
static void Main(){// Parsing from XmlEnum attributevar str = "Second Value";var me = str.ParseXmlEnumAttribute<MyEnum>();System.Console.WriteLine(me.ReadXmlEnumAttribute());// Parsing without XmlEnumstr = "Three";me = str.ParseXmlEnumAttribute<MyEnum>();System.Console.WriteLine(me.ReadXmlEnumAttribute());me = MyEnum.One;System.Console.WriteLine(me.ReadXmlEnumAttribute());}

我一直喜欢这个(你可以根据需要修改):

public static IEnumerable<TEnum> GetEnumValues(){Type enumType = typeof(TEnum);
if(!enumType.IsEnum)throw new ArgumentException("Type argument must be Enum type");
Array enumValues = Enum.GetValues(enumType);return enumValues.Cast<TEnum>();}

我试着稍微改进一下代码:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible{if (Enum.IsDefined(typeof(T), value)){return (T)Enum.Parse(typeof(T), value, true);}return defaultValue;}

此功能终于在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.Thingextends [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 strValuecall bool [mscorlib]System.String::IsNullOrEmpty(string)brfalse.s HASVALUEbr RETURNDEF         // return default it empty    
// foreach (T item in Enum.GetValues(typeof(T)))HASVALUE:// Enum.GetValues.GetEnumerator()ldtoken !!Tcall 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 enumeratorcallvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()brfalse.s LEAVE        
STATEMENTS:// T item = (T)Enumerator.Currentldloc enumeratorcallvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()unbox.any !!Tstloc templdloca.s tempconstrained. !!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 strValuecallvirt instance string [mscorlib]System.String::Trim()callvirt instance string [mscorlib]System.String::ToLower()callvirt instance bool [mscorlib]System.String::Equals(string)brfalse.s CONDITIONldloc tempstloc return_valueleave.s RETURNVAL        
LEAVE:leave.s RETURNDEF}finally{// ArrayList's Enumerator may or may not inherit from IDisposableldloc enumeratorisinst [mscorlib]System.IDisposablestloc.s disposerldloc.s disposerldnullceqbrtrue.s LEAVEFINALLYldloc.s disposercallvirt instance void [mscorlib]System.IDisposable::Dispose()LEAVEFINALLY:endfinally}  
RETURNDEF:ldarg defaultValuestloc return_value  
RETURNVAL:ldloc return_valueret}}

如果它是有效的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.NoThing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.OkayThing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum}

不幸的是,这意味着这部分代码是用MSIL而不是C#编写的,唯一的额外好处是你能够通过System.Enum约束此方法。这也有点令人沮丧,因为它被编译成一个单独的程序集。然而,这并不意味着你必须以这种方式部署它。

通过删除.assembly MyThing{}行并调用ILASM,如下所示:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

您将获得一个net模块而不是程序集。

不幸的是,VS2010(以及更早的版本)不支持添加net模块引用,这意味着你在调试时必须将其留在两个单独的程序集中。将它们添加为程序集的一部分的唯一方法是使用/addmodule:{files}命令行参数自己运行csc.exe。在MSBuild脚本中这不会是最痛苦的。当然,如果你勇敢或愚蠢,你每次都可以自己手动运行csc。而且它肯定会变得更复杂,因为多个程序集需要访问它。

所以,它可以在网上完成。值得额外的努力吗?嗯,好吧,我想我会让你决定那个。


F#解决方案作为替代

额外信用:事实证明,除了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库,就像任何其他外部库一样,它需要成为你的发行版的一部分。

以下是如何使用它(基本上与MSIL解决方案相同),并显示它在其他同义结构上正确失败:

// works, result is inferred to have type StringComparisonvar result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);// type restriction is recognized by C#, this fails at compile timevar result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);

我喜欢克里斯多夫货币使用IL的解决方案,但对于那些不想处理将MSIL包含到构建过程中的棘手业务的人,我用C#编写了类似的函数。

请注意,您不能使用像where T : Enum这样的泛型限制,因为枚举是特殊类型。因此,我必须检查给定的泛型类型是否真的是枚举。

我的功能是:

public static T GetEnumFromString<T>(string strValue, T defaultValue){// Check if it realy enum at runtimeif (!typeof(T).IsEnum)throw new ArgumentException("Method GetEnumFromString can be used with enums only");
if (!string.IsNullOrEmpty(strValue)){IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();while (enumerator.MoveNext()){T temp = (T)enumerator.Current;if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))return temp;}}
return defaultValue;}

编辑

这个问题现在已经被JulienLebosquain很好地回答了。我还想用ignoreCasedefaultValue和可选参数扩展他的回答,同时添加TryParseParseOrDefault

public abstract class ConstrainedEnumParser<TClass> where TClass : class// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]{// internal constructor, to prevent this class from being inherited outside this codeinternal ConstrainedEnumParser() {}// Parse using pragmatic/adhoc hard cast://  - struct + class = enum//  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtilspublic static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass{return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);}public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T{var didParse = Enum.TryParse(value, ignoreCase, out result);if (didParse == false){result = defaultValue;}return didParse;}public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T{if (string.IsNullOrEmpty(value)) { return defaultValue; }TEnum result;if (Enum.TryParse(value, ignoreCase, out result)) { return result; }return defaultValue;}}
public class EnumUtils: ConstrainedEnumParser<System.Enum>// reference type constraint to any <System.Enum>{// call to parse will then contain constraint to specific <System.Enum>-class}

使用示例:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);WeekDay parsedDayOrDefault;bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

我通过使用评论和“新”开发对Vivek的回答的旧改进:

  • 使用TEnum为用户提供清晰度
  • 为额外的约束检查添加更多的接口约束
  • #0用现有参数处理ignoreCase(在VS2010/. Net 4中引入)
  • 可选地使用泛型#0值(在VS2005/. Net 2中引入)
  • 使用可选参数(在VS2010/. Net 4中引入)和默认值,用于defaultValueignoreCase

导致:

public static class EnumUtils{public static TEnum ParseEnum<TEnum>(this string value,bool ignoreCase = true,TEnum defaultValue = default(TEnum))where TEnum : struct,  IComparable, IFormattable, IConvertible{if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }if (string.IsNullOrEmpty(value)) { return defaultValue; }TEnum lResult;if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }return defaultValue;}}

我已经将Vivek的解决方案封装到一个您可以重用的实用程序类中。请注意,您仍然应该在您的类型上定义类型约束“T: struct, IConverable”。

using System;
internal static class EnumEnforcer{/// <summary>/// Makes sure that generic input parameter is of an enumerated type./// </summary>/// <typeparam name="T">Type that should be checked.</typeparam>/// <param name="typeParameterName">Name of the type parameter.</param>/// <param name="methodName">Name of the method which accepted the parameter.</param>public static void EnforceIsEnum<T>(string typeParameterName, string methodName)where T : struct, IConvertible{if (!typeof(T).IsEnum){string message = string.Format("Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",typeParameterName,methodName);
throw new ArgumentException(message);}}
/// <summary>/// Makes sure that generic input parameter is of an enumerated type./// </summary>/// <typeparam name="T">Type that should be checked.</typeparam>/// <param name="typeParameterName">Name of the type parameter.</param>/// <param name="methodName">Name of the method which accepted the parameter.</param>/// <param name="inputParameterName">Name of the input parameter of this page.</param>public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)where T : struct, IConvertible{if (!typeof(T).IsEnum){string message = string.Format("Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",typeParameterName,methodName,inputParameterName);
throw new ArgumentException(message);}}
/// <summary>/// Makes sure that generic input parameter is of an enumerated type./// </summary>/// <typeparam name="T">Type that should be checked.</typeparam>/// <param name="exceptionMessage">Message to show in case T is not an enum.</param>public static void EnforceIsEnum<T>(string exceptionMessage)where T : struct, IConvertible{if (!typeof(T).IsEnum){throw new ArgumentException(exceptionMessage);}}}

这是我的看法。结合答案和MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable{if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)throw new ArgumentException("TEnum must be an Enum type");
try{var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);return enumValue;}catch (Exception){throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));}}

MSDN Source

C#≥7.3

从C#7.3(Visual Studio 2017≥v15.7可用)开始,此代码现在完全有效:

public static TEnum Parse<TEnum>(string value)where TEnum : struct, Enum{...}

C#≤7.2

您可以通过滥用约束继承来让真正的编译器强制枚举约束。以下代码同时指定classstruct约束:

public abstract class EnumClassUtils<TClass>where TClass : class{
public static TEnum Parse<TEnum>(string value)where TEnum : struct, TClass{return (TEnum) Enum.Parse(typeof(TEnum), value);}
}
public class EnumUtils : EnumClassUtils<Enum>{}

用法:

EnumUtils.Parse<SomeEnum>("value");

注意:这在C#5.0语言规范中特别说明:

如果类型参数S依赖于类型参数T,则:[…]它适用于S具有值类型约束,T具有引用类型约束。实际上,这将T限制为System. Object类型,System. ValueType、System. Enum和任何接口类型。

我创建了一个扩展方法to get integer value from enum查看方法实现

public static int ToInt<T>(this T soure) where T : IConvertible//enum{if (typeof(T).IsEnum){return (int) (IConvertible)soure;// the tricky part}//else//    throw new ArgumentException("T must be an enumerated type");return soure.ToInt32(CultureInfo.CurrentCulture);}

这是用法

MemberStatusEnum.Activated.ToInt()// using extension Method(int) MemberStatusEnum.Activated //the ordinary way

正如之前其他答案所述;虽然这不能在源代码中表达,但实际上可以在IL级别上完成。@克里斯多夫货币回答显示了IL如何做到这一点。

使用福迪s Add-In额外的限制。Fo dy有一种非常简单的方法来实现这一点,并使用构建工具。只需将它们的nuget包(FodyExtraConstraints.Fody)添加到您的项目中,并添加约束,如下所示(摘自ExtraConstraint的自述):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}
public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

和Fody将为约束添加必要的IL。还要注意约束委托的附加功能:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> (){...}
public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> (){...}

关于枚举,您可能还需要注意非常有趣的Enums.NET

如果之后可以使用直接转换,我想你可以在必要时在方法中使用System.Enum基类。你只需要仔细替换类型参数。所以方法实现如下:

public static class EnumUtils{public static Enum GetEnumFromString(string value, Enum defaultValue){if (string.IsNullOrEmpty(value)) return defaultValue;foreach (Enum item in Enum.GetValues(defaultValue.GetType())){if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;}return defaultValue;}}

然后你可以像这样使用它:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);

从C#<=7.2开始,现有答案为真。然而,有一个C#语言功能需求(绑定到corefx功能请求)允许以下内容;

public class MyGeneric<TEnum> where TEnum : System.Enum{ }

在撰写本文时,该功能是语言开发会议上的“讨论中”。

编辑

根据nawfal的信息,这是在C#7.3中引入的。

编辑2

这是现在在C#7.3前进(发行说明

样品;

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;}

还应该考虑到,由于使用Enum约束的C#7.3版本得到了开箱即用的支持,而无需进行额外的检查和内容。

因此,展望未来,鉴于您已将项目的语言版本更改为C#7.3,以下代码将完全正常工作:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum{// Your code goes here...}

如果您不知道如何将语言版本更改为C#7.3,请参阅以下屏幕截图:输入图片描述

编辑1-所需的Visual Studio版本和考虑ReSharper

要让Visual Studio识别新语法,您至少需要15.7版。您可以在Microsoft的发行说明中找到也提到了这一点,请参阅Visual Studio 2017 15.7发行说明。感谢@MohamedElshawaf指出这个有效的问题。

请注意,在我的情况下,ReSharper 2018.1在撰写此编辑时还不支持C#7.3。激活ReSharper后,它会突出显示枚举约束为错误,告诉我无法使用“System. Array”、“System.代理”、“System. Enum”、“System. ValueType”、“对象”作为类型参数约束。ReSharper建议快速修复删除方法类型参数T的“枚举”约束

但是,如果您在工具->选项->ReSharper Ultimate->常规下暂时关闭ReSharper,您会发现语法非常好,因为您使用VS 15.7或更高版本和C#7.3或更高版本。

为了完整起见,以下是一个Java的解决方案。我相信在C#中也可以做到这一点。它避免了必须在代码中的任何位置指定类型-相反,您可以在尝试解析的字符串中指定它。

问题是没有任何方法可以知道String可能匹配哪个枚举-所以答案是解决这个问题。

不要只接受字符串值,而是接受一个具有枚举和值的String,格式为“enumeration.value”。工作代码如下-需要Java1.8或更高版本。这也会使XML更精确,因为在这里你会看到类似颜色=“Color.red”而不仅仅是颜色=“红色”。

您可以使用一个包含枚举名称点值名称的字符串来调用它。

该方法返回正式枚举值。

import java.util.HashMap;import java.util.Map;import java.util.function.Function;

public class EnumFromString {
enum NumberEnum {One, Two, Three};enum LetterEnum {A, B, C};

Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();
public static void main(String[] args) {EnumFromString efs = new EnumFromString();
System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());
}
public EnumFromString() {enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});}
public Enum acceptEnumeratedValue(String enumDotValue) {
int pos = enumDotValue.indexOf(".");
String enumName = enumDotValue.substring(0, pos);String value = enumDotValue.substring(pos + 1);
Enum enumeratedValue = enumsByName.get(enumName).apply(value);
return enumeratedValue;}

}

这是我的实现。基本上,您可以设置任何属性并且它可以工作。

public static class EnumExtensions{public static string GetDescription(this Enum @enum){Type type = @enum.GetType();FieldInfo fi = type.GetField(@enum.ToString());DescriptionAttribute[] attrs =fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];if (attrs.Length > 0){return attrs[0].Description;}return null;}}

请注意,System.EnumParse()TryParse()方法仍然具有where struct约束而不是where Enum,因此不会编译:

    bool IsValid<TE>(string attempted) where TE : Enum{return Enum.TryParse(attempted, out TE _);}

但这将:

bool Ok<TE>(string attempted) where TE : struct,Enum{return Enum.TryParse(attempted, out var _)}

因此,where struct,Enum可能比where Enum更可取