枚舉是 C 中最有意思的一部分,大部分開發人員只瞭解其中的一小部分,甚至網上絕大多數的教程也只講解了枚舉的一部分。那麼,我將通過這篇文章向大傢具體講解一下枚舉的知識。我將從大家都瞭解的部分開始講解,然後再講解大家所不知道的或者瞭解很少的部分。 零、基礎知識 枚舉是由開發人員聲明的一種 值類型 ,它在 ...
枚舉是 C# 中最有意思的一部分,大部分開發人員只瞭解其中的一小部分,甚至網上絕大多數的教程也只講解了枚舉的一部分。那麼,我將通過這篇文章向大傢具體講解一下枚舉的知識。我將從大家都瞭解的部分開始講解,然後再講解大家所不知道的或者瞭解很少的部分。
零、基礎知識
枚舉是由開發人員聲明的一種 值類型 ,它在編譯時就聲明瞭一種 具名常量值 。使用枚舉可以使我們的代碼簡單易讀,我們先來看一下兩個代碼段:
// 代碼段 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;
}
}
從上面的兩個代碼段我們可以看到兩者有明顯的區別。第一段代碼中的 case 值我們幾乎完全不知道代表了什麼是什麼意思,但是第二段代碼我們使用了枚舉,通過 case 值馬上就可以知道所要表達的意思。同樣利用枚舉值替代布爾值也可以改善代碼的可讀性,例如我們要開發控制臺燈打開關閉的程式,代碼可以這麼寫 LightOperating(True)
,但是這種代碼我們無法看出具體要乾什麼,現在我們將代碼改動一下 LightOperating(Light.On)
。經過修改代碼就很容易看出所要表達的意思。
枚舉定義與取值
定義枚舉有兩種方式,分別是普通方式和自定義方式。不管使用哪種方式都需要用的關鍵字 enum 來標識這個類型為枚舉類型,並且枚舉值都是作為整數常量來實現的。下麵我們就來看一下這兩種方式怎麼定義枚舉的。普通方式是我們經常用到的,也是預設的方式。這種方式很簡單,代碼如下:
csharp enum Country { CN, UK, JP, USA }
在上面的代碼段中我們定義了一個國家枚舉,第一個枚舉值對應的整數常量是 0 ,第二個枚舉值對應的整數常量是 1 ,以此類推後面的枚舉值分別對應的整數常量是 2 和 3 。但是在部分情況下我們需要自定義枚舉值對應的整數常量,這個時候我們就需要用到自定義的方式。自定義方式又稱為為枚舉值顯式賦值,它的方法如下所示:
csharp enum Country { CN = 3, UK, JP = 70, USA = 67 }
我們在代碼中將第一個枚舉值對應的整數常量設置為了 3 ,這時第二個枚舉值的整數常量就不是 1 了,而是 4 ,因為當枚舉值沒有顯示賦值時,將會按照上一個枚舉值對應的整數值加 1 來作為自己本身對應的整數值。最後兩個枚舉值因為顯式賦值了因此對應的整數值就是所賦值的數值。
枚舉取值也很簡單,只需要 枚舉名.枚舉值 即可,例如Country.UK
。Tip:這裡我提幾點建議:
- 枚舉值的名稱不應包含枚舉名稱;
- 枚舉名稱應以單數的形式出現(除了屬性)。
枚舉的類型
到目前為止我們定義枚舉類型使用的基礎類型 int 類型,但是枚舉不僅僅可以使用 int 類型,還可以使用除了 char 類型之外的所有基礎類型。我們可以使用繼承語法來指定其他類型。enum Country:short { CN = 3, UK, JP = 70, USA = 67 }
上面代碼中我們顯式定義了枚舉所使用的基礎類型為 short 。這裡雖然使用了繼承語法但是並沒有建立繼承關係,所有的枚舉基類都是 System.Enum ,這些類都是密封類,無法從現有的枚舉類型派生出新的成員。
對於枚舉類型的變數,值不限於聲明中命名的值,因此值能轉換成基礎類型,那麼就能轉換為枚舉類型。之所以這麼設計是因在以後的 API 中有很大的可能在不破換老版本的同時為枚舉添加新的值。但是這其中也存在一個缺陷,枚舉允許在運行時分配未知的值,對於這一點我們在開發時需要考慮到。並且在後期向枚舉中添加新的枚舉值時應將其添加到所有枚舉值的後面,或者顯示指定枚舉值對應的數值,這樣才能避免因添加新值導致枚舉類型中的枚舉值對應的數值改變。Tip:在開發中我們應該儘量使用 int 作為枚舉的基礎類型,除非因性能問題或互操作方面的考慮時才會考慮使用較小的類型。
一、枚舉轉換
枚舉轉換主要涉及到了枚舉與枚舉的轉換、枚舉與數字和字元串的轉換。枚舉之間轉換
首先我要說明的是在 C# 中不支持不同枚舉數組之間的直接轉換,所以如果想要實現不同枚舉數組之間的轉換我們可以利用 CLR 寬鬆的賦值相容性這一特點來進行轉換,需要轉換的兩個枚舉必須具有相同的基礎類型。同樣,我們通過一個例子來看一下具體實現方法。
在使用這種方法時有可能會出現意外的錯誤或結果,並且相關開發規範中並沒有說這種方式每次都起作用,因此我不建議這麼使用,除非在一些極端場景中。static void Main(string[] args) { CountryAllName[] can = (CountryAllName[])(Array)new Country[4]; } enum Country { CN, UK, JP, USA } enum CountryAllName { China, UnitedKingdom, Japan, UnitedStates }
- 枚舉和字元串之間轉換
枚舉轉換為字元串可以直接使用 ToString() 方法, 枚舉值 ToString 後會直接輸出枚舉值標識符的字元串形式,例如Country.CN.ToString()
得到的結果是字元串 CN 。當然,你也可以利用 Enum.GetNames 和 Enum.GetName 方法來獲取。下麵我簡單來講解一下這兩個方法的使用。- GetNames
GetNames 方法需要傳入一個枚舉類型,返回值是一個字元串數組。例如需要獲取到 Country 的第二個國家,那麼就可以這麼來寫Enum.GetNames(typeof(Country))[1]
,返回結果是 UK 。 GetName
GetName 方法返回的是一個字元串,這個字元串就是需要獲取的指定枚舉值的字元串形式。同樣我們獲取第二個國家,Enum.GetName(typeof(Country),1)
,返回的值同樣是 UK 。
字元串轉換為枚舉也很簡單,同樣用到了 Enum 基類的一個靜態方法 Parse ,例如我們將 JP 轉換為枚舉 Country 的枚舉值可以這麼做(Country)Enum.Parse(typeof(Country),"JP")
。這裡有一點需要註意,TryParse 方法是在 .net 4.0 才出現的,因此如果要在 .net 4.0 以下版本中將字元串轉換為枚舉時,需要進行恰當的錯誤處理防止字元串不存在與枚舉類型中的枚舉值中。Tip:字元串向枚舉轉換不可本地化,如果必須本地化,就必須是那些對上層用戶不可見的消息。因此在實際開發中應該儘量避免枚舉和字元串之間的轉換。
- GetNames
- 枚舉和數字之間轉換
枚舉轉換為數字我們可以使用強轉,例如(int)Country.CN
返回結果是 0 。從數字轉換為枚舉我們有兩種方法,一種是使用強轉,另一種是使用 Enum 的靜態方發 ToObject 。- 強轉
強轉就比較簡單了,Country country = (Country)2
- ToObject
ToObject 方法需要傳入枚舉類型和需要轉換的數字,例如Country country = (Country)Enum.ToObject(typeof(Country),2)
- 強轉
註意
字元串轉換為枚舉和數字轉換為枚舉都必須先進行判斷所要轉換的值是否包含在枚舉中,判斷的方法也很簡單隻需要調用 Enum 的靜態方法 IsDefined 即可,例如我要將 0 和 HK 轉換為枚舉,代碼如下:Type type = typeof(Country); if(Enum.IsDefined(type,0)) { Enum.ToObject(type, 0); } if(Enum.IsDefined(type,"HK")) { Enum.Parse(typeof(Country), "HK"); }
上述代碼中只有 0 會成功轉換為枚舉值 CN ,因為 0 所對應的枚舉值是 CN ,而 HK 並沒有在枚舉中。
三、標誌與屬性
這一小節我們來講解一下標誌與屬性,標誌和屬性屬於在開發中用的比較少,並且大部分程式員瞭解的也不多。標誌
在開發中有時我們希望能對枚舉進行組合使用來表示覆合值,那麼這時我們就需要定義標誌枚舉了,標誌枚舉的名稱為複數形式,代表了一個標誌的集合。一般我們會使用按位或操作符鏈接枚舉值,使用 HasFlags 方法或者按位與操作符來判斷特定的位是否存在。比較經典的標誌枚舉是位於 System.IO 命名空間中的 FileAttributes 標誌枚舉,它列出了文件的所有屬性,比如只讀、隱藏、所在磁碟等等,它所包含的所有枚舉值皆可相互組合,例如一個文件既是隱藏文件又是只讀文件。定義標誌枚舉的方法如下:
在上面的代碼中你會發現一個規律,每個枚舉值對應的整數值都是 2的n次方,這是為什麼呢。在標誌枚舉中要求多個枚舉值相互組合後的結果不能包含在標誌枚舉中,並且基於按位運算的特性可以很方便的使用位運算符來計算一個枚舉值是否包含了另外一個枚舉值,這在許可權系統中相當有用。[Flags] enum WeekDays { Monday = 1, Tuesday = 2, Wednesday = 4, Thursday = 8, Friday = 16, Saturday = 32, Sunday = 64 }
屬性
枚舉值上同樣也可以使用屬性,例如我們需要列印輸出枚舉值的中文名,我們就可以通過屬性的形式進行設置,首先我們需要定義一個屬性: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("中國")] CN, [EnumChinese("英國")] UK, [EnumChinese("日本")] JP, [EnumChinese("美國")] 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(); }
通過上面的代碼我們就能獲取到 CN 對應的中文名稱了,這段代碼並沒有進行進一步優化,在實際項目中必須進行封裝和優化。
四、小結
這篇文章主要講解了枚舉相關的知識,內容有點瑣碎,但是在實際開發中還是比較實用的。文章中我所提到的要點和規定在實際開發中已經經過驗證,各位讀者可以直接拿來使用。
本文由博客一文多發平臺 OpenWrite 發佈!