Extension methods declared on Object need one more parameter than others?

Extension methods on Object can be declared on Object but cannot be used like obj.ExtMethod(). This is by design. On the other hand, any extension method can be also used like ExtMethod(obj). Why calling of extension methods declared on Object differ from extension methods declared on other types? I'm seeking for logic behind this. Or is it a bug?

To spot the difference, please see below example and compare ordinary ToString1() to ToString2()/ToString3().

Imports System.Runtime.CompilerServices

Module CompilerExtensionsModule

    ' standard one, works as expected
    <Extension>
    Function ToString1(value As Integer) As String
        Return value.ToString()
    End Function

    ' obj isn't expected as parameter on actual usage, context is supplied instead
    <Extension>
    Function ToString2(obj As Object) As String
        Return If(obj Is Nothing, "", obj.ToString())
    End Function

    ' this is way how to have obj as parameter - first parameter is ignored
    <Extension>
    Function ToString3(base As Object, obj As Object) As String
        Return If(obj Is Nothing, "", obj.ToString())
    End Function

    ' let's try with something different than Object
    <Extension>
    Function ToStringClass1(obj As Class1) As String
        Return obj.ToString()
    End Function

End Module

Usage in class:

ToString1(3)    ' as expected - 1 parameter declared, 1 expected
ToString2()     ' 1 parameter declared, no parameters expected in call
ToString3(Nothing) ' 2 parameters declared, 1 expected in call - passed as second parameter

Added details: (minimum complete working example – 3 files – including the above one)

Full calling context: Class:

Public Class Class1

    Sub Action1()
        Dim value1 As Integer = 1
        Dim obj1 As Object = Nothing
        Dim obj2 As New Class1

        Console.WriteLine(ToString1(value1))
        Console.WriteLine(ToString2())
        Console.WriteLine(ToString3(obj1))
        Console.WriteLine(ToStringClass1())
    End Sub

End Class

Full calling context: Class different from Class1 – no issues posted in question, but weird effects:

Public Class Class2

    Sub Action1()
        Dim value1 As Integer = 1
        Dim obj1 As Object = Nothing
        Dim obj2 As New Class1

        Console.WriteLine(ToString1(value1))
        Console.WriteLine(ToString2())
        Console.WriteLine(ToString3(obj1))
        Console.WriteLine(ToStringClass1(obj2))

        obj2.ToString2()
        ToString2(obj2) ' INVALID - won't compile in any class (but will do in any module)
        ToString3(obj2) ' EDIT: VALID because two parameters are actually supplied here

        ' EDIT (see comments below the answer):
        CompilerExtensionsModule.ToString2(obj2) ' VALID - switching the context solves it
        ' Note: for ext.mehods of Object, this form of call is needed in any class
        ' Reason: any class is descendant of Object => VB wants to supply 1st parameter
        '    in calling context of class => use calling context of ext.module instead

    End Sub

End Class

Full calling context: Module – no issues:

Module Module1

    Sub Main()
        Dim value1 As Integer = 1
        Dim obj1 As Object = Nothing
        Dim obj2 As New Class1

        Console.WriteLine(ToString1(value1))
        Console.WriteLine(ToString2(obj1))
        Console.WriteLine(ToString3(obj1, obj1))
        Console.WriteLine(ToStringClass1(obj2))

        ' unlike in Class2, no issues here:
        obj2.ToString2()
        ToString2(obj2)

    End Sub

End Module
Jon Skeet
people
quotationmark

Why calling of extension methods declared on Object differ from extension methods declared on other types if Option Strict On is present?

Because your calling context (which you haven't shown) isn't convertible to Integer, but is convertible to Object. Imagine you're explicitly calling:

Me.ToString2()
Me.ToString3(Nothing)

That's the converted to:

ToString2(Me)
ToString3(Me, Nothing)

That doesn't happen with ToString1 (which is treated as a regular shared module-wide method) because Me isn't implicitly convertible to Integer. (I don't know the details of method invocation in VB, but it sounds like extension methods are searched before module-wide shared methods invoked in the regular way.)

people

See more on this question at Stackoverflow