为什么枚举权限通常具有0、1、2、4值?


159

人们为什么总是使用枚举值0, 1, 2, 4, 8,而不喜欢0, 1, 2, 3, 4

这与位操作等有关吗?

我将不胜感激关于如何正确使用它的一个小样本片段:)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}


25
我不同意全民投票。
zzzzBov 2012年

UNIX设置权限的方法也基于相同的逻辑。
鲁迪(Rudy)2012年

3
@Pascal:您可能会发现阅读有关(与)表示的按位或(和按位AND)很有帮助。各种答案都假定您熟悉它。|&
Brian

2
@IAdapter我可以看到您为什么会这样认为,因为两者的答案都相同,但是我认为问题有所不同。另一个问题只是询问C#中Flags属性的示例或解释。这个问题似乎与位标志的概念及其背后的基本原理有关。
杰里米·S

Answers:


268

因为它们是二的幂,所以我可以这样做:

var permissions = Permissions.Read | Permissions.Write;

也许以后...

if( (permissions & Permissions.Write) == Permissions.Write )
{
    // we have write access
}

它是一个位字段,其中每个设置位都对应于某个权限(或逻辑上对应的任何枚举值)。如果将这些定义为,1, 2, 3, ...您将无法以这种方式使用按位运算符并获得有意义的结果。深入研究...

Permissions.Read   == 1 == 00000001
Permissions.Write  == 2 == 00000010
Permissions.Delete == 4 == 00000100

注意这里的模式吗?现在,如果我们以我的原始示例为例,

var permissions = Permissions.Read | Permissions.Write;

然后...

permissions == 00000011

看到?无论是ReadWrite位设置,我可以检查独立(另请注意,Delete设置,因此该值不传达权限删除)。

它允许将多个标志存储在单个位字段中。


2
@马尔科姆:是的;myEnum.IsSet。我认为这是一个完全没有用的抽象,只能减少键入,但是……
Ed S.

1
很好的答案,但是您应该提及为什么应用Flags属性,以及何时不希望将Flags应用于某些枚举。
安迪

3
@Andy:实际上,该Flags属性仅能为您提供“漂亮打印”的iirc。您可以将枚举值用作标志,而不管该属性是否存在。
Ed S.

3
@detly:因为C#中的if语句需要布尔表达式。 0不是false; falsefalse。但是您可以写if((permissions & Permissions.Write) > 0)
Ed S.

2
(permissions & Permissions.Write) == Permissions.Write现在,您可以使用enum.HasFlag()
Louis Kottmann

147

如果从其他答案中仍然不清楚,请这样考虑:

[Flags] 
public enum Permissions 
{   
   None = 0,   
   Read = 1,     
   Write = 2,   
   Delete = 4 
} 

只是一种较短的编写方式:

public enum Permissions 
{   
    DeleteNoWriteNoReadNo = 0,   // None
    DeleteNoWriteNoReadYes = 1,  // Read
    DeleteNoWriteYesReadNo = 2,  // Write
    DeleteNoWriteYesReadYes = 3, // Read + Write
    DeleteYesWriteNoReadNo = 4,   // Delete
    DeleteYesWriteNoReadYes = 5,  // Read + Delete
    DeleteYesWriteYesReadNo = 6,  // Write + Delete
    DeleteYesWriteYesReadYes = 7, // Read + Write + Delete
} 

有八种可能性,但您可以将它们表示为仅四个成员的组合。如果存在十六种可能性,则可以将它们表示为仅五个成员的组合。如果有40亿种可能性,那么您可以将它们表示为只有33个成员的组合!仅具有33个成员(每个成员(零除外)具有2的幂)显然要比试图枚举40亿个项目好得多。


32
enum拥有40亿成员的的心理形象+1 。可悲的是,可能有人在尝试过。
丹尼尔·普赖登

23
@DanielPryden作为Daily WTF的每日读者,我相信这一点。
蓬松的2012年

1
2 ^ 33 =〜86亿。对于40亿个不同的值,您仅需要32位。
CVn 2012年

5
@MichaelKjörling33之一是默认值0
棘手怪胎

@MichaelKjörling:公平地说,只有32个成员具有2的幂,因为0不是2的幂。因此,“ 33个成员,每个成员均为2的幂”并不是完全正确的(除非您算作2 2 ** -infinity的幂)。
Brian

36

因为这些值表示二进制中的唯一位位置,所以:

1 == binary 00000001
2 == binary 00000010
4 == binary 00000100

等,所以

1 | 2 == binary 00000011

编辑:

3 == binary 00000011

二进制数中的3在一个位置和两个位置均由值1表示。它实际上与值相同1 | 2。因此,当您尝试使用二进制位置作为表示某些状态的标志时,3通常是没有意义的(除非存在逻辑值实际上是两者的组合)

为了进一步说明,您可能需要扩展示例枚举,如下所示:

[Flags]
public Enum Permissions
{
  None = 0,   // Binary 0000000
  Read = 1,   // Binary 0000001
  Write = 2,  // Binary 0000010
  Delete = 4, // Binary 0000100
  All = 7,    // Binary 0000111
}

因此,在我有Permissions.All,我也有含蓄Permissions.ReadPermissions.WritePermissions.Delete


2 | 3是什么问题?
Pascal

1
@Pascal:因为311二进制,即它不映射到单个设置位,所以您失去了将任意位置的1位映射到有意义的值的能力。
Ed S.

8
@Pascal提出另一种方式2|3 == 1|3 == 1|2 == 3。所以,如果你的二进制值00000011,和你们的旗帜包括值123,那么你就不会知道,如果这个值表示1 and 32 and 31 and 2only 3。这使它的用处大大减少。
yshavit 2012年

10
[Flags]
public Enum Permissions
{
    None   =    0; //0000000
    Read   =    1; //0000001
    Write  = 1<<1; //0000010
    Delete = 1<<2; //0000100
    Blah1  = 1<<3; //0001000
    Blah2  = 1<<4; //0010000
}

我认为这样写更容易理解和阅读,您无需计算。


5

这些用于表示允许枚举值组合的位标志。我认为如果用十六进制表示法写值会更清楚

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}

4
@Pascal:也许这时候您对它的可读性更高,但是随着您获得经验,以十六进制查看字节已成为第二天性。十六进制中的两位数字映射到一个字节映射到8位(好吧……一个字节无论如何通常都是8位……并不总是正确的,但是对于本示例,可以将其概括化)。
Ed S.

5
@Pascal quick,乘以41943042 会得到什么?怎么0x4000000x800000与相比8388608,将其识别为正确答案要容易得多,并且键入十六进制值也不太容易出错。
phoog 2012年

6
一眼就能看出,如果使用了十六进制,则标志设置是否正确(即2的幂)容易得多。是二0x10000的幂?是的,它以1、2、4或8开头,之后为全0。您无需在思维上将0x10转换为16(尽管这样做最终可能会成为第二本性),只需将其视为“ 2的幂”即可。
Brian

1
我完全同意Jared的观点,即用十六进制记号要容易得多。您只需使用1 2 4 8并移动
bevacqua 2012年

1
就个人而言,我更喜欢仅使用例如P_READ = 1 << 0,P_WRITE = 1 <,1,P_RW = P_READ | P_WRITE。我不确定这种常量折叠是否可以在C#中工作,但是在C / C ++(以及Java,我认为)中是否可以正常工作。
蓬松的2012年

1

这确实是更多评论,但是由于它不支持格式设置,因此我只想包括一种我用来设置标志枚举的方法:

[Flags]
public enum FlagTest
{
    None = 0,
    Read = 1,
    Write = Read * 2,
    Delete = Write * 2,
    ReadWrite = Read|Write
}

在您希望按字母顺序维护标志的情况下,我发现这种方法在开发过程中特别有用。如果确定需要添加一个新的标志值,则可以按字母顺序插入它,唯一需要更改的值就是它前面的值。

但是请注意,一旦将解决方案发布到任何生产系统(尤其是在枚举没有紧密耦合的情况下(例如通过Web服务)公开),则强烈建议不要更改枚举中的任何现有值。


1

好答案,这样一地块的......我只想说..如果你不喜欢,或不容易掌握什么<<语法所要表达的。我个人更喜欢另一种(我敢说,简单枚举声明样式) …

typedef NS_OPTIONS(NSUInteger, Align) {
    AlignLeft         = 00000001,
    AlignRight        = 00000010,
    AlignTop          = 00000100,
    AlignBottom       = 00001000,
    AlignTopLeft      = 00000101,
    AlignTopRight     = 00000110,
    AlignBottomLeft   = 00001001,
    AlignBottomRight  = 00001010
};

NSLog(@"%ld == %ld", AlignLeft | AlignBottom, AlignBottomLeft);

LOG 513 == 513

(至少对我自己而言)容易理解。排列它们……描述您想要的结果,获得您想要的结果。.无需“计算”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.