侯文涛 發表於 2020-5-14 08:07:00

C#枚举高级战术

<p style="text-align: center; margin-bottom: 20px"><img style="display: inline-block" src="https://img2020.cnblogs.com/blog/191097/202005/191097-20200514001002688-1155327685.png" alt=""></p>
<p style="font-size: 15px; line-height: 1.85; letter-spacing: 1px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; white-space: normal">文章开头先给大家出一道面试题:</p>
<blockquote style="font-size: 14px; line-height: 1.85; letter-spacing: 1px; color: rgba(119, 119, 119, 1); border-left-color: rgba(221, 221, 221, 1); padding-top: 0; padding-left: 18px; quotes: none; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; white-space: normal">
<p style="line-height: 1.85">在设计某小型项目的数据库(假设用的是 MySQL)时,如果给用户表(User)添加一个字段(Roles)用来存储用户的角色,你会给这个字段设置什么类型?提示:要考虑到角色在后端开发时需要用枚举表示,且一个用户可能会拥有多个角色。</p>
</blockquote>
<p>映入你脑海的第一个答案可能是:varchar 类型,用分隔符的方式来存储多个角色,比如用&nbsp;<code style="letter-spacing: 0; font-size: 13px; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; margin-top: -1px; margin-right: 1px; margin-left: 1px; padding: 0.1em 0.3em 0.2em; background-color: rgba(241, 243, 245, 1)">1|2|3</code>&nbsp;或&nbsp;<code style="letter-spacing: 0; font-size: 13px; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; margin-top: -1px; margin-right: 1px; margin-left: 1px; padding: 0.1em 0.3em 0.2em; background-color: rgba(241, 243, 245, 1)">1,2,3</code>&nbsp;来表示用户拥有多个角色。当然如果角色数量可能超过个位数,考虑到数据库的查询方便(比如用 INSTR 或 POSITION 来判断用户是否包含某个角色),角色的值至少要从数字 10 开始。方案是可行的,可是不是太简单了,有没有更好的方案?更好的回答应是整型(int、bigint 等),优点是写 SQL 查询条件更方便,性能、空间上都优于 varchar。但整型毕竟只是一个数字,怎么表示多个角色呢?此时想到了二进制位操作的你,心中应该早有了答案。且保留你心中的答案,接着看完本文,或许你会有意外的收获,因为实际应用中可能还会遇到一连串的问题。为了更好的说明后面的问题,我们先来回顾一下枚举的基础知识。</p>
<h2 style="font-weight: bold; line-height: 1.5; font-size: 19px; color: rgba(34, 34, 34, 1); border-bottom: 2px solid rgba(255, 190, 0, 1); padding-bottom: 3px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; text-align: start; white-space: normal; margin-bottom: 20px">枚举基础</h2>
<p>枚举类型的作用是限制其变量只能从有限的选项中取值,这些选项(枚举类型的成员)各自对应于一个数字,数字默认从 0 开始,并以此递增。例如:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Days<br>{<br>    Sunday, Monday, Tuesday, <span style="color: rgba(92, 99, 112, 1); font-style: italic">// ...</span><br>}</code></pre>
<p>其中 Sunday 的值是 0,Monday 是 1,以此类推。为了一眼能看出每个成员代表的值,一般推荐显示地将成员值写出来,不要省略:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Days<br>{<br>    Sunday = <span style="color: rgba(209, 154, 102, 1)">0</span>, Monday = <span style="color: rgba(209, 154, 102, 1)">1</span>, Tuesday = <span style="color: rgba(209, 154, 102, 1)">2</span>, <span style="color: rgba(92, 99, 112, 1); font-style: italic">// ...</span><br>}</code></pre>
<p>C# 枚举成员的类型默认是 int 类型,通过继承可以声明枚举成员为其它类型,比如:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Days : <span style="color: rgba(198, 120, 221, 1)">byte</span><br>{<br>    Monday = <span style="color: rgba(209, 154, 102, 1)">1</span>,<br>    Tuesday = <span style="color: rgba(209, 154, 102, 1)">2</span>,<br>    Wednesday = <span style="color: rgba(209, 154, 102, 1)">3</span>,<br>    Thursday = <span style="color: rgba(209, 154, 102, 1)">4</span>,<br>    Friday = <span style="color: rgba(209, 154, 102, 1)">5</span>,<br>    Saturday = <span style="color: rgba(209, 154, 102, 1)">6</span>,<br>    Sunday = <span style="color: rgba(209, 154, 102, 1)">7</span><br>}</code></pre>
<p>枚举类型一定是继承自 byte、sbyte、short、ushort、int、uint、long 和 ulong 中的一种,不能是其它类型。下面是几个枚举的常见用法(以上面的 Days 枚举为例):</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 枚举转字符串</span><br><span style="color: rgba(198, 120, 221, 1)">string</span> foo = Days.Saturday.ToString(); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// "Saturday"</span><br><span style="color: rgba(198, 120, 221, 1)">string</span> foo = Enum.GetName(<span style="color: rgba(198, 120, 221, 1)">typeof</span>(Days), <span style="color: rgba(209, 154, 102, 1)">6</span>); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// "Saturday"</span><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 字符串转枚举</span><br>Enum.TryParse(<span style="color: rgba(152, 195, 121, 1)">"Tuesday"</span>, <span style="color: rgba(198, 120, 221, 1)">out</span> Days bar); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// true, bar = Days.Tuesday</span><br>(Days)Enum.Parse(<span style="color: rgba(198, 120, 221, 1)">typeof</span>(Days), <span style="color: rgba(152, 195, 121, 1)">"Tuesday"</span>); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// Days.Tuesday</span><br><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 枚举转数字</span><br><span style="color: rgba(198, 120, 221, 1)">byte</span> foo = (<span style="color: rgba(198, 120, 221, 1)">byte</span>)Days.Monday; <span style="color: rgba(92, 99, 112, 1); font-style: italic">// 1</span><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 数字转枚举</span><br>Days foo = (Days)<span style="color: rgba(209, 154, 102, 1)">2</span>; <span style="color: rgba(92, 99, 112, 1); font-style: italic">// Days.Tuesday</span><br><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 获取枚举所属的数字类型</span><br>Type foo = Enum.GetUnderlyingType(<span style="color: rgba(198, 120, 221, 1)">typeof</span>(Days))); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// System.Byte</span><br><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 获取所有的枚举成员</span><br>Array foo = Enum.GetValues(<span style="color: rgba(198, 120, 221, 1)">typeof</span>(MyEnum);<br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 获取所有枚举成员的字段名</span><br><span style="color: rgba(198, 120, 221, 1)">string</span>[] foo = Enum.GetNames(<span style="color: rgba(198, 120, 221, 1)">typeof</span>(Days));</code></pre>
<p>另外,值得注意的是,枚举可能会得到非预期的值(值没有对应的成员)。比如:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)">Days d = (Days)<span style="color: rgba(209, 154, 102, 1)">21</span>; <span style="color: rgba(92, 99, 112, 1); font-style: italic">// 不会报错</span><br>Enum.IsDefined(<span style="color: rgba(198, 120, 221, 1)">typeof</span>(Days), d); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// false</span></code></pre>
<p>即使枚举没有值为 0 的成员,它的默认值永远都是 0。</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">var</span> z = <span style="color: rgba(198, 120, 221, 1)">default</span>(Days); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// 0</span></code></pre>
<p>枚举可以通过 Description、Display 等特性来为成员添加有用的辅助信息,比如:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> <span style="color: rgba(97, 174, 238, 1)">ApiStatus</span><br>{<br>    [<span style="color: rgba(97, 174, 238, 1)">Description(<span style="color: rgba(152, 195, 121, 1)">"成功"</span>)</span>]<br>    OK = <span style="color: rgba(209, 154, 102, 1)">0</span>,<br>    [<span style="color: rgba(97, 174, 238, 1)">Description(<span style="color: rgba(152, 195, 121, 1)">"资源未找到"</span>)</span>]<br>    NotFound = <span style="color: rgba(209, 154, 102, 1)">2</span>,<br>    [<span style="color: rgba(97, 174, 238, 1)">Description(<span style="color: rgba(152, 195, 121, 1)">"拒绝访问"</span>)</span>]<br>    AccessDenied = <span style="color: rgba(209, 154, 102, 1)">3</span><br>}<br><br><span style="color: rgba(198, 120, 221, 1)">static</span> <span style="color: rgba(198, 120, 221, 1)">class</span> <span style="color: rgba(97, 174, 238, 1)">EnumExtensions</span><br>{<br>    <span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">static</span> <span style="color: rgba(198, 120, 221, 1)">string</span> <span style="color: rgba(97, 174, 238, 1)">GetDescription</span>(<span style="color: rgba(198, 120, 221, 1)">this</span> Enum val)<br>    {<br>      <span style="color: rgba(198, 120, 221, 1)">var</span> field = val.GetType().GetField(val.ToString());<br>      <span style="color: rgba(198, 120, 221, 1)">var</span> customAttribute = Attribute.GetCustomAttribute(field, <span style="color: rgba(198, 120, 221, 1)">typeof</span>(DescriptionAttribute));<br>      <span style="color: rgba(198, 120, 221, 1)">if</span> (customAttribute == <span style="color: rgba(86, 182, 194, 1)">null</span>) { <span style="color: rgba(198, 120, 221, 1)">return</span> val.ToString(); }<br>      <span style="color: rgba(198, 120, 221, 1)">else</span> { <span style="color: rgba(198, 120, 221, 1)">return</span> ((DescriptionAttribute)customAttribute).Description; }<br>    }<br>}<br><br><span style="color: rgba(198, 120, 221, 1)">static</span> <span style="color: rgba(198, 120, 221, 1)">void</span> <span style="color: rgba(97, 174, 238, 1)">Main</span>(<span style="color: rgba(198, 120, 221, 1)">string</span>[] args)<br>{<br>    Console.WriteLine(ApiStatus.Ok.GetDescription()); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// "成功"</span><br>}</code></pre>
<p>上面这些我认为已经包含了大部分我们日常用到的枚举知识了。下面我们继续回到文章开头说的用户角色存储问题。</p>
<h2 style="font-weight: bold; line-height: 1.5; font-size: 19px; color: rgba(34, 34, 34, 1); border-bottom: 2px solid rgba(255, 190, 0, 1); padding-bottom: 3px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; text-align: start; white-space: normal; margin-bottom: 20px">用户角色存储问题</h2>
<p>我们先定义一个枚举类型来表示两种用户角色:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Roles<br>{<br>    Admin = <span style="color: rgba(209, 154, 102, 1)">1</span>,<br>    Member = <span style="color: rgba(209, 154, 102, 1)">2</span><br>}</code></pre>
<p>这样,如果某个用户同时拥有 Admin 和 Member 两种角色,那么 User 表的 Roles 字段就应该存 3。那问题来了,此时若查询所有拥有 Admin 角色的用户的 SQL 该怎么写呢?对于有基础的程序员来说,这个问题很简单,只要用位操作符逻辑与(‘&amp;’)来查询即可。</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">SELECT</span> * <span style="color: rgba(198, 120, 221, 1)">FROM</span> <span style="color: rgba(152, 195, 121, 1)">`User`</span> <span style="color: rgba(198, 120, 221, 1)">WHERE</span> <span style="color: rgba(152, 195, 121, 1)">`Roles`</span> &amp; <span style="color: rgba(209, 154, 102, 1)">1</span> = <span style="color: rgba(209, 154, 102, 1)">1</span>;</code></pre>
<p>同理,查询同时拥有这两种角色的用户,SQL 语句应该这么写:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">SELECT</span> * <span style="color: rgba(198, 120, 221, 1)">FROM</span> <span style="color: rgba(152, 195, 121, 1)">`User`</span> <span style="color: rgba(198, 120, 221, 1)">WHERE</span> <span style="color: rgba(152, 195, 121, 1)">`Roles`</span> &amp; <span style="color: rgba(209, 154, 102, 1)">3</span> = <span style="color: rgba(209, 154, 102, 1)">3</span>;</code></pre>
<p>对这条 SQL 语句用 C# 来实现查询是这样的(为了简单,这里使用了 Dapper):</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">class</span> <span style="color: rgba(97, 174, 238, 1)">User</span><br>{<br>    <span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">int</span> Id { <span style="color: rgba(198, 120, 221, 1)">get</span>; <span style="color: rgba(198, 120, 221, 1)">set</span>; }<br>    <span style="color: rgba(198, 120, 221, 1)">public</span> Roles Roles { <span style="color: rgba(198, 120, 221, 1)">get</span>; <span style="color: rgba(198, 120, 221, 1)">set</span>; }<br>}<br><br>connection.Query&lt;User&gt;(<br>    <span style="color: rgba(152, 195, 121, 1)">"SELECT * FROM `User` WHERE `Roles` &amp; @roles = @roles;"</span>,<br>    <span style="color: rgba(198, 120, 221, 1)">new</span> { roles = Roles.Admin | Roles.Member });</code></pre>
<p>对应的,在 C# 中要判断用户是否拥有某个角色,可以这么判断:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 方式一</span><br><span style="color: rgba(198, 120, 221, 1)">if</span> ((user.Roles &amp; Roles.Admin) == Roles.Admin)<br>{<br>    <span style="color: rgba(92, 99, 112, 1); font-style: italic">// 做管理员可以做的事情</span><br>}<br><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 方式二</span><br><span style="color: rgba(198, 120, 221, 1)">if</span> (user.Roles.HasFlag(Roles.Admin))<br>{<br>    <span style="color: rgba(92, 99, 112, 1); font-style: italic">// 做管理员可以做的事情</span><br>}</code></pre>
<p>同理,在 C# 中你可以对枚举进行任意位逻辑运算,比如要把角色从某个枚举变量中移除:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">var</span> foo = Roles.Admin | Roles.Member;<br><span style="color: rgba(198, 120, 221, 1)">var</span> bar = foo &amp; ~Roles.Admin;</code></pre>
<p>这就解决了文章前面提到的用整型来存储多角色的问题,不论数据库还是 C# 语言,操作上都是可行的,而且也很方便灵活。</p>
<h2 style="font-weight: bold; line-height: 1.5; font-size: 19px; color: rgba(34, 34, 34, 1); border-bottom: 2px solid rgba(255, 190, 0, 1); padding-bottom: 3px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; text-align: start; white-space: normal; margin-bottom: 20px">枚举的 Flags 特性</h2>
<p>下面我们提供一个通过角色来查询用户的方法,并演示如何调用,如下:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">public</span> IEnumerable&lt;User&gt; <span style="color: rgba(97, 174, 238, 1)">GetUsersInRoles</span>(Roles roles)<br>{<br>    _logger.LogDebug(roles.ToString());<br>    _connection.Query&lt;User&gt;(<br>      <span style="color: rgba(152, 195, 121, 1)">"SELECT * FROM `User` WHERE `Roles` &amp; @roles = @roles;"</span>,<br>      <span style="color: rgba(198, 120, 221, 1)">new</span> { roles });<br>}<br><br><span style="color: rgba(92, 99, 112, 1); font-style: italic">// 调用</span><br>_repository.GetUsersInRoles(Roles.Admin | Roles.Member);</code></pre>
<p><code style="letter-spacing: 0; font-size: 13px; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; margin-top: -1px; margin-right: 1px; margin-left: 1px; padding: 0.1em 0.3em 0.2em; background-color: rgba(241, 243, 245, 1)">Roles.Admin | Roles.Member</code>&nbsp;的值是 3,由于 Roles 枚举类型中并没有定义一个值为 3 的字段,所以在方法内 roles 参数显示的是 3。3 这个信息对于我们调试或打印日志很不友好。在方法内,我们并不知道这个 3 代表的是什么。为了解决这个问题,C# 枚举有个很有用的特性:FlagsAtrribute。</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)">[<span style="color: rgba(97, 174, 238, 1)">Flags</span>]<br><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Roles<br>{<br>    Admin = <span style="color: rgba(209, 154, 102, 1)">1</span>,<br>    Member = <span style="color: rgba(209, 154, 102, 1)">2</span><br>}</code></pre>
<p>加上这个 Flags 特性后,我们再来调试&nbsp;<code style="letter-spacing: 0; font-size: 13px; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; margin-top: -1px; margin-right: 1px; margin-left: 1px; padding: 0.1em 0.3em 0.2em; background-color: rgba(241, 243, 245, 1)">GetUsersInRoles(Roles roles)</code>&nbsp;方法时,roles 参数的值就会显示为&nbsp;<code style="letter-spacing: 0; font-size: 13px; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; margin-top: -1px; margin-right: 1px; margin-left: 1px; padding: 0.1em 0.3em 0.2em; background-color: rgba(241, 243, 245, 1)">Admin|Member</code>&nbsp;了。简单来说,加不加 Flags 的区别是:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(198, 120, 221, 1)">var</span> roles = Roles.Admin | Roles.Member;<br>Console.WriteLing(roles.ToString()); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// "3",没有 Flags 特性</span><br>Console.WriteLing(roles.ToString()); <span style="color: rgba(92, 99, 112, 1); font-style: italic">// "Admin, Member",有 Flags 特性</span></code></pre>
<p>给枚举加上 Flags 特性,我觉得应当视为 C# 编程的一种最佳实践,在定义枚举时尽量加上 Flags 特性。</p>
<h2 style="font-weight: bold; line-height: 1.5; font-size: 19px; color: rgba(34, 34, 34, 1); border-bottom: 2px solid rgba(255, 190, 0, 1); padding-bottom: 3px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; text-align: start; white-space: normal; margin-bottom: 20px">解决枚举值冲突:2 的幂</h2>
<p>到这,枚举类型 Roles 一切看上去没什么问题,但如果现在要增加一个角色:Mananger,会发生什么情况?按照数字值递增的规则,Manager 的值应当设为 3。</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)">[<span style="color: rgba(97, 174, 238, 1)">Flags</span>]<br><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Roles<br>{<br>    Admin = <span style="color: rgba(209, 154, 102, 1)">1</span>,<br>    Member = <span style="color: rgba(209, 154, 102, 1)">2</span>,<br>    Manager = <span style="color: rgba(209, 154, 102, 1)">3</span><br>}</code></pre>
<p>能不能把 Manager 的值设为 3?显然不能,因为 Admin 和 Member 进行位的或逻辑运算(即:Admin | Member) 的值也是 3,表示同时拥有这两种角色,这和 Manager 冲突了。那怎样设值才能避免冲突呢?既然是二进制逻辑运算“或”会和成员值产生冲突,那就利用逻辑运算或的规律来解决。我们知道“或”运算的逻辑是两边只要出现一个 1 结果就会 1,比如 1|1、1|0 结果都是 1,只有 0|0 的情况结果才是 0。那么我们就要避免任意两个值在相同的位置上出现 1。根据二进制满 2 进 1 的特点,只要保证枚举的各项值都是 2 的幂即可。比如:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)"><span style="color: rgba(209, 154, 102, 1)">1</span>:<span style="color: rgba(209, 154, 102, 1)">00000001</span><br><span style="color: rgba(209, 154, 102, 1)">2</span>:<span style="color: rgba(209, 154, 102, 1)">00000010</span><br><span style="color: rgba(209, 154, 102, 1)">4</span>:<span style="color: rgba(209, 154, 102, 1)">00000100</span><br><span style="color: rgba(209, 154, 102, 1)">8</span>:<span style="color: rgba(209, 154, 102, 1)">00001000</span></code></pre>
<p>再往后增加的话就是 16、32、64...,其中各值不论怎么相加都不会和成员的任一值冲突。这样问题就解决了,所以我们要这样定义 Roles 枚举的值:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)">[<span style="color: rgba(97, 174, 238, 1)">Flags</span>]<br><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Roles<br>{<br>    Admin = <span style="color: rgba(209, 154, 102, 1)">1</span>,<br>    Member = <span style="color: rgba(209, 154, 102, 1)">2</span>,<br>    Manager = <span style="color: rgba(209, 154, 102, 1)">4</span>,<br>    Operator = <span style="color: rgba(209, 154, 102, 1)">8</span><br>}</code></pre>
<p>不过在定义值的时候要在心中小小计算一下,如果你想懒一点,可以用下面这种“位移”的方法来定义:</p>
<pre><code style="letter-spacing: 0; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding: 7px 15px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-width: 0; border-radius: 8px; overflow: auto; display: block; color: rgba(171, 178, 191, 1)">[<span style="color: rgba(97, 174, 238, 1)">Flags</span>]<br><span style="color: rgba(198, 120, 221, 1)">public</span> <span style="color: rgba(198, 120, 221, 1)">enum</span> Roles<br>{<br>    Admin    = <span style="color: rgba(209, 154, 102, 1)">1</span> &lt;&lt; <span style="color: rgba(209, 154, 102, 1)">0</span>,<br>    Member   = <span style="color: rgba(209, 154, 102, 1)">1</span> &lt;&lt; <span style="color: rgba(209, 154, 102, 1)">1</span>,<br>    Manager= <span style="color: rgba(209, 154, 102, 1)">1</span> &lt;&lt; <span style="color: rgba(209, 154, 102, 1)">2</span>,<br>    Operator = <span style="color: rgba(209, 154, 102, 1)">1</span> &lt;&lt; <span style="color: rgba(209, 154, 102, 1)">3</span><br>}</code></pre>
<p>一直往下递增编值即可,阅读体验好,也不容易编错。两种方式是等效的,常量位移的计算是在编译的时候进行的,所以相比不会有额外的开销。</p>
<h2 style="font-weight: bold; line-height: 1.5; font-size: 19px; color: rgba(34, 34, 34, 1); border-bottom: 2px solid rgba(255, 190, 0, 1); padding-bottom: 3px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; text-align: start; white-space: normal; margin-bottom: 20px">总结</h2>
<p style="font-size: 15px; line-height: 1.85; letter-spacing: 1px; font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial; white-space: normal">本文通过一道小小的面试题引发一连串对枚举的思考。在小型系统中,把用户角色直接存储在用户表是很常见的做法,此时把角色字段设为整型(比如 int)是比较好的设计方案。但与此同时,也要考虑到一些最佳实践,比如使用 Flags 特性来帮助更好的调试和日志输出。也要考虑到实际开发中的各种潜在问题,比如多个枚举值进行或(‘|’)运算与成员值发生冲突的问题。</p>

</div>
<div id="MySignature" role="contentinfo">
   
<div style="border: #e0e0e0 1px dashed; padding: 15px 20px; padding-left: 20px; background: #e5f1f4; position: relative">

<p style="margin-bottom: 5px">作者:精致码农-王亮</p>
<p style="margin-bottom: 5px; font-weight:bold;">
    出处:https://liamw.cn
</p>
<p style="margin-bottom: 5px">
    联系:liam.wang@live.com
</p>
<div>
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如有问题或建议,请多多赐教,非常感谢。
</div>
</div><br><br>
来源:https://www.cnblogs.com/willick/p/csharp-enum-superior-tactics.html
頁: [1]
查看完整版本: C#枚举高级战术