Ответ 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
, но даже это недостаточно - "Ожидается постоянное значение" ).