views:

211

answers:

7

Can anyone explain this?

alt text

using System;

namespace TestEnum2342394834
{
    class Program
    {
        static void Main(string[] args)
        {
            //with "var"
            foreach (var value in Enum.GetValues(typeof(ReportStatus)))
            {
                Console.WriteLine(value);
            }

            //with "int"
            foreach (int value in Enum.GetValues(typeof(ReportStatus)))
            {
                Console.WriteLine(value);
            }

        }
    }

    public enum ReportStatus
    {
        Assigned = 1,
        Analyzed = 2,
        Written = 3,
        Reviewed = 4,
        Finished = 5
    }
}
+22  A: 

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.

SLaks
So the underlying IEnumerable returned relies on implicit conversions, does it?
Andreas
Good explanation, +1. Note that if the enum's underlying type was another numeric type (uint or short for instance), the second foreach would fail too.
Thomas Levesque
@Andreas, there is no conversion going on in the first loop, and on the second loop, only inheritance casting is allowed. That is, not implicitly nor explicitly defined casts.
Dykam
+4  A: 

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.

Dan Tao
Yes, the `foreach(ReportStatus` compiles and prints the names.
Hans Kesting
+2  A: 

You implicitely call ToString() on each element when you use Console.WriteLine.

And when you say you want an int (using the explicit type) it will convert it to an int - and then ToString() it.

The first one is the Enum value ToString()'ed

Goblin
A: 

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.

Shirik
Close, but the `var` does not evaluate to the enumeration type, actually; it evaluates to `object` (see SLaks's answer).
Dan Tao
Technically, true, the compiler evaluates var to object as SLaks points out, but at runtime the value is of the enumeration type (although boxed). The fact that the value is boxed is irrelevant to the behavior being asked by the question.
Dr. Wily's Apprentice
A: 

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.

Hans Kesting
Wrong. `var` becomes `object` here.
SLaks
This answer isn't actually wrong, though not as thorough as SLaks' answer. The value variable IS actually of type ReportStatus, it's just the compiler that evaluates var to object because it isn't able to determine the specific type of the array returned by Enum.GetValues. However, evaluating `(value is ReportStatus)` at runtime results in true.Whether or not the value variable is an object boxing a ReportStatus or whether it is actually a ReportStatus is actually irrelevant to the behavior being asked about.
Dr. Wily's Apprentice
+3  A: 

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...

Tim Coker
Now some s##t stirrer is gonna have at you for posting disassembled code... +1 anyway.
Mau
My position on all that is if MS was anti-disassembling, they could easily obfuscate the libraries...
Tim Coker
A: 

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.

Dr. Wily's Apprentice
Like most other answers, wrong. `var` becomes `object`.
SLaks
Granted, I was incorrect about var becoming object, but the fact that the value variable is an object boxing the enum values is actually irrelevant to the question of why the behavior is different. I've added a code example that demonstrates that the behavior is the same regardless of whether var is object or var is ReportStatus.
Dr. Wily's Apprentice