1. 問題
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指定第一個成員的值:
enum Alignment
{
Left,
Center,
Right
}
Code #01的輸出結果將是:
0
0
我們再把Code #01的Main反編譯成IL:
.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 b = Alignment.Left;
Alignment d = (Alignment)0;
Alignment c = 0;
2.2 第一個枚舉成員的值非0
如果我們手動指定Alignment的第一個成員的值呢?
enum Alignment
{
Left = 1,
Center,
Right
}
Code #01的輸出結果將有點令人疑惑:
0
1
爲什麼會這樣呢?讓我們從IL代碼中看看編譯器是如何理解此時的Main的:
.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
也就是說,使用枚舉的第一個成員來初始化枚舉變量,編譯器懂得根據枚舉的定義來作出相應的調整,從而編譯出符合我們預期的代碼。這種處理方式實際上使用了多態性的思維。
而此時,
相當於
或者
3. new、值類型的默認構造函數和值類型的默認值
通常我們使用new來調用引用類型的實例構造函數(Instance Constructors),或者自定義值類型的非默認實例構造函數(Non-Default Instance Constructors)。然而,我們也可以使用new來調用值類型(包括內置簡單類型和自定義類型)的默認構造函數,例如:
這裏,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的成員被賦值如下:
enum Alignment
{
Left = 1,
Center = 0,
Right = 2
}
那麼,下面這句
將等效於
3.2.2 沒有0值成員
如果枚舉類型中任何一個成員都不爲0,例如
enum Alignment
{
Left = 1,
Center = 2,
Right = 3
}
那麼
將等效於
或者
而此時,枚舉變量a所儲存的值我們可以稱爲非預定義枚舉(成員)值。
3.2.3 有兩個或以上的0值成員
那麼,如果枚舉類型裏存在多於一個成員被賦予0值呢?例如
enum Alignment
{
Left = 0,
Center = 1,
Right = 0
}
你能猜得出下面代碼的運行結果嗎?
Alignment a = new Alignment();
Console.WriteLine(a.ToString());
從該代碼的運行結果中我們可以看到,new把Alignment.Left“許配”給枚舉變量a。現在讓我們看看下面這段代碼:
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。
// See Code #02 for Alignment.
public struct MyStruct
{
public int Integer;
public Alignment Align;
public string Text;
}
那麼,如下代碼:
Console.WriteLine(m.Integer);
Console.WriteLine(m.Align);
Console.WriteLine(m.Text == null);
的運行結果將是:
0
Left
True
4. 你認爲如何使用枚舉才恰當?
現在,把本文之前的都忘了,認真地考慮一下這個問題:
你認爲如何使用枚舉才恰當?