CSharp - 创建一个常规的约束方法制约T到Enum

  显示原文与译文双语对照的内容

我正在构建一个函数来扩展 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'。

合理,但有一个方法允许一个通用的枚举,还是我要模拟解析函数,通过一个类型属性,这迫使丑陋的拳击要求代码。

编辑所有建议,非常感谢。

已经确定( 我离开了循环以保持不区分大小写- 在解析XML时我是 usng )


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

编辑: ( 16 2015年月 ) 朱利安Lebosquain最近发布了一个编译器执行解决方案,这是很值得一看,一个 upvote 。 如果解决方案气泡更多,我将删除这里编辑。

时间:

由于 Enum 类型实现了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# ( 或者 VB.NET), 中) 不可能,但在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 约束这个方法。 它也是一种 bummer,因为它被编译到一个单独的程序集中。 但是,这并不意味着你必须以这种方式部署它。

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


ilasm.exe/DLL/OUTPUT=MyThing.netmodule

获取一个netmodule而不是程序集。

不幸的是,VS2010 ( 更早的时候,显然) 不支持添加netmodule引用,这意味着在调试时必须将它放在 2个单独的程序集中。 唯一的办法你可以把它们作为大会的一部分将会运行 csc.exe 自己使用 /addmodule:{files} 命令行 论点。 it not be也将在一个艰难msbuild脚本。 当然,如果你勇敢或者愚蠢,你可以每次手动运行 csc 。 而且随着多个程序集需要访问它,它也变得更加复杂。

所以,它可以在. NET 中完成。 是否值得额外的努力? 呃我想我可以让你决定那个。


额外的信用:除了MSIL之外,还可以使用至少一种语言: F# 。


namespace MyThing

open System;
open System.Linq;

[<Sealed>]
[<AbstractClass>]
type MyThing() =
 static member GetEnumFromString<'T when 'T : struct and 'T : (new : unit -> 'T) and 'T :> Enum> (str : string) (defaultValue : 'T) : 'T =
 let returnValue = defaultValue
 if System.String.IsNullOrEmpty(str) then defaultValue
 else
 let values : seq<'T> = Seq.cast(System.Enum.GetValues(typedefof<'T>))
 let foundVal = values |> Seq.tryFind(fun v -> v.ToString().ToUpper().Equals(str.Trim().ToUpper()))
 if foundVal.IsNone then defaultValue else foundVal.Value

这一点稍微易于维护,因为在 Visual Studio 中有对语言的支持。 但是,它产生了大量不同的IL ( 代码非常不同) 并且它依赖于 FSharp.Core 库,该库不是所有( 任意) 版本的.NET 框架。?

通过滥用约束继承,你可以有一个真正的编译器强制的枚举约束。 以下代码同时指定 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语言规范中特别说明:

如果类型参数依赖于类型参数T: [...]: 对于1 来说,值类型约束和T 具有引用类型约束是有效的。 有效地限制了类型 system 。对象,system 。valuetype,System.Enum, 和任何接口类型。

可以将泛型类型参数约束为值类型( 例如 int,bool和枚举) 或者使用结构约束的任何自定义结构:


public class MyClass<T> where T : struct
{
//...
}

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


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

然后在parse方法中,你可以只使用 Enum.Parse(typeof(T), 输入,真) 来从字符串转换到枚举。 最后一个实参是忽略输入的大小写。

虽然你的问题回答了关于限制t Enum Vivek和额外的评论,我觉得你得到的函数可以提高 ParseEnum的评论和'新建'进展:

  • 为用户透明使用 TEnum
  • 添加更多interface-constraints用于其他 constraint-checking
  • TryParse 用现有参数处理 ignoreCase ( 在 VS2010/.Net 4中引入)
  • 可以选择使用通用的default ( 在 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;
 }
}

我通过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);
 }
}

我试着改进代码:


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

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


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 attribute
 var str ="Second Value";
 var me = str.ParseXmlEnumAttribute<MyEnum>();
 System.Console.WriteLine(me.ReadXmlEnumAttribute());
//Parsing without XmlEnum
 str ="Three";
 me = str.ParseXmlEnumAttribute<MyEnum>();
 System.Console.WriteLine(me.ReadXmlEnumAttribute());
 me = MyEnum.One;
 System.Console.WriteLine(me.ReadXmlEnumAttribute());
}

这是我的选择。 从答案和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源

...