Есть ли способ проверить, является ли int допустимым перечислением в C #?


167

Я прочитал несколько 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. Есть ли способ проверить значение, входящее в перечисление?


2
Возможный дубликат Validate Enum Values
Erik

Ответы:


271

Проверьте 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

@matti: преобразовать "78" в любое представление числа, LoggingLevelиспользуемое в качестве хранилища, а затем представить его как LoggingLevelзначение перечисления.
thecoop

9
Кажется, что IsDefinedэто не работает для заумных членов enum.
Саид Нимати

29

Вышеуказанные решения не имеют отношения к [Flags] ситуациям.

Мое решение ниже может иметь некоторые проблемы с производительностью (я уверен, что можно оптимизировать различными способами), но по сути это всегда будет доказывать, является ли значение перечисления действительным или нет .

Он опирается на три предположения:

  • Перечислять значения в C # можно только int , абсолютно ничем иным
  • Имена перечислений в C # должны начинаться с буквенного символа
  • Никакое допустимое имя перечисления не может быть со знаком минус: -

Вызов ToString()enum возвращает либоint значение, если enum (флаг или нет) не найден. Если допустимое значение перечисления совпадает, будет напечатано имя совпадения.

Так:

[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 выполнит свою работу правильно, то любые вызовы допустимого ToString()метода enum приведут к чему-то, что в качестве первого символа будет иметь буквенный символ:

public static bool IsValid<TEnum>(this TEnum enumValue)
    where TEnum : struct
{
    var firstChar = enumValue.ToString()[0];
    return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}

Можно было бы назвать это «взломом», но преимущества в том, что полагаясь на собственную реализацию Microsoft Enumи стандарты C #, вы не полагаетесь на свой собственный потенциально ошибочный код или проверки. В ситуациях, когда производительность не является исключительно критической, это сэкономит много неприятных switchутверждений или других проверок!

редактировать

Спасибо @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());
    }
}

1
Спасибо за это, у меня была похожая проблема, связанная с допустимыми комбинациями флагов. В качестве альтернативы проверке первого символа перечисления, вы также можете попробовать int.TryParse (enumValue.ToString ()) ... Если это не удастся, у вас есть действительный набор флагов. Это может быть медленнее, чем ваше решение, хотя.
MadHenchbot

Эта реализация не может правильно проверить отрицательные значения, так как проверка для нецифровых символов
ChaseMedallion

Хороший улов!! Я обновлю свой ответ, чтобы учесть такое, спасибо @ChaseMedallion
Джошкомли

Мне больше всего нравится это решение, представленные математические приемы работают, только если [Flags]имеют разумные целочисленные значения.
MrLore

17

Канонический ответ был бы 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");
}

не знаком с перечислениями [Flags], и производительность не является проблемой, поэтому ваш ответ кажется причиной, по которой перечисления были изобретены в первую очередь;) просматривая ваши «точки» или как они там называются, так что вы должны иметь точку там , Держу пари, вы не получили их даром, но подумайте о ситуации чтения конфигурационного файла, где в одном определении перечисления есть 257 значений. Не говоря уже о десятках других перечислений. Было бы много строк с делами ...
char m

@matti - это звучит как крайний пример; десериализация в любом случае является специализированной областью - большинство механизмов сериализации предлагают бесплатную проверку перечислений.
Марк Гравелл

@matti - на заметку; Я бы сказал, относиться к ответам исходя из их индивидуальных достоинств. Я иногда делаю вещи совершенно неправильно, и кто-то с «представителем 17» может так же дать идеальный ответ.
Марк Гравелл

Ответ переключения быстрый, но не общий.
Eldritch Conundrum



4

Чтобы справиться с этим, [Flags]вы также можете использовать это решение из C # 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);
    }
}

2

Один из способов сделать это - полагаться на приведение и преобразование перечисления в строку. При приведении int к типу Enum int либо преобразуется в соответствующее значение перечисления, либо результирующее перечисление просто содержит int в качестве значения, если значение перечисления не определено для int.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();

Не проверено для любых крайних случаев.


1

Как сказали другие, 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доступное только с C # 7.3. Вам нужно передать object valueв старых версиях и позвонить GetType()по нему.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.