#c# #generics #reflection #enums #type-conversion
#c# #перечисления #тип изменения
Вопрос:
Я получил Int16
значение из базы данных, и мне нужно преобразовать его в тип перечисления. К сожалению, это делается на уровне кода, который очень мало знает об объектах, за исключением того, что он может собрать с помощью отражения.
Таким образом, он завершает вызов Convert.ChangeType
, который завершается ошибкой с недопустимым исключением приведения.
Я нашел то, что считаю вонючим обходным путем, например:
String name = Enum.GetName(destinationType, value);
Object enumValue = Enum.Parse(destinationType, name, false);
Есть ли лучший способ, чтобы мне не приходилось выполнять эту строковую операцию?
Вот короткая, но полная программа, которую можно использовать, если кому-то нужно поэкспериментировать:
using System;
public class MyClass
{
public enum DummyEnum
{
Value0,
Value1
}
public static void Main()
{
Int16 value = 1;
Type destinationType = typeof(DummyEnum);
String name = Enum.GetName(destinationType, value);
Object enumValue = Enum.Parse(destinationType, name, false);
Console.WriteLine("" value " = " enumValue);
}
}
Комментарии:
1. Ой… Мне нужно перестать пытаться отвечать на подобные вопросы до того, как я выпью кофе…
2. Теперь я вижу консоль. Линия записи также находится на уровне, который не имеет доступа к типу перечисления. Я совершенно неправильно понял. Удалил мой (глупый) ответ.
Ответ №1:
Enum.ToObject(....
это то, что вы ищете!
C#
StringComparison enumValue = (StringComparison)Enum.ToObject(typeof(StringComparison), 5);
VB.NET
Dim enumValue As StringComparison = CType([Enum].ToObject(GetType(StringComparison), 5), StringComparison)
Если вы выполняете много преобразований перечислений, попробуйте использовать следующий класс, это сэкономит вам много кода.
public class Enum<EnumType> where EnumType : struct, IConvertible
{
/// <summary>
/// Retrieves an array of the values of the constants in a specified enumeration.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType[] GetValues()
{
return (EnumType[])Enum.GetValues(typeof(EnumType));
}
/// <summary>
/// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType Parse(string name)
{
return (EnumType)Enum.Parse(typeof(EnumType), name);
}
/// <summary>
/// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.
/// </summary>
/// <param name="name"></param>
/// <param name="ignoreCase"></param>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType Parse(string name, bool ignoreCase)
{
return (EnumType)Enum.Parse(typeof(EnumType), name, ignoreCase);
}
/// <summary>
/// Converts the specified object with an integer value to an enumeration member.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType ToObject(object value)
{
return (EnumType)Enum.ToObject(typeof(EnumType), value);
}
}
Теперь вместо записи (StringComparison)Enum.ToObject(typeof(StringComparison), 5);
вы можете просто писать Enum<StringComparison>.ToObject(5);
.
Комментарии:
1. Легенда! Не знал об этом. Разве stackoverflow не блестящий ?! 🙂
2. Более часа боролся с подобной проблемой, и это решило ее! Хорошо заработанный 1 даже через два года после его публикации 😉
3.
ToObject()
по-видимому, допускает значения, которых нет в перечислении, а также значения за пределами диапазона базового типа:Enum.ToObject(typeof(IntEnumType), (long)Int32.MaxValue 1)
4. @NelsonRothermel: сам C # допускает значения enum, которые не определены в перечислении, поэтому неудивительно. Ваша другая точка зрения более точна по сравнению с приведением, но, похоже, это просто частный случай вашей первой точки (кажется, сначала она приводится к базовому типу?)
5. Я видел этот метод много раз (в intellisense), но имел представление о его задаче. Разработчики могли бы дать этому методу более понятное название.
Ответ №2:
Основываясь на ответе @Peter, вот метод Nullable<int>
Enum
преобразования в:
public static class EnumUtils
{
public static bool TryParse<TEnum>(int? value, out TEnum result)
where TEnum: struct, IConvertible
{
if(!value.HasValue || !Enum.IsDefined(typeof(TEnum), value)){
result = default(TEnum);
return false;
}
result = (TEnum)Enum.ToObject(typeof(TEnum), value);
return true;
}
}
Использование EnumUtils.TryParse<YourEnumType>(someNumber, out result)
становится полезным для многих сценариев. Например, контроллер WebAPI в Asp.NET не имеет защиты по умолчанию от недопустимых параметров перечисления. Asp.NET будет просто использовать default(YourEnumType)
значение, даже если некоторые пропускают null
, -1000
, 500000
, "garbage string"
или полностью игнорируют параметр. Более того, ModelState
будет действительным во всех этих случаях, поэтому одним из решений является использование int?
type с пользовательской проверкой
public class MyApiController: Controller
{
[HttpGet]
public IActionResult Get(int? myEnumParam){
MyEnumType myEnumParamParsed;
if(!EnumUtils.TryParse<MyEnumType>(myEnumParam, out myEnumParamParsed)){
return BadRequest($"Error: parameter '{nameof(myEnumParam)}' is not specified or incorrect");
}
return this.Get(washingServiceTypeParsed);
}
private IActionResult Get(MyEnumType myEnumParam){
// here we can guarantee that myEnumParam is valid
}
Ответ №3:
Если вы сохраняете перечисление в таблице данных, но не знаете, какой столбец является перечислением, а какой — строкой / int, вы можете получить доступ к значению таким образом:
foreach (DataRow dataRow in myDataTable.Rows)
{
Trace.WriteLine("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
foreach (DataColumn dataCol in myDataTable.Columns)
{
object v = dataRow[dataCol];
Type t = dataCol.DataType;
bool e = false;
if (t.IsEnum) e = true;
Trace.WriteLine((dataCol.ColumnName ":").PadRight(30)
(e ? Enum.ToObject(t, v) : v));
}
}