Подтвердить что ты не робот

Как я могу предотвратить побитовые или комбинации значений enum?

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

Есть ли способ указать, что значения перечисления не могут быть объединены с побитовым ИЛИ?

Пример:

enum OnlyOneOption
{
   Option1,
   Option2,
   ...
}

В этом примере ничто не мешает разработчику писать OnlyOneOption.Option1 | OnlyOneOption.Option2. Я бы хотел запретить это во время компиляции, если это возможно.

4b9b3361

Ответ 1

Проверка времени компиляции

Недавно Эрик Липперт (один из парней, который работал над С# -компилятором, когда он был в Microsoft) в блоге о своих лучших 10 сожалеет о С#, номер четыре заключается в том, что

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

Итак, в принципе, вы не можете заставить компилятор задохнуться

OnlyOneOption option = OnlyOneOption.Option1 | OnlyOneOption.Option2;

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

Так как вы не можете перегружать операторов на enum, вам нужно прибегнуть к проверке времени выполнения.

Проверка времени выполнения

Что вы можете сделать, в любое время вам нужно перечислить, проверить точное равенство и throw исключение, когда используется комбинация значений. Самый быстрый и чистый способ - использовать switch:

// Use the bit pattern to guarantee that e.g. OptionX | OptionY
// never accidentally ends up as another valid option.
enum OnlyOneOption { Option1 = 0x001, Option2 = 0x002, Option3 = 0x004, ... };

switch(option) {
  case OnlyOneOption.Option1: 
    // Only Option1 selected - handle it.
    break;

  case OnlyOneOption.Option2: 
    // Only Option2 selected - handle it.
    break;

  default:
    throw new InvalidOperationException("You cannot combine OnlyOneOption values.");
}

Решение без enum

Если вы не настаиваете на использовании enum, вы можете прибегнуть к классическому (Java-ish) статическому шаблону:

class OnlyOneOption
{
    // Constructor is private so users cannot create their own instances.
    private OnlyOneOption() {} 

    public static OnlyOneOption OptionA = new OnlyOneOption();
    public static OnlyOneOption OptionB = new OnlyOneOption();
    public static OnlyOneOption OptionC = new OnlyOneOption();
}

и здесь OnlyOneOption option = OnlyOneOption.OptionA | OnlyOneOption.OptionB; завершится с ошибкой CS0019: "Оператор" | не может применяться к операндам типа "OnlyOneOption" и "OnlyOneOption".

Недостатком является то, что вы теряете возможность писать операторы switch, потому что на самом деле требуется преобразование константы времени компиляции в int (я попытался предоставить static public implicit operator int, который возвращает поле readonly, но даже это недостаточно - "Ожидается постоянное значение" ).

Ответ 2

Нет, вы не можете этого предотвратить. Не указывая атрибут [Flags] не будет отключать пользователя из следующего:

enum Option
{
    One = 1,
    Two,
    Three
}

var myOption = Option.One | Option.Two;

Кроме того, что-то совершенно законное:

var myOption = 0; //language quirk IMO, 0 being implicitly convertible to any enumeration type.

или

var myOption = (Option)33;

Это проблема? Ну нет, не совсем; если ваша логика учитывает только опции One, Two или Three, тогда просто выполните ее:

public Foo Bar(Option myOption)
{
     switch (myOption)
     {
         case Option.One: ...
         case Option.Two: ...
         case Option.Three: ...
         default: //Not a valid option, act in consequence
     }
}

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

if (myOption < Option.Three) { //expecting myOption to be either One or Two...

Тогда вы определенно не используете правильный инструмент для работы; myOption = 0; или myOption = (Option)-999 будет проблемой.

Ответ 3

Добавить подтверждение

enum MyEnum
{
  Value1 = 1,
  Value2 = 2
}

  MyEnum e = MyEnum.Value1 | MyEnum.Value2;
  var isValid = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>().Count(x => e.HasFlag(x)) == 1;