我正在构建一个函数来扩展Enum。解析概念

允许在没有找到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。

很好,但是是否有一种变通方法来允许通用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日)Christopher currents在下面的MSIL或f#中发布了一个编译器强制类型安全的通用解决方案,非常值得一看,并获得好评。如果解决方案在页面上方出现气泡,我将删除此编辑。

编辑2:(2021年4月13日)自从c# 7.3以来,这个问题已经得到解决和支持,我已经更改了公认的答案,尽管出于学术和历史兴趣,充分阅读顶部的答案是值得的:)


当前回答

如果之后可以使用直接施法,我猜你可以使用系统。必要时,在方法中枚举基类。您只需要小心地替换类型参数。所以这个方法的实现是这样的:

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

在撰写本文时,该特性正在语言开发会议上“讨论中”。

EDIT

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

我试着改进了一下代码:

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

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

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

不是只接受字符串值,而是接受一个同时具有枚举值和"enumeration.value"形式值的string。工作代码如下-需要Java 1.8或更高版本。这也将使XML更加精确,因为您将看到类似color=" color "的内容。而不是color=" Red "。

您可以使用包含枚举名称点值名称的字符串调用acceptEnumeratedValue()方法。

该方法返回形式枚举值。

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


}

我将Vivek的解决方案封装到一个可以重用的实用程序类中。请注意,你仍然应该在你的类型上定义类型约束“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);
        }
    }
}

还应该考虑到,由于使用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。你可以在微软的发布说明中找到,参见Visual Studio 2017 15.7发布说明。谢谢@ mohammedelshawaf指出了这个有效的问题。

请注意,在我写这篇文章时,ReSharper 2018.1还不支持c# 7.3。ReSharper激活它突出显示Enum约束作为一个错误告诉我不能使用“系统”。数组”、“系统。委托”、“系统。枚举”、“系统。ValueType', 'object'作为类型参数约束。 ReSharper建议作为一个快速修复删除'Enum'约束类型参数T的方法

然而,如果你在Tools -> Options -> ReSharper Ultimate -> General下暂时关闭ReSharper,你会发现如果你使用VS 15.7或更高版本和c# 7.3或更高版本,语法是完美的。