原文:
http://www.blogcn.com/User8/flier_lu/index.html?id=3236734 为了在一个标志字段中保存多种类型的标志,C 语言中定常见模式之一,是先定义一个 XXX_MASK,再定义一个 XXX_SHIFT,然后通过移位操作定义这段位上的标志,如 WinCrypt.h 中定义证书存储位置标志时,将位置标志位放在高16位中:
// Includes flags and location
#define CERT_SYSTEM_STORE_MASK 0xFFFF0000

// Location of the system store:
#define CERT_SYSTEM_STORE_LOCATION_MASK 0x00FF0000
#define CERT_SYSTEM_STORE_LOCATION_SHIFT 16

// Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE
#define CERT_SYSTEM_STORE_CURRENT_USER_ID 1
#define CERT_SYSTEM_STORE_LOCAL_MACHINE_ID 2

//

#define CERT_SYSTEM_STORE_CURRENT_USER
(CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
#define CERT_SYSTEM_STORE_LOCAL_MACHINE
(CERT_SYSTEM_STORE_LOCAL_MACHINE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
 | |
而在 C# 中一般使用以 FlagsAttribute 标记后的 Enum 来模拟类似语义,如
[Flags]
public enum CertSystemStoreFlag : uint
  {
// Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE
CERT_SYSTEM_STORE_CURRENT_USER_ID = 1,
CERT_SYSTEM_STORE_LOCAL_MACHINE_ID = 2,

// Includes flags and location
CERT_SYSTEM_STORE_MASK = 0xFFFF0000,

// Set if pvPara points to a CERT_SYSTEM_STORE_RELOCATE_PARA structure
CERT_SYSTEM_STORE_RELOCATE_FLAG = 0x80000000,
CERT_SYSTEM_STORE_LOCATION_MASK = 0x00FF0000,
CERT_SYSTEM_STORE_LOCATION_SHIFT = 16,
CERT_SYSTEM_STORE_CURRENT_USER = 
}
 | |
FlagsAttribute 标记使得此 Enum 能够以位域(bit field)形式进行操作,既有 Flags 的类型安全特性,又有进行位操作的灵活性。同时还能定义此 Enum 的基本类型,如上面指定的 uint,以最大限度兼容现有 C 代码。
不过这样组合使用 Enum 的多个特性时有一个小小的语法陷阱,不能将 C# 完全等同于 C++ 的语法,例如要这样照搬 C++ 定义语法:
[Flags]
public enum CertSystemStoreFlag : uint
  {
CERT_SYSTEM_STORE_CURRENT_USER_ID = 1,
CERT_SYSTEM_STORE_LOCATION_SHIFT = 16,
CERT_SYSTEM_STORE_CURRENT_USER = CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT,
}
 | |
编译时就会获得一个让人困惑的警告消息:
以下为引用:
CS0019: Operator '<<' cannot be applied to operands of type 'uint' and 'uint'
|
以一个 C++ 背景的程序员角度来看,实在是无法想像为什么移位操作竟然不能对 uint 进行处理,不过在 C# 中这恰恰是语法所要求的。ECMA-334 C# Language Specification 的
第 14.8 节 Shift operators 是这样定义 C# 的移位操作符的:
以下为引用:
The << and >> operators are used to perform bit shifting operations. shift-expression : additive-expression shift-expression << additive-expression shift-expression >> additive-expression ...
The predefined shift operators are listed below.
2 Shift left:
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);
3 The << operator shifts x left by a number of bits computed as described below. 4 The high-order bits outside the range of the result type of x are discarded, the remaining bits are shifted left, and the low-order empty bit positions are set to zero. 5 Shift right: int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count); 6 The >> operator shifts x right by a number of bits computed as described below. 7 When x is of type int or long, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if x is non-negative and set to one if x is negative. 8 When x is of type uint or ulong, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero.
|
可以看到,双目移位操作符的两个参数实际上是不一样的,操作符左的参数可以是有/无符号的int和long,但操作符右边的则都是 int。因此上面那个错误的表达式中,移位操作符右边数字应该被显式转换为 int 来符合 C# 的语法要求。
[Flags]
public enum CertSystemStoreFlag : uint
  {
CERT_SYSTEM_STORE_CURRENT_USER_ID = 1,
CERT_SYSTEM_STORE_LOCATION_SHIFT = 16,
CERT_SYSTEM_STORE_CURRENT_USER = CERT_SYSTEM_STORE_CURRENT_USER_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT,
}
| |
呵呵,虽然比较别扭,但没办法,谁让 C# 定义得这么严谨呢 :P
btw: 感谢 Junfeng Zhang 帮忙指出问题所在