C#枚举类型的默认值一定是0说起

来源:互联网 发布:mac迅雷一直是0 编辑:程序博客网 时间:2024/06/05 01:03

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
boolfalsebyte0char'\0'decimal0.0Mdouble0.0Dfloat0.0Fint0long0Lsbyte0short0uint0ulong0ushort0

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. 你认为如何使用枚举才恰当?

现在,把本文之前的都忘了,认真地考虑一下这个问题:

你认为如何使用枚举才恰当?

原创粉丝点击