Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot understand the behavior of Enums

Tags:

c#

.net

enums

I was practicing enums in C#, and I am not able to understand the output of these

void Main()
{
    MyEnum a = MyEnum.Top;
    Console.WriteLine(a);
}

For this test my enum and output are

enum MyEnum
{
    Left, Right, Top, Bottom // Top
}

enum MyEnum
{
    Left, Right, Top = 0, Bottom // Left
}

I thought at rum time program chooses the 1st item with value 0, to confirm this I assigned value of 0 to Bottom

enum MyEnum
{
    Left, Right, Top = 0, Bottom = 0 // Bottom
}

Then I thought maybe program chooses the 1st item with value 0 but searches alphabetically so I changed Top to ATop and changed the test case

void Main()
{
    MyEnum a = MyEnum.ATop;
    Console.WriteLine(a);
}

enum MyEnum
{
    Left, Right, ATop = 0, Bottom = 0 // Bottom
}

enum MyEnum
{
    Left, Right, ATop = 0, Bottom // Left
}

I know nobody uses enum in this way, but I want to know this particular behavior of Enum.

like image 411
Master Chief Avatar asked Sep 08 '14 05:09

Master Chief


Video Answer


1 Answers

Seems like you figured out already that the compiler starts counting at zero by default.

enum MyEnum
{
    Left, Right, Top = 0, Bottom = 0 // Bottom
}

gets translated to this

.class nested private auto ansi sealed MyEnum
    extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname int32 value__
    .field public static literal valuetype X/MyEnum Left = int32(0)
    .field public static literal valuetype X/MyEnum Right = int32(1)
    .field public static literal valuetype X/MyEnum Top = int32(0)
    .field public static literal valuetype X/MyEnum Bottom = int32(0)
}

The runtime actually works with the underlying types most of the time. So the funny thing is that this here

static void Main()
{
    MyEnum a = MyEnum.Top;
    Console.WriteLine(a);

    Console.ReadKey();
}

doesn't even use the actual enum member:

.method private hidebysig static 
    void Main () cil managed 
{
    .maxstack 1
    .entrypoint
    .locals init (
        [0] valuetype X/MyEnum a
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: box X/MyEnum
    IL_0009: call void [mscorlib]System.Console::WriteLine(object)
    IL_000e: nop
    IL_000f: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    IL_0014: pop
    IL_0015: ret
}

It just uses the value 0. The decision making of which name to print out with Console.WriteLine begins in System.Enum.ToString and climaxes in System.Type.GetEnumName(object).

public virtual string GetEnumName(object value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (!this.IsEnum)
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType");
    }
    Type type = value.GetType();
    if (!type.IsEnum && !Type.IsIntegerType(type))
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnumBaseTypeOrEnum"), "value");
    }
    Array enumRawConstantValues = this.GetEnumRawConstantValues();
    int num = Type.BinarySearch(enumRawConstantValues, value);
    if (num >= 0)
    {
        string[] enumNames = this.GetEnumNames();
        return enumNames[num];
    }
    return null;
}

As you can see the actual way to search for the name to print is a binary search through the field names (found by reflection).

This is just the current implementation, though and might differ with different compilers and/or runtime versions. The language specification doesn't guarantee any particular order or outcome for code like the one above.

like image 199
bstenzel Avatar answered Sep 25 '22 01:09

bstenzel