当前位置:  开发笔记 > 编程语言 > 正文

创建将T限制为枚举的通用方法

如何解决《创建将T限制为枚举的通用方法》经验,为你挑选了9个好方法。

我正在构建一个扩展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;
}

我得到一个Error Constraint不能是特殊类System.Enum.

很公平,但是有一个解决方法允许Generic 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日)Julien Lebosquain最近在MSIL或F#下面发布了编译器强制类型安全通用解决方案,这非常值得一看,并且是一个upvote.如果解决方案在页面上向上冒泡,我将删除此编辑.



1> Vivek..:

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

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

   //...
}

这仍然允许传递实现的值类型IConvertible.但机会很少.


另一个建议是使用标识符TEnum定义泛型类型.因此:public TEnum GetEnumFromString (字符串值)其中TEnum:struct,IConvertible,IComparible,IFormattable {}
那么,如果你选择沿着这条路走下去,那就让它更加受约束...使用"类TestClass 其中T:struct,IComparable,IFormattable,IConvertible"
通过包含其他接口不会获得太多收益,因为几乎所有内置值类型都实现了所有这些接口.对于通用扩展方法的约束尤其如此,这对于在枚举上操作非常方便,除了这些扩展方法就像感染所有对象的病毒一样.IConvertable至少可以缩小它的范围.
从.NET 2.0开始,泛型可用.因此它们也可以在vb 2005中使用.
@SamIam:当你发布时,这个帖子就是那个,6岁半,你是对的,没有编译时检查任何答案.然后仅仅3天之后,经过6年,你实现了自己的愿望 - 请看Julien Lebosquain在下面的帖子.
非常古老的话题,但自C#7.3以来有了很大的改进.现在完全支持使用Enum约束.在底部看到我的更长的答案.
从C#7.3开始支持此功能

2> Christopher ..:

C#7.3最终支持此功能!

以下代码段(来自dotnet示例)演示了它的用法:

public static Dictionary EnumNamedValues() 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引用,这意味着在调试时必须将它保留在2个独立的程序集中.将它们作为程序集的一部分添加的唯一方法是使用/addmodule:{files}命令行参数自行运行csc.exe .它在MSBuild脚本中不会痛苦.当然,如果你是勇敢或愚蠢的,你可以每次手动运行csc.当多个程序集需要访问它时,它肯定会变得更加复杂.

所以,它可以在.Net中完成.值得付出额外的努力吗?嗯,好吧,我想我会让你决定那一个.


F#解决方案作为替代

额外信用:事实证明enum除了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);


是的,非常铁杆.我非常尊重可以在IL中编码的人,**和**知道如何在更高的语言级别支持这些功能 - 我们许多人仍然认为这些级别在应用程序,业务规则,UI,组件库等
@MgSam - 来自[Eric Lippert](http://stackoverflow.com/a/1331811/721276):"没有特别不寻常的原因可以解释; 我们还有许多其他的事情要做,预算有限,而且这个从来没有超越过"这不是很好吗?" 在语言设计团队中进行讨论
我真正想知道的是,为什么C#团队还没有开始允许这个,因为它已经得到了MSIL的支持.
我不认为我曾经有过一个我没有在这里结束的项目.C#6是110%的语法糖,而且没有进入?切废话.
@LordofScripts:我认为原因在于,因为一个将`T`限制为`System.Enum`的类将无法用人们可能期望的`T`做所有事情,C#的作者认为他们也可以完全禁止它.我认为这个决定很不幸,因为C#只是忽略了对`System.Enum`约束的任何特殊处理,就可以编写一个`HasAnyFlags (这个T it,T other)`扩展方法,这是订单比'Enum.HasFlag(Enum)`更快,并且对其参数进行了类型检查.
@MichaelBlackburn它比听起来更复杂,主要是由于枚举上的位标志.名为HaloFour的github用户在[此Roslyn问题](https://github.com/dotnet/roslyn/issues/262)中给出了一个很好的总结.
@ruslan - 它说你无法在答案第一段的c#中完成这个.这实际上是这个答案显示的内容:cil非常可能(因为上面的代码在其他.net语言中使用时成功运行),但在C#本身是不可能的.

3> Julien Lebos..:
C#≥7.3

从C#7.3开始(Visual Studio2017≥v15.7提供),此代码现在完全有效:

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

C#≤7.2

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

public abstract class EnumClassUtils
where TClass : class
{

    public static TEnum Parse(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils
{
}

用法:

EnumUtils.Parse("value");

注意:这在C#5.0语言规范中有明确说明:

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


@ DavidI.McIntosh`EnumClassUtils `足以将T限制为任何`System.Enum`和任何派生类型.`Parse`上的`struct`然后将它进一步限制为真正的枚举类型.你需要在某些时候限制为"Enum".为此,您的类必须嵌套.请参阅https://gist.github.com/MrJul/7da12f5f2d6c69f03d79
为了清楚起见,我的评论"不愉快"并不是对你的解决方案的评论 - 它真的是一个美丽的黑客.只是"不愉快",MS强迫我们使用这样一个令人费解的黑客.
什么`TClass:class`约束在这里获得了什么?
有没有办法将其用于扩展方法?

4> Yahoo Seriou..:

编辑

Julien Lebosquain现在已经很好地回答了这个问题.我也想延长他的答案ignoreCase,defaultValue和可选的参数,同时加入TryParseParseOrDefault.

public abstract class ConstrainedEnumParser 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 code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived -constrained type EnumUtils
    public static TEnum Parse(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse(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(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
// reference type constraint to any 
{
    // call to parse will then contain constraint to specific -class
}

用法示例:

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

通过使用评论和"新"发展,我对Vivek答案的旧改进:

使用TEnum的清晰度,为用户

为附加的约束检查添加更多的接口约束

让我们TryParse处理ignoreCase现有参数(在VS2010/.Net 4中引入)

可选使用通用default值(在VS2005/.Net 2中引入)

使用带有默认值的可选参数(在VS2010/.Net 4中引入)for defaultValueignoreCase

导致:

public static class EnumUtils
{
    public static TEnum ParseEnum(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;
    }
}



5> Karg..:

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

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

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



6> baumgarb..:

还应该考虑到,因为使用Enum约束的C#7.3的发布支持开箱即用而无需进行额外的检查和填充.

因此,如果您已将项目的语言版本更改为C#7.3,则以下代码将完美地运行:

    private static T GetEnumFromString(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在编写此EDIT时尚不支持C#7.3.激活ReSharper会突出显示Enum约束,因为它告诉我不能使用'System.Array','System.Delegate','System.Enum','System.ValueType','object'作为类型参数约束.ReSharper建议快速修复删除方法类型参数T的'Enum'约束

但是,如果您在工具 - >选项 - > ReSharper Ultimate - >常规下暂时关闭ReSharper,您将看到语法完全正常,因为您使用VS 15.7或更高版本以及C#7.3或更高版本.



7> Bivoauc..:

我通过dimarzionist修改了样本.此版本仅适用于Enums,不允许结构通过.

public static T ParseEnum(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);
    }
}


我不会在失败时返回默认值; 我会让异常传播(就像Enum.Parse一样).相反,使用TryParse返回一个bool并使用out参数返回结果.

8> Martin..:

我试着改进一下代码:

public T LoadEnum(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;
}



9> Sunny Rajwad..:

我确实有特定的要求,我需要使用enum与enum值相关联的文本.例如,当我使用enum指定错误类型时,它需要描述错误详细信息.

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

推荐阅读
谢谢巷议
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有