如何尝试解析枚举值?

我想编写一个函数,它可以根据 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;
}
137900 次浏览

Have a look at the Enum class (struct ? ) itself. There is a Parse method on that but I'm not sure about a tryparse.

The only way to avoid exception handling is to use the GetNames() method, and we all know that exceptions shouldn't be abused for common application logic :)

There's currently no out of the box Enum.TryParse. This has been requested on Connect (Still no Enum.TryParse) and got a response indicating possible inclusion in the next framework after .NET 3.5. You'll have to implement the suggested workarounds for now.

Is caching a dynamically generated function/dictionary permissable?

Because you don't (appear to) know the type of the enum ahead of time, the first execution could generate something subsequent executions could take advantage of.

You could even cache the result of Enum.GetNames()

Are you trying to optimize for CPU or Memory? Do you really need to?

In the end you have to implement this around 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;
}

Additional notes:

  • Enum.TryParse is included in .NET 4. See here http://msdn.microsoft.com/library/dd991876(VS.100).aspx
  • Another approach would be to directly wrap Enum.Parse catching the exception thrown when it fails. This could be faster when a match is found, but will likely to slower if not. Depending on the data you are processing this may or may not be a net improvement.

EDIT: Just seen a better implementation on this, which caches the necessary information: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5

Enum.IsDefined will get things done. It may not be as efficient as a TryParse would probably be, but it will work without exception handling.

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

Worth noting: a TryParse method was added in .NET 4.0.

As others have said, you have to implement your own TryParse. Simon Mourier is providing a full implementation which takes care of everything.

If you are using bitfield enums (i.e. flags), you also have to handle a string like "MyEnum.Val1|MyEnum.Val2" which is a combination of two enum values. If you just call Enum.IsDefined with this string, it will return false, even though Enum.Parse handles it correctly.

Update

As mentioned by Lisa and Christian in the comments, Enum.TryParse is now available for C# in .NET4 and up.

MSDN Docs

As others already said, if you don't use Try&Catch, you need to use IsDefined or GetNames... Here are some samples...they basically are all the same, the first one handling nullable enums. I prefer the 2nd one as it's an extension on strings, not enums...but you can mix them as you want!

  • 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

I have an optimised implementation you could use in UnconstrainedMelody. Effectively it's just caching the list of names, but it's doing so in a nice, strongly typed, generically constrained way :)

This method will convert a type of enum:

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

It checks the underlying type and get the name against it to parse. If everything fails it will return default value.

There is not a TryParse because the Enum's type is not known until runtime. A TryParse that follows the same methodology as say the Date.TryParse method would throw an implicit conversion error on the ByRef parameter.

I suggest doing something like this:

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

Here is a custom implementation of EnumTryParse. Unlike other common implementations, it also supports enum marked with the Flags attribute.

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

Based on .NET 4.5

Sample code below

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

Reference : http://www.dotnetperls.com/enum-parse

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


}