vblang icon indicating copy to clipboard operation
vblang copied to clipboard

[Bug] Can't call an extension method for an Object

Open VBAndCs opened this issue 3 years ago • 6 comments

I have these extensions:

Imports System.Runtime.CompilerServices

Module Extensions

    <Extension()>
    Public Sub Print(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()>
    Public Function IsSomething(ByVal o As String) As Boolean
        Return o IsNot Nothing
    End Function

    Public Function IsSomething(ByVal o As Object) As Boolean
        Return o IsNot Nothing
    End Function
End Module

now, I have this code:

        Dim o As Object = ""
        If o.IsSomething Then

        End If

It gives me the runtime exception:

Public member 'IsSomething' on type 'String' not found

Why can't vb use the extension Object.IsSomething or String.IsSomething?

Note that this code works:

        Dim o As String = ""
        If o.IsSomething Then

        End If

The only code that works with Object is this:

        Dim o As Object = Nothing
        If o?.IsSomething Then

        End If

I can accept that extension methods don't work with boxed values because of late binding, but why doesn't VB use the extension for the object and compile O.IsSomething: to IsSomething(O)? I think this is a bug that needs to be fixed.

VBAndCs avatar Aug 04 '20 03:08 VBAndCs

My understanding of the issue is as follows: with Option Strict Off and late binding, before the extension method's namespace is imported, IsSomething cannot be resolved at compile time, so it's left for the runtime to resolve IsSomething to a member on the underlying object's type.

If you then only import the namespace with the extension method, and the compiler would resolve to the extension method, the code's behavior will silently change -- there will be no error or warning that the compiler has now found an extension method instead.

(For the same reason, the compiler will prefer an instance method over an extension method, all else being equal.)

(I'm not sure what this has to do with boxing.)

This isn't as wide a problem as it appears at first glance, because it prevents extension methods being resolved only on expressions typed as Object. If the expression is more strongly typed, an extension method targeting Object can be used.

Having different compiler resolutions between Option Strict On (resolve the extension method) and Option Strict Off (don't resolve the extension method) doesn't really make sense either.

(Personally, I think the compiler should resolve the extension method -- if you're not using Option Strict, you're already living dangerously; what's a little more danger between friends?)

Link

zspitz avatar Aug 04 '20 09:08 zspitz

Seems to be by design (a bad one in deed, and I hope it can be changed): https://devblogs.microsoft.com/vbteam/extension-methods-and-late-binding-extension-methods-part-4/ It goes more odd to prevent using extension methods from objects even when Option Strict On (no late binding)! This makes no sense!

VBAndCs avatar Aug 04 '20 11:08 VBAndCs

This makes no sense!

Why can't vb use the extension Object.IsSomething...?

Object in VB has an ambiguity to it: (1) It is either the lowest-common-type of .NET; or (2) it is the mechanism for late-bound (a.k.a. dynamic) calls. Sense (2) comes by way of legacy VB idioms; Option Strict On doesn't remove that, it merely disallows that. This ambiguity is what makes extensions and late-bindings clash. Therefore an extension like Object.IsSomething won't get recognized because of that ambiguity, as explained in that "Extension methods and late binding" article @zspitz and you cited.

Speaking as someone who makes a living having to analyze/repair/enhance various mud-balls of code generated by various somebodies over various spans of years, I for one think they made the right choice here. We mud-ball people don't like silent-at-compile-time surprises that become cacophonous-at-run-time production crises.

However, as @zspitz mentioned, casting to something other than Object, so as to avoid that late-bound/dynamic meaning, makes it possible to use Object.IsSomething. This is not as constraining as it may seem: If you're incline to make use of Option Strict On, then Object is rarely useful (one ends up casting anyway). Most of the time you're using a subclass of Object. So extensions for Object can still be useful.

Why can't vb use ... String.IsSomething?

An extension isn't a proper method at all: It's a function defined in a Module so that it isn't dependent on any class or structure definition or instance. An extension masquarades as a method, and is recognized only at compile-time. Extension methods are strictly a compiler feature. Late-binding works by querying an instance for the names and signatures of its methods and properties. The instance has no information about any pertaining extensions, hence such extensions never get found at run-time.

rskar-git avatar Aug 06 '20 00:08 rskar-git

I'd like to mention another issue here: Object implies boxing value types which are non-null anyway, so I'd suggest

<Extension>
Public Function IsSomething(Of T As Class)(o As T) As Boolean
  Return o IsNot Nothing
End Function

hartmair avatar Aug 06 '20 04:08 hartmair

@hartmair: Nothing in VB doesn’t mean null, it means default value. Having IsSomething mean “not the default value” can make sense. Might not want that class constraint.

jrmoreno1 avatar Aug 14 '20 02:08 jrmoreno1

Having IsSomething mean “not the default value” can make sense.

@jrmoreno1 The Is Operator compares (boxed) reference types only. You would need to compare for equality instead.

Console.WriteLine($"0.IsSomething: {IsSomething(0)}") ' 0.IsSomething: True
Console.WriteLine($"1.IsSomething: {IsSomething(1)}") ' 1.IsSomething: True

I am not fond of mixing these things, nevertheless, the best I came up with:

<Extension>
Public Function IsSomething(Of T As IEquatable(Of T))(o As T) As Boolean
  ' Return o <> Nothing ' Error BC30452 Operator '<>' is not defined for types 'T' and 'T'.
  Return o IsNot Nothing AndAlso Not o.Equals(Nothing)
End Function

hartmair avatar Aug 14 '20 04:08 hartmair