再谈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 型的变量相加没有意义,不允许。
------解决方案--------------------
要除去一个值,应该取反再与,哪能直接减啊……
[解决办法]
[解决办法]
想从枚举量 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标记的枚举时不建议使用+和-,建议使用 | 和 ^