Reputation: 48558
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
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.
Accessibility. It eliminates any overload with an access level that prevents the calling code from calling it.
Number of Parameters. It eliminates any overload that defines a different number of parameters than are supplied in the call.
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.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.
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.
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 membersMP
andMQ
with parameter types{ P1, P2, ..., PN }
and{ Q1, Q2, ..., QN }
,MP
is defined to be a better function member thanMQ
if
- for each argument, the implicit conversion from
EX
toQX
is not better than the implicit conversion fromEX
toPX
, and- for at least one argument, the conversion from
EX
toPX
is better than the conversion fromEX
toQX
.
Ok, now how is better defined (§7.5.3.4)?
Given a conversion
C1
that converts from a typeS
to a typeT1
, and a conversionC2
that converts from a typeS
to a typeT2
,C1
is a better conversion thanC2
if at least one of the following holds:
- An identity conversion exists from
S
toT1
but not fromS
toT2
T1
is a better conversion target thanT2
(§7.5.3.5)
Let's see at §7.5.3.5:
Given two different types
T1
andT2
,T1
is a better conversion target thanT2
if at least one of the following holds:
- An implicit conversion from
T1
toT2
exists, and no implicit conversion fromT2
toT1
existsT1
is a signed integral type andT2
is an unsigned integral type.
So, we're converting from Color
to either Object
or Int32
. Which one is better according to these rules?
Color
to Object
Object
to Color
(obviously)Color
to Int32
(these are explicit in C#)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
anddynamic
, toSystem.ValueType
and to any interface-type implemented by the non-nullable-value-type. Furthermore an enum-type can be converted to the typeSystem.Enum
.
Upvotes: 5
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