1. 程式人生 > >C#中Enum用法小結

C#中Enum用法小結

列舉
  列舉型別宣告為一組相關的符號常數定義了一個型別名稱。列舉用於“多項選擇”場合,就是程式執行時從編譯時已經設定的固定數目的“選擇”中做出決定。
  列舉型別(也稱為列舉)為定義一組可以賦給變數的命名整數常量提供了一種有效的方法。例如,假設您必須定義一個變數,該變數的值表示一週中的一天。該變數只能儲存七個有意義的值。若要定義這些值,可以使用列舉型別。列舉型別是使用 enum 關鍵字宣告的。

enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

預設情況下,列舉中每個元素的基礎型別是 int。可以使用冒號指定另一種整數值型別。
如果不為列舉數列表中的元素指定值,則它們的值將以 1 為增量自動遞增。在前面的示例中,Days.Sunday 的值為 0,Days.Monday 的值為 1,依此類推。建立新的 Days 物件時,如果不顯式為其賦值,則它將具有預設值 Days.Sunday (0)。建立列舉時,應選擇最合理的預設值並賦給它一個零值。這便使得只要在建立列舉時未為其顯式賦值,則所建立的全部列舉都將具有該預設值。列舉中大小寫敏感,但是建議不要這樣。
列舉的優點:
<1>列舉可以使程式碼更易於維護,有助於確保給變數指定合法的、期望的值。
<2>列舉使程式碼更清晰,允許用描述性的名稱表示整數值,而不是用含義模糊的數來表示。
<3>列舉使程式碼更易於鍵入。在給列舉型別的例項賦值時,

VS.NET IDE會通過IntelliSense彈出一個包含可接受值的列表框,減少了按鍵次數,並能夠讓我們回憶起可能的值
列舉例項
  宣告:

public enum TimeOfDay
{
    Moning = 0,
    Afternoon = 1,
    Evening = 2,
}

使用:

  public string getTimeOfDay(TimeOfDay time)
    {
        string result = string.Empty;
        switch (time)
        {
            case TimeOfDay.Moning:
                result = "上午";
                break;
            case TimeOfDay.Afternoon:
                result = "下午";
                break;
            case TimeOfDay.Evening:
                result = "晚上";
                break;
            default:
                result = "未知";
                break;
        }
        return result;
    }

列舉方法
  <1>獲取列舉字串
TimeOfDay time = TimeOfDay.Afternoon;
Console.WriteLine(time.ToString());//輸出:Afternoon
  <2>Enum.Parse()方法。這個方法帶3個引數,第一個引數是要使用的列舉型別。其語法是關鍵字typeof後跟放在括號中的列舉類名。typeof運算子將在第5章詳細論述。第二個引數是要轉換的字串,第三個引數是一個bool,指定在進行轉換時是否忽略大小寫。最後,注意Enum.Parse()方法實際上返回一個物件引用—— 我們需要把這個字串顯式轉換為需要的列舉型別(這是一個取消裝箱操作的例子)。對於上面的程式碼,將返回1,作為一個物件,對應於TimeOfDay.Afternoon的列舉值。在顯式轉換為int時,會再次生成1。
TimeOfDay time2 = (TimeOfDay) Enum.Parse(typeof(TimeOfDay), “afternoon”, true);
Console.WriteLine((int)time2);//輸出1
  <3>得到列舉的某一值對應的名稱
lbOne.Text = Enum.GetName(typeof(TimeOfDay), 0);
  <4>得到列舉的所有的值
foreach (int i in Enum.GetValues(typeof(TimeOfDay)))
lbValues.Text += i.ToString();
 <5>列舉所有的名稱
foreach(string temp in Enum.GetNames(typeof(TimeOfDay)))
lbNames.Text+=temp;
列舉和常量
  優先考慮列舉。
  在C#中,列舉的真正強大之處是它們在後臺會例項化為派生於基類System.Enum的結構。這表示可以對它們呼叫方法,執行有用的任務。

注意因為.NET Framework的執行方式,在語法上把列舉當做結構是不會有效能損失的。實際上,一旦程式碼編譯好,列舉就成為基本型別,與int和float類似。
  但是在實際應用中,你也許會發現,我們經常用英語定義列舉型別,因為開發工具本來就是英文開發的,美國人用起來,就直接能夠明白列舉型別的含義。其實,我們在開發的時候就多了一步操作,需要對列舉型別進行翻譯。沒辦法,誰讓程式語言是英語寫的,如果是漢語寫的,那我們也就不用翻譯了,用起列舉變得很方便了。舉個簡單的例子,TimeOfDay.Morning一看到Morning,美國人就知道是上午,但是對於中國的使用者來說,可能有很多人就看不懂,這就需要我們進行翻譯、解釋,就向上面的getTimeOfDay()的方法,其實就是做了翻譯工作。所以,在使用列舉的時候,感覺到並不是很方便,有的時候我們還是比較樂意建立常量,然後在類中,宣告一個集合來容納常量和其意義。
  使用常量定義:這種方法固然可行,但是不能保證傳入的引數day就是實際限定的。

using System;
using System.Collections.Generic;

public class TimesOfDay
{
    public const int Morning = 0;
    public const int Afternoon = 1;
    public const int Evening = 2;
    public static Dictionary<int, string> list;
    /// <summary>
    /// 獲得星期幾
    /// </summary>
    /// <param name="day"></param>
    /// <returns></returns>
    public static string getTimeNameOfDay(int time)
    {
        if (list == null || list.Count <= 0)
        {
            list = new Dictionary<int, string>();
            list.Add(Morning, "上午");
            list.Add(Afternoon, "下午");
            list.Add(Evening, "晚上");
        }

        return list[time];
    }
}

希望能夠找到一種比較好的方法,將列舉轉為我們想要的集合。搜尋了半天終於找到了一些線索。通過反射,得到針對某一列舉型別的描述。
列舉的定義中加入描述

using System;
using System.ComponentModel;

public enum TimeOfDay
{
    [Description("上午")]
    Moning,
    [Description("下午")]
    Afternoon,
    [Description("晚上")]
    Evening,
};

獲得值和表述的鍵值對

  /// <summary>
        /// 從列舉型別和它的特性讀出並返回一個鍵值對
        /// </summary>
        /// <param name="enumType">Type,該引數的格式為typeof(需要讀的列舉型別)</param>
        /// <returns>鍵值對</returns>
        public static NameValueCollection GetNVCFromEnumValue(Type enumType)
        {
            NameValueCollection nvc = new NameValueCollection();
            Type typeDescription = typeof(DescriptionAttribute);
            System.Reflection.FieldInfo[] fields = enumType.GetFields();
            string strText = string.Empty;
            string strValue = string.Empty;
            foreach (FieldInfo field in fields)
            {
                if (field.FieldType.IsEnum)
                {
                    strValue = ((int)enumType.InvokeMember(field.Name, BindingFlags.GetField, null, null, null)).ToString();
                    object[] arr = field.GetCustomAttributes(typeDescription, true);
                    if (arr.Length > 0)
                    {
                        DescriptionAttribute aa = (DescriptionAttribute)arr[0];
                        strText = aa.Description;
                    }
                    else
                    {
                        strText = field.Name;
                    }
                    nvc.Add(strText, strValue);
                }
            }
            return nvc;
        }

.NET中Flags列舉的使用

.NET中的列舉我們一般有兩種用法,一是表示唯一的元素序列,例如一週裡的各天;還有就是用來表示多種複合的狀態。這個時候一般需要為列舉加上[Flags]特性標記為位域,例如:

[Flags] 
enum Styles{ 

ShowBorder = 1,         //是否顯示邊框
ShowCaption = 2,        //是否顯示標題
ShowToolbox = 4         //是否顯示工具箱
} 

這樣我們就可以用"或"運算子組合多個狀態,例如:
myControl.Style = Styles.ShowBorder | Styles.ShowCaption;
  這時myControl.Style列舉的值將變成 1+2=3,它的ToString()將變成"Styles.ShowBorder , Styles.ShowCaption"
這裡我們可以解釋為什麼第三個值ShowToolbox可以為4,5…而不能為3。也就是說它的值不應該是前幾項值的複合值。有一個比較簡單的方法就是用2的n次方來依次為每一項賦值,例如 1,2,4,8,16,32,64…
現在舉個常見的Flags應用例子。例如一個簡單的許可權系統,有"Admin"和"User"兩種角色,我們可以在表中放一個 varchar()欄位,以文字形式存放許可權字"Admin,User"。但是用Flags型列舉的話,我們就可以直接將 Roles.Admin | Roles.User 的值放在一個int欄位裡。
以下是關於列舉的一些常見操作:
將列舉的值變回列舉物件:
Styles style = (Styles) Enum.Parse(typeof(Styles), 4 ); // -> style = Styles.Toolbox;
  檢查列舉是否包含某個元素:
bool hasFlag = ((style & Styles.ShowBorder) != 0);
其實我們還會碰到一種情況,就是需要從組合狀態中去掉一個元素。用"^"運算子可以做到:
Styles style = Styles.ShowBorder | Styles.ShowCaption;
style = style ^ Styles.ShowBorder;
  這個時候style的值就會變成 Styles.ShowCaption
但這裡有一個很嚴重的問題(偶現在才發現)
我們這個時候再執行一次
style = style ^ Styles.ShowBorder;
按照我們的設想,這個時候 style 的值是 Styles.ShowCaption,不包含 Styles.ShowBorder,所以我們就算去掉這個元素,style應該還是不會變。但實際的 style 的值卻又變成了 Styles.ShowBorder | Styles.ShowCaption !! 再執行一遍,又會去掉這個元素,周而復始。
當然我們可以在去掉某個元素前做一番檢查,如果列舉包含這個元素,再去掉它:
if ((style & Styles.ShowBorder) != 0){
style = style ^ Styles.ShowBorder;
}
  不知道有沒有其它方法可以方便地從Flags列舉狀態中去掉一個元素。。
  Thanks to mobilebilly:
style = style & (~Styles.ShowBorder) 可以方便去掉一個元素。
好好利用列舉
這段時間手裡有個有關訂單的專案,訂單一般有個狀態的,以前很多要時候都會想到訂單的狀態就那幾個種,就把它寫死吧,不用一個數據庫表了,太浪費資源了,但寫死了用一個數字來代表一種訂單狀態,這樣在編碼時還要記得什麼數字程式碼什麼狀態,如果不小心把它寫錯了,會導致資料出錯。
後來想到.NET有個列舉,這麼好的東西為何不用上來呢,這不但可以方便以後的程式碼維護,也方便編碼。

public enum OrderState
{
    /// <summary>
    /// 無效狀態
    /// </summary>
    Invalid = 0,
    /// <summary>
    /// 客戶詢價
    /// </summary>
    CustomerQuery = 1,
    /// <summary>
    /// 客戶落單
    /// </summary>
    CustomerOrdered = 2,
    /// <summary>
    /// 客戶付款
    /// </summary>
    ReceiverCustomerPayment = 4,
    /// <summary>
    /// 向供貨商訂貨
    /// </summary>
    SupplierOrdered = 8,
    /// <summary>
    /// 供貨商確認貨期
    /// </summary>
    SupplierOrderTerm = 16,
    /// <summary>
    /// 收到貨品
    /// </summary>
    RecieverGoods = 32,
    /// <summary>
    /// 客戶取消訂單
    /// </summary>
    CustomerCanceled = 64,
    /// <summary>
    /// 供貨商取消訂單
    /// </summary>
    SupplierCancelded = 128
}

但要從UI層看這些狀態怎麼處理呢?
利用switch case

public static string GetOrderStateString(OrderState state)
    {
        switch (state)
        {
            case OrderState.Invalid:
                return "無效值";
            case OrderState.CustomerOrdered:
                return "客戶下單";
            case OrderState.CustomerCanceled:
                return "客戶取消訂單";
            case OrderState.CustomerQuery:
                return "客戶詢價";
            case OrderState.ReceiverCustomerPayment:
                return "客戶已付款";
            case OrderState.RecieverGoods:
                return "訂單到貨";
            case OrderState.SupplierCancelded:
                return "供貨商取消";
            case OrderState.SupplierOrdered:
                return "已向供貨商訂貨";
            case OrderState.SupplierOrderTerm:
                return "供貨商確認貨期";
        }
        return "";
    }

如果以後還有更多的訂單狀態就修改這個列舉和一個方法就行了,這麼方便的東西為何就不用到我的程式中呢,我們在編碼中,要想盡方法使程式碼簡單、易用、易維護。
列舉中有兩個很實用的方法
1、GetHashCode() //返回該例項的值的雜湊程式碼
2、ToString() //將此例項的值轉換為其等效的字串表示
這兩個方法在編碼的時候會用到,GetHashCode()這個方法使用機會會更多。
有前列舉的使用,我得出以上一點心得,與大家共享一下。