读书人

再谈enum-=运算[抛砖引玉]解决方法

发布时间: 2012-01-13 22:43:30 作者: rapoo

再谈enum-=运算[抛砖引玉]
问题出发点http://topic.csdn.net/u/20100404/11/fa54404c-1510-4027-a799-1bc420c53444.html?14163
原文:

C# code
    enum TU    {        A=0x01,        B=0x04,        C=0x05,        D=0x15,        E=0x0    }TU t=TU.D;t-=TU.A;//这是对的t+=TU.A//错误t=t+TU.A//错误t=t-TU.A//错误

-----------------------------------------
1.-=在枚举中的意义
二元 - 运算符是为所有数值类型和枚举类型预定义的,其功能是从第一个操作数中减去第二个操作数。
猜想:从整体上来看在此处t -= TU.A的应该是移除效果.
为什么会猜想是移除,原因要从enum的[FlagsAttribute]说起.
enum按位运算很常用,例如: FileAttributes.ReadOnly | FileAttributes.Hidden;
说到enum的按位运算,当定义的enum元素用 2 的幂(即 1、2、4、8 等)定义枚举常量。这意味着组合的枚举常量中的各个标志都不重叠.在使用ToString()时,才能枚举出所有的元素值.有以下例子:
C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.IO;namespace ConsoleApplication5{    class Program    {        static void Main(string[] args)        {            TU t =  TU.One| TU.Two | TU.Four;            Console.WriteLine(((Int32)t).ToString());            Console.WriteLine(t.ToString());            t -= TU.One;//从t中移除TU.One            Console.WriteLine(((Int32)t).ToString());            Console.WriteLine(t.ToString());        }    }    [FlagsAttribute]    enum TU    {        One = 1,        Two = 2,        Four = 4,        Eight = 8,        Sixteen =16    }}

运行结果

7
One,Two,Four
6
Two,Four


2.为什么不提供+=
a.从意义上来说+=和|=的作用相当.所以不必提供该操作.
b.实际上个人觉得+=(|=)比-=更容易产生不可预知的问题.例如FileAttributes.Normal和 FileAttributes.Hidden;FileAttributes.Normal标识文件未设置任何属性,FileAttributes.Hidden则是标识文件是隐藏的,两者相异.这其中有一些微妙的地方,下文将说到.

3.enum的微妙之处
以下引自MSDN中<<对FlagsAttribute 和 Enum 的准则>>的第二条.
用 2 的幂(即 1、2、4、8 等)定义枚举常量。这意味着组合的枚举常量中的各个标志都不重叠。
enum在实际应用中会有类似FileAttributes.Normal的情况.例如存在一个自定义的文件操作枚举FileOperations,假设该枚举存在一个值None ,该值是用于标识未指定其他值的情况.
该enum如下.
C# code
        [FlagsAttribute]        enum FileOperations        {            Read = 1,            Write = 2,            Delete = 4,            Hidden = 8,            None = 16        }

然后进行以下实验
C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{    class Program    {        static void Main(string[] args)        {            FileOperations f = FileOperations.Delete | FileOperations.Read;            Console.WriteLine("原始的值\r\n" + f.ToString());            f -= FileOperations.Delete;            Console.WriteLine("移除Delete后的值\r\n"+f.ToString());            f -= FileOperations.Read;            Console.WriteLine("移除Read后的值\r\n" + f.ToString());        }        [FlagsAttribute]        enum FileOperations        {            Read = 1,            Write = 2,            Delete = 4,            Hidden = 8,            None = 16,        }    }}

运行结果:

原始的值
Read,Delete
移除Delete后的值
Read
移除Read后的值
0
请按任意键继续. . .

这里我们可以看到,FileOperations f = FileOperations.Delete | FileOperations.Read依次移除后,状态应该是前面提到的该值是用于标识未指定其他值的情况.,即None,但实际上f并没得到我们想要的值.
然后我们将代码加经改进
C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{    class Program    {        static void Main(string[] args)        {            FileOperations f = FileOperations.Delete | FileOperations.Read;            Console.WriteLine("原始的值\r\n" + f.ToString());            f -= FileOperations.Delete;            Console.WriteLine("移除Delete后的值\r\n"+f.ToString());            f -= FileOperations.Read;            Console.WriteLine("移除Read后的值\r\n" + f.ToString());        }        [FlagsAttribute]        enum FileOperations        {            Read = 1,            Write = 2,            Delete = 4,            Hidden = 8,            None = 0,//这里是主要变化        }    }} 


主要的变化是我们将None的值由16变成了0,运行结果如下:

原始的值
Read,Delete
移除Delete后的值
Read
移除Read后的值
None
请按任意键继续. . .

这样就达到了我们的目的.
假设我们需要扩展一个包含所有操作的值All,加以改进就行.
C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{    class Program    {        static void Main(string[] args)        {            FileOperations f = FileOperations.Delete | FileOperations.Read | FileOperations.Write | FileOperations.Hidden;            Console.WriteLine("原始的值\r\n" + f.ToString());            f -= FileOperations.Delete;            Console.WriteLine("移除Delete后的值\r\n"+f.ToString());            f -= FileOperations.Read;            Console.WriteLine("移除Read后的值\r\n" + f.ToString());            f -= FileOperations.Write;            Console.WriteLine("移除Write后的值\r\n" + f.ToString());            f -= FileOperations.Hidden;            Console.WriteLine("移除Hidden后的值\r\n" + f.ToString());        }        [FlagsAttribute]        enum FileOperations        {            None = 0,            Read = 1,            Write = 2,            Delete = 4,            Hidden = 8,            All = 15//此处是关键        }    }}

运行结果:

原始的值
All
移除Delete后的值
Read, Write, Hidden
移除Read后的值
Write, Hidden
移除Write后的值
Hidden
移除Hidden后的值
None

注意上面的All不再是2的次幂.此前的None也不再是.All为前面所有值的和.None则为0;
这样的指定并没有破坏enum的位运算.
建义使用2的次幂做值的原因是因为1,2,4,8……2^n可以组合成任何1~2^n之间的数.如3=1+2,5=4+1,7=4+2+1……。
这样在做逻辑运算产生新值的时候会与原定义值重复。
而0和2^n+1不在此列。但微软似乎出于别的原因考虑,不推荐这样使用。FileAttributes 枚举使用的值是128。
4。意外的结果
C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.IO;namespace ConsoleApplication1{    class Program    {        static void Main(string[] args)        {            FileOperations f = FileOperations.Hidden;            Console.WriteLine("原始的值\r\n" + f.ToString());            f -= FileOperations.Delete;            Console.WriteLine("移除Delete后的值\r\n"+f.ToString());        }        [FlagsAttribute]        enum FileOperations        {            None = 0,            Read = 1,            Write = 2,            Delete = 4,            Hidden = 8,            All = 15        }    }}


虽然应用中不会从一个只包含Hidden的enum中减去一个Delete。,但这个结果着实让我吓一跳。

上例从一个enum实例中移除一个值。原本f= Hidden;但移除Delete后,居然变成Delete了,因为8 - 4 = 4;
如果-=是移除的话,理论应该是None。-=是什么操作已经不重要了,不管它是什么,都带来了意料之中、情理之外的结果。
-=真的需要要达到一个移除操作的话。只需将要运算的变量进行一次|=运算,再执行-=即可。


[解决办法]
值为2的幂是为了应用 FlagsAttribute 特性。
[解决办法]
C# code
enum TU { A,B,C,D,E }TU t = TU.D;t -= TU.A; // 按我的理解,这应该是等价于 t = (TU)(t - TU.A);
[解决办法]
C# code
enum TU { A,B,C,D,E }TU t = TU.D;t -= TU.A;           // 可以t = (TU)(t - TU.A);  // 可以,等价于上行t = t - TU.A;        // 错误// 类比:byte b = 78;b -= 5;              // 可以b = (byte)(b - 5);   // 可以,等价于上行b = b - 5;           // 错误
[解决办法]
两个 Enum 类型的变量相减的结果为 int,这点和 byte 类型的变量相减类似。

但两个 Enum 类型的变量不能相加,这和 byte 类型是不同的。



两个枚举变量相减的意思就是它们之间相隔多远,结果是 int。
两个枚举变量相加就没有明显的、自然的意义了,所以不允许。

正如两个 DateTime 型的变量相减的意思是时间间隔,结果是 TimeSpan 型的变量,
而两个 DateTime 型的变量相加没有意义,不允许。
------解决方案--------------------


要除去一个值,应该取反再与,哪能直接减啊……
[解决办法]

探讨
枚举的实现机制注定了它们相减得到的不是一个Int,虽然得到的枚举本质上是Int,但显现出来的却是枚举特性,除非找不到匹配的情况..

[解决办法]
想从枚举量 a 中移除枚举量 b,正确而简单的做法应该是:a &= ~b;

想用 a -= b; 的办法移除 b,就必须先判断 a 是否含有 b,
并且不是总是有效的,楼下说明。

C# code
using System;[Flags]enum FileOperations{  Delete = 4,  Hidden = 8,}class Program{  static void Main()  {    FileOperations a, b = FileOperations.Delete;        // 想从 a 中移除 b,正确的做法如下:    a = FileOperations.Hidden;    a &= ~b;  // 不论 a 是否含有 b,都可以得到正确的结果    Console.WriteLine(a); // 输出 Hideden,正确。    // 想从 a 中移除 b,不正确的做法如下:    a = FileOperations.Hidden;    a -= b;  // 如果 a 不含有 b,就不能得到正确的结果    Console.WriteLine(a); // 输出 Delete,错误。    // 想从 a 中移除 b,另一种做法:    a = FileOperations.Hidden;    if (( a & b) == b) a -= b;  // 先判断 a 是否含有 b,再相减,可以。    Console.WriteLine(a);       // 输出 Hideden,正确。  }}
[解决办法]
如果枚举量 a 只含有部分的 b,从 a 移除 b 正确而简单的做法仍然是:a &= ~b;

a -= b; 就没有办法做到从 a 移除 b 了,无论是是否判断 a 含有 b。

C# code
using System;[Flags]enum FileOperations{  Read = 1,  Write = 2,  Delete = 4,  Hidden = 8,}class Program{  static void Main()  {    FileOperations a, b = FileOperations.Delete | FileOperations.Read;        // 想从 a 中移除 b,正确的做法如下:    a = FileOperations.Hidden | FileOperations.Delete;    a &= ~b;              // 不论 a 是否部分含有 b,都正确。    Console.WriteLine(a); // 输出 Hideden,正确。    // 想从 a 中移除 b,不正确的做法如下:    a = FileOperations.Hidden | FileOperations.Delete;    a -= b;                     // 如果 a 含有部分的 b,结果不知所云。    Console.WriteLine(a);       // 输出 Read, Write, Delete,错误。    // 想从 a 中移除 b,另一种做法(错误):    a = FileOperations.Hidden | FileOperations.Delete;    if (( a & b) == b) a -= b;  // 先判断 a 是否完全含有 b,再相减。    Console.WriteLine(a);       // 输出 Delete, Hidden,错误。    // 想从 a 中移除 b,还一种做法(也不正确):    a = FileOperations.Hidden | FileOperations.Delete;    if (( a & b) != 0) a -= b;  // 先判断 a 是否部分含有 b,再相减。    Console.WriteLine(a);       // 输出 Read, Write, Delete,也不正确。  }}
[解决办法]
总结:两个枚举量相减的结果是 int,意义是它们的间隔,
再转换为枚举没有什么实际意义,只是在某些特定的情况下表现为移除的效果。
所以 a -= b; 是不好的写法,因为把枚举量的间隔转换为枚举在很多情况下没意义。

移除的正确而简单的写法是:a &= ~b;

[解决办法]
两个枚举量相减的结果是有意义的,就是它们的间隔(结果是 int 型的,而不是枚举型的)。

如果 a 和 b 是两个相同类型的枚举量,则表达式 a - b 是有意义的,如上所述。


但 a -= b; 只不过是 a = (a的类型)(a - b); 的简便写法而已,把一个 int 型的量(枚举量的间隔)转换为枚举量,在很多情况下没意义。
[解决办法]
枚举量的 a -= b; 在语法上是正确的,但在语义上不正确,是不好的写法。
[解决办法]
a &= ~b;

这个去除功能不错
[解决办法]
在使用FlagAttribute标记的枚举时不建议使用+和-,建议使用 | 和 ^

读书人网 >C#

热点推荐