一、枚舉類型 枚舉類型(enumerated types)定義了一組"符號名稱/值"配對。 例如,以下Color類型定義了一組符號,每個符號都標識一種顏色:
internal enum Color {While, //賦值0Red, //賦值1Green, //賦值2Blue, //賦值3Orange //賦值4}使用枚舉類型的好處:
1)枚舉類型使程序更容易編寫、閱讀和維護。有了枚舉類型,符號名稱可在代碼中隨便使用,開發人員不需要記住每個硬編碼的含義。而且,一旦與符號名稱對應的值發生變化,代碼也可以簡單的重新編譯,不需要對源代碼做出任何修改。除此之外,文檔工具和其他實用程序能向開發人員顯示有意義的符號名稱。
2)枚舉類型是強類型的。
在Microsoft .NET Framework中,枚舉類型不只是編譯器所關心的符號,它在類型系統中還具有"一等公民"的地位,能實現非常強大的操作。枚舉類型都直接從System.Enum派生,后者從System.ValueType派生,而System.ValueType用從System.Object派生。所以,枚舉類型是值類型,可表示成未裝箱和已裝箱形式。然而,有別于其他值類型,枚舉類型不能定義任何方法、屬性和事件。不過可利用C#的"擴展方法"功能模擬向枚舉類型添加方法。
編譯枚舉類型時,C#編譯器會把每個符號轉換成為類型的一個常量字段。例如,編譯器會把前面的Color枚舉類型看成以下代碼:
internal struct Color : System.Enum{ //以下是一些公共常量,它們定義了Color的符號和值 public const Color While = (Color)0; public const Color Red = (Color)1; public const Color Green = (Color)2; public const Color Bule = (Color)3; public const Color Orange = (Color)4; //以下是一個公共實例字段,它包含一個Color變量的值, //不能寫代碼來直接引用這個實例字段 public Int32 value__;}C#編譯器實際上并不編譯這段代碼,因為它禁止定義從System.Enum這一特殊類型派生的類型。不過,可以通過上述偽類型定義了解內部工作方式。簡單的說,枚舉類型只是一個結構,其中定義了一組常量字段和一個實例字段。常量字段會嵌入程序集的元數據中,并可以通過反射來訪問。這意味著可以在運行時獲得與枚舉類型關聯的所有符號及其值。還意味著可以將一個字符串符號轉換成對應的數值。這些操作是通過System.Enum基類型來提供的,該類型提供了幾個靜態和實例方法,可利用它們操作枚舉類型的一個實例,從而避免了必須使用反射的麻
煩。
提示:枚舉類型定義的符號是常量值。所以當編譯器發現代碼引用了一個枚舉類型的符號,就會在編譯時用數值替換符號,代碼將不再引用定義了符號的枚舉類型。這意味著在運行時可能不需要定義了枚舉類型的程序集,在
編譯時需要。
例如,System.Enum類型有一個GetUnderlyingType的靜態方法,而System.Type類型有一個GetEnumUnderlyingType的實例方法。
public static Type GetUnderlyingType (Type enumType); //System.Enum中定義public Type GetEnumUnderlyingType (Type enumType); //System.Type中定義
這些方法返回用于容納一個枚舉類型的值的基礎類型。每個枚舉類型都有一個基礎類型,它可以是byte,sbyte,short,ushort,int(最常用,也是C#默認的),uint,long,或ulong。雖然這些C#基元類型都有都有對象的FCL類型,但C#編譯器為了簡化本身的實現,要求只能指定基元類型名稱。如果使用FCL類型名稱(如Int32),就會報
錯。 以下代碼演示了如何聲明一個基礎類型為byte(System.Byte)的枚舉類型:
inter enum Color :byte { While, Red, Green, Blue, Orange }基于這個Color枚舉類型,一下代碼顯示了GetUnderlyingType 的返回結果:
//以下代碼會顯示"System.Byte"Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));
C#編譯器將枚舉類型視為基元類型。所以,可以運用許多操作符(==,!=,<,>等等)來操作枚舉類型的實例。所有這些操作符實際作用于枚舉類型實例內部的value__實例字段。此外,C#編譯器還運行將枚舉類型的實例顯式的轉型為一個不通過的枚舉類型。也可以顯式將一個枚舉類型實例轉型為一個數值類型。
可以調用System.Enum的靜態方法GetValue或者System.Type的實例方法GetEnumValue獲取一個數組,該數組的每一個元素都對應枚舉類型中的一個符號名稱,每個元素都包含符號名稱的數值:
public static Array GetValues(Type enumType); //System.Enum中定義public Array GetEnumValues(Type enumType); //System.Type中定義
這個方法結合ToString方法使用,可顯示枚舉類型中所有符號名稱及其對應的數值,如下所示:
public static void Go() { Color[] colors = (Color[])Enum.GetValues(typeof(Color)); Console.WriteLine("Number of symbols defined: " + colors.Length); Console.WriteLine("Value/tSymbol/n-----/t------"); foreach (Color color in colors) { // 以十進制和常規格式顯示每個符號 Console.WriteLine("{0,5:D}/t{0:G}", color); }}以上代碼產生的輸出如下:
Number of symbols defined: 5Value Symbol----- ------ 0 While 1 Red 2 Green 3 Blue 4 Orange
還有其他枚舉類型成員,就不一一敘述了!
二、位標志 程序員經常要與位標識(bit flag)集合打交道。調用System.IO.File類型的GetAttributes方法,會返回FileAttributes類型的一個實例。FileAttributes類型是基本類型為Int32的枚舉類型,其中每一位都反映了文件的一項屬性。FileAttibutes類型在FCL中的定義如下:
[Flags][Serializable][ComVisible (true)]public enum FileAttributes{ Archive = 0x00020, ComPRessed = 0x00800, Device = 0x00040, // Reserved for future use (NOT the w32 value). Directory = 0x00010, Encrypted = 0x04000, // NOT the w32 value Hidden = 0x00002, Normal = 0x00080, NotContentIndexed = 0x02000, Offline = 0x01000, ReadOnly = 0x00001, ReparsePoint = 0x00400, SparseFile = 0x00200, System = 0x00004, Temporary = 0x00100, #if NET_4_5 IntegrityStream = 0x8000, NoScrubData = 0x20000, #endif}為了判斷一個文件是否隱藏,可執行下面這樣的代碼:
String file = Assembly.GetEntryAssembly().Location;FileAttributes attributes = File.GetAttributes(file);Console.WriteLine("Is {0} hidden? {1}",file,(attributes & FileAttributes.Hidden) !=0);以下代碼演示了如何將一個文件的屬性改為只讀和隱藏:
File.SetAttributes(file,FileAttributes.ReadOnly | FileAttribute.Hidden);
正如FileAttributes類型展示的那樣,經常都要用枚舉類型來表示一組可以組合的位標志。不過,雖然枚舉類型和位標志相似,但它們的語義不盡相同。例如,枚舉類型表示單個數值,而位標識表示一組位,其中有些位是1,有些位是0.
定義用于標識位標志的枚舉類型時,當然應該顯式為每個符號分配一個數值。通常,每個符號都有單獨的一個位處于on(1)狀態.此外,經常都要定義一個值為0的None符號。還可以定義一些代表常用位組合的符號。另外,強烈建議向枚舉類型應用System.Flags.Attribute這個定制的attribute類型,如下所示
[Flags] public enum Actions { Read = 0x0001, Write = 0x0002, ReadWrite = Actions.Read | Actions.Write, Delete = 0x0004, Query = 0x0008, Sync = 0x0010}因為Actions是枚舉類型,所以在操作位標志枚舉類型時,可以使用上一節描述的所有方法。
Actions actions = Actions.Read | Actions.Delete; //0x0005Console.WriteLine(actions.ToString()); //"Read,Delete"
調用ToString時,它會視圖將數值轉換為對應的符號。現在的數值是0x0005,它沒有對應的符號。不過,ToString方法檢測到Actions類型上存在[Flags]這個attribute,所以ToString方法現在不會將該數值視為單獨的值。相反,會將它視為一組位標志。由于0x0005有0x0001和0x0004組合而成,所以ToString會生成字符串"Read,Delete",如果從Actions類型中刪除[Flags]這個attribute,ToString方法返回"5"。
永遠不要對位標志枚舉類型使用IsDefined方法,理由如下: 1)如果向IsDefined方法傳遞一個字符串,它不會將這個字符串拆分為單獨的token來進行查找,而是視圖查找整個字符串,把它看成是包含逗號的一個更大的符號。由于不能在枚舉類型中定義含有逗號的符號,所以這個符號永遠找不到。 2)如果向IsDefined方法傳遞一個數值,它會檢查枚舉類型是否定義了一個其對應數值和傳入數值匹配的符號。由于位標志不能這樣簡單匹配,所以IsDefined通常會返回flase。
三、向枚舉類型添加方法 現在,可以使用C#的擴展方法功能向枚舉類型模擬添加方法。 如果想為FileAttributes枚舉類型添加一些方法,可以定義一個包含了擴展方法的靜態類,如下所示:
public static Boolean Set(this FileAttributes flags, FileAttributes testFlags) {return flags | testFlags;}從表面看,我似乎真的在枚舉類型上調用這些方法:
FileAttributes fa = FileAttributes.System;fa = fa.Set(FileAttributes.ReadOnly);
新聞熱點
疑難解答