Проверить перечисление значений - PullRequest
64 голосов
/ 17 августа 2008

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

Каков наилучший способ сделать это в C #?

Ответы [ 10 ]

75 голосов
/ 26 января 2011

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

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)

20 голосов
/ 02 апреля 2013

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

ПОЧЕМУ

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

WHERE

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

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

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

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

РЕЗУЛЬТАТ

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

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

Также обратите внимание, что этот метод почти такой же быстрый, как и обычный, если "<" ">" на обычных 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;
}

ПРОИЗВОДИТЕЛЬНОСТЬ

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

Профилирование выполнялось при компиляции релиза в цикле по миллиону раз для каждого метода со случайным целочисленным входным значением. Каждый тест проводился более 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, но возвращает Default, если выходит за пределы.

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

System.Enum Convert Int32 с приведениями = 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 () Мин. / Макс. Константы = 43 000 тиков = победитель в 42 раза и в 316 раз быстрее.

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

-eol-

10 голосов
/ 05 декабря 2014

Как уже упоминали другие, 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
}
9 голосов
/ 17 августа 2008

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

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

6 голосов
/ 24 февраля 2014

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

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

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 в цикле (фабрика Create, по сути, делает это заранее). Вы заметите немного лицемерия, для краткости этот код ограничивает функциональность int-enums. Я должен использовать IConvertible, а не напрямую использовать int, но этот ответ уже достаточно многословен!

2 голосов
/ 26 сентября 2013

Вот как я это делаю, основываясь на нескольких постах в сети. Причина этого заключается в том, чтобы убедиться, что перечисления, отмеченные атрибутом 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);
    }
}
1 голос
/ 17 августа 2008

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

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)
0 голосов
/ 06 марта 2019

Вот быстрое универсальное решение, использующее статически построенное 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)));
        }
    }
}

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

0 голосов
/ 17 октября 2018

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

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

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

0 голосов
/ 12 июля 2016

Чтобы проверить, является ли значение допустимым значением в перечислении, вам нужно только вызвать статический метод 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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...