.NET枚举类型优化探讨(二)

昨天在.NET中的枚举值(一)中我提到,如果将该文中的实现进一步架构,提炼出一个抽象类作为自定义枚举类型的基类的话,肯定会对后续开发有很好的帮助。今天我们就来继续探讨一下!

需要补充的是,在第一集中我就说过,这种使用类或结构来替代枚举类型的方案并不能替代所有的内置枚举值,而是在必要的时候用用即可。钻牛角尖的朋友们要小心在意了!


这个方案貌似是可以重构的。但是在上一集中我们应该留意到,代码中重写了很多运算符,运算符是静态成员,而静态成员是无法被继承的,还有Parse等帮助性成员也是静态的,这就意味着,想要以抽象类完全重构包含必要的静态成员的类是不可能的事情。遇到这类问题,我们就无法从代码上直接控制约束了,只能从口头上(或者文档上)约定好如何去做。

也就是说,由于老陈的一时兴奋,犯下了一个思维定势的严重错误,如果要重构,也只能部分重构,不够给力。既然如此,今天我们就深入研究一下这种方案,看看它的利害。


首先我们看看这种方案的最精简模式:

 1 [Serializable] // 不是必要的
2 public sealed class UserOperates
3 {
4 public static readonly UserOperates None = new UserOperates(0);
5 public static readonly UserOperates Read = new UserOperates(1);
6 public static readonly UserOperates Write = new UserOperates(2);
7 public static readonly UserOperates Delete = new UserOperates(4);
8
9 private UserOperates(int value) { this.Value = value; }
10
11 public int Value { get; private set; }
12 }

这里有几个要素:

  1. 枚举值字段必须是只读的;
  2. 构造函数必须不能从外部改变,因此标记为私有的;
  3. 类被声明为密封的,限定了它的多态性;
  4. 为了能够从外部获取枚举值字段的原始值,我们定义了一个Value成员,从这个角度来讲,本示例还不算是最精简的;

把这个类修改一下,再来看看:

 1 [Serializable] // 不是必要的
2 public class UserOperates
3 {
4 public static readonly UserOperates None = new UserOperates(0);
5 public static readonly UserOperates Read = new UserOperates(1);
6 public static readonly UserOperates Write = new UserOperates(2);
7 public static readonly UserOperates Delete = new UserOperates(4);
8
9 public static readonly List<UserOperates> EnumFields = new List<UserOperates> {
10 None,
11 Read,
12 Write,
13 };
14
15 protected UserOperates(int value) { this.Value = value; }
16
17 public int Value { get; private set; }
18
19 public static UserOperates operator |(UserOperates p1, UserOperates p2) { return Parse(p1.Value | p2.Value); }
20
21 public static UserOperates operator ^(UserOperates p1, UserOperates p2) { return Parse(p1.Value ^ p2.Value); }
22
23 public static UserOperates operator &(UserOperates p1, UserOperates p2) { return Parse(p1.Value & p2.Value); }
24
25 public bool HasFlag(UserOperates flag) { return (this.Value & flag.Value) == flag.Value; }
26
27 public static UserOperates Parse(int value)
28 {
29 // 注意:这里没有考虑位域操作
30 return EnumFields.FirstOrDefault(item => item.Value == value) ?? None;
31 }
32 }

我们增加了一些操作符,但是我们来看看如下代码,您认为它们分别输出什么?

 1 var o1 = UserOperates.Write | UserOperates.Delete;
2 var o2 = UserOperates.Read | UserOperates.Write | UserOperates.Delete;
3 var o3 = (UserOperates.Read | UserOperates.Write | UserOperates.Delete) ^ UserOperates.Read;
4 var o4 = (UserOperates.Read | UserOperates.Write | UserOperates.Delete) & UserOperates.Read;
5 var o5 = (UserOperates.Read | UserOperates.Write | UserOperates.Delete).HasFlag(UserOperates.Write);
6
7 Trace.WriteLine(o1.Value);
8 Trace.WriteLine(o2.Value);
9 Trace.WriteLine(o3.Value);
10 Trace.WriteLine(o4.Value);
11 Trace.WriteLine(o5);

答案是:

1 0
2 0
3 2
4 0
5 False

没有一个是对的!怎么会这样呢?

这是我们代码中的bug,我们在将各个枚举值的原始值分别计算之后,试图在字典中查找与之匹配的项,结果都是无法找到,因此都返回为默认字段None,它的值是0。弥补方法之一:

 1 public static readonly UserOperates None = new UserOperates(0);
2 public static readonly UserOperates Read = new UserOperates(1);
3 public static readonly UserOperates Write = new UserOperates(2);
4 public static readonly UserOperates ReadWrite = new UserOperates(3);
5 public static readonly UserOperates Delete = new UserOperates(4);
6 public static readonly UserOperates ReadDelete = new UserOperates(5);
7 public static readonly UserOperates WriteDelete = new UserOperates(6);
8 public static readonly UserOperates ReadWriteDelete = new UserOperates(7);
9
10 public static readonly List<UserOperates> EnumFields = new List<UserOperates> {
11 None,
12 Read,
13 Write,
14 ReadWrite,
15 Delete,
16 ReadDelete,
17 WriteDelete,
18 ReadWriteDelete
19 };

再来运行:

1 6
2 7
3 5
4 1
5 True

输出完全正确!其实,这正是“使用类来替代某些枚举值”的一种缺陷,我们不得不将任何可能出现的组合都列出来才能保证运行正常。鉴于这个事实,老陈建议:对于需要位域操作的枚举类型,尤其是那些较为复杂的位域枚举类型,千万不要使用此方案,这可能会加重我们的编码负担!

还有一个潜在的bug,就是EnumFields字段的值在外部是可更改的。也需要规避掉。很简单,将它的修饰符public修改为protected即可。

此外还发现,昨天的ToString()方法写的也有问题,应当修改为:

1 public override string ToString() { return this.Name; }

在整个重构过程中还遇到了其他的困难,比如运算符无法使用泛型,这意味着如果要使用多态来重构这个代码的话,运算符就需要在每个类重写,而且还会牵扯到各种类型转换问题,其细节非常繁杂。

总而言之,昨天所提到的使用抽象类重构的梦想已经基本破灭,因为这种重构带来的痛苦比喜悦要多的多,不如不重构。这种替代枚举类型的方案用途很有限,不要滥用!


对于我自身而言,以后写文章应当更加谨慎,做过所有测试之后再来显摆,狂妄自大终归会自食其果的!欢迎大家继续批评指导!

预报:明天将完成第三集,讨论一下如何使用Attribute特性来实现枚举值的多常量绑定,同时将会放出我自己使用的两个枚举类型封装类。

 

posted @ 2012-03-21 11:48  O.C  阅读(1073)  评论(0编辑  收藏  举报