【问题标题】:How to TryParse for Enum value?如何尝试解析枚举值?
【发布时间】:2009-07-04 16:33:24
【问题描述】:

我想编写一个函数,它可以根据enum 的可能值验证给定值(作为字符串传递)。在匹配的情况下,它应该返回枚举实例;否则,它应该返回一个默认值。

函数不能在内部使用try/catch,这不包括使用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;
}

【问题讨论】:

  • 我不明白这个问题;你是说“我想解决这个问题,但我不想使用任何能给我解决方案的方法。”有什么意义?
  • 您对 try/catch 解决方案的反感是什么?如果您试图避免异常,因为它们“昂贵”,请让自己休息一下。在 99% 的情况下,与您的主代码相比,抛出/捕获成本异常的成本可以忽略不计。
  • @Domenic:我只是在寻找比我已经知道的更好的解决方案。你会去铁路查询询问你已经知道的路线或火车吗:)。
  • @Yogi, @Thorarin:try...catch 永远是我最后的选择。关于昂贵,我们永远不知道。如果有人在 100 多个项目的列表上调用我的实用程序方法怎么办?
  • @Amby,简单地输入一个 try/catch 块的成本可以忽略不计。抛出异常的成本不是,但那应该是异常的,不是吗?另外,不要说“我们永远不知道”……分析代码并找出答案。不要浪费你的时间去想是不是有些慢,找出来!

标签: c# enums


【解决方案1】:

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

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

【讨论】:

  • 迄今为止我见过的最佳答案...没有 try/catch,没有 GetNames :)
  • IsDefined 上也没有忽略大小写
  • @Anthony:如果你想支持不区分大小写,你需要GetNames。在内部,所有这些方法(包括Parse)都使用GetHashEntry,它进行实际的反射——一次。从好的方面来说,.NET 4.0 有一个 TryParse,它也是通用的 :)
  • +1 它拯救了我的一天!我正在将一堆代码从 .NET 4 反向移植到 .NET 3.5,你救了我 :)
【解决方案2】:

正如其他人所说,您必须实现自己的TryParse。 Simon Mourier 提供了一个完整的实现来处理所有事情。

如果您使用位域枚举(即标志),您还必须处理像 "MyEnum.Val1|MyEnum.Val2" 这样的字符串,它是两个枚举值的组合。如果你只是用这个字符串调用Enum.IsDefined,它会返回false,即使Enum.Parse正确处理它。

更新

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

MSDN Docs

【讨论】:

  • 也许最不性感,但我同意这绝对是最好的,直到您的代码迁移到 .NET 4。
  • 如下所述,但并不真正可见:截至 .Net 4 Enum.TryParse 可用并且无需额外编码即可工作。更多信息可从 MSDN 获得:msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
  • 值得注意的是(截至撰写本文时 .NET 4.8 的参考源)Enum.TryParse 的 Microsoft 实现在某些情况下仍会在内部引发异常 - 如果字符串以数字开头或+/- 并且无法将完整字符串解析为数字,它将在内部捕获来自 Convert.ChangeType 的异常,并且可能会在内部从对 ToObject 的调用中抛出其他异常,然后在导致 @ 的函数中捕获987654331@ 返回码。如果需要真正无异常的解析器,Enum.TryParse 不会这样做。
【解决方案3】:

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

【讨论】:

  • 你提供了最好的实现,我已经将它用于我自己的目的;但是,我想知道为什么您使用 Activator.CreateInstance(type) 创建默认枚举值而不是 Enum.ToObject(type, 0)。只是口味问题?
  • @Pierre - 嗯......不,当时它看起来更自然:-) 也许 Enum.ToObject 更快,因为它在内部使用内部调用 InternalBoxEnum?我从来没有检查过...
  • 如下所述,但并不真正可见:截至 .Net 4 Enum.TryParse 可用并且无需额外编码即可工作。更多信息可从 MSDN 获得:msdn.microsoft.com/library/vstudio/dd991317%28v=vs.100%29.aspx
【解决方案4】:

最后你必须围绕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,这可能对枚举有效,也可能无效。
  • Damieng 博客中的实现支持带有Flags 属性的枚举。
【解决方案5】:

基于 .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

【讨论】:

    【解决方案6】:
    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)) {
    
    }
    

    【讨论】:

    • 我得到,当前上下文中不存在状态
    • 在我的情况下,这可以代替Enum.TryParse&lt;EnumStatus&gt;(someStatusAsInt.ToString(), out EnumStatus status)
    【解决方案7】:

    我有一个优化的实现,您可以在UnconstrainedMelody 中使用。实际上,它只是在缓存名称列表,但它是以一种很好的、​​强类型的、一般受限的方式来缓存的:)

    【讨论】:

      【解决方案8】:

      目前没有开箱即用的 Enum.TryParse。这已在 Connect (Still no Enum.TryParse) 上提出请求,并得到响应,表明可能包含在 .NET 3.5 之后的下一个框架中。您现在必须实施建议的解决方法。

      【讨论】:

        【解决方案9】:

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

        【讨论】:

        • 这不是唯一的方式。 Enum.IsDefined(..) 将防止在用户代码中抛出异常。
        【解决方案10】:

        是否允许缓存动态生成的函数/字典?

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

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

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

        【讨论】:

        • 想法是优化CPU。同意我可以在成本记忆中做到这一点。但它不是我正在寻找的解决方案。谢谢。
        【解决方案11】:

        正如其他人已经说过的,如果您不使用 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

        【讨论】:

          【解决方案12】:

          没有 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`方法返回bool,并将结果作为@传递987654326@参数。对于那些返回null不是有效结果的类类型,使用null返回表示失败没有困难。
          【解决方案13】:

          看看 Enum 类 (struct ? ) 本身。有一个 Parse 方法,但我不确定 tryparse。

          【讨论】:

          • 我知道 Enum.Parse(typeof(TEnum), strEnumValue) 方法。如果 strEnumValue 无效,它会抛出 ArgumentException。正在寻找 TryParse.......
          【解决方案14】:

          这个方法会转换一个枚举类型:

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

          它检查底层类型并根据它获取名称以进行解析。如果一切都失败了,它将返回默认值。

          【讨论】:

          • 这是在做什么 "Enum.GetName(typeof(HLink.ViewModels.ClaimHeaderViewModel.ClaimStatus), EnumValue)" 可能对你的本地代码有些依赖。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-19
          • 1970-01-01
          • 2014-09-24
          • 2021-10-24
          相关资源
          最近更新 更多