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

Mic*_*rel 23 c# enums flags

我知道你可以FlagsAttribute用来指示编译器使用位域进行枚举.

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

例:

enum OnlyOneOption
{
   Option1,
   Option2,
   ...
}
Run Code Online (Sandbox Code Playgroud)

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

Com*_*hip 27

编译时检查

最近,Eric Lippert(他在微软期间从事过C#编译器工作的人之一)发表了关于他对C#的十大遗憾的博客,其中四位是

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

所以原则上,你不能让编译器窒息

OnlyOneOption option = OnlyOneOption.Option1 | OnlyOneOption.Option2;
Run Code Online (Sandbox Code Playgroud)

因为在整数方面,这种操作看起来非常好.正如您所指出的,您可以做的是提供FlagsAttribute- 这对开发人员来说已经是一个很好的暗示.

既然你不能enums 上重载运算符,你必须求助于运行时检查.

运行时检查

您可以做的是,只要您需要枚举,检查确切的相等性以及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.");
}
Run Code Online (Sandbox Code Playgroud)

非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();
}
Run Code Online (Sandbox Code Playgroud)

OnlyOneOption option = OnlyOneOption.OptionA | OnlyOneOption.OptionB;将失败,错误CS0019:" 运算符'|' 不能应用于'OnlyOneOption'和'OnlyOneOption'类型的操作数.

缺点是您失去了编写switch语句的能力,因为它们实际上需要编译时常量转换int(我尝试提供一个static public implicit operator int返回readonly字段,但即使这还不够 - "预期值为常量值").

  • 静态模式的缺点将被解决*如果*(交叉手指)模式匹配使其成为C#.坦率地说,我发现它比目前的枚举更好. (3认同)

InB*_*een 5

不,你不能阻止这一点.不指定[Flags]属性不会禁止以下用户:

enum Option
{
    One = 1,
    Two,
    Three
}

var myOption = Option.One | Option.Two;
Run Code Online (Sandbox Code Playgroud)

此外,完全合法的东西如下:

var myOption = 0; //language quirk IMO, 0 being implicitly convertible to any enumeration type.
Run Code Online (Sandbox Code Playgroud)

要么

var myOption = (Option)33;
Run Code Online (Sandbox Code Playgroud)

这是一个问题吗?不,不是真的; 如果您的逻辑只考虑选项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
     }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您有(可疑)代码根据枚举的基础值决定:

if (myOption < Option.Three) { //expecting myOption to be either One or Two...
Run Code Online (Sandbox Code Playgroud)

那么你肯定没有使用合适的工具来完成工作; myOption = 0;或者myOption = (Option)-999会有问题.