0

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
Community
  • 1
  • 1
miroxlav
  • 11,796
  • 5
  • 58
  • 99
  • Please show a short but complete example demonstrating the problem. (Your module doesn't even compile for me at the moment.) My *guess* is that you're implicitly passing `Me` as the first argument.. – Jon Skeet Sep 08 '15 at 16:32
  • @JonSkeet - my apologies - fixed... let's delete these comments now, because they are needed no longer. – miroxlav Sep 08 '15 at 16:45
  • In `ToString3` you don't use the first parameter - why have it? – OneFineDay Sep 08 '15 at 16:46
  • @OneFineDay - I don't want it there, check example of `ToString3()` when called in class. – miroxlav Sep 08 '15 at 16:50

1 Answers1

3

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.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • You are absolutely right with pointing calling context. It seems it is **automatically supplied as first parameter of extension methods** when they are called in class in form `ExtMethod(obj)` instead of `obj.ExtMethod`. Moreover, this is reproducible with *all* extension methods, not only with those declared on Object. Perhaps you can update the answer accordingly (if you think it needs update based on this) and I can accept it. – miroxlav Sep 08 '15 at 17:05
  • I don't think any update is needed - in the context of an instance member, a call to Foo is implicitly tried as Me.Foo(). Btw, C# doesn't work that way - you'd need to call this.ToString2. This may have changed for C# 6, or it may just be discussed for C# 7 - not sure. – Jon Skeet Sep 08 '15 at 17:09
  • I verified that with different calling contexts (they confirm your answer) and honestly, this "feature" is very surprising to me. I've never seen such inconsistent "automatic passing of first parameter" at other places of the framework. If feels quite unnatural. Worse, I need **two methods for the same functionality:** `obj2.ToString2()` plus `ToString3(obj2)` and there is probably no way around this. – miroxlav Sep 08 '15 at 17:30
  • @miroxlav: I don't understand why you need two methods. If you call `obj.ToString3()` it won't implicitly pass `Me`. (That only happens if you use the method in an unqualified way.) Why do you think you need two methods? – Jon Skeet Sep 08 '15 at 17:44
  • I think I clearly understand your intention but it looks that `Me` is getting implicitly passed in any context. Please see lines below "consequence" comment in example of `Class2` above. I can put it to *any* class and context is still passed as first parameter. (Only modules are behaving normally.) The only way around is `obj2.ToString2()` plus `ToString3(obj2)` so method with same purpose is needed twice. Oh, and if you change method to `Shared`, `Me` isn't passed but compiler throws errors anyway. Looks like dead end. – miroxlav Sep 08 '15 at 18:30
  • @miroxlav: I don't quite follow you but don't have time to look right now... will check later. – Jon Skeet Sep 08 '15 at 18:48
  • Thank you. If looking into it, just put those sources into the VS2015 VB Console Application project and you should be able reproduce both things I complained about in my last comment. – miroxlav Sep 08 '15 at 18:50
  • Looking at it again, it's still not clear what you're trying to achieve. Why don't you always just call `obj.ToString2()`? And I'm not sure where you think I've specified a workaround... but if you *do* want to call it in a more qualified way, you can call `CompilerExtensionsModule.ToString2("Foo")`. I think I understand all the behaviour at the moment, but I don't understand what you're trying to achieve or why you think you need two methods. – Jon Skeet Sep 09 '15 at 05:48
  • I got it! Behavior you explained in the answer is in case of extension methods of *Object* (e.g. `ToString2()`) present in ***any*** class, because any class is descendant of *Object*. Thank you for your patience with me, Jon. – miroxlav Sep 09 '15 at 09:58
  • @miroxlav: No problem - glad we got there in the end. – Jon Skeet Sep 09 '15 at 10:00