.NET枚举类型优化探讨(一)
昨天晚上通过博文《Java中的枚举值》和大家分享探讨了Java枚举值语法的非常规性和它给力的地方,该文引起了.NET猴子的一些非议,因为Java能做到的,.NET基本上也能做到。那么今天老陈就来和大家共同研究一下.NET中的枚举类型,看看它和Java相比有没有神马优势。
有言在先:老陈从2000年写下第一行代码开始,这10几年来接触过的开发语言少说也有十几种了。我从来不会对语言、开发环境等施以抱怨或膜拜。我们应该以平常心去看待这些问题,不要提到Java,.NET阵营就不爽,提到.NET,Java阵营不爽。总是搞那些个平台偏见有用么?不如安下心来看看这些语言中有哪些好处,有哪些坏处,好的地方我们发扬光大,坏的地方我们可以联名请书官方对语言进行改进,这样不好么?
以下代码演示了C#中带有位域特性的枚举类型的定义:
1 [Flags]
2 public enum UserOperates
3 {
4 None = 0,
5 Read = 1,
6 Write = 2,
7 FullAccess = Read | Write
8 }
.NET枚举类型的位域操作在这里我不再解释(在老陈的博文中,永远不会去讲解那些太过基础的玩意儿,如果需要的话请找度娘,度娘不行找谷歌,谷歌不行再来找老陈~),在Java中,其枚举类型可以定义非字段成员,比如各种方法、构造函数等,显得非常给力,因此我在《Java中的枚举值》一文中说道“.NET猴子各种羡慕嫉妒恨”,不过您今天有幸看到了这篇博文之后,就无需再羡慕嫉妒恨了,老陈告诉你C#(.NET支持的开发语言有很多,但我钟爱C#)照样可以!
如果枚举类型能够定义方法成员和构造函数的话,那么我们可以做很多事情,比如在获取某枚举值的时候,还能获取到与它相关的其他信息,比如与枚举值绑定的中文说明,与枚举值绑定的操作信息等等。之前老陈使用.NET中的Attribute特性实现过一个类,专门来解决这样的问题,不过昨天看了Java的语法之后,我失眠了!想出了以下办法,我认为这种方式更加灵活一些——使用类(或struct)替代某些枚举类型。
我们来看一段代码:
1 public sealed class UserOperates
2 {
3 public static readonly UserOperates None = new UserOperates(0, "未定义");
4 public static readonly UserOperates Read = new UserOperates(1, "读");
5 public static readonly UserOperates Write = new UserOperates(2, "写");
6 public static readonly UserOperates FullAccess = new UserOperates(3, "读写");
7
8 /// <summary>
9 /// 初始化 <see cref="UserOperates"/> 类的新实例。
10 /// </summary>
11 /// <param name="value">枚举类型的原始值。</param>
12 /// <param name="title">枚举类型的标题。</param>
13 /// <remarks>这里将构造函数定义为私有的,以符合枚举类型的约束</remarks>
14 private UserOperates(int value, string title)
15 {
16 this.Value = value;
17 this.Title = title;
18 }
19
20 /// <summary>
21 /// 获取枚举类型的原始值。
22 /// </summary>
23 public int Value { get; private set; }
24
25 /// <summary>
26 /// 获取枚举类型的标题。
27 /// </summary>
28 public string Title { get; private set; }
29
30 /// <summary>
31 /// 返回表示当前对象的字符串。
32 /// </summary>
33 /// <returns>返回 <see cref="System.String"/>。</returns>
34 public override string ToString()
35 {
36 switch (this.Value)
37 {
38 case 1:
39 return "Read";
40
41 case 2:
42 return "Write";
43
44 case 3:
45 return "FullAccess";
46
47 default:
48 return "None";
49 }
50 }
51 }
看了以上的代码实现,是不是眼前一亮?是的,枚举类型本来就是一个精简了的类,只不过.NET做出了更多的限制而已。如下截图演示了如上方案的简单实践:
细心的朋友应该可以发觉这里存在几个问题:
- 缺少位域操作的直接支持;
- 缺少从数值或字符串转换为枚举类型的支持;
木有关系,.NET的强大不是被吹嘘出来的,只是某些人们不懂得利用而已(比如京东、12306不够给力,实际上是因为他们没用好.NET,并非因为.NET很垃圾)!我们可以通过定义额外的方法成员来满足以上两个操作的需要,也可以“重写操作符”。
从数值转换为“枚举类型”的方法:
1 // 这里只是简单举例,从架构角度来讲这里的写法没有任何可扩展性
2 public static UserOperates Parse(int value)
3 {
4 switch (value)
5 {
6 case 1:
7 return UserOperates.Read;
8
9 case 2:
10 return UserOperates.Write;
11
12 case 3:
13 return UserOperates.FullAccess;
14
15 default:
16 return UserOperates.None;
17 }
18 }
从字符串转换为“枚举类型”的方法:
1 // 这里只是简单举例,从架构角度来讲这里的写法没有任何可扩展性
2 public static UserOperates Parse(string name)
3 {
4 switch (name)
5 {
6 case "Read":
7 return UserOperates.Read;
8
9 case "Write":
10 return UserOperates.Write;
11
12 case "FullAccess":
13 return UserOperates.FullAccess;
14
15 default:
16 return UserOperates.None;
17 }
18 }
位域操作的问题可以通过重载运算符来解决,下面的代码实现了常见的几种位域操作:
1 // 实现“|”运算符
2 public static UserOperates operator |(UserOperates p1, UserOperates p2)
3 {
4 var value = p1.Value | p2.Value;
5
6 return Parse(value);
7 }
8
9 // 实现“^”运算符
10 public static UserOperates operator ^(UserOperates p1, UserOperates p2)
11 {
12 var value = p1.Value ^ p2.Value;
13
14 return Parse(value);
15 }
16
17 // 实现“&”运算符
18 public static UserOperates operator &(UserOperates p1, UserOperates p2)
19 {
20 var value = p1.Value & p2.Value;
21
22 return Parse(value);
23 }
24
25 // 实现位域判断操作:即判断当前“枚举值”中是否已经包含了给定的“枚举值”
26 // 具体的原理请参考:数学!
27 public bool HasFlag(UserOperates flag)
28 {
29 // 这里直接使用 Value 来操作,省得拐弯..
30 return (this.Value & flag.Value) == flag.Value;
31 }
此外,我们还知道,class在.NET中属于引用类型,如果直接比较的话对于枚举值来说并不准确,因为对于引用类型比较的仅仅是引用。因此我们需要重写(其实叫做实现更合适)比较操作,.NET内置了比较接口,我们来实现一下IEquatable<UserOperates>即可:
1 #region IEquatable<UserOperates> Members
2
3 public bool Equals(UserOperates other)
4 {
5 if (ReferenceEquals(null, other)) return false;
6 if (ReferenceEquals(this, other)) return true;
7
8 return other.Value == this.Value;
9 }
10
11 #endregion
这里我们实现的是泛型接口,非泛型定义也需要重写一下:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is UserOperates && this.Equals((UserOperates)obj);
}
public override int GetHashCode() { return this.Value.GetHashCode(); }
public static bool operator ==(UserOperates left, UserOperates right) { return Equals(left, right); }
public static bool operator !=(UserOperates left, UserOperates right) { return !Equals(left, right); }
在这里,根据MSDN的建议,如果要重写Equals(object obj)方法,那我们就需要同时重写GetHashCode()方法(具体请参阅:http://technet.microsoft.com/zh-cn/library/ms182358(v=vs.80).aspx)。虽然我们的UserOperates是一个类,也就是引用类型,但在这里我们是将它当作值类型来用的,因此,又重写了“==”和“!=”这两个运算符(如果是值类型的话,微软也有相应的建议:http://technet.microsoft.com/zh-cn/library/ms182359(v=vs.80).aspx)。
有的童鞋就会问了:“老陈,你为什么不使用struct而是使用class呢?”
好吧,我来解答一下。如果仅仅是上文所属的实现,我们也用不着class,使用struct会更加合适。但我们还要做一些事情,比如代码重用,提炼出一个抽象类(又称超类,Java猴子一定会想起super.xxx,哈哈)。这些需求,struct是做不到的,因为它不支持从别的类或者struct继承,struct可以通过接口实现多态,但不能是抽象类。不过今天我们不谈论这个“抽象类”的实现,想要知道如何实现的话,请等待播出第二集吧!
本文一开始我便将一句话加粗显示,大家还有印象吗?或许很多猴子也产生了某些疑问,那么现在我来总结一下吧!
- 之前我们提到,我们建议使用类(或struct)替代某些枚举类型,但这种做法并不是适合于任何需要枚举值的环境的,以下的枚举值就没有必要使用这种替代方案:
- 枚举值根本不需要与常量绑定的时候;
- 枚举值虽然要与常量绑定,但我们并没有除此之外的其他需求;
- 枚举值已经被确定以后是固定不变的,不需要扩展,而且使用类来替代的话可能会更加麻烦;
- 或许还有其他情况;
- 使用类(或struct)替代某些枚举类型的方法有好处也有坏处:
- 因为没有Java那样的内置编码支持,所以很多东西都需要自己写,当然,明天我会放出一个封装的抽象类;
- 即使使用如上的抽象类来实现一个枚举定义,但或许某些猴子会在性能和编码体验之间纠结;
- 这种方案具有很强的可扩展性;
- 这种方案可以让你的枚举值具备多态性质——如果你使用类的话!
如上是老陈继《Java中的枚举值》之后又深入思考.NET中的枚举类型的一些心得,如果存在不妥之处,还请大家多提建议!
相关代码下载(此代码与文中列出的代码有很大出入,因为我进行了很多重构,细心的朋友应该可以看得出来我明天的博文要写什么了):