C#枚舉類型的默認值一定是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. 你認爲如何使用枚舉才恰當?

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

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

發佈了79 篇原創文章 · 獲贊 91 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章