Сведения о вопросе

Ислам

19:09, 26th August, 2020

Теги

c#   validation   enums    

Проверка Значений Перечисления

Просмотров: 529   Ответов: 10

Мне нужно проверить целое число, чтобы узнать, является ли оно допустимым значением перечисления.

Как лучше всего это сделать в C#?



  Сведения об ответе

baggs

20:57, 27th August, 2020

Вы должны любить этих людей, которые считают, что данные не только всегда поступают от UI, но и UI в пределах вашего контроля!

IsDefined подходит для большинства сценариев, вы можете начать с:

public static bool TryParseEnum<TEnum>(this int enumValue, out TEnum retVal)
{
 retVal = default(TEnum);
 bool success = Enum.IsDefined(typeof(TEnum), enumValue);
 if (success)
 {
  retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue);
 }
 return success;
}

(Очевидно, просто отбросьте ‘это’, если вы не считаете, что это подходящее расширение int)


  Сведения об ответе

DAAA

07:13, 10th August, 2020

ИМХО пост помеченный как ответ неверен.
Проверка параметров и данных - это одна из тех вещей, которые были вбиты в меня десятилетия назад.

WHY

Проверка необходима, поскольку практически любое целочисленное значение может быть присвоено перечислению без возникновения ошибки.
Я провел много дней, исследуя C# enum validation, потому что это необходимая функция во многих случаях.

WHERE

Основная цель проверки enum для меня заключается в проверке данных, считанных из файла: вы никогда не знаете, был ли файл поврежден, или был изменен внешне, или был взломан специально.
И с enum проверкой данных приложения, вставленных из буфера обмена: вы никогда не знаете, отредактировал ли пользователь содержимое буфера обмена.

Тем не менее, я потратил дни на изучение и тестирование многих методов, включая профилирование производительности каждого метода, который я смог найти или разработать.

Выполнение вызовов во что-либо в System.Enum настолько медленно, что это было заметным снижением производительности для функций, которые содержали сотни или тысячи объектов, имеющих один или несколько объектов enums в своих свойствах, которые должны были быть проверены на наличие границ.

В итоге, держитесь подальше от всего в классе System.Enum при проверке значений enum, это ужасно медленно.

RESULT

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

Я определяю одну или две константы, которые являются верхней и (необязательно) нижней границами перечисления, и использую их в паре операторов if() для проверки.
Одним из недостатков является то, что вы должны быть уверены, что обновите константы, если вы измените перечисление.
Этот метод также работает только в том случае, если перечисление является стилем "auto", где каждый элемент перечисления является инкрементным целочисленным значением, таким как 0,1,2,3,4,.... Это не будет работать должным образом с флагами или enums, которые имеют значения, которые не являются инкрементными.

Также обратите внимание, что этот метод почти так же быстр, как обычный if "<" ">" на обычных int32s (которые набрали 38 000 тиков на моих тестах).

Например:

public const MyEnum MYENUM_MINIMUM = MyEnum.One;
public const MyEnum MYENUM_MAXIMUM = MyEnum.Four;

public enum MyEnum
{
    One,
    Two,
    Three,
    Four
};

public static MyEnum Validate(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

PERFORMANCE

Для тех, кто заинтересован, я профилировал следующие варианты проверки перечисления, и вот результаты.

Профилирование было выполнено при компиляции выпуска в цикле по одному миллиону раз для каждого метода со случайным целочисленным входным значением. Каждый тест выполнялся более 10 раз и усреднялся. Результаты тика включают в себя общее время выполнения, которое будет включать в себя генерацию случайных чисел и т.д. но они будут постоянными во всех тестах. 1 тик = 10нс.

Обратите внимание, что приведенный здесь код не является полным тестовым кодом, а только базовым методом проверки перечисления. Было также много дополнительных вариаций на них, которые были протестированы,и все они с результатами, похожими на те, которые были показаны здесь, что принесло 1,800,000 тиков.

Список самых медленных и быстрых с округленными результатами, надеюсь, без опечаток.

Границы, определенные в методе = 13 600 000 тиков

public static T Clamp<T>(T value)
{
    int minimum = Enum.GetValues(typeof(T)).GetLowerBound(0);
    int maximum = Enum.GetValues(typeof(T)).GetUpperBound(0);

    if (Convert.ToInt32(value) < minimum) { return (T)Enum.ToObject(typeof(T), minimum); }
    if (Convert.ToInt32(value) > maximum) { return (T)Enum.ToObject(typeof(T), maximum); }
    return value;
}

Enum.IsDefined = 1,800,000 тиков
Примечание: эта версия кода не зажимается до Min/Max, но возвращает значение по умолчанию, если она выходит за пределы диапазона.

public static T ValidateItem<T>(T eEnumItem)
{
    if (Enum.IsDefined(typeof(T), eEnumItem) == true)
        return eEnumItem;
    else
        return default(T);
}

System.Enum преобразование Int32 с casts = 1,800,000 тиков

public static Enum Clamp(this Enum value, Enum minimum, Enum maximum)
{
    if (Convert.ToInt32(value) < Convert.ToInt32(minimum)) { return minimum; }
    if (Convert.ToInt32(value) > Convert.ToInt32(maximum)) { return maximum; }
    return value;
}

if() Min/Max константы = 43 000 тиков = победитель на 42x и 316x быстрее.

public static MyEnum Clamp(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

-Эол (эльф)-


  Сведения об ответе

darknet

16:26, 19th August, 2020

Как уже упоминали другие, Enum.IsDefined -это медленный процесс, о котором вы должны знать, если он находится в цикле.

При выполнении множественных сравнений более быстрый метод заключается в том, чтобы сначала поместить значения в HashSet . Затем просто используйте Contains , чтобы проверить, является ли значение допустимым, например:

int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet<int> validVals = new HashSet<int>((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
    // is valid
}


  Сведения об ответе

pumpa

15:18, 29th August, 2020

Брэд Абрамс специально предупреждает против Enum.IsDefined в своем посте опасность чрезмерного упрощения .

Лучший способ избавиться от этого требования (то есть от необходимости проверять enums) - это удалить способы, с помощью которых пользователи могут ошибиться, например, какое-то поле ввода. Используйте enums с выпадающими списками, например, чтобы применить только допустимый enums.


  Сведения об ответе

ASER

16:53, 27th August, 2020

Этот ответ является ответом на ответ диги, который поднимает проблемы производительности System.Enum, поэтому не следует принимать как мой предпочтительный общий ответ, больше обращаясь к проверке перечисления в жестких сценариях производительности.

Если у вас есть критически важная проблема производительности, когда медленный, но функциональный код выполняется в узком цикле, то я лично хотел бы рассмотреть возможность перемещения этого кода из цикла, если это возможно, вместо того, чтобы решать путем уменьшения функциональности. Ограничение кода только поддержкой Continuous enums может стать кошмаром для поиска ошибки, если, например, кто-то в будущем решит отказаться от некоторых значений enum. Проще говоря, вы можете просто вызвать Enum.GetValues один раз, прямо в начале, чтобы избежать запуска всех отражений и т. д. тысячи раз. Это должно дать вам немедленное повышение производительности. Если вам нужно больше производительности и вы знаете, что многие из ваших enums являются смежными (но вы все еще хотите поддерживать 'gappy' enums), вы можете пойти дальше и сделать что-то вроде:

public abstract class EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    protected static bool IsContiguous
    {
        get
        {
            int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();

            int lowest = enumVals.OrderBy(i => i).First();
            int highest = enumVals.OrderByDescending(i => i).First();

            return !Enumerable.Range(lowest, highest).Except(enumVals).Any();
        }
    }

    public static EnumValidator<TEnum> Create()
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("Please use an enum!");
        }

        return IsContiguous ? (EnumValidator<TEnum>)new ContiguousEnumValidator<TEnum>() : new JumbledEnumValidator<TEnum>();
    }

    public abstract bool IsValid(int value);
}

public class JumbledEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int[] _values;

    public JumbledEnumValidator()
    {
        _values = Enum.GetValues(typeof (TEnum)).Cast<int>().ToArray();
    }

    public override bool IsValid(int value)
    {
        return _values.Contains(value);
    }
}

public class ContiguousEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int _highest;
    private readonly int _lowest;

    public ContiguousEnumValidator()
    {
        List<int> enumVals = Enum.GetValues(typeof (TEnum)).Cast<int>().ToList();

        _lowest = enumVals.OrderBy(i => i).First();
        _highest = enumVals.OrderByDescending(i => i).First();
    }

    public override bool IsValid(int value)
    {
        return value >= _lowest && value <= _highest;
    }
}

Где ваша петля становится чем-то вроде:

//Pre import-loop
EnumValidator< MyEnum > enumValidator = EnumValidator< MyEnum >.Create();
while(import)   //Tight RT loop.
{
    bool isValid = enumValidator.IsValid(theValue);
}

Я уверен, что классы EnumValidator могут быть написаны более эффективно (это просто быстрый хак для демонстрации), но, честно говоря, кого волнует, что происходит за пределами цикла импорта? Единственный бит, который должен быть сверхбыстрым, находится в цикле. Это было причиной для принятия маршрута абстрактного класса, чтобы избежать ненужного if-enumContiguous-then-else в цикле (factory Create по существу делает это заранее). Вы заметите немного лицемерия, для краткости этот код ограничивает функциональность до int-enums. Я должен был бы использовать IConvertible, а не использовать int напрямую, но этот ответ уже достаточно многословен!


  Сведения об ответе

P_S_S

21:06, 1st October, 2020

Вот как я это делаю, основываясь на нескольких сообщениях в интернете. Причина этого состоит в том, чтобы убедиться, что enums, помеченный атрибутом Flags , также может быть успешно проверен.

public static TEnum ParseEnum<TEnum>(string valueString, string parameterName = null)
{
    var parsed = (TEnum)Enum.Parse(typeof(TEnum), valueString, true);
    decimal d;
    if (!decimal.TryParse(parsed.ToString(), out d))
    {
        return parsed;
    }

    if (!string.IsNullOrEmpty(parameterName))
    {
        throw new ArgumentException(string.Format("Bad parameter value. Name: {0}, value: {1}", parameterName, valueString), parameterName);
    }
    else
    {
        throw new ArgumentException("Bad value. Value: " + valueString);
    }
}


  Сведения об ответе

darknet

19:15, 2nd August, 2020

Я нашел эту ссылку , которая отвечает на него довольно хорошо. Он использует:

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)


  Сведения об ответе

P_S_S

00:56, 1st August, 2020

Я остановился на этом решении как на смеси производительности, удобства и ремонтопригодности.

public enum DogBreed
{
    Unknown = 0,
    Beagle = 1,
    Labrador = 2,
    PeruvianIncaOrchid = 3,
}
public static class DogBreedExtensions
{
    public static bool IsValidDogBreed(this DogBreed breed)
    {
        var v = (int)breed;
        return v >= 1 && v <= 3;
    }
}

И используйте его таким образом:

var goodInput = 2;
var goodDog = (DogBreed)goodInput;
goodDog.IsValidDogBreed(); // true.

var badInput = 7;
var badDog = (DogBreed)badInput; // no problem, bad data here we come.
badDog.IsValidDogBreed(); // false, you're in the doghouse!

В моем случае, позвонив Enum.IsDefined бы только меня наполовину, как я, как правило, хотят, чтобы отвергать значение "Unknown" перечисление.

Этот подход позволяет избежать проблем с производительностью Enum.IsDefined и определяет правило проверки, близкое к перечислению, которое мне нравится.

Метод расширения легко адаптируется к изменяющимся требованиям и при желании может быть применен к int - (т. е. public static bool IsValidDogBreed(this int breed) )


  Сведения об ответе

VCe znayu

11:04, 8th August, 2020

Вот быстрое универсальное решение, использующее статически сконструированный HashSet<T> .

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

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// Throws if the type parameter is not an enum type.
    /// </summary>
    public static bool IsDefined<T>(T enumValue)
    {
        if (typeof(T).BaseType != typeof(System.Enum)) throw new ArgumentException($"{nameof(T)} must be an enum type.");

        return EnumValueCache<T>.DefinedValues.Contains(enumValue);
    }

    /// <summary>
    /// Statically caches each defined value for each enum type for which this class is accessed.
    /// Uses the fact that static things exist separately for each distinct type parameter.
    /// </summary>
    internal static class EnumValueCache<T>
    {
        public static HashSet<T> DefinedValues { get; }

        static EnumValueCache()
        {
            if (typeof(T).BaseType != typeof(System.Enum)) throw new Exception($"{nameof(T)} must be an enum type.");

            DefinedValues = new HashSet<T>((T[])System.Enum.GetValues(typeof(T)));
        }
    }
}

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


  Сведения об ответе

darknet

20:57, 17th August, 2020

Чтобы проверить, является ли значение допустимым в перечислении, вам нужно только вызвать статический метод Enum.IsDefined .

int value = 99;//Your int value
if (Enum.IsDefined(typeof(your_enum_type), value))
{
   //Todo when value is valid
}else{
   //Todo when value is not valid
}


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться