Incorrect Overloaded functions called when specifying Enum Parameters

I was sent the following code with an subject similar to this blog entry title:

Option Strict On

Option Explicit On

Option Compare Text

Public Class ExampleClass

    Public Overloads Function Member(ByVal IntegerArgument As Long) As String

        Return CStr(IntegerArgument)

    End Function

    Public Overloads Function Member(ByVal IntegerArgument As Integer) As String

        Return CStr(IntegerArgument)

    End Function

    Public Overloads Function Member(ByVal IntegerArgument As Int16) As String

        Return CStr(IntegerArgument)

    End Function

    Public Enum Enumeration

        EnumerationValue0

        EnumerationValue1

    End Enum

    Public Overloads Function Member(ByVal EnumerationArgument As Enumeration) As String

        Return System.Enum.GetName(GetType(Enumeration), EnumerationArgument)

    End Function

    Public Shared Sub Main()

        Dim obj As New ExampleClass

        If Not obj.Member(Enumeration.EnumerationValue1) = "EnumerationValue1" Then Stop 'Test 1 works fine

        If Not obj.Member(Enumeration.EnumerationValue0) = "EnumerationValue0" Then Stop 'Test 2 works fine

        If Not obj.Member(1) = "1" Then Stop 'Test 3 works fine

        If Not obj.Member(1 - 1) = "0" Then Stop 'Test 4 works fine

        If Not obj.Member(CShort(0)) = "0" Then Stop 'Test 5 works fine

        If Not obj.Member(0) = "0" Then Stop 'But test 6 returns "EnumerationValue0"

    End Sub

End Class

The problem is that when he called obj.Member(0), the code will run the overloads function with enum function but not the long version. While obj.Memeber(1) will run the long version.

I spent some time investigating this, have a look at what I did:

When you specify constant values within your source code the compiler tries to do a type resolution on the constant value. The compiler checks for local type declarations (class level, then in the namespace, then in the references). In this case the compiler finds the declaration for the Enum and evaluates 0 to be EnumerationValue0. We can check this by looking at the source code and the IL:

                    If Not obj.Member(0) = "0" Then Stop 'But test 6 returns "EnumerationValue0"

IL for the above code:

  IL_0085: ldc.i4.0
IL_0086: beq.s IL_008e
IL_0088: call void [mscorlib]System.Diagnostics.Debugger::Break()
IL_008d: nop
IL_008e: ldloc.0
IL_008f: ldc.i4.0
IL_0090: callvirt instance string ConsoleApplication1.Module1/ExampleClass::Member(valuetype ConsoleApplication1.Module1/ExampleClass/Enumeration) IL_0095: ldstr "0"
IL_009a: ldc.i4.0
IL_009b: call int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Operators::CompareString(string,
string,
bool)

Things are about to get interesting!

We can do something really cool to prove our point. Let's break the application even more! Add another enumeration and an overloaded method that accepts this new enumeration. You'll see that when you try to compile you'll get the following error on the source line mentioned previously.

Overload resolution failed because no accessible 'Member' is most specific for these arguments:
'Public Overloads Function Member(EnumerationArgument As e2) As String': Not most specific.
'Public Overloads Function Member(EnumerationArgument As Enumeration) As String': Not most specific.

Here's what I added to the code:

        Public Enum e2

            a

            b

        End Enum

   

        Public Overloads Function Member(ByVal EnumerationArgument As e2) As String

            Return System.Enum.GetName(GetType(e2), EnumerationArgument)

        End Function

You can actually make this error go away if you change the call to Member to use a value that is not declared in any Enum (like 5) except the value 0 (I’ll get to this in a little later). We can take this a step further to show things are really broken. Change the enum declarations so that each enum as different values. I modified EnumerationValue to be 5 & 7 and e2 to have values of 3 & 4.

        Public Enum Enumeration As Integer

            EnumerationValue0 = 5

            EnumerationValue1 = 7

        End Enum

        Public Enum e2 As Integer

            a = 3

            b = 4

        End Enum

Now when you modify the code so that the call to Member contains any non-zero integer value that the Integer overloaded method is called (Public Overloads Function Member(ByVal IntegerArgument As Integer) As String). When you specify 0 in the call to Member we again get the overload resolution failure error message.

So, what does all of this tell us?

Basically that there are some problems with the compiler! From the documentation we know that we are supposed to reference all enums with a variable of the enum type or by the name of the enumeration. However, this doesn’t help us as we want the integer overloaded function to be called when we pass in the value of zero. I thought that there might be a default value of zero associated with either System.Enum or System.ValueType (since System.Enum inherits from System.ValueType) so I checked these classes using the object browser. I didn’t see anything useful.

Can you tell me what’s going on?

 

 

BTW, this all works as you'd expect in C# J

This all started here: https://msdn.microsoft.com/newsgroups/default.aspx?dg=microsoft.public.dotnet.framework&mid=ca24595b-4d00-400b-9d17-b8a79b19eade