1. 程式人生 > >C#圖解教程 第十一章 列舉

C#圖解教程 第十一章 列舉

列舉

列舉


列舉是由程式設計師定義的型別與類或結構一樣。

  • 與結構一樣,列舉是值型別,因此直接儲存它們的資料,而不是分開儲存成引用和資料
  • 列舉只有一種型別的成員:命名的整數值常量

例:列舉示例

關鍵字 列舉名稱
  ↓      ↓
enum TrafficLight
{
    Green,    ←  逗號分隔,沒有分號
    Yellow,
    Red
}

每個列舉型別都有一個底層整數型別,預設為int。

  • 每個列舉成員都被賦予一個底層型別的常量值
  • 在預設情況下,編譯器把第一個成員賦值為0,並對每個後續成員賦的值比前一個多1
var t1=TrafficLight.Green;
var t2=TrafficLight.Yellow;
var t3=TrafficLight.Red;
Console.WriteLine("{0},\t{1}",t1,(int)t1);
Console.WriteLine("{0},\t{1}",t2,(int)t2);
Console.WriteLine("{0},\t{1}",t3,(int)t3);

設定底層型別和顯式值

可以把冒號和型別名放在列舉名之後,這樣就可以使用int以外的整數型別。型別可以是任何整數型別。所有成員常量都屬於列舉的底層型別。

enum TrafficLight:ulong
{
    ...
}

成員常量的值可以是底層型別的任何值。列舉成員不能有重複的名稱,但可以有重複的值。

enum TrafficLight
{
    Green  =10,
    Yellow =15,
    Red    =15
}
隱式成員編號

如果不初始化一個成員常量,編譯器隱式給它賦一個值。 
例:

enum CardSuit
{
    Hearts,    //0 因為這是第一項
    Clubs,     //1 比之前大1
    Diamonds,  //2 比之前大1,下面的以此類推
    Spades,
    MaxSuits
}
enum FaceCards
{
    //Member             //所賦的值
    Jack    =11,         //11 顯式設定
    Queen,               //12 比之前大1
    King,                //13 比之前大1
    Ace,                 //14 比之前大1
    NumberOfFaceCards=4, //4 顯式設定
    SomeOtherValue,      //5 比之前大1
    HighestFaceCard=Ace  //14 以上定義了Ace
}

位標誌


程式設計師們長期使用單個字(single word)的不同位作為一組開/關標誌的緊湊方法。本節將其稱為標誌字(flag word)。列舉提供了實現它的簡便方法。

一般步驟如下。

  1. 確定需要多少個位標誌,並選擇一個有足夠多位的無符號型別來儲存它
  2. 確定每個位位置代表什麼,並給它們名稱。宣告一個選中的整數型別列舉,每個成員由一個位位置表示
  3. 使用按位或(OR)運算子設定保持該位標誌的字中的適當的位
  4. 使用按位與(AND)運算子,或HasFlag方法解開位標誌

例:下面列舉表示紙牌遊戲中的一副牌的選項。

  • 成員有表示二進位制選項的名稱
    • 每個選項由字中的一個特殊的位表示,位位置保持一個0或一個1
    • 因為一個位標誌表示一個或開或關的位,所以你不會想用0作為一個成員值。它已經有了一個意思:所有的位標誌都是關
  • 在16進製表示法中,每個16機制數字用4位來表示。由於位模式和16進製表示法之間的聯絡,所以在處理位模式時,常使用16進位制而不是10進位制
  • 使用Flags特性裝飾(decorate)列舉實際上不必要,但可以有一些額外的便利,很快會討論這一點。特性表現為用中括號括起來的字串,出現在語言構造之前。在本例中,特性出現在列舉宣告之前。特性在第24章闡述
[Flags]
enum CardDeckSettings:uint
{
    SingleDeck    =0x01, //位0
    LargePictures =0x02, //位1
    FancyNumbers  =0x04, //位2
    Animation     =0x08  //位3
}

要建立一個帶有適當位標誌的字,需要宣告一個該列舉型別的變數,並使用按位或運算子設定需要的位。

CardDeckSettings ops= CardDeckSettings.SingleDeck
                     |CardDeckSettings.FancyNumbers
                     |CardDeckSettings.Animation;

判斷標誌字是否包含特定的位標誌集,可以使用列舉型別中的HasFlag布林方法。在標誌字上呼叫HasFlag方法,並將要檢查的位標誌作為引數。如果設定了指定的位標誌,HasFlag返回true,否則返回false。

bool useFancyNumbers=ops.HasFlag(CardDeckSettings.FancyNumbers);

HasFlag還可以檢測多個位標誌。

  • 第一行建立了一個測試字例項,叫做testFlags,設定了Animation和FancyNumbers標誌位
  • 然後把叫做testFlags作為引數傳給HasFlag方法
  • HasFlag檢測是否測試字中的所有標誌都在ops標誌字中進行了設定。如果是返回true,否則返回false
CardDeckSettings testFlags=CardDeckSettings.Animation|CardDeckSettings.FancyNumbers;
bool useAnimationAndFancyNumbers=ops.HasFlag(testFlags);

另外一種判斷是否設定了一個或多個指定為的方法是使用按位與運算子。

bool useFancyNumbers=
    (ops&CardDeckSettings.FancyNumbers)==CardDeckSettings.FancyNumbers;

Flags特性
[Flags]
enum CardDeckSettings:uint
{
    SingleDeck    =0x01, //位0
    LargePictures =0x02, //位1
    FancyNumbers  =0x04, //位2
    Animation     =0x08  //位3
}

Flags特性不會改變計算結果,但卻提供了一些方便的特性。首先,它通知編譯器、物件瀏覽器以及其他檢視這段程式碼的工具,該列舉的成員不僅可以用作單獨的值,還可以按位標誌進行組合。這樣瀏覽器就可以更恰當地解釋該列舉型別的變數。 
其次,它允許列舉的ToString方法為位標誌的值提供更多的格式化資訊,ToString方法以一個列舉值位引數,將其與列舉的常量成員相比較。如果與某個成員相匹配,ToString返回該成員的字串名稱。 
例:沒有Flags的列舉

enum CardDeckSettings:uint
{
    SingleDeck    =0x01, //位0
    LargePictures =0x02, //位1
    FancyNumbers  =0x04, //位2
    Animation     =0x08  //位3
}
class Program
{
    static void Main()
    {
        CardDeckSettings ops;
        ops=CardDeckSettings.FancyNumbers;
        Console.WriteLine(ops.ToString());
        ops=CardDeckSettings.FancyNumbers|CardDeckSettings.Animation;
        Console.WriteLine(ops.ToString());
    }
}

上面輸出結果的第二行12=8+4。因為FancyNumbers將位設定為值4,Animation將位設定為值8。 
然而,如果在列舉宣告前加上Flags特性,將告訴ToString方法位可以分開考慮。執行包含Flags特性的程式碼,結果如下:


使用位標誌的示例
[Flags]
enum CardDeckSettings:uint
{
    SingleDeck    =0x01, //位0
    LargePictures =0x02, //位1
    FancyNumbers  =0x04, //位2
    Animation     =0x08  //位3
}
class MyClass
{
    bool UseSingleDeck               =false,
         UseBigPics                  =false,
         UseFancyNumbers             =false,
         UseAnimation                =false,
         UseAnimationAndFancyNumbers =false;
    public void SetOptions(CardDeckSettings ops)
    {
        UseSingleDeck=ops.HasFlag(CardDeckSettings.SingleDeck);
        UseBigPics=ops.HasFlag(CardDeckSettings.LargePictures);
        UseFancyNumbers=ops.HasFlag(CardDeckSettings.FancyNumbers);
        UseAnimation=ops.HasFlag(CardDeckSettings.Animation);
        CardDeckSettings testFlags=CardDeckSettings.Animation|CardDeckSettings.FancyNumbers;
        UseAnimationAndFancyNumbers=ops.HasFlag(testFlags);
    }
    public void PrintOptions()
    {
        Console.WriteLine("Option settings:");
        Console.WriteLine("Use Single Deck                   - {0}",UseSingleDeck);
        Console.WriteLine("Use Large Pictures                - {0}",UseBigPics);
        Console.WriteLine("Use Fancy Numbers                 - {0}",UseFancyNumbers);
        Console.WriteLine("Show Animation                    - {0}",UseAnimation);
        Console.WriteLine("Show Animation And FancyNumbers   - {0}",UseAnimationAndFancyNumbers);
    }
}
class Program
{
    static void Main()
    {
        var mc=new MyClass();
        CardDeckSettings ops=CardDeckSettings.SingleDeck
                             |CardDeckSettings.FancyNumbers
                             |CardDeckSettings.Animation;
        mc.SetOption(ops);
        mc.PrintOptions();
    }
}

關於列舉的補充


列舉只有單一的成員型別:宣告的成員常量

  • 不能對成員使用修飾符。它們都隱式地具有和列舉相同的可訪問性
  • 由於成員是常量,即使在沒有該列舉變數時也可以訪問。使用列舉型別名.成員名

例:直接訪問列舉常量

Console.WriteLine("{0}",TrafficLight.Green);

列舉是一種獨特的型別。比較不同列舉型別的成員會導致編譯時錯誤。 
例: 列舉比較

  • 第一個if正確,因為它比較同一列舉型別的不同成員
  • 第二個if會產生一個錯誤,因為它比較來自不同列舉型別的成員,儘管它們的結構和成員名稱相同
enum FirstEnum
{
    Mem1,
    Mem2
}
enum SecondEnum
{
    Mem1,
    Mem2
}
class Program
{
    static void Main()
    {
        if(FirstEnum.Mem1<FirstEnum.Mem2)
            Console.WriteLine("True");
        if(FirstEnum.Mem1<SecondEnum.Mem1)  //錯誤,不同列舉型別
            Console.WriteLine("True");
    }
}

.NET Enum型別還包括一些有用的靜態方法:

  • GetName方法以列舉型別物件和整數為引數,返回響應的列舉成員的名稱
  • GetNames方法以列舉型別物件為引數,返回該列舉中所有成員的名稱

例:GetName、GetNames示例

enum TrafficLight
{
    Green,
    Yellow,
    Red
}
class Program
{
    static void Main()
    {
        Console.WriteLine("Second member of TrafficLight is {0}\n",Enum.GetName(typeof(TrafficLight),1));
        foreach(var name in Enum.GetNames(typeof(TrafficLight)))
        {
            Console.WriteLine(name);
        }
    }
}

from: http://www.cnblogs.com/moonache/p/6225313.html