Nikhil Agrawal
Nikhil Agrawal

Reputation: 48558

Console.WriteLine(Enum.Value) gives different output in C# and VB.Net

I am basically a C# guy, but writing VB.Net code these days.

Today I came across a very different behaviour of .Net

C# Code

enum Color
{
   Red,
   Green,
   Blue
}

class Demo
{
    public static void Main()
    {
        System.Console.WriteLine(Color.Red);
    }
}

This prints Red

But when this code is written in VB.Net it prints 0.

VB.Net Code

Module Module1

    Sub Main()
        System.Console.WriteLine(Color.Red)
    End Sub

End Module

Enum Color
    Red
    Green
    Blue
End Enum

Why so different?

Upvotes: 4

Views: 3110

Answers (2)

Lucas Trzesniewski
Lucas Trzesniewski

Reputation: 51330

C# and VB.NET have different method overload resolution rules.

C# Picks Console.WriteLine(Object), while VB.NET picks Console.WriteLine(Int32). Let's see why it does so.

VB.NET rules:

  1. Accessibility. It eliminates any overload with an access level that prevents the calling code from calling it.

  2. Number of Parameters. It eliminates any overload that defines a different number of parameters than are supplied in the call.

  3. Parameter Data Types. The compiler gives instance methods preference over extension methods. If any instance method is found that requires only widening conversions to match the procedure call, all extension methods are dropped and the compiler continues with only the instance method candidates. If no such instance method is found, it continues with both instance and extension methods.
    In this step, it eliminates any overload for which the data types of the calling arguments cannot be converted to the parameter types defined in the overload.

  4. Narrowing Conversions. It eliminates any overload that requires a narrowing conversion from the calling argument types to the defined parameter types. This is true whether the type checking switch (Option Strict Statement) is On or Off.

  5. Least Widening. The compiler considers the remaining overloads in pairs. For each pair, it compares the data types of the defined parameters. If the types in one of the overloads all widen to the corresponding types in the other, the compiler eliminates the latter. That is, it retains the overload that requires the least amount of widening.

  6. Single Candidate. It continues considering overloads in pairs until only one overload remains, and it resolves the call to that overload. If the compiler cannot reduce the overloads to a single candidate, it generates an error.

There are a lot of overloads for WriteLine, some of them are discarded at step 3. We're basically left with the following possibilities: Object and the numeric types.

The 5th point is interesting here: Least Widening. So what do the widening rules say?

Any enumerated type (Enum) widens to its underlying integral type and any type to which the underlying type widens.

Any type widens to Object

So, your Color enum first widens to Int32 (its underlying data type) - and this is a 100% match for Console.WriteLine(Int32). It would require yet another widening conversion to go from Int32 to Object, but the rules above say to retain the overload that requires the least amount of widening.


As for C# (from the C# 5 spec at §7.5.3.2):

Given an argument list A with a set of argument expressions { E1, E2, ..., EN } and two applicable function members MP and MQ with parameter types { P1, P2, ..., PN } and { Q1, Q2, ..., QN }, MP is defined to be a better function member than MQ if

  • for each argument, the implicit conversion from EX to QX is not better than the implicit conversion from EX to PX, and
  • for at least one argument, the conversion from EX to PX is better than the conversion from EX to QX.

Ok, now how is better defined (§7.5.3.4)?

Given a conversion C1 that converts from a type S to a type T1, and a conversion C2 that converts from a type S to a type T2, C1 is a better conversion than C2 if at least one of the following holds:

  • An identity conversion exists from S to T1 but not from S to T2
  • T1 is a better conversion target than T2 (§7.5.3.5)

Let's see at §7.5.3.5:

Given two different types T1 and T2, T1 is a better conversion target than T2 if at least one of the following holds:

  • An implicit conversion from T1 to T2 exists, and no implicit conversion from T2 to T1 exists
  • T1 is a signed integral type and T2 is an unsigned integral type.

So, we're converting from Color to either Object or Int32. Which one is better according to these rules?

  • There is an implicit conversion from Color to Object
  • There is no implicit conversion from Object to Color (obviously)
  • There is no implicit conversion from Color to Int32 (these are explicit in C#)
  • There is no implicit conversion from Int32 to Color (except for 0)

Spec §6.1:

The following conversions are classified as implicit conversions:

  • Identity conversions
  • Implicit numeric conversions
  • Implicit enumeration conversions.
  • Implicit nullable conversions
  • Null literal conversions
  • Implicit reference conversions
  • Boxing conversions
  • Implicit dynamic conversions
  • Implicit constant expression conversions
  • User-defined implicit conversions
  • Anonymous function conversions
  • Method group conversions

Implicit numeric conversions make no mention of enum types, and Implicit enumeration conversions deal with the other way around:

An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type and to any nullable-type whose underlying type is an enum-type. In the latter case the conversion is evaluated by converting to the underlying enum-type and wrapping the result (§4.1.10).

Enums are handled by boxing conversions (§6.1.7):

A boxing conversion permits a value-type to be implicitly converted to a reference type. A boxing conversion exists from any non-nullable-value-type to object and dynamic, to System.ValueType and to any interface-type implemented by the non-nullable-value-type. Furthermore an enum-type can be converted to the type System.Enum.

Upvotes: 5

Hans Passant
Hans Passant

Reputation: 941327

There is no Console.WriteLine(Enum) overload so the compilers are forced to pick one of the other ones. Overload resolution rules are very arcane and the VB.NET and C# rules are not the same, but both compilers are willing to pick one when there's an implicit conversion to the target argument type and pick the one that takes the least amount of work.

Which is where another rule applies, this kind of statement in VB.NET is perfectly valid:

   Dim example As Integer = Color.Red  '' Fine

But the C# compiler spits at:

   int example = Color.Red;            // CS0266

Insisting that you apply an (int) cast. It only has an explicit conversion, not an implicit one like VB.NET.

So the C# compiler is going to ignore all the overloads that take an integral argument, none are candidates because only explicit conversions exist for them. Except one, the Console.WriteLine(Object) overload. There is an implicit conversion for that one, it takes a boxing conversion.

The VB.NET compiler sees it as well, but now the "better" conversion comes into play. A boxing conversion is a very expensive conversion, converting to Integer is very cheap. It requires no extra code. So it likes that one better.

Workarounds are simple:

    System.Console.WriteLine(CObj(Color.Red))         '' or
    System.Console.WriteLine(Color.Red.ToString())

Upvotes: 6

Related Questions