從枚舉的初始化說起 [C#]

來源:互聯網
上載者:User

從枚舉的初始化說起 [C#]

 

Written by Allen Lee

 

我知道你的痛,是我給的承諾。你說給過我縱容,沉默是因為包容。如果要走,請你記得我;如果難過,請你忘了我。——周杰倫,《借口》

 

0. 緣起

本文寫作緣於netwy的《枚舉類型的變數的預設值一定是 0 !》。

 

1. 問題

//Code #01

class Tester
{
    static void Main()
    {
        Alignment a = new Alignment();
        Console.WriteLine(a.ToString("D"));

        Alignment b = Alignment.Left;
        Console.WriteLine(b.ToString("D"));
    }
}

假定Left是Alignment枚舉的第一個成員,你認為這兩種初始化枚舉變數的方式是否等效?如果不等效,它們有什麼差別?

 

2. 兩種初始化方法的對比

2.1 第一個枚舉成員的值為0

如果我們沒有為Alignment指定第一個成員的值:

//Code #02

enum Alignment
{
    Left,
    Center,
    Right
}

Code #01的輸出結果將是:

0
0

我們再把Code #01的Main反編譯成IL:

//Code #03

.method private hidebysig static void Main(string[] args) cil managed
{
      .entrypoint
      // Code Size: 50 byte(s)
      .maxstack 2
      .locals (
            CsWritingLab.Alignment alignment1,
            CsWritingLab.Alignment alignment2)
      L_0000: nop 
      L_0001: ldc.i4.0 
      L_0002: stloc.0 
      L_0003: ldloc.0 
      L_0004: box CsWritingLab.Alignment
      L_0009: ldstr "D"
      L_000e: call instance string [mscorlib]System.Enum::ToString(string)
      L_0013: call void [mscorlib]System.Console::WriteLine(string)
      L_0018: nop 
      L_0019: ldc.i4.0 
      L_001a: stloc.1 
      L_001b: ldloc.1 
      L_001c: box CsWritingLab.Alignment
      L_0021: ldstr "D"
      L_0026: call instance string [mscorlib]System.Enum::ToString(string)
      L_002b: call void [mscorlib]System.Console::WriteLine(string)
      L_0030: nop 
      L_0031: ret 
}

從上面的代碼中,我們可以看到這兩種初始化方式是等效的。實質上,下面這4句(在此時)是等效的(它們產生一樣的IL代碼):

Alignment a = new Alignment();
Alignment b = Alignment.Left;
Alignment d = (Alignment)0;
Alignment c = 0;

2.2 第一個枚舉成員的值非0

如果我們手動指定Alignment的第一個成員的值呢?

//Code #04

enum Alignment
{
    Left = 1,
    Center,
    Right
}

Code #01的輸出結果將有點令人疑惑:

0
1

為什麼會這樣呢?讓我們從IL代碼中看看編譯器是如何理解此時的Main的:

// Code #05

.method private hidebysig static void Main(string[] args) cil managed
{
      .entrypoint
      // Code Size: 50 byte(s)
      .maxstack 2
      .locals (
            CsWritingLab.Alignment alignment1,
            CsWritingLab.Alignment alignment2)
      L_0000: nop 
      L_0001: ldc.i4.0 
      L_0002: stloc.0 
      L_0003: ldloc.0 
      L_0004: box CsWritingLab.Alignment
      L_0009: ldstr "D"
      L_000e: call instance string [mscorlib]System.Enum::ToString(string)
      L_0013: call void [mscorlib]System.Console::WriteLine(string)
      L_0018: nop 
      L_0019: ldc.i4.1 
      L_001a: stloc.1 
      L_001b: ldloc.1 
      L_001c: box CsWritingLab.Alignment
      L_0021: ldstr "D"
      L_0026: call instance string [mscorlib]System.Enum::ToString(string)
      L_002b: call void [mscorlib]System.Console::WriteLine(string)
      L_0030: nop 
      L_0031: ret 
}

從上面的IL代碼中,我們可以看出這兩種初始化方式已經不再被理解為一樣的了。對比Code #03和Code #05,你會發現,改變的僅僅是L_0019行:

ldc.i4.0 -> ldc.i4.1

也就是說,使用枚舉的第一個成員來初始化枚舉變數,編譯器懂得根據枚舉的定義來作出相應的調整,從而編譯出符合我們預期的代碼。這種處理方式實際上使用了多態性的思維。

而此時,

Alignment a = new Alignment();

相當於

Alignment a = (Alignment)0;

或者

Alignment a = 0;

 

3. new、實值型別的預設建構函式和實值型別的預設值

通常我們使用new來調用參考型別的執行個體建構函式(Instance Constructors),或者自訂實值型別的非預設執行個體建構函式(Non-Default Instance Constructors)。然而,我們也可以使用new來調用實值型別(包括內建簡單類型和自訂類型)的預設建構函式,例如:

int i = new int();

這裡,new調用int的預設建構函式把i初始化為對應的預設值——0。當然,這個預設建構函式由.NET自動提供(但你不能手動提供)。也就是說,使用new來調用實值型別的預設建構函式,該實值型別將被自動設為對應的預設值。.NET的實值型別分為簡單類型(Simple types)、枚舉類型(Enum types)和結構類型(Struct types)。

3.1 簡單類型(Simple types)的預設值

對於簡單類型(Simple types),它們的預設值如下表所示:

Simple Type Default Value
bool false
byte 0
char '\0'
decimal 0.0M
double 0.0D
float 0.0F
int 0
long 0L
sbyte 0
short 0
uint 0
ulong 0
ushort 0

3.2 枚舉類型(Enum types)的預設值

對於枚舉類型(Enum types),.NET會自動將字面值0(literal 0)隱式地轉換為對應的枚舉類型。

3.2.1 有一個0值成員

如果枚舉類型中的某個成員被賦予0值(不要求是第一個成員),那麼枚舉變數所儲存的值就是該成員的值。假定Alignment的成員被賦值如下:

//Code #06

enum Alignment
{
    Left = 1,
    Center = 0,
    Right = 2
}

那麼,下面這句

Alignment a = new Alignment();

將等效於

Alignment a = Alignment.Center;

3.2.2 沒有0值成員

如果枚舉類型中任何一個成員都不為0,例如

// Code #07

enum Alignment
{
    Left = 1,
    Center = 2,
    Right = 3
}

那麼

Alignment a = new Alignment();

將等效於

Alignment a = (Alignment)0;

或者

Alignment a = 0;

而此時,枚舉變數a所儲存的值我們可以稱為非預定義枚舉(成員)值。

3.2.3 有兩個或以上的0值成員

那麼,如果枚舉類型裡存在多於一個成員被賦予0值呢?例如

// Code #08

enum Alignment
{
    Left = 0,
    Center = 1,
    Right = 0
}

你能猜得出下面代碼的運行結果嗎?

// Code #09

Alignment a = new Alignment();
Console.WriteLine(a.ToString());

從該代碼的運行結果中我們可以看到,new把Alignment.Left“許配”給枚舉變數a。現在讓我們看看下面這段代碼:

// Code #10

string a = Enum.GetName(typeof(Alignment), 0);
Console.WriteLine(a.ToString());

其實,Code #10和Code #09的輸出結果一樣的,從.NET的原始碼中我們也可以看到,選擇對象的規則是先用Array.Sort(Array keys, Array items);對枚舉成員名稱及其值進行排序,再用迴圈挑選第一個出現的幸運兒。

3.3 結構類型(Struct types)的預設值

對於結構類型(Struct types),其所包含的實值型別欄位會被初始化為對應的預設值,而參考型別欄位會被初始化為null。

// Code #11
// See Code #02 for Alignment.

public struct MyStruct
{
    public int Integer;
    public Alignment Align;
    public string Text;
}

那麼,如下代碼:

MyStruct m = new MyStruct();
Console.WriteLine(m.Integer);
Console.WriteLine(m.Align);
Console.WriteLine(m.Text == null);

的運行結果將是:

0
Left
True

 

4. 你認為如何使用枚舉才恰當?

現在,把本文之前的都忘了,認真地考慮一下這個問題:

你認為如何使用枚舉才恰當?

嗯,這是一個非常大的問題,要對其進行較全面的回答明顯超出我的能力範圍,如果勉強為之必定貽笑大方。但我不能置之不理,於是我只好把自己的想法作拋磚引玉之用。

太極的精髓不在其招式而在其理念,沒有思想的軀體猶如行屍走肉。語言的最基本作用是交流。要成功進行交流,你得先有想法,再運用語言規則把它表達出來,傳達給你的受眾,進而達到交流的目的。至於交流的效果,就要看你對語言規則的熟悉程度和運用功底了。

程式設計語言亦然,它不但是你跟電腦交流的橋樑,更是你跟下一個代碼維護者交流的橋樑。或許你的代碼是合法的,因為電腦能讀懂它,但這不代表你的代碼是友好的,因為下一個代碼維護者可能根本摸不著頭腦。易地而處,如果你將要維護某人龍飛鳳舞的作品,你可能早早就拍台踢凳了。我認為你絕對有責任為下一個代碼維護者著想一下。再者,下一個代碼維護者也可能是你自己,不知你有否嘗過隔了一段較長的時間後再回顧自己曾經的代碼時所感到的陌生感覺?

每一種語言都有著不為人所推薦的雷區和陷阱。由於某些原因,人們不能把這些雷區和陷阱徹底清除,為了眾人,人們把這些惱人的東西給隱藏起來,而你卻偏偏要把它挖出來看個究竟。在這灰色地帶的國度裡,對與錯的界限非常模糊,很多東西既不禁止也不推薦,於是沉默可能是最好的選擇。“沉默是因為包容”,然而,這卻被你拿來當作你那句“你說給過我縱容”的理由。細細品味周杰倫的《借口》,你或許也能從中體會到其中的無奈。

我們所處的世界極其複雜,以至於到目前還不能徹底摸清它的來頭,但我們從未放過任何一個探索的機會。零散散亂的知識對我們更好的認識這個世界基本不起什麼作用,我們需要的是結構化、系統化的知識體系,以便協助我們更全面的俯瞰這個世界的微妙。合理建立分類使得我們能夠更好的整理現有的知識,每一種分類方式又從某個側面把相關的知識聯絡並展現出來,使得我們能夠更加有效運用這些相關聯的知識。

我覺得一個枚舉類型就是一種分類方法。它使得你能夠從某一側面透視你的目標群體。對於一段給定的文字(string Class),我們既可以從文字樣式(FontStyle Enumeration)的角度來看待它,也可以從對齊類型(Alignment Enumeration)的角度去分析它,還可以從顏色(KnownColor Enumeration)的角度去考察它。我們的行動應該是本著一定的目的的,如果你壓根就不知道為何要這樣做,那麼不這樣做可能是最合適的。

回顧本文上面所說的一切,你認為我們真的有必要用new來初始化枚舉變數嗎?如果枚舉代表一種分類的思想,那麼為什麼你還要冒險令枚舉變數(有可能)儲存非預定義枚舉(成員)值呢(見3.2.2節)?

在本文結束之時,我想說

東西是否有用要看你是否會用;東西是否有效要看你是否用對。請使用語言有利的一面來協助我們的工作,而不是使用其有害的一面來傷害自己和別人。

 

See Also:

  • Allen Lee,《關於枚舉的種種 [C#, IL, BCL]》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.