如何防止枚举值的按位OR组合?

时间:2016-07-22 09:07:36

标签: c# enums flags

我知道您可以使用FlagsAttribute指示编译器使用位域进行枚举。

有没有办法指定枚举值不能与按位OR组合?

示例:

enum OnlyOneOption
{
   Option1,
   Option2,
   ...
}

在这个例子中,没有什么能阻止开发人员编写OnlyOneOption.Option1 | OnlyOneOption.Option2。如果可能的话,我想在编译时禁止它。

3 个答案:

答案 0 :(得分:27)

编译时检查

最近,Eric Lippert(他在微软期间从事C#编译器工作的人之一)blogged关于他对C#的十大遗憾,第四个就是那个

  

在C#中,枚举只是基础整数类型的薄类型系统包装。枚举上的所有操作都被指定为实际上对整数的操作,枚举值的名称类似于命名常量。

原则上,你不能让编译器阻塞

OnlyOneOption option = OnlyOneOption.Option1 | OnlyOneOption.Option2;

因为就整数而言,这种操作看起来非常好。正如您所指出的,您可以做的是提供FlagsAttribute - 这对开发人员来说已经很好了。

enum上的you cannot overload operators以来,您必须求助于运行时检查。

运行时检查

您可以做的是,只要您需要枚举,检查确切的相等性,并在使用值组合时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,则可以使用经典(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的{​​{1}} 1}}字段,但即使这还不够 - "预期值为常数")。

答案 1 :(得分:5)

不,你无法阻止这一点。不指定[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;

这是一个问题吗?不,不是真的;如果您的逻辑只考虑选项OneTwoThree,那么只需执行它:

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会有问题。

答案 2 :(得分:2)

添加验证

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;