Reputation:
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
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
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
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
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