細(xì)說(shuō)C#中有意思的枚舉:轉(zhuǎn)換、標(biāo)志和屬性
原創(chuàng)【51CTO.com原創(chuàng)稿件】枚舉是 C# 中最有意思的一部分,大部分開(kāi)發(fā)人員只了解其中的一小部分,甚至網(wǎng)上絕大多數(shù)的教程也只講解了枚舉的一部分。那么,我將通過(guò)這篇文章向大家具體講解一下枚舉的知識(shí)。我將從大家都了解的部分開(kāi)始講解,然后再講解大家所不知道的或者了解很少的部分。
一、基礎(chǔ)知識(shí)
枚舉是由開(kāi)發(fā)人員聲明的一種 值類型 ,它在編譯時(shí)就聲明了一種 具名常量值 。使用枚舉可以使我們的代碼簡(jiǎn)單易讀,我們先來(lái)看一下兩個(gè)代碼段:
- // 代碼段 1
- void Method(int country)
- {
- switch (country)
- {
- case 0:
- // more code
- break;
- case 1:
- // more code
- break;
- case 2:
- // more code
- break;
- case 3:
- // more code
- break;
- default:
- // more code
- break;
- }
- }
- // 代碼段 2
- void Method(Country country)
- {
- switch (country)
- {
- case Country.CN:
- // more code
- break;
- case Country.JP:
- // more code
- break;
- case Country.UK:
- // more code
- break;
- case Country.USA:
- // more code
- break;
- default:
- // more code
- break;
- }
- }
從上面的兩個(gè)代碼段我們可以看到兩者有明顯的區(qū)別。第一段代碼中的 case 值我們幾乎完全不知道代表了什么是什么意思,但是第二段代碼我們使用了枚舉,通過(guò) case 值馬上就可以知道所要表達(dá)的意思。同樣利用枚舉值替代布爾值也可以改善代碼的可讀性,例如我們要開(kāi)發(fā)控制臺(tái)燈打開(kāi)關(guān)閉的程序,代碼可以這么寫(xiě) LightOperating(True)
,但是這種代碼我們無(wú)法看出具體要干什么,現(xiàn)在我們將代碼改動(dòng)一下 LightOperating(Light.On)
。經(jīng)過(guò)修改代碼就很容易看出所要表達(dá)的意思。
1.枚舉定義與取值
定義枚舉有兩種方式,分別是普通方式和自定義方式。不管使用哪種方式都需要用的關(guān)鍵字 enum 來(lái)標(biāo)識(shí)這個(gè)類型為枚舉類型,并且枚舉值都是作為整數(shù)常量來(lái)實(shí)現(xiàn)的。下面我們就來(lái)看一下這兩種方式怎么定義枚舉的。普通方式是我們經(jīng)常用到的,也是默認(rèn)的方式。這種方式很簡(jiǎn)單,代碼如下:
- enum Country
- {
- CN,
- UK,
- JP,
- USA
- }
0 ,第二個(gè)枚舉值對(duì)應(yīng)的整數(shù)常量是 1 ,以此類推后面的枚舉值分別對(duì)應(yīng)的整數(shù)常量是 2 和 3 。但是在部分情況下我們需要自定義枚舉值對(duì)應(yīng)的整數(shù)常量,這個(gè)時(shí)候我們就需要用到自定義的方式。自定義方式又稱為為枚舉值顯式賦值,它的方法如下所示:
- enum Country
- {
- CN = 3,
- UK,
- JP = 70,
- USA = 67
- }
枚舉取值也很簡(jiǎn)單,只需要 枚舉名.枚舉值 即可,例如 Country.UK
。
Tip:這里我提幾點(diǎn)建議:
-
枚舉值的名稱不應(yīng)包含枚舉名稱;
-
枚舉名稱應(yīng)以單數(shù)的形式出現(xiàn)(除了屬性)。
到目前為止,我們定義枚舉類型使用的基礎(chǔ)類型 int 類型,但是枚舉不僅僅可以使用 int 類型,還可以使用除了 char 類型之外的所有基礎(chǔ)類型。我們可以使用繼承語(yǔ)法來(lái)指定其他類型。
- enum Country:short
- {
- CN = 3,
- UK,
- JP = 70,
- USA = 67
- }
short 。這里雖然使用了繼承語(yǔ)法但是并沒(méi)有建立繼承關(guān)系,所有的枚舉基類都是 System.Enum ,這些類都是密封類,無(wú)法從現(xiàn)有的枚舉類型派生出新的成員。 對(duì)于枚舉類型的變量,值不限于聲明中命名的值,因此值能轉(zhuǎn)換成基礎(chǔ)類型,那么就能轉(zhuǎn)換為枚舉類型。之所以這么設(shè)計(jì)是因在以后的 API 中有很大的可能在不破換老版本的同時(shí)為枚舉添加新的值。但是這其中也存在一個(gè)缺陷,枚舉允許在運(yùn)行時(shí)分配未知的值,對(duì)于這一點(diǎn)我們?cè)陂_(kāi)發(fā)時(shí)需要考慮到。并且在后期向枚舉中添加新的枚舉值時(shí)應(yīng)將其添加到所有枚舉值的后面,或者顯示指定枚舉值對(duì)應(yīng)的數(shù)值,這樣才能避免因添加新值導(dǎo)致枚舉類型中的枚舉值對(duì)應(yīng)的數(shù)值改變。
Tip:在開(kāi)發(fā)中我們應(yīng)該盡量使用 int 作為枚舉的基礎(chǔ)類型,除非因性能問(wèn)題或互操作方面的考慮時(shí)才會(huì)考慮使用較小的類型。
二、枚舉轉(zhuǎn)換
枚舉轉(zhuǎn)換主要涉及到了枚舉與枚舉的轉(zhuǎn)換、枚舉與數(shù)字和字符串的轉(zhuǎn)換。
首先我要說(shuō)明的是在 C# 中不支持不同枚舉數(shù)組之間的直接轉(zhuǎn)換,所以如果想要實(shí)現(xiàn)不同枚舉數(shù)組之間的轉(zhuǎn)換我們可以利用 CLR 寬松的賦值兼容性這一特點(diǎn)來(lái)進(jìn)行轉(zhuǎn)換,需要轉(zhuǎn)換的兩個(gè)枚舉必須具有相同的基礎(chǔ)類型。同樣,我們通過(guò)一個(gè)例子來(lái)看一下具體實(shí)現(xiàn)方法。
- static void Main(string[] args)
- {
- CountryAllName[] can = (CountryAllName[])(Array)new Country[4];
- }
- enum Country
- {
- CN,
- UK,
- JP,
- USA
- }
- enum CountryAllName
- {
- China,
- UnitedKingdom,
- Japan,
- UnitedStates
- }
2.枚舉和字符串之間轉(zhuǎn)換
枚舉轉(zhuǎn)換為字符串可以直接使用 ToString() 方法, 枚舉值 ToString 后會(huì)直接輸出枚舉值標(biāo)識(shí)符的字符串形式,例如 Country.CN.ToString()
得到的結(jié)果是字符串 CN 。當(dāng)然,你也可以利用 Enum.GetNames 和 Enum.GetName 方法來(lái)獲取。下面我簡(jiǎn)單來(lái)講解一下這兩個(gè)方法的使用。
-
GetNames 方法需要傳入一個(gè)枚舉類型,返回值是一個(gè)字符串?dāng)?shù)組。例如需要獲取到 Country 的第二個(gè)國(guó)家,那么就可以這么來(lái)寫(xiě)
Enum.GetNames(typeof(Country))[1]
,返回結(jié)果是 UK 。 -
GetName GetName 方法返回的是一個(gè)字符串,這個(gè)字符串就是需要獲取的指定枚舉值的字符串形式。同樣我們獲取第二個(gè)國(guó)家,
Enum.GetName(typeof(Country),1)
,返回的值同樣是 UK 。 字符串轉(zhuǎn)換為枚舉也很簡(jiǎn)單,同樣用到了 Enum 基類的一個(gè)靜態(tài)方法 Parse ,例如我們將 JP 轉(zhuǎn)換為枚舉 Country 的枚舉值可以這么做(Country)Enum.Parse(typeof(Country),"JP")
。這里有一點(diǎn)需要注意,TryParse 方法是在 .net 4.0 才出現(xiàn)的,因此如果要在 .net 4.0 以下版本中將字符串轉(zhuǎn)換為枚舉時(shí),需要進(jìn)行恰當(dāng)?shù)腻e(cuò)誤處理防止字符串不存在與枚舉類型中的枚舉值中。
Tip:字符串向枚舉轉(zhuǎn)換不可本地化,如果必須本地化,就必須是那些對(duì)上層用戶不可見(jiàn)的消息。因此在實(shí)際開(kāi)發(fā)中應(yīng)該盡量避免枚舉和字符串之間的轉(zhuǎn)換。
枚舉轉(zhuǎn)換為數(shù)字我們可以使用強(qiáng)轉(zhuǎn),例如 (int)Country.CN
返回結(jié)果是 0 。從數(shù)字轉(zhuǎn)換為枚舉我們有兩種方法,一種是使用強(qiáng)轉(zhuǎn),另一種是使用 Enum 的靜態(tài)方發(fā) ToObject 。
-
強(qiáng)轉(zhuǎn) 強(qiáng)轉(zhuǎn)就比較簡(jiǎn)單了,
Country country = (Country)2
-
ToObject ToObject 方法需要傳入枚舉類型和需要轉(zhuǎn)換的數(shù)字,例如
Country country = (Country)Enum.ToObject(typeof(Country),2)
字符串轉(zhuǎn)換為枚舉和數(shù)字轉(zhuǎn)換為枚舉都必須先進(jìn)行判斷所要轉(zhuǎn)換的值是否包含在枚舉中,判斷的方法也很簡(jiǎn)單只需要調(diào)用 Enum 的靜態(tài)方法 IsDefined 即可,例如我要將 0 和 HK 轉(zhuǎn)換為枚舉,代碼如下:
- Type type = typeof(Country);
- if(Enum.IsDefined(type,0))
- {
- Enum.ToObject(type, 0);
- }
- if(Enum.IsDefined(type,"HK"))
- {
- Enum.Parse(typeof(Country), "HK");
- }
上述代碼中只有 0 會(huì)成功轉(zhuǎn)換為枚舉值 CN ,因?yàn)?0 所對(duì)應(yīng)的枚舉值是 CN ,而 HK 并沒(méi)有在枚舉中。
三、標(biāo)志與屬性
這一小節(jié)我們來(lái)講解一下標(biāo)志與屬性,標(biāo)志he和屬性屬于在開(kāi)發(fā)中用的比較少,并且大部分程序員了解的也不多。
1.標(biāo)志
在開(kāi)發(fā)中有時(shí)我們希望能對(duì)枚舉進(jìn)行組合使用來(lái)表示復(fù)合值,那么這時(shí)我們就需要定義標(biāo)志枚舉了,標(biāo)志枚舉的名稱為復(fù)數(shù)形式,代表了一個(gè)標(biāo)志的集合。一般我們會(huì)使用按位或操作符鏈接枚舉值,使用 HasFlags 方法或者按位與操作符來(lái)判斷特定的位是否存在。比較經(jīng)典的標(biāo)志枚舉是位于 System.IO 命名空間中的 FileAttributes 標(biāo)志枚舉,它列出了文件的所有屬性,比如只讀、隱藏、所在磁盤(pán)等等,它所包含的所有枚舉值皆可相互組合,例如一個(gè)文件既是隱藏文件又是只讀文件。定義標(biāo)志枚舉的方法如下:
- [Flags]
- enum WeekDays
- {
- Monday = 1,
- Tuesday = 2,
- Wednesday = 4,
- Thursday = 8,
- Friday = 16,
- Saturday = 32,
- Sunday = 64
- }
在上面的代碼中你會(huì)發(fā)現(xiàn)一個(gè)規(guī)律,每個(gè)枚舉值對(duì)應(yīng)的整數(shù)值都是 2的n次方,這是為什么呢。在標(biāo)志枚舉中要求多個(gè)枚舉值相互組合后的結(jié)果不能包含在標(biāo)志枚舉中,并且基于按位運(yùn)算的特性可以很方便的使用位運(yùn)算符來(lái)計(jì)算一個(gè)枚舉值是否包含了另外一個(gè)枚舉值,這在權(quán)限系統(tǒng)中相當(dāng)有用。
枚舉值上同樣也可以使用屬性,例如我們需要打印輸出枚舉值的中文名,我們就可以通過(guò)屬性的形式進(jìn)行設(shè)置,首先我們需要定義一個(gè)屬性:
- public class EnumChineseAttribute : Attribute
- {
- private string m_strDescription;
- public EnumChineseAttribute(string chineseName)
- {
- m_strDescription = chineseName;
- }
- public string Description
- {
- get { return m_strDescription; }
- }
- }
- enum Country
- {
- [EnumChinese("中國(guó)")]
- CN,
- [EnumChinese("英國(guó)")]
- UK,
- [EnumChinese("日本")]
- JP,
- [EnumChinese("美國(guó)")]
- USA
- }
- static void Main(string[] args)
- {
- Country country = Country.CN;
- FieldInfo fieldInfo = country.GetType().GetField("CN");
- object[] attribArray = fieldInfo.GetCustomAttributes(false);
- EnumChineseAttribute attrib = (EnumChineseAttribute)attribArray[0];
- Console.WriteLine(attrib.Description);
- Console.Read();
- }
通過(guò)上面的代碼我們就能獲取到 CN 對(duì)應(yīng)的中文名稱了,這段代碼并沒(méi)有進(jìn)行進(jìn)一步優(yōu)化,在實(shí)際項(xiàng)目中必須進(jìn)行封裝和優(yōu)化。
四、小結(jié)
這篇文章主要講解了枚舉相關(guān)的知識(shí),內(nèi)容有點(diǎn)瑣碎,但是在實(shí)際開(kāi)發(fā)中還是比較實(shí)用的。文章中我所提到的要點(diǎn)和規(guī)定在實(shí)際開(kāi)發(fā)中已經(jīng)經(jīng)過(guò)驗(yàn)證,各位讀者可以直接拿來(lái)使用。
作者簡(jiǎn)介:
朱鋼,筆名喵叔,國(guó)內(nèi)某技術(shù)博客認(rèn)證專家,.NET高級(jí)開(kāi)發(fā)工程師,7年一線開(kāi)發(fā)經(jīng)驗(yàn),參與過(guò)電子政務(wù)系統(tǒng)和AI客服系統(tǒng)的開(kāi)發(fā),以及互聯(lián)網(wǎng)招聘網(wǎng)站的架構(gòu)設(shè)計(jì),目前就職于一家初創(chuàng)公司,從事企業(yè)級(jí)安全監(jiān)控系統(tǒng)的開(kāi)發(fā)。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】