Подтвердить значения перечисления

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

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

Ответ 1

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

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)

Ответ 2

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

Почему

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

WHERE

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

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

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

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

РЕЗУЛЬТАТ

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

Я определяю одну или две константы, которые являются верхними и (необязательно) нижними границами перечисления и используют их в паре операторов if() для проверки.
Один недостаток заключается в том, что вы обязательно должны обновить константы, если вы измените перечисление.
Этот метод также работает только в том случае, если enum является "авто", где каждый элемент перечисления является инкрементным целочисленным значением, таким как 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 tick = 10ns.

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

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

Оценки, определенные в методе= 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 с помощью= 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 Constants= 43,000 ticks = победитель на 42x и 316x быстрее.

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

-eol -

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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 напрямую, но этот ответ уже достаточно многословен!

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// </summary>
    public static bool IsDefined<T>(this T enumValue)
        where T : Enum
        => EnumValueCache<T>.DefinedValues.Contains(enumValue);

    /// <summary>
    /// Caches the defined values for each enum type for which this class is accessed.
    /// </summary>
    private static class EnumValueCache<T>
        where T : Enum
    {
        public static readonly HashSet<T> DefinedValues = new HashSet<T>((T[])Enum.GetValues(typeof(T)));
    }
}

Использование:

if (!myEnumValue.IsDefined())
   // ...

Ответ 10

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