C# 枚举的flags 标志位应用
枚举有个特性叫标志位,使用方法如下
[Flags] enum Foo { a =1, b = 2, c = 4, d = 8 }
每个值需要为2的n次方,保证多个值的组合不会重复.
这样在判断其中一个枚举值c 是否在a,b,c这个范围中就可以简化写法
常规写法如下
var c = Foo.c; if( c == Foo.a || c == Foo.b || c == Foo.c ) { }
因为值为2的n次方,所以可以通过按位相与来得出是否在范围内
var c = Foo.c; if( (c & ( Foo.a | Foo.b | Foo.c ) != c ) { }
注意,如果c在范围内则结果等于c,否则结果为0
可以写个扩展方法
public static class EnumExtension { /// <summary> /// 判断该枚举是否在范围内 注意:该枚举类型需要有[Flags] 标注 /// </summary> /// <param name="source">待比较的值</param> /// <param name="range">枚举范围</param> /// <returns></returns> public static bool IsIn(this Enum source, params Enum[] range) { if(range.Length == 0) { return false; } else if(range.Length == 1) { return source == range[0]; } int r = Convert.ToInt32(range[0]); for (int i = 1; i < range.Length; i++) { r |= Convert.ToInt32(range[i]); } return !((Convert.ToInt32(source) & r) == 0);//也可写作return (Convert.ToInt32(source) & r) == Convert.ToInt32(source); } }
注意:不建议在EF的linq查询中调用扩展方法,可能会导致查询变为客户端评估,使得所有数据是查询全表后在内存中过滤,效率会低很多
调用如下
var c = Foo.c; if(c.IsIn(Foo.a, Foo.b, Foo.c)) { }
//如果判断单个枚举,则有官方提供的方法HasFlag
if(c.HasFlag(Foo.a))
{
}
拆分枚举
public static List<T> SplitEnum<T>(this T e) where T : Enum { var result = new List<T>(); foreach (T item in Enum.GetValues(typeof(T))) { if ((Convert.ToInt32(item) & Convert.ToInt32(e)) > 0) { result.Add(item); } } return result; }
合并枚举
public static T MergeEnum<T>(this IEnumerable<T> enums) where T : Enum { var enumValue = 0; foreach (T item in enums) { enumValue |= Convert.ToInt32(item); } return (T)enumValue; }
此外flags还会重写该枚举的ToString()
比如 由于3 = 1 | 2,所以3就相当于a|b
当对3强转为Foo后进行ToString会输出 a,b 而不是3
这样简单组合就可以得到新的有效枚举值,这种设计在权限等方面应用很多.
比如linux的文件权限 read write execute,缩写为r w x,对应值为 4 2 1
当需要一个值为
可读可写, r w, 4 | 2 = 6
可写可执行, w x, 2 | 1 = 3
可读可执行, r x, 4 | 1 = 5
可读可写可执行 r w x, 4 | 2 | 1 = 7
当然,也可以加上命名指定枚举值组合,比如定义一个ac 来替代a|c
[Flags] enum Foo { a = 1, b = 2, c = 4, ac = a | c, d = 8 }