Can anyone explain this?
alt text http://www.deviantsart.com/upload/g4knqc.png
using System;
namespace TestEnum2342394834
{
class Program
{
According to the MSDN documentation, the overload of Console.WriteLine
that takes an object
internally calls ToString
on its argument.
When you do foreach (var value in ...)
, your value
variable is typed as object
(since, as SLaks points out, Enum.GetValues returns an untyped Array
) and so your Console.WriteLine
is calling object.ToString
which is overriden by System.Enum.ToString
. And this method returns the name of the enum.
When you do foreach (int value in ...)
, you're casting the enum values to int
values (instead of object
); so Console.WriteLine
is calling System.Int32.ToString
.
Enum.GetValues
is declared as returning Array
.
The array that it returns contains the actual values as ReportStatus
values.
Therefore, the var
keyword becomes object
, and the value
variable holds (boxed) typed enum values.
The Console.WriteLine
call resolves to the overload that takes an object
and calls ToString()
on the object, which, for enums, returns the name.
When you iterate over an int
, the compiler implicitly casts the values to int
, and the value
variable holds normal (and non-boxed) int
values.
Therefore, the Console.WriteLine
call resolves to the overload that takes an int
and prints it.
If you change int
to DateTime
(or any other type), it will still compile, but it will throw an InvalidCastException
at runtime.
EDIT: added some sample code that explores many (perhaps all?) possible ways of iterating over the array.
Enum types are considered to be "derived" from int by default. You can choose to derive it from one of the other integer types if you want, such as byte, short, long, etc.
In both cases, the call to Enum.GetValues
is returning an array of ReportStatus objects.
Using the var keyword in the first loop tells the compiler to use the specified type of the array, which is ReportStatus, to determine the type of the value variable. The ToString implementation for enums is to return the name of the enum entry, not the integer value it represents, which is why the names are being output from the first loop.
Using an int variable in the second loop causes the values returned by Enum.GetValues
to be implicitly converted from ReportStatus to int. Calling ToString on an int will, of course, return a string representing the integer value. The implicit conversion is what causes the difference in behavior.
UPDATE: As others have pointed out, the Enum.GetValues function returns an object typed as Array, and as a result it is an enumerable of Object types, not ReportStatus types.
Regardless, the end result is the same whether iterating over Array or ReportStatus[]:
class Program
{
enum ReportStatus
{
Assigned = 1,
Analyzed = 2,
Written = 3,
Reviewed = 4,
Finished = 5,
}
static void Main(string[] args)
{
WriteValues(Enum.GetValues(typeof(ReportStatus)));
ReportStatus[] values = new ReportStatus[] {
ReportStatus.Assigned,
ReportStatus.Analyzed,
ReportStatus.Written,
ReportStatus.Reviewed,
ReportStatus.Finished,
};
WriteValues(values);
}
static void WriteValues(Array values)
{
foreach (var value in values)
{
Console.WriteLine(value);
}
foreach (int value in values)
{
Console.WriteLine(value);
}
}
static void WriteValues(ReportStatus[] values)
{
foreach (var value in values)
{
Console.WriteLine(value);
}
foreach (int value in values)
{
Console.WriteLine(value);
}
}
}
Just for some extra fun, I've added some code below demonstrating several different ways of iterating over the specified array with a foreach loop, including comments that describes in detail what's going on in each case.
class Program
{
enum ReportStatus
{
Assigned = 1,
Analyzed = 2,
Written = 3,
Reviewed = 4,
Finished = 5,
}
static void Main(string[] args)
{
Array values = Enum.GetValues(typeof(ReportStatus));
Console.WriteLine("Type of array: {0}", values.GetType().FullName);
// Case 1: iterating over values as System.Array, loop variable is of type System.Object
// The foreach loop uses an IEnumerator obtained from System.Array.
// The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function.
// The value variable is passed to Console.WriteLine(System.Object).
// Summary: 0 box operations, 0 unbox operations, 1 usage of TypedReference
Console.WriteLine("foreach (object value in values)");
foreach (object value in values)
{
Console.WriteLine(value);
}
// Case 2: iterating over values as System.Array, loop variable is of type ReportStatus
// The foreach loop uses an IEnumerator obtained from System.Array.
// The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function.
// The current value is immediatly unboxed as ReportStatus to be assigned to the loop variable, value.
// The value variable is then boxed again so that it can be passed to Console.WriteLine(System.Object).
// Summary: 1 box operation, 1 unbox operation, 1 usage of TypedReference
Console.WriteLine("foreach (ReportStatus value in values)");
foreach (ReportStatus value in values)
{
Console.WriteLine(value);
}
// Case 3: iterating over values as System.Array, loop variable is of type System.Int32.
// The foreach loop uses an IEnumerator obtained from System.Array.
// The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function.
// The current value is immediatly unboxed as System.Int32 to be assigned to the loop variable, value.
// The value variable is passed to Console.WriteLine(System.Int32).
// Summary: 0 box operations, 1 unbox operation, 1 usage of TypedReference
Console.WriteLine("foreach (int value in values)");
foreach (int value in values)
{
Console.WriteLine(value);
}
// Case 4: iterating over values as ReportStatus[], loop variable is of type System.Object.
// The foreach loop is compiled as a simple for loop; it does not use an enumerator.
// On each iteration, the current element of the array is assigned to the loop variable, value.
// At that time, the current ReportStatus value is boxed as System.Object.
// The value variable is passed to Console.WriteLine(System.Object).
// Summary: 1 box operation, 0 unbox operations
Console.WriteLine("foreach (object value in (ReportStatus[])values)");
foreach (object value in (ReportStatus[])values)
{
Console.WriteLine(value);
}
// Case 5: iterating over values as ReportStatus[], loop variable is of type ReportStatus.
// The foreach loop is compiled as a simple for loop; it does not use an enumerator.
// On each iteration, the current element of the array is assigned to the loop variable, value.
// The value variable is then boxed so that it can be passed to Console.WriteLine(System.Object).
// Summary: 1 box operation, 0 unbox operations
Console.WriteLine("foreach (ReportStatus value in (ReportStatus[])values)");
foreach (ReportStatus value in (ReportStatus[])values)
{
Console.WriteLine(value);
}
// Case 6: iterating over values as ReportStatus[], loop variable is of type System.Int32.
// The foreach loop is compiled as a simple for loop; it does not use an enumerator.
// On each iteration, the current element of the array is assigned to the loop variable, value.
// The value variable is passed to Console.WriteLine(System.Int32).
// Summary: 0 box operations, 0 unbox operations
Console.WriteLine("foreach (int value in (ReportStatus[])values)");
foreach (int value in (ReportStatus[])values)
{
Console.WriteLine(value);
}
// Case 7: The compiler evaluates var to System.Object. This is equivalent to case #1.
Console.WriteLine("foreach (var value in values)");
foreach (var value in values)
{
Console.WriteLine(value);
}
// Case 8: The compiler evaluates var to ReportStatus. This is equivalent to case #5.
Console.WriteLine("foreach (var value in (ReportStatus[])values)");
foreach (var value in (ReportStatus[])values)
{
Console.WriteLine(value);
}
}
}
-- Updated my comments in the sample above; upon double-checking I saw that the System.Array.GetValue method actually uses the TypedReference class in order to extract an element of the array and return it as System.Object. I had originally written that there was a boxing operation happening there, but that is technically not the case. I'm unsure what the comparison of a box operation is vs. a call to TypedReference.InternalToObject; I assume it depends on the CLR implementation. Regardless, I believe the details are more or less correct now.
An enumeration type is distinct from an integer. In your sample, var
does not evaluate to int, it evaluates to the enumeration type. You would get the same output if you had used the enumeration type itself.
Enumeration types output the name when printed, not their value.
FWIW, here's the disassembled code from Enum.GetValues() (via Reflector):
[ComVisible(true)]
public static Array GetValues(Type enumType)
{
if (enumType == null)
{
throw new ArgumentNullException("enumType");
}
if (!(enumType is RuntimeType))
{
throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "enumType");
}
if (!enumType.IsEnum)
{
throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType");
}
ulong[] values = GetHashEntry(enumType).values;
Array array = Array.CreateInstance(enumType, values.Length);
for (int i = 0; i < values.Length; i++)
{
object obj2 = ToObject(enumType, values[i]);
array.SetValue(obj2, i);
}
return array;
}
Looks like what everyone's saying about the var
being an object
and calling object.ToString()
returning the name is correct...
The var value
is actually an enum value (of type ReportStatus), so you see the standard behaviour of enumValue.ToString() - it's name.
EDIT:
When you do a Console.WriteLine(value.GetType())
you will see that it really is a 'ReportStatus', although it's boxed in a plain Object
.