Есть ли способ проверить, является ли int законным перечислением в С#?
Я прочитал несколько сообщений SO, и кажется, что основная операция отсутствует.
public enum LoggingLevel
{
Off = 0,
Error = 1,
Warning = 2,
Info = 3,
Debug = 4,
Trace = 5
};
if (s == "LogLevel")
{
_log.LogLevel = (LoggingLevel)Convert.ToInt32("78");
_log.LogLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), "78");
_log.WriteDebug(_log.LogLevel.ToString());
}
Это не вызывает никаких исключений, он счастлив сохранить 78
. Есть ли способ проверить значение, входящее в перечисление?
Ответы
Ответ 1
Отъезд Enum.IsDefined
Использование:
if(Enum.IsDefined(typeof(MyEnum), value))
MyEnum a = (MyEnum)value;
Это пример с этой страницы:
using System;
[Flags] public enum PetType
{
None = 0, Dog = 1, Cat = 2, Rodent = 4, Bird = 8, Reptile = 16, Other = 32
};
public class Example
{
public static void Main()
{
object value;
// Call IsDefined with underlying integral value of member.
value = 1;
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
// Call IsDefined with invalid underlying integral value.
value = 64;
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
// Call IsDefined with string containing member name.
value = "Rodent";
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
// Call IsDefined with a variable of type PetType.
value = PetType.Dog;
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
value = PetType.Dog | PetType.Cat;
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
// Call IsDefined with uppercase member name.
value = "None";
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
value = "NONE";
Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
// Call IsDefined with combined value
value = PetType.Dog | PetType.Bird;
Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
value = value.ToString();
Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
}
}
В примере показан следующий вывод:
// 1: True
// 64: False
// Rodent: True
// Dog: True
// Dog, Cat: False
// None: True
// NONE: False
// 9: False
// Dog, Bird: False
Ответ 2
Вышеупомянутые решения не связаны с ситуациями [Flags]
.
Мое решение ниже может иметь некоторые проблемы с производительностью (я уверен, что можно было бы оптимизировать по-разному), но по существу он всегда будет проверять, действительно ли значение enum или нет.
Он опирается на три предположения:
- Значения Enum в С# разрешены только
int
, абсолютно ничего другого
- Имена перечислений в С# должны начинаться с буквенного символа
- Нет допустимого имени перечисления со знаком минус:
-
Вызов ToString()
в перечислении возвращает либо значение int
, если нет перечисления (флаг или нет). Если допустимое значение перечисления сопоставлено, оно будет печатать имя совпадения().
Итак:
[Flags]
enum WithFlags
{
First = 1,
Second = 2,
Third = 4,
Fourth = 8
}
((WithFlags)2).ToString() ==> "Second"
((WithFlags)(2 + 4)).ToString() ==> "Second, Third"
((WithFlags)20).ToString() ==> "20"
С учетом этих двух правил можно предположить, что если .NET Framework корректно выполняет свою работу, то любые вызовы действительного метода enum ToString()
приведут к тому, что в качестве первого символа будет иметь алфавитный символ:
public static bool IsValid<TEnum>(this TEnum enumValue)
where TEnum : struct
{
var firstChar = enumValue.ToString()[0];
return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}
Можно назвать это "взломом", но преимущества заключаются в том, что, полагаясь на собственную реализацию Microsoft стандартов Enum
и С#, вы не полагаетесь на свой собственный потенциально ошибочный код или проверки. В ситуациях, когда производительность не является исключительно критичной, это сэкономит много неприятных утверждений switch
или других проверок!
Edit
Благодаря @ChaseMedallion за указание, что моя первоначальная реализация не поддерживает отрицательные значения. Это было исправлено и предоставлены тесты.
И тесты для резервного копирования:
[TestClass]
public class EnumExtensionsTests
{
[Flags]
enum WithFlags
{
First = 1,
Second = 2,
Third = 4,
Fourth = 8
}
enum WithoutFlags
{
First = 1,
Second = 22,
Third = 55,
Fourth = 13,
Fifth = 127
}
enum WithoutNumbers
{
First, // 1
Second, // 2
Third, // 3
Fourth // 4
}
enum WithoutFirstNumberAssigned
{
First = 7,
Second, // 8
Third, // 9
Fourth // 10
}
enum WithNagativeNumbers
{
First = -7,
Second = -8,
Third = -9,
Fourth = -10
}
[TestMethod]
public void IsValidEnumTests()
{
Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
Assert.IsTrue(((WithFlags)(1 | 4 | 2)).IsValid());
Assert.IsTrue(((WithFlags)(2)).IsValid());
Assert.IsTrue(((WithFlags)(3)).IsValid());
Assert.IsTrue(((WithFlags)(1 + 2 + 4 + 8)).IsValid());
Assert.IsFalse(((WithFlags)(16)).IsValid());
Assert.IsFalse(((WithFlags)(17)).IsValid());
Assert.IsFalse(((WithFlags)(18)).IsValid());
Assert.IsFalse(((WithFlags)(0)).IsValid());
Assert.IsTrue(((WithoutFlags)1).IsValid());
Assert.IsTrue(((WithoutFlags)22).IsValid());
Assert.IsTrue(((WithoutFlags)(53 | 6)).IsValid()); // Will end up being Third
Assert.IsTrue(((WithoutFlags)(22 | 25 | 99)).IsValid()); // Will end up being Fifth
Assert.IsTrue(((WithoutFlags)55).IsValid());
Assert.IsTrue(((WithoutFlags)127).IsValid());
Assert.IsFalse(((WithoutFlags)48).IsValid());
Assert.IsFalse(((WithoutFlags)50).IsValid());
Assert.IsFalse(((WithoutFlags)(1 | 22)).IsValid());
Assert.IsFalse(((WithoutFlags)(9 | 27 | 4)).IsValid());
Assert.IsTrue(((WithoutNumbers)0).IsValid());
Assert.IsTrue(((WithoutNumbers)1).IsValid());
Assert.IsTrue(((WithoutNumbers)2).IsValid());
Assert.IsTrue(((WithoutNumbers)3).IsValid());
Assert.IsTrue(((WithoutNumbers)(1 | 2)).IsValid()); // Will end up being Third
Assert.IsTrue(((WithoutNumbers)(1 + 2)).IsValid()); // Will end up being Third
Assert.IsFalse(((WithoutNumbers)4).IsValid());
Assert.IsFalse(((WithoutNumbers)5).IsValid());
Assert.IsFalse(((WithoutNumbers)25).IsValid());
Assert.IsFalse(((WithoutNumbers)(1 + 2 + 3)).IsValid());
Assert.IsTrue(((WithoutFirstNumberAssigned)7).IsValid());
Assert.IsTrue(((WithoutFirstNumberAssigned)8).IsValid());
Assert.IsTrue(((WithoutFirstNumberAssigned)9).IsValid());
Assert.IsTrue(((WithoutFirstNumberAssigned)10).IsValid());
Assert.IsFalse(((WithoutFirstNumberAssigned)11).IsValid());
Assert.IsFalse(((WithoutFirstNumberAssigned)6).IsValid());
Assert.IsFalse(((WithoutFirstNumberAssigned)(7 | 9)).IsValid());
Assert.IsFalse(((WithoutFirstNumberAssigned)(8 + 10)).IsValid());
Assert.IsTrue(((WithNagativeNumbers)(-7)).IsValid());
Assert.IsTrue(((WithNagativeNumbers)(-8)).IsValid());
Assert.IsTrue(((WithNagativeNumbers)(-9)).IsValid());
Assert.IsTrue(((WithNagativeNumbers)(-10)).IsValid());
Assert.IsFalse(((WithNagativeNumbers)(-11)).IsValid());
Assert.IsFalse(((WithNagativeNumbers)(7)).IsValid());
Assert.IsFalse(((WithNagativeNumbers)(8)).IsValid());
}
}
Ответ 3
Канонический ответ будет Enum.IsDefined
, но это: немного медленный, если он используется в узком цикле, и b: не полезно для [Flags]
перечислений.
Лично я перестану беспокоиться об этом, и просто switch
надлежащим образом, вспомнив:
- если это нормально, чтобы не распознать все (и просто ничего не делать), тогда не добавляйте
default:
(или введите пустой default:
, объясняя почему)
- если существует разумное поведение по умолчанию, поместите это в
default:
- в противном случае обрабатывайте те, о которых вы знаете, и бросаете исключение для остальных:
Так же:
switch(someflag) {
case TriBool.Yes:
DoSomething();
break;
case TriBool.No:
DoSomethingElse();
break;
case TriBool.FileNotFound:
DoSomethingOther();
break;
default:
throw new ArgumentOutOfRangeException("someflag");
}
Ответ 4
Использование:
Enum.IsDefined ( typeof ( Enum ), EnumValue );
Ответ 5
Используйте Enum.IsDefined.
Ответ 6
Чтобы иметь дело с [Flags]
, вы также можете использовать это решение из Cookbook в С#:
Сначала добавьте новое значение ALL
к вашему перечислению:
[Flags]
enum Language
{
CSharp = 1, VBNET = 2, VB6 = 4,
All = (CSharp | VBNET | VB6)
}
Затем проверьте, находится ли значение в ALL
:
public bool HandleFlagsEnum(Language language)
{
if ((language & Language.All) == language)
{
return (true);
}
else
{
return (false);
}
}
Ответ 7
Один из способов сделать это - полагаться на листинг и перечисление на преобразование строк. При литье int в тип Enum int либо преобразуется в соответствующее значение перечисления, либо результирующее перечисление просто содержит int как значение, если значение переменной enum не определено для int.
enum NetworkStatus{
Unknown=0,
Active,
Slow
}
int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();
Не проверен для любых случаев с краями.
Ответ 8
Как сказали другие, Enum.IsDefined
возвращает false
даже если у вас есть допустимая комбинация битовых флагов для перечисления, украшенного FlagsAttribute
.
К сожалению, единственный способ создать метод, возвращающий true для допустимых битовых флагов, это немного длинная:
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
// Check if a simple value is defined in the enum.
Type enumType = typeof(T);
bool valid = Enum.IsDefined(enumType, value);
// For enums decorated with the FlagsAttribute, allow sets of flags.
if (!valid && enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true)
{
long mask = 0;
foreach (object definedValue in Enum.GetValues(enumType))
mask |= Convert.ToInt64(definedValue);
long longValue = Convert.ToInt64(value);
valid = (mask & longValue) == longValue;
}
return valid;
}
Вы можете кэшировать результаты GetCustomAttribute
в словаре:
private static readonly Dictionary<Type, bool> _flagEnums = new Dictionary<Type, bool>();
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
// Check if a simple value is defined in the enum.
Type enumType = typeof(T);
bool valid = Enum.IsDefined(enumType, value);
if (!valid)
{
// For enums decorated with the FlagsAttribute, allow sets of flags.
if (!_flagEnums.TryGetValue(enumType, out bool isFlag))
{
isFlag = enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true;
_flagEnums.Add(enumType, isFlag);
}
if (isFlag)
{
long mask = 0;
foreach (object definedValue in Enum.GetValues(enumType))
mask |= Convert.ToInt64(definedValue);
long longValue = Convert.ToInt64(value);
valid = (mask & longValue) == longValue;
}
}
return valid;
}
Обратите внимание, что в приведенном выше коде используется новое ограничение Enum
для T
которое доступно только с С# 7.3. Вам нужно передать object value
в более старых версиях и вызвать GetType()
для него.