Чи є спосіб перевірити, чи є 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
Можливий дублікат валідаційних цінностей Enum
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це не працює для укушених членів перерахунків.
Саїд Неаматі

29

Вищезазначені рішення не стосуються [Flags]ситуацій.

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

Він спирається на три припущення:

  • Значення Enum у C # дозволяються лише int, абсолютно нічого іншого
  • Імена Enum у 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
Дякую за це, у мене була подібна проблема, що стосувалася дійсних комбінацій прапорів. Як альтернативу для перевірки першого символу enum, ви також можете спробувати int.TryParse (enumValue.ToString ()) ... Якщо це не вдасться, у вас є дійсний набір прапорів. Насправді це може бути повільніше, ніж ваше рішення.
MadHenchbot

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

Гарний улов !! Я оновлю свою відповідь, щоб розмістити таких, дякую @ChaseMedallion
joshcomley

Мені подобається це рішення найкраще, представлені математичні трюки працюють лише у тому випадку, якщо [Flags]мають розумні цілі значення.
МістерЛоре

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");
}

не знайомі з [Прапорами] перерахуваннями та продуктивністю - це не проблема, тому ваша відповідь здається причиною того, що передумки були винайдені в першу чергу;) дивлячись на ваші "точки" чи як вони там називаються, тож ви повинні мати там крапку . Зрозуміло, ви їх не отримали ні за що, але подумайте про ситуацію зчитування конфігураційного файлу, де в одному визначенні перерахування є 257 значень. Не кажучи вже про десятки інших переліків. Буде багато речей із справ ...
Чар м

@matti - це звучить крайнім прикладом; дезаріалізація - це все-таки спеціалізована область - більшість механізмів серіалізації пропонують перевірку перерахунків безкоштовно.
Marc Gravell

@matti - на бічній ноті; Я б сказав, що ставтесь до відповідей, виходячи з їх індивідуальних достоїнств. Іноді я розумію речі зовсім неправильно, і хтось із "реп 17" міг би так само дати ідеальну відповідь.
Марк Гравелл

Відповідь на перемикання швидко, але не є загальним.
Eldritch Conundrum

8

Використання:

Enum.IsDefined ( typeof ( Enum ), EnumValue );


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 або перетворюється на відповідне значення enum, або отриманий enum просто містить int як значення, якщо значення enum не визначене для 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.