[C#] ENUM

摘要:[C#] ENUM

文章轉自

http://www.dotblogs.com.tw/johnny/archive/2010/01/25/13300.aspx

 

Enum 通稱為「列舉型別」, 在 .Net 中應該可以算是一門顯學, 意思就是說絕大部份 .Net 開發者都知道有這個東西, 也一定會用。不過對於許多初學者來說, 他們知道列舉型別, 也會用, 卻不一定知道列舉型別用在什麼地方、有什麼好用之處。所以在這篇文章裡, 我除了向各位介紹 Enum 如何使用之外, 也會告訴你它的好用之處。

Enum 的宣告

在 .Net 中 Enum 的使用是很容易、很直學的, 看以下的例子就應該可以了解:

VB -

Enum eBugStatus As Integer
   Open
   ReOpen
   ClosedFixed
   ClosedPending
   ClosedUseAsIs
   ClosedNotABug
End Enum

C# -

enum eBugStatus : int
{
   Open,
   ReOpen,
   ClosedFixed,
   ClosedPending,
   ClosedUseAsIs,
   ClosedNotABug
}; 

宣告這個 Enum 之後, 你在程式裡馬上就可以使用 Intellisense 功能進行選取。 

Enum 型別和 .Net 其它型別一樣可以加上各種 Modifier (Public, Protected, Private 等等)。此外, 你可以不需要為 Enum 宣告型別; 寫成下列方式是可以的:

VB -

Enum eBugStatus
   Open
   ReOpen
   ClosedFixed
   ClosedPending
   ClosedUseAsIs
   ClosedNotABug
End Enum

C# -

enum eBugStatus
{
   Open,
   ReOpen,
   ClosedFixed,
   ClosedPending,
   ClosedUseAsIs,
   ClosedNotABug
}; 

如果你一定要宣告型別, 那麼你不一定要宣告為 Integer/int, 在 .Net 中你可以宣告你自訂的 Enum 使用以下各種型別:

  • byte
  • sbyte
  • short
  • ushort
  • int
  • uint
  • long
  • ulong

Enum 型別的本質 

你可能想要知道, 為什麼 .Net 會需要替 Enum 宣告型別? Enum 本身不就是一種型別了嗎?

其實 Enum 是一種蠻特殊的型別; 當你宣告一個 Enum 型別的時候, 它實際上和建立一群列舉的數字常數沒什麼兩樣。你可以試著在程式中逐項檢視個別的值:

eBugStatusbug = eBugStatus.Open;
Response.Write(((int)bug).ToString() + "<br />");
bug = eBugStatus.ReOpen;
Response.Write(((int)bug).ToString() + "<br />");
bug = eBugStatus.ClosedFixed;
Response.Write(((int)bug).ToString() + "<br />");
...

如果照以上程式逐項列出 eBugStatus的值, 你會發現它事實上是以 0, 1, 2, ... 方式儲存在記憶體的。換句話說, 在 Enum 中宣告的各個項目, 就是預設以 0, 1, 2, 3, 4... 等整數數列的方式存放的, 當你使用 (int)bug 取出值的時候就可以發現。

但是, 我們也可以直接去指定 Enum 數列中個別的數值, 方法如下:

public enum eBugStatus: int
{
    Open = 0,
    ReOpen = 10,
    ClosedFixed = 20,
    ClosedPending = 30,
    ClosedUseAsIs = 40,
    ClosedNotABug = 50
}

當你採用這個方式指定其值的時候, 數值中的值就不再依 0, 1, 2, ... 的順序自動指定了, 而會使用你所指定的值。若使用上面的程式, 你將發現列出來的值將變成 0, 10, 20, 30, ...。

常見的使用時機

或許你會問, 我們應該在什麼時候使用 Enum, 又為什麼要直接指定數列裡的值呢?

首先, 當你使用 Enum 的時候, 你就用到了物件導向中的資訊封裝 (Encapsulatioin) 功能。在本例中, 你可以在任何地方以 eBugStatus.Open 來代表一個 Bug 的 Status 為 Open, 而無需硬性指定 "0" 這個數字來代表。萬一哪天你必須更改這個值 (例如把 0 改成 1), 你只要更改 Enum 的宣告就行了, 不需要改動整個程式。

前面講過, Enum 列舉值實際上就是整數, 因此你可以很方便在兩者之間切換:

eBugStatus bug;
int status = (int)bug;

在上例中, 使用 (int)bug 就可以取得列舉項目的隱含整數值。

相反的, 你可以從整數轉換為列舉項目:

eBugStatusbug = (eBugType)10;

依照這種做法, 你可以很方便的透過 Enum 物件把資料寫進資料庫或 Cookies, 或者讀取出來。

應用於資料繫結控制項 

Enum 宣告之後, 除了可以在程式中方便的使用之外, 在某些場合中也很適合讓資料繫結控制項使用。在下例中, 我把 eBugStatus 列舉型別指定為 DropDownList1 的繫結資料來源:

DropDownList1.DataSource = Enum.GetNames(typeof(eBugStatus));
DropDownList1.DataBind();

如此, 這個 DropDownList 就可以列出 Open, ReOpen, ClosedFixed... 等項目供使用者挑選。

 

那麼, 當使用者選中某個項目時, 又該如何取回列舉值呢? 在這裡我們必須使用 Enum.Parse 方法:

string selection = DropDownList1.SelectedValue;
eBugStatus status = (eBugStatus)Enum.Parse(typeof(eBugStatus), selection);

如果在 DropDownList 中你希望它的 Value 為 Enum 的數值而非文字, 你可以藉由採用一個 SortedList 或 ArrayList 物件, 如下例:

public string selectedValue
    {
        get
        {
            if (ddl.Items.Count == 0)
                bindDdl();
            return ddl.SelectedValue;
        }
        set
        {
            ddl.SelectedValue = value;
        }
    }

protected void ddl_Load(object sender, EventArgs e)
    {
        if (ddl.Items.Count == 0)
            bindDdl();
    }

private void bindDdl()
    {
        SortedList<int, string> sl = new SortedList<int, string> { };
        foreach (string e in Enum.GetNames(typeof(clsWebBase.eOfficialFilterType)))
            sl.Add((int)Enum.Parse(typeof(clsWebBase.eOfficialFilterType), e), e);
        ddl.DataSource = sl;
        ddl.DataTextField = "Value";
        ddl.DataValueField = "Key";
        ddl.DataBind();
    }

使用 ObjectdataSource 作為資料繫結來源

與上述同樣的道理, 我們也可以透過使用 ObjectDataSource 把 Enum 物件當作繫結的資料來源:

public class Johnny
{
    public enum eBugStatus: int
    {
        Open = 0,
        ReOpen = 10,
        ClosedFixed = 20,
        ClosedPending = 30,
        ClosedUseAsIs = 40,
        ClosedNotABug = 50
    }

    public SortedList<int, string> getBugStatusList()
    {
        SortedList<int, string> sl = new SortedList<int, string> { };
        foreach (string e in Enum.GetNames(typeof(eBugStatus)))
            sl.Add((short)Enum.Parse(typeof(eBugStatus), e), e);
        return sl;
    }
}

接著, 在 .aspx 程式中使用一個 DropDownList 來展示資料, 並使用一個 ObjectDataSource 作為資料來源:

<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True"
        DataSourceID="odsBugStatus" DataTextField="Value" DataValueField="Key" />
<asp:ObjectDataSource ID="odsBugStatus" runat="server" SelectMethod="getBugStatusList"
        TypeName="Johnny" />

Enum 的型別轉換

從剛才的範例中, 我們已經看到 Enum 可以輕易的和整數(或者 byte、sbyte、short、ushort... 等等)型別互相轉換(端看你一開始宣告為什麼型別), 簡單的使用 CType 或 (int) 方法就行了:

VB -

Dim bugId As Integer = CType(bug, Integer)

C# - 

int bugId = (int) bug;

其次, 針對 Enum 的列舉名稱 (Name), 則可以透過 Enum.Parse() 方法進行轉換, 在上面我們已經看過例子了。這個方法可能並不是所有人都熟悉, 或者不曉得可以用在什麼地方。但在上面範例中, 我們直接把 eBugStatus 列舉名稱當作 DropDownList 的資料繫結來源, 當其中某個項目被選取時, 你就可以拿被選取項目的 Value 作為 Enum.Parse 的參數並解析出對應的 Enum 項目:

VB -

Dim status As eBugStatus = CType(Enum.Parse(typeof(eBugStatus), DropDownList1.SelectedValue), eBugStatus)

C# -

eBugStatus status = (eBugStatus)Enum.Parse(typeof(eBugStatus), DropDownList1.SelectedValue);

還有, Enum 物件的列舉名稱可以簡單的使用 ToString() 取得:

VB -

Dim name As String = eBugStatus.Open.ToString

C# -

string name = eBugStatus.Open.ToString()

Enum 的好處和進階使用技巧 

我在一開始的地方提過, Enum 宣告列舉值的方式等同於常數 (Constant) 的宣告; 像這種宣告方式, 就是俗稱的 Hard-code 宣告 (如果用通俗的話來講, 意思就是「寫死的」)。其實 Hard-coded 的數值完全的缺乏彈性, 當程式 Compile 完畢之後就已確實, 也無法在 Runtime 時期變更, 所以運用的時機十分有限。然而, 畢竟是有一些東西確實是不會變的, 例如公司名稱、作者大名、程式版本等等, 這些資訊都是在 Compile 時已經確定, 大概不會有人會希望在 Runtime 時期更動它們。

在上面的範例中, 當我們要自行開發一個 Bug Tracking 系統時, Bug Status 有哪幾種, 我們必須在程式發行之前就已經討論完畢並且確定 (除非你是要設計一個 Commercial 版本的 Bug Tracking 系統, 你或許會允許使用者自行決定 Bug Status; 在這種情況下, 你就不適合把它宣告為 Enum)。

那麼, 既然要以 Hard-code 方式設計常數結構, 像 Bug Status 這種不會改變的東西 (說實在的, 即使在不同公司、不同專案, Bug Status 幾乎都是那幾個), 你就應該設計為 Enum 結構。一來, 它可以讓程式設計師馬上享有 Intellisense 的便利性, 二來, 它也讓你可以避免寫出像 int status = 10; 這種恐怕會導致連錯誤發生在什麼地方都找不到的程式出來。

當你在設計 Enum 結構時, 你可以使用一個很多人都沒想到的一個小技巧, 也就是刻意的設計不同的 Enum, 但是賦予部份項目相同的值:

public enum eBugStatus: int
{
    Open = 0,
    ReOpen = 10,
    ClosedFixed = 20,
    ClosedCanNotReproduce = 30,
    ClosedPending = 40,
    ClosedUseAsIs = 50,
    ClosedNotABug = 60
}

public enum eDeveloperStatus: int
{
    Unread = 0,
    Inspecting = 110,
    Fixed = 120,
    CanNotReproduced = 130,
    Pending = 140,
    UseAsIs = 150,
    NotABug = 160
}

在上面程式中, 你可以注意到 eBugStatus.Open 和 eDeveloperStatus.Unread 兩個項目的數值都是 0, 而其它的數值都不一樣。運用這種刻意宣告數值的方式, 你可以賦允同一個常數在不同情況下擁有不同的解釋。

這個小技巧其實可以應用在許多情況之下。例如, Space 鍵的鍵盤掃描碼是固定的常數, 但是 Space 鍵可能在不同情況下代表不同的意義, 例如平常它會輸出一個空白字元, 在使用倉頡輸入法打字時, Space 鍵卻是代表「送出」的意思。這時候, 你或許可以運用 Enum 來設計資料結構, 剛好就可以用上這個小技巧。