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
}

 

posted @ 2020-12-03 12:00  turingguo  阅读(3821)  评论(0编辑  收藏  举报