tags:

views:

475

answers:

3

Fun with enums in C#. Take one generic list that is created to store some Enum that you had defined previously and add few items in it. Iterate with foreach or GetEnumerator<T>() but specify some other enum then the original and see what happens. I was expecting InvalidCastException or something like that but it perfectly works :).

For the illustration let's take a simple console application and create two enums there: Cars and Animals:

    public enum Cars
    {
        Honda = 0,
        Toyota = 1,
        Chevrolet = 2
    }
    public enum Animals
    {
        Dog = 0,
        Cat = 1,
        Tiger = 2
    }

And do this in main method:

    public static void Main()
    {
        List<Cars> cars = new List<Cars>();
        List<Animals> animals = new List<Animals>();
        cars.Add(Cars.Chevrolet);
        cars.Add(Cars.Honda);
        cars.Add(Cars.Toyota);

        foreach (Animals isItACar in cars)
        {
            Console.WriteLine(isItACar.ToString());
        }
        Console.ReadLine();
    }

It will print this in console:

Tiger
Dog
Cat

Why is this happening? My first guess was that enum is not actually a Type by himself it's just and int but that's not true: If we write:

Console.WriteLine(Animals.Tiger.GetType().FullName); We will get his fully qualified name printed! So why this?

+1  A: 

Conceptually, an Enum is a statically-typed value with a string representation and a number. When you call ToString() on an enum, it will return the string representation. When you call GetType() on an enum, you'll get the static enumeration type. If you cast an enum to int, you'll get the integer value for the enum.

Enums are supposed to be strongly-typed but there are some things you need to be aware of, like the fact that any integer can be cast to any enum even if it doesn't have a corresponding declaration (in which case the string representation will be the same as the number's).

In the CLR, enums (like bools) are just treated as ints but if you called GetType() or GetString() it calls the versions that do what was described above.

Mark Cidade
+19  A: 

Enum types are distinct, but you're being confused by an implicit cast which is in foreach.

Let's rewrite your loop a bit:

public static void Main()
{
    List<Cars> cars = new List<Cars>();
    List<Animals> animals = new List<Animals>();
    cars.Add(Cars.Chevrolet);
    cars.Add(Cars.Honda);
    cars.Add(Cars.Toyota);

    foreach (Cars value in cars)
    {
        // This time the cast is explicit.
        Animals isItACar = (Animals) value;
        Console.WriteLine(isItACar.ToString());
    }
    Console.ReadLine();
}

Now does the result surprise you? Hopefully not, except possibly the fact that you can cast from one enum to another. This is just a more explicit version of what your original code is doing.

The fact that there's a cast implicit in every foreach loop (even though it's usually a no-op) is the bit which most developers would find confusing, I think.

From section 8.8.4 of the C# 3.0 spec:

The above steps, if successful, unambiguously produce a collection type C, enumerator type E and element type T. A foreach statement of the form

foreach (V v in x)  embedded-statement

is then expanded to:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
        while (e.MoveNext()) {
            v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        ... // Dispose e
    }
}

The enumeration conversion itself is covered in section 6.2.2:

The explicit enumeration conversions are:

  • From sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, or decimal to any enum-type.
  • From any enum-type to sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, or decimal.
  • From any enum-type to any other enum-type.

An explicit enumeration conversion between two types is processed by treating any participating enum-type as the underlying type of that enum-type, and then performing an implicit or explicit numeric conversion between the resulting types. For example, given an enum-type E with and underlying type of int, a conversion from E to byte is processed as an explicit numeric conversion (§6.2.1) from int to byte, and a conversion from byte to E is processed as an implicit numeric conversion (§6.1.2) from byte to int.

Jon Skeet
Very good answer!
Stefan
Yes very good answer. I realize now that explicit cast between enums of any type would not fail since they are practically an integers, and there was the root of the problem.
Aleksandar
A: 

You can also derive an enum from a specific type.

public enum Cats : byte { ... }
public enum Dogs : int { ... }
Steven Behnke