Come è possibile implementare il metodo Enum.TryParse di .NET 4 in .NET 3.5?Implementazione di Enum.TryParse in .NET 3.5
public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct
Come è possibile implementare il metodo Enum.TryParse di .NET 4 in .NET 3.5?Implementazione di Enum.TryParse in .NET 3.5
public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct
Ho impiegato più tempo di quanto speravo per farlo correttamente, ma funziona ed è stato testato. Spero che questo salvi qualcuno un po 'di tempo!
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;
}
Non sarà un metodo statico su Enum (metodi di estensione statici non del tutto ha senso), ma dovrebbe funzionare
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
{
return false;
}
return true;
}
}
Non mi piace utilizzare un try-catch
per gestire eventuali errori di conversione o altri eventi non eccezionali come parte del normale flusso della mia applicazione, quindi il mio metodo Enum.TryParse
per .NET 3.5 e versioni precedenti utilizza il metodo Enum.IsDefined()
per assicurarci che non ci sia un'eccezione generata da Enum.Parse() . È anche possibile includere alcuni assegni null su value
per impedire un ArgumentNullException
se il valore è nullo.
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;
}
Ovviamente questo metodo non risiederanno nella classe Enum
quindi sarà necessario una classe per includere questo in quanto sarebbe opportuno.
Una limitazione è la mancanza di un vincolo enum
su metodi generici, quindi è necessario considerare come si desidera gestire i tipi errati. Enum.IsDefined
getterà uno 10 se TEnum
non è un enum
ma l'unica altra opzione è un controllo di runtime e un'eccezione diversa, quindi in genere non aggiungo un controllo aggiuntivo e semplicemente permetto al controllo dei tipi in questi metodi di gestire per me. Prenderò in considerazione l'aggiunta di IConvertible
come un altro vincolo, solo per aiutare a limitare ulteriormente il tipo.
mi piace questo uno migliore del mio approccio (ora cancellato) di sicuro. –
+1 Concordato, se avessi passato un altro minuto a esaminare i metodi Enum e visto il metodo IsDefined, è molto probabile che cosa sarebbe successo. :) –
Grazie per la risposta. Sebbene sia stato un buon inizio, ci sono parecchie altre considerazioni per renderlo alla pari con l'implementazione di .NET 4. (ad es. Valore come flag separati da virgole, valore come numero, tipo enum numerico) –
A NLog Abbiamo anche bisogno Enum.TryParse
per .Net 3.5. Abbiamo implementato le funzionalità di base (solo analisi, maiuscole/minuscole e insensibili, senza bandiere) influenzate da questo post.
Questa implementazione di base è altamente testata in unità, quindi ha lo stesso comportamento dell'implementazione di Net 4 di Microsoft.
Il codice può essere trovato alla the NLog GitHub, e anche il unit tests are on GitHub (xUnit)
Uso (tutte le versioni) - .Net stessa firma Net 4,0
EnumHelpers.TryParse(value, true, out parsedValue) //case insensitive
//or
EnumHelpers.TryParse(value, out parsedValue)
Puoi cambiare quelle istruzioni switch in reflection anziché elencando tutti i tipi possibili e abbrevia il codice. –
@NickTurner: le prestazioni sono importanti per questo metodo, la riflessione non è buona per le prestazioni. –
nota: il 'Enum.Tryparse' in .net 4+ non getterà queste eccezioni. https://msdn.microsoft.com/en-us/library/dd991317%28v=vs.110%29.aspx – Julian