如何尝试为Enum值解析?


94

我想编写一个函数,可以针对的可能值验证给定值(作为字符串传递)enum。如果匹配,则应返回枚举实例;否则,应返回默认值。

该函数可能在内部不使用try/ catch,这排除了using Enum.Parse,当给定无效参数时会抛出异常。

我想沿TryParse函数使用一些东西来实现这一点:

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
   object enumValue;
   if (!TryParse (typeof (TEnum), strEnumValue, out enumValue))
   {
       return defaultValue;
   }
   return (TEnum) enumValue;
}

8
我不明白这个问题。您说的是“我想解决这个问题,但我不想使用任何可以给我解决方案的方法。” 重点是什么?
多梅尼克2009年

1
您对尝试/捕获解决方案的厌恶是什么?如果您试图避免异常,因为它们“昂贵”,请让自己休息一下。在99%的情况下,与主代码相比,投掷/捕获成本异常例外可以忽略不计。
SolutionYogi

1
异常处理的成本还不错。地狱,所有这种枚举转换的内部实现充满了异常处理。我真的不喜欢在正常应用程序逻辑期间抛出和捕获的异常。有时打破所有抛出的异常(即使被捕获)也很有用。到处都是抛出异常将使使用起来更加烦人:)
Thorarin

3
@Domenic:我正在寻找比我已经知道的更好的解决方案。您是否会去铁路询问以询问您已经知道的路线或火车:)。
Manish Basantani

2
@Amby,只需输入try / catch块的成本就可以忽略不计。不是抛出异常的代价,但是那应该是例外的,不是吗?另外,不要说“我们永远不知道” ...分析代码并找出答案。不要浪费时间想知道事情是否缓慢,找出来!
akmad

Answers:


31

正如其他人所说,您必须实现自己的TryParse。西蒙·穆里尔(Simon Mourier)提供了一个全面的实施方案,可解决所有问题。

如果使用位域枚举(即标志),则还必须处理类似"MyEnum.Val1|MyEnum.Val2"两个枚举值的组合的字符串。如果仅Enum.IsDefined使用此字符串进行调用,即使Enum.Parse正确处理它也将返回false 。

更新资料

正如Lisa和Christian在评论中提到的那样Enum.TryParse,.NET4及更高版本中的C#现在可用。

MSDN文件


或许最不性感,但我同意这绝对是最好的,直到你的代码迁移到.NET 4
丽莎

1
如下所述,但并不是真正可见:从.Net 4开始,Enum.TryParse可用并且无需额外编码即可工作。可从MSDN获得更多信息:msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

106

Enum.IsDefined将完成任务。它可能不如TryParse那样高效,但是它可以在没有异常处理的情况下工作。

public static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
{
    if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
        return defaultValue;

    return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
}

值得注意的是:TryParse.NET 4.0中添加了一种方法。


1
到目前为止,我看到的最好的答案...没有尝试/捕获,没有GetNames :)
Thomas Levesque


6
IsDefined上也没有忽略的案例
Anthony Johnston

2
@Anthony:如果要支持不区分大小写,则需要GetNames。在内部,所有这些方法(包括Parse)都使用进行GetHashEntry一次实际的反射-。从好的方面来说,.NET 4.0具有TryParse,它也很通用:)
Thorarin 2010年

+1拯救了我的一天!我将一堆代码从.NET 4移植到.NET 3.5,您救了我:)
daitangio 2011年

20

这是的自定义实现EnumTryParse。与其他常见实现不同,它还支持标有Flags属性的枚举。

    /// <summary>
    /// Converts the string representation of an enum to its Enum equivalent value. A return value indicates whether the operation succeeded.
    /// This method does not rely on Enum.Parse and therefore will never raise any first or second chance exception.
    /// </summary>
    /// <param name="type">The enum target type. May not be null.</param>
    /// <param name="input">The input text. May be null.</param>
    /// <param name="value">When this method returns, contains Enum equivalent value to the enum contained in input, if the conversion succeeded.</param>
    /// <returns>
    /// true if s was converted successfully; otherwise, false.
    /// </returns>
    public static bool EnumTryParse(Type type, string input, out object value)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        if (!type.IsEnum)
            throw new ArgumentException(null, "type");

        if (input == null)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        input = input.Trim();
        if (input.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        string[] names = Enum.GetNames(type);
        if (names.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        Type underlyingType = Enum.GetUnderlyingType(type);
        Array values = Enum.GetValues(type);
        // some enums like System.CodeDom.MemberAttributes *are* flags but are not declared with Flags...
        if ((!type.IsDefined(typeof(FlagsAttribute), true)) && (input.IndexOfAny(_enumSeperators) < 0))
            return EnumToObject(type, underlyingType, names, values, input, out value);

        // multi value enum
        string[] tokens = input.Split(_enumSeperators, StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length == 0)
        {
            value = Activator.CreateInstance(type);
            return false;
        }

        ulong ul = 0;
        foreach (string tok in tokens)
        {
            string token = tok.Trim(); // NOTE: we don't consider empty tokens as errors
            if (token.Length == 0)
                continue;

            object tokenValue;
            if (!EnumToObject(type, underlyingType, names, values, token, out tokenValue))
            {
                value = Activator.CreateInstance(type);
                return false;
            }

            ulong tokenUl;
            switch (Convert.GetTypeCode(tokenValue))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                    tokenUl = (ulong)Convert.ToInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;

                //case TypeCode.Byte:
                //case TypeCode.UInt16:
                //case TypeCode.UInt32:
                //case TypeCode.UInt64:
                default:
                    tokenUl = Convert.ToUInt64(tokenValue, CultureInfo.InvariantCulture);
                    break;
            }

            ul |= tokenUl;
        }
        value = Enum.ToObject(type, ul);
        return true;
    }

    private static char[] _enumSeperators = new char[] { ',', ';', '+', '|', ' ' };

    private static object EnumToObject(Type underlyingType, string input)
    {
        if (underlyingType == typeof(int))
        {
            int s;
            if (int.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(uint))
        {
            uint s;
            if (uint.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ulong))
        {
            ulong s;
            if (ulong.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(long))
        {
            long s;
            if (long.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(short))
        {
            short s;
            if (short.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(ushort))
        {
            ushort s;
            if (ushort.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(byte))
        {
            byte s;
            if (byte.TryParse(input, out s))
                return s;
        }

        if (underlyingType == typeof(sbyte))
        {
            sbyte s;
            if (sbyte.TryParse(input, out s))
                return s;
        }

        return null;
    }

    private static bool EnumToObject(Type type, Type underlyingType, string[] names, Array values, string input, out object value)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (string.Compare(names[i], input, StringComparison.OrdinalIgnoreCase) == 0)
            {
                value = values.GetValue(i);
                return true;
            }
        }

        if ((char.IsDigit(input[0]) || (input[0] == '-')) || (input[0] == '+'))
        {
            object obj = EnumToObject(underlyingType, input);
            if (obj == null)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
            value = obj;
            return true;
        }

        value = Activator.CreateInstance(type);
        return false;
    }

1
您提供了最佳的实现,而我已将其用于自己的目的;但是,我想知道为什么您使用Activator.CreateInstance(type)创建默认的枚举值而不是Enum.ToObject(type, 0)。只是口味问题?
皮埃尔·阿诺

1
@Pierre-Hmmm ...不,那似乎更自然了:-)也许Enum.ToObject更快,因为它在内部使用内部调用InternalBoxEnum?我从没检查过……
Simon Mourier

2
如下所述,但并不是真正可见:从.Net 4开始,Enum.TryParse可用并且无需额外编码即可工作。可从MSDN获得更多信息:msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
Christian

16

最后,您必须实现以下目标Enum.GetNames

public bool TryParseEnum<T>(string str, bool caseSensitive, out T value) where T : struct {
    // Can't make this a type constraint...
    if (!typeof(T).IsEnum) {
        throw new ArgumentException("Type parameter must be an enum");
    }
    var names = Enum.GetNames(typeof(T));
    value = (Enum.GetValues(typeof(T)) as T[])[0];  // For want of a better default
    foreach (var name in names) {
        if (String.Equals(name, str, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) {
            value = (T)Enum.Parse(typeof(T), name);
            return true;
        }
    }
    return false;
}

补充笔记:

  • Enum.TryParse 包含在.NET 4中。请参见此处 http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • 另一种方法是直接包装Enum.Parse捕获在失败时引发的异常。找到匹配项时,速度可能会更快,但如果找不到则可能会更慢。根据您正在处理的数据,这可能不是净改进。

编辑:刚刚看到了一个更好的实现,它缓存了必要的信息:http : //damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net- 3-5


我建议使用default(T)设置默认值。事实证明,这不适用于所有枚举。例如,如果枚举的基础类型为int,则default(T)将始终返回0,这可能对枚举无效。
Daniel Ballinger

Damieng博客上的实现支持带有Flags属性的枚举。
Uwe Keim

9

基于.NET 4.5

下面的示例代码

using System;

enum Importance
{
    None,
    Low,
    Medium,
    Critical
}

class Program
{
    static void Main()
    {
    // The input value.
    string value = "Medium";

    // An unitialized variable.
    Importance importance;

    // Call Enum.TryParse method.
    if (Enum.TryParse(value, out importance))
    {
        // We now have an enum type.
        Console.WriteLine(importance == Importance.Medium);
    }
    }
}

参考:http : //www.dotnetperls.com/enum-parse



4
enum EnumStatus
{
    NAO_INFORMADO = 0,
    ENCONTRADO = 1,
    BLOQUEADA_PELO_ENTREGADOR = 2,
    DISPOSITIVO_DESABILITADO = 3,
    ERRO_INTERNO = 4,
    AGARDANDO = 5
}

...

if (Enum.TryParse<EnumStatus>(item.status, out status)) {

}

2

当前没有开箱即用的Enum.TryParse。这是在Connect上请求的(仍然没有Enum.TryParse),并且得到了指示可能包含在.NET 3.5之后的下一个框架中的响应。您现在必须实施建议的解决方法。


1

避免异常处理的唯一方法是使用GetNames()方法,而且我们都知道,不应将异常用于常见的应用程序逻辑:)


1
这不是唯一的方法。Enum.IsDefined(..)将防止在用户代码中引发异常。
Thorarin

1

是否可以缓存动态生成的函数/字典?

因为您不(似乎)提前知道枚举的类型,所以第一次执行可能会产生一些后续执行可以利用的东西。

您甚至可以缓存Enum.GetNames()的结果

您是否要针对CPU或内存进行优化?您真的需要吗?


想法是优化CPU。同意我可以付出代价的代价做到这一点。但它不是我正在寻找的解决方案。谢谢。
Manish Basantani

0

正如其他人已经说过的,如果您不使用Try&Catch,则需要使用IsDefined或GetNames ...这是一些示例...它们基本上都是相同的,第一个处理可为空的枚举。我更喜欢第二个,因为它是字符串的扩展,而不是枚举...但是您可以根据需要混合它们!

  • www.objectreference.net/post/Enum-TryParse-Extension-Method.aspx
  • flatlinerdoa.spaces.live.com/blog/cns!17124D03A9A052B0!605.entry
  • mironabramson.com/blog/post/2008/03/Another-version-for-the-missing-method-EnumTryParse.aspx
  • lazyloading.blogspot.com/2008/04/enumtryparse-with-net-35-extension.html

0

没有TryParse,因为直到运行时才知道Enum的类型。遵循与Date.TryParse方法相同的方法的TryParse会在ByRef参数上引发隐式转换错误。

我建议做这样的事情:

//1 line call to get value
MyEnums enumValue = (Sections)EnumValue(typeof(Sections), myEnumTextValue, MyEnums.SomeEnumDefault);

//Put this somewhere where you can reuse
public static object EnumValue(System.Type enumType, string value, object NotDefinedReplacement)
{
    if (Enum.IsDefined(enumType, value)) {
        return Enum.Parse(enumType, value);
    } else {
        return Enum.Parse(enumType, NotDefinedReplacement);
    }
}

对于Try结果可能是值类型或null可能是合法结果的Dictionary.TryGetValue, which has both such traits), the normal pattern is for a 方法(例如,使用Try`方法return bool,并将结果作为out参数传递。对于那些返回类类型null为无效结果的方法,使用nullreturn 并不困难)来表示失败。
supercat

-1

看一下Enum类(结构?)本身。上面有一个Parse方法,但是我不确定tryparse。


我知道Enum.Parse(typeof(TEnum),strEnumValue)方法。如果strEnumValue无效,则抛出ArgumentException。寻找TryParse .......
Manish Basantani 09年

-2

此方法将转换一种枚举类型:

  public static TEnum ToEnum<TEnum>(object EnumValue, TEnum defaultValue)
    {
        if (!Enum.IsDefined(typeof(TEnum), EnumValue))
        {
            Type enumType = Enum.GetUnderlyingType(typeof(TEnum));
            if ( EnumValue.GetType() == enumType )
            {
                string name = Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue);
                if( name != null)
                    return (TEnum)Enum.Parse(typeof(TEnum), name);
                return defaultValue;
            }
        }
        return (TEnum)Enum.Parse(typeof(TEnum), EnumValue.ToString());
    } 

它检查基础类型,并获取要解析的名称。如果一切失败,它将返回默认值。


3
这是做什么的“ Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus),EnumValue)”可能对您的本地代码有些依赖性。
Manish Basantani 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.