vblang
vblang copied to clipboard
[Bug] Can't call an extension method for an Object
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.
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?)
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!
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.
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: 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.
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