.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做出了更多的限制而已。如下截圖演示了如上方案的簡單實踐:

細心的朋友應該可以發覺這里存在幾個問題:

  1. 缺少位域操作的直接支持;
  2. 缺少從數值或字符串轉換為枚舉類型的支持; 

木有關系,.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)替代某些枚舉類型,但這種做法並不是適合於任何需要枚舉值的環境的,以下的枚舉值就沒有必要使用這種替代方案:
    1. 枚舉值根本不需要與常量綁定的時候;
    2. 枚舉值雖然要與常量綁定,但我們並沒有除此之外的其他需求;
    3. 枚舉值已經被確定以后是固定不變的,不需要擴展,而且使用類來替代的話可能會更加麻煩;
    4. 或許還有其他情況;
  • 使用類(或struct)替代某些枚舉類型的方法有好處也有壞處:
    1. 因為沒有Java那樣的內置編碼支持,所以很多東西都需要自己寫,當然,明天我會放出一個封裝的抽象類;
    2. 即使使用如上的抽象類來實現一個枚舉定義,但或許某些猴子會在性能和編碼體驗之間糾結;
    3. 這種方案具有很強的可擴展性;
    4. 這種方案可以讓你的枚舉值具備多態性質——如果你使用類的話!

如上是老陳繼《Java中的枚舉值》之后又深入思考.NET中的枚舉類型的一些心得,如果存在不妥之處,還請大家多提建議!

相關代碼下載(此代碼與文中列出的代碼有很大出入,因為我進行了很多重構,細心的朋友應該可以看得出來我明天的博文要寫什么了)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM