2017年8月29日 星期二

C# - 實現Enum多功能的程式技巧

在以前寫程式時,我們常會定義一些常數的使用,然後集中到一個Class上面。

class Window
{
    int Close = 0;
    int Open = 1;
}

但其實這樣的效能和記憶體在使用上會有點肥(如果大量使用的話),後來程式的演進後,發現這樣的需求變多了,因為同樣的型別我們可以直接集中控管,然後定義一次就好,後來就演進了Enum這樣的一個型別存在。例如,你可能需要一個稱為“等級”的相同屬性選項,且僅可被賦值為“A”,“B”,“C”,“F”等值的類型,任何其他值在此類型中都是非法的。

EnumJava第一個使用的一個型別,後來C#也開始跟進。

Enum 通稱為「列舉型別」,對於許多初學者來說, 他們知道列舉型別, 也會用, 卻不一定知道列舉型別用在什麼地方、有什麼好用之處。所以在這篇文章裡, 我除了向各位介紹 Enum 如何使用之外, 也會告訴你它的好用之處。
enum 可以用來定義常數。

Enum 的宣告

.Net Enum 的使用是很容易、很直覺(其實跟你在用class直接定義很像)

enum windowStatus : int
{
    Close,
    Open
};

其實 Enum 是一種蠻特殊的型別; 當你宣告一個 Enum 型別的時候, 它實際上和建立一群列舉的數字常數沒什麼兩樣。我們也可以直接去指定 Enum 數列中個別的數值:

enum windowStatus : int
{
    Close=0,
Open=1
};

上面是很基本的使用方式,但是以.Net目前的演進上,還是跟Java有些許的不同,例如:

public enum Window {

     close(0, "Close"), open (1, "Open") ;

     int value = 0;
     String text = "";

     private Window(int value, String text) {
         this.value = value;
         this.text = text;
     }

     public int getValue() {
         return this.value;
     }

     public String getText() {
         return this.text;
     }

     public static Window ofValue(int value) {
         for (LawMasterRole r : values()) {
              if (r.getValue() == value) {
                  return r;
              }
         }
         return undefined;
     }

}


上面的例子我們可以看到Java可以在 enum 內再加上方法。而且Java Enum下有values()方法,這方法其實是取Enum裡的所有值,很奇妙。我去查了JAVA DOC可知道java.lang.Enum<ElementType>下根本沒有這個方法,該方法在java.lang.annotation.ElementType類下(public enum ElementType extends Enum<ElementType>)。Enum的聲明是:Enum<? extends Enum<E>>,即後面這個泛型裡必須是Enum子類別。所以合理的推理是這樣的:在解譯它時會看到一個TestTestEnum的子類別,ElementType也是Enum的子類別。為什麼ElementType會有values()valueOf()方法,是因為ElementType本身也是Enum的子類別,編譯時自動添加了這些方法。任何類別也不像是ElementType的子類別,因為任何enum編譯後都是final修飾的,除非它的某個enumclass body,而ElementType沒有,final修飾的類別不可繼承,這感覺是編譯器自動產生的。

這些是目前C#Enum沒有的,一個假設是,我們設計了一個Enum

enum windowStatus : int
{
    Close = 0,
    Open
};

這時我們在使用時,一般來說沒有太大問題,就是直取出Enum裡面的參數,而它是int沒有錯。但是人的需求是無止盡的,這時候我們會想說,那能不能也能實現一邊是int的對應,可以取出它的名稱呢?

當然,其實我們可以自已做一個Enum來實現一些方法,例如,自行定義Enum裡條目的字串名稱,並取出它的值:

public sealed class Window
{

    private string name;
    private int value;

    public static readonly Window Close = new Window("Close",0);

    public static readonly Window Open = new Window("Open", 1);

  private Window(string name, int value)
    {
          
   this.name = name;
   this.value = value;
  }

    public override string ToString()
    {
       return this.name;
    }
}

上面也是可以呈現把Enum轉換成String,但是如果你有實際這樣跑過,會發現有一個問題。
那就是結果永遠只會呈現String,而不會呈現class本身的value


Console.WriteLine("Window Enum:" + Window.Close);
Console.WriteLine("Window Enum ToString:" + Window.Close.ToString());
Console.WriteLine("Window Enum:" + Window.Open);
Console.WriteLine("Window Enum ToString:" + Window.Open.ToString());




其它關鍵的地方就是override string ToString()。這地方會將一個物件產生之後,定義此類別的型態。所以我不管丟了幾個參數,結果就是只能呈現一種型別,這樣用不用ToString都沒有作用,但是山不轉入轉,我們可以這樣寫就好了:

public sealed class Window
{

    private string name;
    private int value;

    public static readonly Window Close = new Window("Close",0);

    public static readonly Window Open = new Window("Open", 1);

  private Window(string name, int value)
    {
          
   this.name = name;
   this.value = value;
  }

    public override string ToString()
    {
       return this.value.ToString();
    }

  public string GetEnumName()
    {
       return this.name;
    }
}

有看到了嗎? 我只要稍微做一個手腳,就是把ToString的地方改用value的值,並且新增一個Funtion實作就好了,俗話說,凡事不用太執著,我們不要去改那完全沒辦法搬動的ToString,只要新增一個Function,就可以同時實現要讓Enum兼具怎樣的條件,就有什樣的條件。


Console.WriteLine("Window Enum:" + Window.Close);
Console.WriteLine("Window Enum ToString:" + Window.Close.ToString());
Console.WriteLine("Window Enum GetEnumName:" + Window.Close.GetEnumName());
Console.WriteLine("Window Enum:" + Window.Open);
Console.WriteLine("Window Enum ToString:" + Window.Open.ToString());
Console.WriteLine("Window Enum GetEnumName:" + Window.Open.GetEnumName());



然後我們只要在value這個帶入的變數動一下手腳,就可以讓WindowEnum一樣,設定它的型態。

public sealed class Window
{

    private string name;
    private object value;

    public static readonly Window Close = new Window("Close",0);

    public static readonly Window Open = new Window("Open", 1);

  private Window(string name, object value)
    {
          
   this.name = name;
   this.value = value;
  }

    public override string ToString()
    {
       return this.value.ToString();
    }

  public string GetEnumName()
    {
       return this.name;
    }
}


只要把它設定成object,就可以讓它帶任何的物件進來,在這範例,就可以實現如Java般一樣的功能。這個範例其實可以有很多種變化,當然每一種寫法,都有它的優缺點,還有效能的問題,就看自已要怎麼去實作它。

下面是我的範例下載:

https://code.msdn.microsoft.com/Implement-enum-multi-b371f5cc


-雲遊山水為知已逍遙一生而忘齡- 電腦神手

沒有留言:

張貼留言