Вопрос

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

ПОЧЕМУ

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

ГДЕ

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

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

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

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

Результат

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

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

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

Перечисление.Определено = 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);
}

Система.Перечисление Преобразует 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-

Как уже упоминали другие, 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
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top