.NET3.5で.NET4のEnum.TryParseメソッドを実装するにはどうすればよいですか?
public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct
try-catch
アプリケーションの通常のフローの一部として、変換の失敗やその他の例外ではないイベントを処理するためにを使用するのは嫌いです。そのため、 Enum.TryParse
.NET 3.5以前の独自のメソッドでは、このEnum.IsDefined()
メソッドを使用して、例外が発生しないようにします。 Enum.Parse()によってスローされます。if値がnull にvalue
なるのを防ぐために、いくつかのnullチェックを含めることもできます。ArgumentNullException
public static bool TryParse<TEnum>(string value, out TEnum result)
where TEnum : struct, IConvertible
{
var retValue = value == null ?
false :
Enum.IsDefined(typeof(TEnum), value);
result = retValue ?
(TEnum)Enum.Parse(typeof(TEnum), value) :
default(TEnum);
return retValue;
}
明らかに、このメソッドはクラスに存在しないEnum
ため、これを含めるためのクラスが必要になります。
1つの制限は、enum
ジェネリックメソッドに制約がないことです。そのため、誤った型をどのように処理するかを検討する必要があります。 ifはEnum.IsDefined
スローされませんが、他の唯一のオプションはランタイムチェックと別の例外のスローです。したがって、通常、追加のチェックを追加せず、これらのメソッドの型チェックで処理できるようにします。タイプをさらに制約するために、別の制約として追加することを検討します。ArgumentException
TEnum
enum
IConvertible
私がこれを正しくすることを望んでいたよりも長くかかりましたが、それは機能し、テストされています。これが誰かの時間を節約することを願っています!
private static readonly char[] FlagDelimiter = new [] { ',' };
public static bool TryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct {
if (string.IsNullOrEmpty(value)) {
result = default(TEnum);
return false;
}
var enumType = typeof(TEnum);
if (!enumType.IsEnum)
throw new ArgumentException(string.Format("Type '{0}' is not an enum", enumType.FullName));
result = default(TEnum);
// Try to parse the value directly
if (Enum.IsDefined(enumType, value)) {
result = (TEnum)Enum.Parse(enumType, value);
return true;
}
// Get some info on enum
var enumValues = Enum.GetValues(enumType);
if (enumValues.Length == 0)
return false; // probably can't happen as you cant define empty enum?
var enumTypeCode = Type.GetTypeCode(enumValues.GetValue(0).GetType());
// Try to parse it as a flag
if (value.IndexOf(',') != -1) {
if (!Attribute.IsDefined(enumType, typeof(FlagsAttribute)))
return false; // value has flags but enum is not flags
// todo: cache this for efficiency
var enumInfo = new Dictionary<string, object>();
var enumNames = Enum.GetNames(enumType);
for (var i = 0; i < enumNames.Length; i++)
enumInfo.Add(enumNames[i], enumValues.GetValue(i));
ulong retVal = 0;
foreach(var name in value.Split(FlagDelimiter)) {
var trimmedName = name.Trim();
if (!enumInfo.ContainsKey(trimmedName))
return false; // Enum has no such flag
var enumValueObject = enumInfo[trimmedName];
ulong enumValueLong;
switch (enumTypeCode) {
case TypeCode.Byte:
enumValueLong = (byte)enumValueObject;
break;
case TypeCode.SByte:
enumValueLong = (byte)((sbyte)enumValueObject);
break;
case TypeCode.Int16:
enumValueLong = (ushort)((short)enumValueObject);
break;
case TypeCode.Int32:
enumValueLong = (uint)((int)enumValueObject);
break;
case TypeCode.Int64:
enumValueLong = (ulong)((long)enumValueObject);
break;
case TypeCode.UInt16:
enumValueLong = (ushort)enumValueObject;
break;
case TypeCode.UInt32:
enumValueLong = (uint)enumValueObject;
break;
case TypeCode.UInt64:
enumValueLong = (ulong)enumValueObject;
break;
default:
return false; // should never happen
}
retVal |= enumValueLong;
}
result = (TEnum)Enum.ToObject(enumType, retVal);
return true;
}
// the value may be a number, so parse it directly
switch (enumTypeCode) {
case TypeCode.SByte:
sbyte sb;
if (!SByte.TryParse(value, out sb))
return false;
result = (TEnum)Enum.ToObject(enumType, sb);
break;
case TypeCode.Byte:
byte b;
if (!Byte.TryParse(value, out b))
return false;
result = (TEnum)Enum.ToObject(enumType, b);
break;
case TypeCode.Int16:
short i16;
if (!Int16.TryParse(value, out i16))
return false;
result = (TEnum)Enum.ToObject(enumType, i16);
break;
case TypeCode.UInt16:
ushort u16;
if (!UInt16.TryParse(value, out u16))
return false;
result = (TEnum)Enum.ToObject(enumType, u16);
break;
case TypeCode.Int32:
int i32;
if (!Int32.TryParse(value, out i32))
return false;
result = (TEnum)Enum.ToObject(enumType, i32);
break;
case TypeCode.UInt32:
uint u32;
if (!UInt32.TryParse(value, out u32))
return false;
result = (TEnum)Enum.ToObject(enumType, u32);
break;
case TypeCode.Int64:
long i64;
if (!Int64.TryParse(value, out i64))
return false;
result = (TEnum)Enum.ToObject(enumType, i64);
break;
case TypeCode.UInt64:
ulong u64;
if (!UInt64.TryParse(value, out u64))
return false;
result = (TEnum)Enum.ToObject(enumType, u64);
break;
default:
return false; // should never happen
}
return true;
}
列挙型の静的メソッドではありませんが(静的拡張メソッドはあまり意味がありません)、機能するはずです
public static class EnumHelpers
{
public static bool TryParse<TEnum>(string value, out TEnum result)
where TEnum : struct
{
try
{
result = (TEnum)Enum.Parse(typeof(TEnum), value);
}
catch
{
result = default;
return false;
}
return true;
}
}
NLogでは、.Net3.5も必要でしEnum.TryParse
た。この投稿の影響を受ける基本機能(解析のみ、大文字と小文字の区別、フラグなし)を実装しました。
この基本的な実装は高度な単体テストが行われているため、Microsoftの.Net4実装と同じ動作をします。
/// <summary>
/// Enum.TryParse implementation for .net 3.5
///
/// </summary>
/// <returns></returns>
/// <remarks>Don't uses reflection</remarks>
// ReSharper disable once UnusedMember.Local
private static bool TryParseEnum_net3<TEnum>(string value, bool ignoreCase, out TEnum result) where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum())
throw new ArgumentException($"Type '{enumType.FullName}' is not an enum");
if (StringHelpers.IsNullOrWhiteSpace(value))
{
result = default(TEnum);
return false;
}
try
{
result = (TEnum)Enum.Parse(enumType, value, ignoreCase);
return true;
}
catch (Exception)
{
result = default(TEnum);
return false;
}
}
そして使用しています:
public static class StringHelpers
{
/// <summary>
/// IsNullOrWhiteSpace, including for .NET 3.5
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[ContractAnnotation("value:null => true")]
internal static bool IsNullOrWhiteSpace(string value)
{
#if NET3_5
if (value == null) return true;
if (value.Length == 0) return true;
return String.IsNullOrEmpty(value.Trim());
#else
return string.IsNullOrWhiteSpace(value);
#endif
}
}
コードはNLogGitHubにあり、単体テストはGitHub (xUnit)にあります。