Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are enums only named integers, types or neither of both?

Tags:

c#

enums

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?

like image 410
Aleksandar Avatar asked Dec 09 '08 09:12

Aleksandar


1 Answers

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.

like image 135
Jon Skeet Avatar answered Oct 31 '22 02:10

Jon Skeet