当前位置 博文首页 > 要有梦想:枚举位逻辑运算从懵懂到似懂非懂
? 相信能看到这篇文章的同学都是对枚举的位逻辑运算有了初步的了解,但是又没有一个全面的认知而来。刚好最近闲来无事,想起来有这么一个逻辑运算方式,简单且高效,并且自己也仅仅是从其它文章中简单看到过一些描述,没有进行过实际的应用。所以今天就分享一下自己的学习过程和Demo,自己做个记录的同时也希望能帮助到同样想快速理解的你。
? 接下来的分享中会涉及到“对整型运算对象按位进行逻辑运算”相关知识,所以推荐对“位运算符”的概念模糊的同学首先看一下以下文章:C# 位运算及实例计算 - 艾三元 - 博客园 (cnblogs.com)。此文章是在查询资料过程中个人感觉最简单清晰好理解“位运算符”的一片文章,看完之后对于后续分享品尝效果更佳。
using System;
namespace EnumDemo
{
class Program
{
static void Main(string[] args)
{
var foo = Roles.萧炎 | Roles.林动 | Roles.霍雨浩;
Console.WriteLine(Convert.ToString((int)foo, 2).PadLeft(4, '0'));
Console.WriteLine(Convert.ToString((int)~foo, 2).PadLeft(4, '0'));
//foo = foo & ~foo; //清空
foo = foo & ~Roles.霍雨浩; //移除具体项,(相同方式下支持移除组合项)
Console.WriteLine(Convert.ToString((int)foo, 2).PadLeft(4, '0'));
if (foo.HasFlag(Roles.萧炎)) Console.WriteLine("检测到萧炎");
if ((foo & Roles.林动) == Roles.林动) Console.WriteLine("检测到林动");
if (foo.HasFlag(Roles.霍雨浩)) Console.WriteLine("检测到霍雨浩");
Console.WriteLine(foo.ToString());
Console.ReadLine();
}
}
[Flags]
public enum Roles
{
萧炎 = 1 << 0,//1
林动 = 1 << 1,//2
霍雨浩 = 1 << 2,//4
小舞 = 1 << 3,//8
唐舞麟 = 1 << 4,//16
戴沐白 = 1 << 5,//32
朱竹青 = 1 << 6,//64
奥斯卡 = 1 << 7,//128
宁荣荣 = 1 << 8,//256
马红俊 = 1 << 9,//512
}
}
0111
11111111111111111111111111111000
0011
检测到萧炎
检测到林动
萧炎, 林动
[Flags]
public enum Roles
{
萧炎 = 1 << 0,//1
林动 = 1 << 1,//2
霍雨浩 = 1 << 2,//4
小舞 = 1 << 3,//8
唐舞麟 = 1 << 4,//16
戴沐白 = 1 << 5,//32
朱竹青 = 1 << 6,//64
奥斯卡 = 1 << 7,//128
宁荣荣 = 1 << 8,//256
马红俊 = 1 << 9,//512
}
假如我们的权限只有萧炎,那么我们对应值为1
同理我们的权限只有林动,那么我们对应值为2
同时拥有萧炎和林动的权限,对应值则直接取(1+2)= 3,代表其既包含萧炎又包含林动
此时如果霍雨浩的值为3,那么会导致无法区分3到底是“萧炎+林动”还是单纯的“霍雨浩”
既然是二进制逻辑运算“或”会和成员值产生冲突,那就利用逻辑运算或的规律来解决。
? 我们知道“或”运算的逻辑是两边只要出现一个 1 结果就是 1,比如 1|1、1|0 结果都是 1,只有 0|0 的情况 结果才是 0。那么我们就要避免任意两个值在相同的位置上出现 1。根据二进制满 2 进 1 的特点,只要保证 枚举的各项值都是 2 的幂即可。比如:
1: 00000001
2: 00000010
4: 00000100
8: 00001000
再往后增加的话就是 16、32、64...,其中各值不论怎么相加都不会和成员的任一值冲突。
枚举的 Flags 特性,看码子说话
void FlagsSimple()
{
var roles = Roles.萧炎 | Roles.林动;
// 没有 Flags 特性输出结果为:3
Console.WriteLine(roles.ToString());
// 有 Flags 特性输出结果则为:"萧炎,林动"
Console.WriteLine(roles.ToString());
}
实际使用总结:加上 Flags 特性品尝效果更佳
static void Main(string[] args)
{
var foo = Roles.萧炎 | Roles.林动 | Roles.霍雨浩;
Console.WriteLine(Convert.ToString((int)foo, 2).PadLeft(4, '0'));
Console.WriteLine(Convert.ToString((int)~foo, 2).PadLeft(4, '0'));
//foo = foo & ~foo; //清空
foo = foo & ~Roles.霍雨浩; //移除具体项,(相同方式下支持移除组合项)
Console.WriteLine(Convert.ToString((int)foo, 2).PadLeft(4, '0'));
if (foo.HasFlag(Roles.萧炎)) Console.WriteLine("检测到萧炎");
if ((foo & Roles.林动) == Roles.林动) Console.WriteLine("检测到林动");
if (foo.HasFlag(Roles.霍雨浩)) Console.WriteLine("检测到霍雨浩");
Console.WriteLine(foo.ToString());
Console.ReadLine();
}
在C#中用以下方式进行判断即可:
if (foo.HasFlag(Roles.萧炎)) Console.WriteLine("检测到萧炎");
if ((foo & Roles.林动) == Roles.林动) Console.WriteLine("检测到林动");
创建Demo表
CREATE TABLE [dbo].[PowersDemo](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NULL,
[PowerNo] [int] NULL,
[Powers] [int] NULL,
CONSTRAINT [PK_PowersDemo] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
插入测试数据
Id | Name | PowerNo | Powers |
---|---|---|---|
1 | 萧炎 | 1 | 3 |
2 | 林动 | 2 | 3 |
3 | 霍雨浩 | 4 | 20 |
4 | 小舞 | 8 | 12 |
5 | 唐舞麟 | 16 | 16 |
6 | 戴沐白 | 32 | 100 |
7 | 朱竹青 | 64 | 64 |
8 | 奥斯卡 | 128 | 384 |
9 | 宁荣荣 | 256 | 384 |
品尝脚本
select * from dbo.PowersDemo
--查询Powers包含霍雨浩权限的角色
select * from dbo.PowersDemo
where Powers & 4 = 4
--移除小舞对霍雨浩的权限
update dbo.PowersDemo set Powers = Powers & ~4
where Id = 4
select * from dbo.PowersDemo
--查询Powers包含霍雨浩权限的角色
select * from dbo.PowersDemo
where Powers & 4 = 4
--清空小舞对所有角色的权限
update dbo.PowersDemo set Powers = Powers & ~Powers
where Id = 4
--查询小舞的信息
select * from dbo.PowersDemo
? 在小型系统中,把用户角色或权限等直接存储在用户表是很常见的做法,此时把角色或权限字段设为整型(比如 int)是比较好的设计方案。但与此同时,也要考虑到一些最佳实践,比如使用 Flags 特性来帮助更好的调试和日志输出。也要考虑到实际开发中的各种潜在问题,比如多个枚举值进行或(‘|’)运算与成员值发生冲突的问题。