简体   繁体   English

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

[英]Create Generic method constraining T to an Enum

I'm building a function to extend the Enum.Parse concept that我正在构建一个函数来扩展Enum.Parse概念

  • Allows a default value to be parsed in case that an Enum value is not found允许在未找到 Enum 值的情况下解析默认值
  • Is case insensitive不区分大小写

So I wrote the following:所以我写了以下内容:

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

I am getting a Error Constraint cannot be special class System.Enum .我得到一个错误约束不能是特殊类System.Enum

Fair enough, but is there a workaround to allow a Generic Enum, or am I going to have to mimic the Parse function and pass a type as an attribute, which forces the ugly boxing requirement to your code.很公平,但是是否有允许通用枚举的解决方法,或者我将不得不模仿Parse函数并将类型作为属性传递,这会强制对您的代码进行丑陋的装箱要求。

EDIT All suggestions below have been greatly appreciated, thanks.编辑下面的所有建议都非常感谢,谢谢。

Have settled on (I've left the loop to maintain case insensitivity - I am using this when parsing XML)已经确定(我已经离开循环以保持不区分大小写 - 我在解析 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;
    }
}

EDIT: (16th Feb 2015) Christopher Currens has posted a compiler enforced type-safe generic solution in MSIL or F# below, which is well worth a look, and an upvote.编辑: (2015 年 2 月 16 日)Christopher Currens下面的MSIL 或 F#中发布了一个编译器强制类型安全的通用解决方案,这非常值得一看,并且是一个赞成票。 I will remove this edit if the solution bubbles further up the page.如果解决方案在页面上进一步冒泡,我将删除此编辑。

EDIT 2: (13th Apr 2021) As this has now been addressed, and supported, since C# 7.3, I have changed the accepted answer, though full perusal of the top answers is worth it for academic, and historical, interest :)编辑 2: (2021 年 4 月 13 日)由于现在已解决并支持此问题,因此自 C# 7.3 以来,我已更改已接受的答案,但对于学术和历史兴趣而言,完全阅读最佳答案是值得的:)

Since Enum Type implements IConvertible interface, a better implementation should be something like this:由于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");
   }

   //...
}

This will still permit passing of value types implementing IConvertible .这仍将允许传递实现IConvertible的值类型。 The chances are rare though.不过机会很少。

This feature is finally supported in C# 7.3! C# 7.3 终于支持这个特性了!

The following snippet (from the dotnet samples ) demonstrates how:以下代码段(来自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;
}

Be sure to set your language version in your C# project to version 7.3.请务必将 C# 项目中的语言版本设置为 7.3 版。


Original Answer below:原答案如下:

I'm late to the game, but I took it as a challenge to see how it could be done.我迟到了,但我把它当作一个挑战,看看它是如何完成的。 It's not possible in C# (or VB.NET, but scroll down for F#), but is possible in MSIL.这在 C#(或 VB.NET,但向下滚动 F#)中是不可能的,但在 MSIL 中是可能的。 I wrote this little....thing我写了这个小....东西

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

Which generates a function that would look like this, if it were valid C#:如果它是有效的 C#,它生成一个看起来像这样的函数:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Then with the following C# code:然后使用以下 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
}

Unfortunately, this means having this part of your code written in MSIL instead of C#, with the only added benefit being that you're able to constrain this method by System.Enum .不幸的是,这意味着这部分代码是用 MSIL 而不是 C# 编写的,唯一增加的好处是您可以通过System.Enum约束此方法。 It's also kind of a bummer, because it gets compiled into a separate assembly.它也是一种无赖,因为它被编译成一个单独的程序集。 However, it doesn't mean you have to deploy it that way.但是,这并不意味着您必须以这种方式部署它。

By removing the line .assembly MyThing{} and invoking ilasm as follows:通过删除.assembly MyThing{}.assembly MyThing{}如下方式调用 ilasm:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

you get a netmodule instead of an assembly.你得到一个网络模块而不是一个程序集。

Unfortunately, VS2010 (and earlier, obviously) does not support adding netmodule references, which means you'd have to leave it in 2 separate assemblies when you're debugging.不幸的是,VS2010(显然更早版本)不支持添加网络模块引用,这意味着您在调试时必须将其保留在 2 个单独的程序集中。 The only way you can add them as part of your assembly would be to run csc.exe yourself using the /addmodule:{files} command line argument.您可以将它们添加为程序集一部分的唯一方法是使用/addmodule:{files}命令行参数自己运行 csc.exe。 It wouldn't be too painful in an MSBuild script.在 MSBuild 脚本中不会痛苦。 Of course, if you're brave or stupid, you can run csc yourself manually each time.当然,如果你勇敢或愚蠢,你可以每次手动运行csc。 And it certainly gets more complicated as multiple assemblies need access to it.而且它肯定会变得更加复杂,因为多个程序集需要访问它。

So, it CAN be done in .Net.所以,它可以在 .Net 中完成。 Is it worth the extra effort?值得付出额外的努力吗? Um, well, I guess I'll let you decide on that one.嗯,我想我会让你决定那个。


F# Solution as alternative F# 解决方案作为替代

Extra Credit: It turns out that a generic restriction on enum is possible in at least one other .NET language besides MSIL: 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

This one is easier to maintain since it's a well-known language with full Visual Studio IDE support, but you still need a separate project in your solution for it.这个更容易维护,因为它是一种众所周知的语言,具有完整的 Visual Studio IDE 支持,但您的解决方案中仍然需要一个单独的项目。 However, it naturally produces considerably different IL (the code is very different) and it relies on the FSharp.Core library, which, just like any other external library, needs to become part of your distribution.然而,它自然会产生很大的不同IL(代码非常不同的),它依赖于FSharp.Core库,其中,就像任何其他外部库,需要成为你的发行版的一部分。

Here's how you can use it (basically the same as the MSIL solution), and to show that it correctly fails on otherwise synonymous structs:以下是如何使用它(与 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);

C# ≥ 7.3 C# ≥ 7.3

Starting with C# 7.3 (available with Visual Studio 2017 ≥ v15.7), this code is now completely valid:从 C# 7.3(适用于 Visual Studio 2017 ≥ v15.7)开始,此代码现在完全有效:

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

C# ≤ 7.2 C# ≤ 7.2

You can have a real compiler enforced enum constraint by abusing constraint inheritance.您可以通过滥用约束继承来获得真正的编译器强制枚举约束。 The following code specifies both a class and a struct constraints at the same time:以下代码同时指定了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>
{
}

Usage:用法:

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

Note: this is specifically stated in the C# 5.0 language specification:注意:这在 C# 5.0 语言规范中有特别说明:

If type parameter S depends on type parameter T then: [...] It is valid for S to have the value type constraint and T to have the reference type constraint.如果类型参数 S 依赖于类型参数 T 则: [...] S 具有值类型约束并且 T 具有引用类型约束是有效的。 Effectively this limits T to the types System.Object, System.ValueType, System.Enum, and any interface type.这实际上将 T 限制为 System.Object、System.ValueType、System.Enum 和任何接口类型。

Edit编辑

The question has now superbly been answered by Julien Lebosquain . Julien Lebosquain现在已经很好地回答了这个问题。 I would also like to extend his answer with ignoreCase , defaultValue and optional arguments, while adding TryParse and ParseOrDefault .我还想用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 code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public 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
}

Examples of usage:用法示例:

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

Old老的

My old improvements on Vivek's answer by using the comments and 'new' developments:我通过使用评论和“新”发展对Vivek 的回答进行了旧改进:

  • use TEnum for clarity for users使用TEnum为用户清晰起见
  • add more interface-constraints for additional constraint-checking添加更多接口约束以进行额外的约束检查
  • let TryParse handle ignoreCase with the existing parameter (introduced in VS2010/.Net 4)TryParse使用现有参数处理ignoreCase (在 VS2010/.Net 4 中引入)
  • optionally use the generic default value (introduced in VS2005/.Net 2)可选择使用通用default(在 VS2005/.Net 2 中引入)
  • use optional arguments (introduced in VS2010/.Net 4) with default values, for defaultValue and ignoreCase使用带有默认值的可选参数(在 VS2010/.Net 4 中引入),用于defaultValueignoreCase

resulting in:导致:

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

The existing answers are true as of C# <=7.2.从 C# <=7.2 开始,现有答案是正确的。 However, there is a C# language feature request (tied to a corefx feature request) to allow the following;但是,有一个 C# 语言功能请求(与corefx功能请求相关联)允许以下操作;

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

At time of writing, the feature is "In discussion" at the Language Development Meetings.在撰写本文时,该功能在语言开发会议上处于“讨论中”。

EDIT编辑

As per nawfal 's info, this is being introduced in C# 7.3 .根据nawfal的信息,这是在 C# 7.3 中引入的。

EDIT 2编辑 2

This is now in C# 7.3 forward ( release notes )这现在在 C# 7.3 中( 发行说明

Sample;样本;

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

You can define a static constructor for the class that will check that the type T is an enum and throw an exception if it is not.您可以为该类定义一个静态构造函数,该构造函数将检查类型 T 是否为枚举,如果不是则抛出异常。 This is the method mentioned by Jeffery Richter in his book CLR via C#.这是 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");
        }
    }
}

Then in the parse method, you can just use Enum.Parse(typeof(T), input, true) to convert from string to the enum.然后在 parse 方法中,您可以使用 Enum.Parse(typeof(T), input, true) 将字符串转换为枚举。 The last true parameter is for ignoring case of the input.最后一个 true 参数用于忽略输入的大小写。

It should also be considered that since the release of C# 7.3 using Enum constraints is supported out-of-the-box without having to do additional checking and stuff.还应该考虑到,由于使用 Enum 约束的 C# 7.3 的发布是开箱即用的,无需进行额外的检查和内容。

So going forward and given you've changed the language version of your project to C# 7.3 the following code is going to work perfectly fine:因此,假设您已将项目的语言版本更改为 C# 7.3,以下代码将完全正常运行:

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

In case you're don't know how to change the language version to C# 7.3 see the following screenshot:如果您不知道如何将语言版本更改为 C# 7.3,请查看以下屏幕截图: 在此处输入图片说明

EDIT 1 - Required Visual Studio Version and considering ReSharper编辑 1 - 需要 Visual Studio 版本并考虑 ReSharper

For Visual Studio to recognize the new syntax you need at least version 15.7.要让 Visual Studio 识别新语法,您至少需要 15.7 版。 You can find that also mentioned in Microsoft's release notes, see Visual Studio 2017 15.7 Release Notes .您可以在 Microsoft 的发行说明中找到也提到的,请参阅Visual Studio 2017 15.7 发行说明 Thanks @MohamedElshawaf for pointing out this valid question.感谢@MohamedElshawaf 指出这个有效的问题。

Pls also note that in my case ReSharper 2018.1 as of writing this EDIT does not yet support C# 7.3.还请注意,就我而言,在撰写此 EDIT 时,ReSharper 2018.1 尚不支持 C# 7.3。 Having ReSharper activated it highlights the Enum constraint as an error telling me Cannot use 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' as type parameter constraint .激活 ReSharper 后,它将 Enum 约束突出显示为错误,告诉我无法使用 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' as type parameter constraint ReSharper suggests as a quick fix to Remove 'Enum' constraint of type paramter T of method ReSharper 建议作为快速解决方法来删除类型参数 T 的“枚举”约束

However, if you turn off ReSharper temporarily under Tools -> Options -> ReSharper Ultimate -> General you'll see that the syntax is perfectly fine given that you use VS 15.7 or higher and C# 7.3 or higher.但是,如果您在Tools -> Options -> ReSharper Ultimate -> General下暂时关闭 ReSharper,您会看到语法非常好,因为您使用的是 VS 15.7 或更高版本和 C# 7.3 或更高版本。

I tried to improve the code a bit:我试着稍微改进一下代码:

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

I modified the sample by dimarzionist.我修改了 dimarzionist 的样本。 This version will only work with Enums and not let structs get through.此版本仅适用于 Enums 而不会让结构体通过。

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

I do have specific requirement where I required to use enum with text associated with enum value.我确实有特定要求,我需要将 enum 与与 enum 值关联的文本一起使用。 For example when I use enum to specify error type it required to describe error details.例如,当我使用 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<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());
}

Hope this is helpful:希望这是有帮助的:

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

Interestingly enough, apparently this is possible in other langauges (Managed C++, IL directly).有趣的是,显然这在其他语言中可能的(Managed C++, IL direct)。

To Quote:报价:

... Both constraints actually produce valid IL and can also be consumed by C# if written in another language (you can declare those constraints in managed C++ or in IL). ...这两个约束实际上都会产生有效的 IL,如果用另一种语言编写,也可以被 C# 使用(您可以在托管 C++ 或 IL 中声明这些约束)。

Who knows谁知道

This is my take at it.这是我的看法。 Combined from the answers and MSDN结合答案和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 MSDN源码

I loved Christopher Currens's solution using IL but for those who don't want to deal with tricky business of including MSIL into their build process I wrote similar function in C#.我喜欢 Christopher Currens 使用 IL 的解决方案,但对于那些不想处理将 MSIL 包含到他们的构建过程中的棘手业务的人,我在 C# 中编写了类似的函数。

Please note though that you can't use generic restriction like where T : Enum because Enum is special type.请注意,您不能使用像where T : Enum这样的通用限制where T : Enum因为 Enum 是特殊类型。 Therefore I have to check if given generic type is really enum.因此我必须检查给定的泛型类型是否真的是枚举。

My function is:我的功能是:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!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;
}

I've encapsulated Vivek's solution into a utility class that you can reuse.我已将 Vivek 的解决方案封装到您可以重用的实用程序类中。 Please note that you still should define type constraints "where T : struct, IConvertible" on your type.请注意,您仍然应该在您的类型上定义类型约束“where T : struct, IConvertible”。

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

I always liked this (you could modify as appropriate):我一直很喜欢这个(你可以适当修改):

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

I created an extension Method to get integer value from enum take look at method implementation我创建了一个扩展方法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);
}

this is usage这是用法

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

As stated in other answers before;正如之前其他答案中所述; while this cannot be expressed in source-code it can actually be done on IL Level.虽然这不能在源代码中表达,但实际上可以在 IL 级别上完成。 @Christopher Currens answer shows how the IL do to that. @Christopher Currens 的回答显示了 IL 如何做到这一点。

With Fody s Add-In ExtraConstraints.Fody there's a very simple way, complete with build-tooling, to achieve this.使用Fody的 Add-In ExtraConstraints.Fody有一种非常简单的方法,包括构建工具,可以实现这一点。 Just add their nuget packages ( Fody , ExtraConstraints.Fody ) to your project and add the constraints as follows (Excerpt from the Readme of ExtraConstraints):只是他们的NuGet包(加FodyExtraConstraints.Fody )到项目和(从ExtraConstraints的自述节选)添加约束如下:

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

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

and Fody will add the necessary IL for the constraint to be present.并且 Fody 将添加必要的 IL 以使约束存在。 Also note the additional feature of constraining delegates:还要注意约束委托的附加功能:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

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

Regarding Enums, you might also want to take note of the highly interesting Enums.NET .关于 Enums,您可能还想注意非常有趣的Enums.NET

This is my implementation.这是我的实现。 Basically, you can setup any attribute and it works.基本上,您可以设置任何属性并且它可以工作。

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

If it's ok to use direct casting afterwards, I guess you can use the System.Enum base class in your method, wherever necessary.如果之后可以使用直接转换,我想您可以在必要时在方法中使用System.Enum基类。 You just need to replace the type parameters carefully.您只需要仔细替换类型参数。 So the method implementation would be like:所以方法实现将是这样的:

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

Then you can use it like:然后你可以像这样使用它:

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

note that System.Enum Parse() & TryParse() methods still have where struct constraints rather than where Enum , so that this won't compile:请注意, System.Enum Parse()TryParse()方法仍然具有where struct约束而不是where Enum ,因此不会编译:

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

but this will:但这将:

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

as a result, where struct,Enum may be preferable to just where Enum因此, where struct,Enum可能比where Enum更可取

Just for completeness, the following is a Java solution.为了完整起见,以下是一个 Java 解决方案。 I am certain the same could be done in C# as well.我确信同样可以在 C# 中完成。 It avoids having to specify the type anywhere in code - instead, you specify it in the strings you are trying to parse.它避免了必须在代码中的任何地方指定类型 - 相反,您可以在您尝试解析的字符串中指定它。

The problem is that there isn't any way to know which enumeration the String might match - so the answer is to solve that problem.问题是没有任何方法可以知道 String 可能匹配哪个枚举 - 所以答案是解决这个问题。

Instead of accepting just the string value, accept a String that has both the enumeration and the value in the form "enumeration.value".不是只接受字符串值,而是接受同时具有枚举和“enumeration.value”形式的值的字符串。 Working code is below - requires Java 1.8 or later.工作代码如下 - 需要 Java 1.8 或更高版本。 This would also make the XML more precise as in you would see something like color="Color.red" instead of just color="red".这也会使 XML 更加精确,因为您会看到类似 color="Color.red" 的内容,而不仅仅是 color="red"。

You would call the acceptEnumeratedValue() method with a string containing the enum name dot value name.您将使用包含枚举名称点值名称的字符串调用 acceptEnumeratedValue() 方法。

The method returns the formal enumerated value.该方法返回正式的枚举值。

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


}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM