Проверьте значения Enum

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

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

17.08.2008 13:14:42
Для подхода с флагами было бы полезно проверить этот ответ на дублирующий вопрос: stackoverflow.com/a/23177585/5190842
Erik 7.10.2015 18:40:41
ПроверкаEnumDataTypeAttribute
Timeless 14.07.2017 07:15:46
Самый быстрый способ - не проверять перечисление и использовать поведение по умолчанию, любое перечисление должно иметь член вроде «Нет», «По умолчанию» или что-то в этом роде. Вы можете использовать переключатель по умолчанию.
M.kazem Akhgary 4.03.2019 04:53:34
11 ОТВЕТОВ
РЕШЕНИЕ

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

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)

80
19.11.2013 14:55:34
Также обратите внимание, что Enum.IsDefined использует отражение, которое может вызвать проблемы с производительностью
Guldan 4.05.2015 13:11:14
Уже проверено , IsDefined()почему не просто бросить Int в условии успеха: retVal = (TEnum)enumValue?
Matt Jenkins 27.06.2019 18:14:39

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

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

8
8.04.2020 23:49:35
Рекомендация о том, чтобы помещать перечисления в раскрывающиеся списки, отлично работает для WinForms, но не подходит для WebForms, где вам нужно проверять наличие вредоносного ввода.
Brad Wilson 17.08.2008 16:04:54
Мои данные взяты из файла XML, созданного одной из многих возможных программ, которые я не контролирую, где качество данных сильно варьируется. Действительно ли Enum.IsDefined настолько плох, потому что мне кажется, что лучше всего в этой ситуации?
Richard Garside 30.07.2011 14:58:44
@Richard Как и во всем в жизни, есть конкретные случаи, когда то, что обычно является плохой идеей, будет подходящим решением для такой ситуации. Если вы считаете, что это лучшее решение для вашего случая, продолжайте. :) Даже синглтоны, глобальные переменные и вложенные if - хорошая идея для определенных ситуаций ...
Jon Limjap 1.08.2011 06:31:14
Вот почему вы всегда определяете регистр по умолчанию в своих инструкциях switch.
Trisped 12.10.2012 20:24:08
Ваш подход (ограничение того, что доступно в пользовательском интерфейсе) имеет те же недостатки, что и Enum.IsDefined: Если код пользовательского интерфейса не синхронизирован с перечислением, то вы можете получить значения вне диапазона (т.е. не объявлено в перечислении). Хотя по-прежнему необходимо обрабатывать эти значения (например, иметь defaultрегистр в switchоператорах), представляется Enum.IsDefinedцелесообразным использовать его перед сохранением значения в поле объекта: он позволяет обнаружить ошибку раньше, чем позволять ей плавать до больше не ясно, откуда появилось поддельное значение.
Suzanne Dupéron 14.03.2013 16:12:59

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

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)
0
16.02.2016 13:17:43
Не приведет ли это просто к исключению, если ему передано целое число, отличное от enum?
Richard Garside 30.07.2011 15:02:58
@Richard - да, это будет ... прошло 3 года с тех пор, как я написал это, и небеса помогут мне, если я знаю, о чем я думал, кроме - если это не так
Mike Polen 4.08.2011 17:57:58
Бросок исключения также снижает производительность. Вы никогда не должны бросать исключение, когда есть лучшее решение.
Kody 30.12.2015 19:01:23

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

ПОЧЕМУ

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

ГДЕ

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

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

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

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

System.Enum Convert Int32 с приведениями = 1800000 тиков

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 тиков = победитель в 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-

22
2.04.2013 02:09:02
Большое количество наших перечислений не является смежным, поэтому не вариант во многих сценариях. 'Enum.IsDefined (typeof (T)') будет медленным в вашем тестовом сценарии, поскольку .net делает много размышлений, упаковывает и т. Д. Вызывать это каждый раз, когда вы анализируете строку в файле импорта, никогда не будет быстрым. производительность является ключевой, тогда я бы посмотрел на вызов Enum.GetValues ​​один раз в начале импорта. Это никогда не будет так просто, просто <> сравнение, но вы знаете, что это будет работать для всех перечислений. В качестве альтернативы вы могли бы иметь более интеллектуальный анализатор перечислений , Я
Vman 24.02.2014 13:26:51
@johnny 5 - Из вышеприведенной информации: этот метод также работает только в том случае, если перечисление является «автоматическим» стилем, где каждый элемент перечисления является инкрементным целочисленным значением, таким как 0,1,2,3,4, .... Он выиграл неправильно работают с флагами или перечислениями, значения которых не являются инкрементными.
deegee 11.06.2015 16:57:31
@Vman - «Вызывать это каждый раз, когда вы анализируете строку в файле импорта, никогда не будет быстро». - Это будет значительно быстрее, чем скорость чтения диска.
deegee 11.06.2015 17:00:25
Возможно, какой-то другой процесс может захотеть часть этого действия жесткого диска? Смерть от тысячи порезов.
Vman 12.06.2015 00:22:34
Преждевременная оптимизация. Подавляющее большинство случаев не будет обрабатывать достаточное количество перечислений из файлов, чтобы оказать существенное влияние на производительность. Проверьте это, и если есть проблемы с производительностью, то протестируйте и выберите лучший метод для решения этой проблемы.
deegee 12.06.2015 02:35:45

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

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

6
24.02.2014 14:10:23
Для больших перечислений вы можете использовать словарь для поиска значений перечислений за время O (1).
M.kazem Akhgary 4.03.2019 05:00:22

Как уже упоминали другие, 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
}
10
4.12.2014 22:16:46
Еще один вариант ответа на этот вопрос будет иметь вариант, HashSet<MyEnum>который устранит необходимость приведения, intесли вы проверяете enumвместо int. Код для установки его будет выглядеть следующим образом : var validVals = new HashSet<MyEnum>(Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>());. Использование будет таким же, конечно.
Erik 7.10.2015 18:48:25
Просто так получилось, что HashSet <Int> по некоторым причинам намного быстрее, чем HashSet <enum>, поэтому приведение к int перед выполнением проверки резко ускоряет процесс.
Dwedit 12.04.2019 14:30:34

Чтобы проверить, является ли значение допустимым значением в перечислении, вам нужно только вызвать статический метод 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
}
0
12.07.2016 14:09:39
Проголосовал не за то, что был неправ, а за то, что написал ответ на 8 лет с опозданием, который не предоставляет никакой новой информации, которой еще нет в принятом ответе.
Ben Voigt 27.10.2016 07:32:46

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

4
6.03.2019 16:50:39

Основываясь на ответе Тимо, я разработал следующий метод расширения (синтаксис C # 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())
   // ...
1
20.08.2019 17:33:44

Вы можете использовать FluentValidation для своего проекта. Вот простой пример для «Проверки Enum»

Давайте создадим класс EnumValidator с использованием FluentValidation;

public class EnumValidator<TEnum> : AbstractValidator<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable
{
    public EnumValidator(string message)
    {
        RuleFor(a => a).Must(a => typeof(TEnum).IsEnum).IsInEnum().WithMessage(message);
    }

}

Теперь мы создали наш класс enumvalidator; давайте создадим класс для вызова enumvalidor class;

 public class Customer 
{
  public string Name { get; set; }
  public Address address{ get; set; }
  public AddressType type {get; set;}
}
public class Address 
{
  public string Line1 { get; set; }
  public string Line2 { get; set; }
  public string Town { get; set; }
  public string County { get; set; }
  public string Postcode { get; set; }

}

public enum AddressType
{
   HOME,
   WORK
}

Пришло время позвонить нашему валидатору перечисления для типа адреса в классе клиента.

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
   {
     RuleFor(x => x.type).SetValidator(new EnumValidator<AddressType>("errormessage");
  }
}
0
24.12.2019 14:41:23