user3004142
user3004142

Reputation:

Internal working of Enum.GetValues()

While reading someone's source-code I came across a C# snippet whose behaviour is confusing for me. Before I start asking, here is an adapted example:

The enum:

private enum Seasons
{
    Spring,
    Summer,
    Autumn,
    Winter
}

The usage:

foreach(Seasons value in Enum.GetValues(typeof(Seasons)))
{
   Console.WriteLine(String.Format("{0} {1}", value, (int) value));
}

The output looks like this:

Spring 0
Summer 1
Autumn 2
Winter 3

My Question:

How does this work inside C#? The return-value from:

Enum.GetValues(typeof(Seasons))

is an int array filled with the values 0-3. When it gets casted to a Seasons-enum I`d first expect it to fail.

I think since an enum is a reference type, on the String.Format()-argument "value", the ToString() method will be called, which will return its name. But when casted to an int, it will use the Property on that "named" value and will return the int value.

I`d be happy if anybody could tell me how this works behind the curtains. I looked through the documentations but cant work it out yet.

Thank you...


Thank you all for your answers!

But... I would really appreciate if one could please say a little more about the C# internals. In Java the enum would be a real reference type and in memory, it would be treated as a reference pointing to another peace of memory in which the integers are stored. Very, very simplified like this.

---------               -----------------
|       |  Reference    |int     Spring |
|Seasons|-------------> |int     Summer |
|       |               |int     Autumn |
---------               |int     Winter |
                        -----------------

It seems to me that in C# the compiler, transfers it to something like a public const (in my case an int). Please dont throw stones...

Upvotes: 5

Views: 3291

Answers (4)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236208

Variable of type Seasons can be assigned any value in the range of underlying type (integer in this case). Values even not limited to the named constants of enumeration. So it's completely legal to instantiate Seasons value by casting integer value 5 to Seasons type:

Seasons season = (Seasons)5;

UPDATE: First thing to note - System.Enum is a value type (and it's inherited from ValueType), like DateTime, Int32, or Boolean. If you will look on IL generated for Seasons enum definition, you'll see

.class public auto ansi sealed Seasons
    extends [mscorlib]System.Enum
{
    .field public static literal valuetype Foo.Seasons Autumn = int32(2)
    .field public static literal valuetype Foo.Seasons Spring = int32(0)
    .field public static literal valuetype Foo.Seasons Summer = int32(1)
    .field public specialname rtspecialname int32 value__
    .field public static literal valuetype Foo.Seasons Winter = int32(3)
}

So, that's a set of public static fields, which are marked as literals - that makes compiler embedding enum value directly into the code. E.g. following code

Seasons season = Seasons.Autumn;
Console.WriteLine(season);

After compilation there would be only value of enum Autumn member:

.locals init (
    [0] valuetype Foo.Seasons seasons) // it's value type!
 L_0000: nop 
 L_0001: ldc.i4.2 // here simple integer value 2 is loaded
 L_0002: stloc.0 
 L_0003: ldloc.0 
 L_0004: box Foo.Seasons 
 L_0009: call void [mscorlib]System.Console::WriteLine(object)

So, actually you have value of underlying type assigned to variable of enum type. No special properties for name or value. Just (any) integer constant.

Now about getting all values of enum - when you call Enum.GetValues it returns values from private Hashtable fieldInfoHash which you can find in Enum class. That Hashtable caches names and values of enumerations. So, when you getting values of your enum first time, HashEntry with values (ulong array) and names (string array) for enum type is created. Actually this cache will be used if you'll call other Enum methods, like GetNames or IdDefined.

After you have cached entry for your enum type, it's values array is returned. But with one trick. That is not simple array of ulong values. Each value is boxed as enumeration type with Enum.ToObject call (you can find implementation in System.RuntimeType):

ulong[] values = Enum.InternalGetValues(this);
Array array = Array.UnsafeCreateInstance(this, values.Length);
for (int i = 0; i < values.Length; i++)
{
    object obj2 = Enum.ToObject(this, values[i]);
    array.SetValue(obj2, i);
}
return array;

That's why each value object will be of Seasons type instead of ulong type.

Upvotes: 10

Jodrell
Jodrell

Reputation: 35716

The Enum.GetValues(...) function returns a System.Array of the values in the Enum that have been implicitly cast back to the type of the interrogated Enum.

This can be confirmed like this

var valEnum = Enum.GetValues(typeof(Seasons)).GetEnumerator();
valEnum.MoveNext();
Console.WriteLine(valEnum.Current.GetType());

You will see that

Seasons

is written to the console.

Since the ToString() of a Enum returns a string of the member name and, when an int derived Enum member is cast to an int it returns an the int value, your code works as it does.


After reading the answer above, you might be wondering, how do I know and why do I care if the Enum values have been implicitly cast back in the System.Array, well consider this Enum

enum Seasons
{
    Spring = 0,
    Summer = 1,
    Autumn = 2,
    Fall = 2,
    Winter = 3
}

If I interrogate this Enum like this,

foreach(var e in Enum.GetValues(typeof(Seasons))
{
    Console.WriteLine(
        "{0}: {1}: {2}",
        e.GetType().Name,
        e,
        (int) e);
}

I get the results

Seasons: Spring, 0

Seasons: Summer, 1

Seasons: Autumn, 2

Seasons: Autumn, 2

Seasons: Winter, 3

this is probably not what you were expecting. The second value for 2, Fall in the definition, has been implicitly cast back to the first Seasons member with a matching value. In this example Autumn.


Incidentally, the test

var valEnum = Enum.GetValues(typeof(Seasons)).GetEnumerator();
valEnum.MoveNext();
Console.WriteLine(valEnum.Current.GetType().IsValueType);

will output,

True

showing that Enums are value types but the distinction is moot in relation to your question.

Upvotes: 1

Selman Gen&#231;
Selman Gen&#231;

Reputation: 101681

As the documentation says the underlying type of each element in the enum is int. And that makes sense because it's an enumeration. Therefore if you cast an enum value to int you will get the corresponding int value.If you cast an integer to an enum type, you will get the corresponding enum value.In your case for example (Season)2 will return Autumun.

Upvotes: 1

Ondrej Janacek
Ondrej Janacek

Reputation: 12616

The return-value from Enum.GetValues() is an int array filled with the values 0-3. When it gets casted to a Seasons-enum I`d first expect it to fail..

No, it's not. The return type is Array and that contains the values of the constants in enumType. Therefore is completely legal to cast it.

As for int values you discovered there, this is from MSDN

By default the underlying type of each element in the enum is int. You can specify another integral numeric type. The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong.

When you do not specify any numbering, it will assign numbers starting from 0 as you discovered. However, you can use whatever numbers you wish

private enum Seasons
{
    Spring = 10,
    Summer = 20,
    Autumn = 30,
    Winter = 40
}

and compare it with numbers as well

if ((int) Seasons.Spring == 10)

Upvotes: 3

Related Questions