23

From previous experience I had been under the impression that it's perfectly legal (though perhaps not advisable) to call extension methods on a null instance. So in C#, this code compiles and runs:

// code in static class
static bool IsNull(this object obj) {
    return obj == null;
}

// code elsewhere
object x = null;
bool exists = !x.IsNull();

However, I was just putting together a little suite of example code for the other members of my development team (we just upgraded to .NET 3.5 and I've been assigned the task of getting the team up to speed on some of the new features available to us), and I wrote what I thought was the VB.NET equivalent of the above code, only to discover that it actually throws a NullReferenceException. The code I wrote was this:

' code in module '
<Extension()> _
Function IsNull(ByVal obj As Object) As Boolean
    Return obj Is Nothing
End Function

' code elsewhere '
Dim exampleObject As Object = Nothing
Dim exists As Boolean = Not exampleObject.IsNull()

The debugger stops right there, as if I'd called an instance method. Am I doing something wrong (e.g., is there some subtle difference in the way I defined the extension method between C# and VB.NET)? Is it actually not legal to call an extension method on a null instance in VB.NET, though it's legal in C#? (I would have thought this was a .NET thing as opposed to a language-specific thing, but perhaps I was wrong.)

Can anybody explain this one to me?

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • 1
    Is the IsNull method just an example or are do you actually want to use x.IsNull() instead of 'x Is Nothing' or 'x == null'? – jrummell Mar 08 '10 at 16:43
  • @jrummell: It's just an example. As I mentioned, I was writing some example code to illustrate how extension methods work for some of my team members. I intended for this method to include a comment along the lines of "You can actually do this with extension methods, although I wouldn't recommend it" -- just to show how under the hood an extension method is really just a static (Shared) method. But then I discovered that I *couldn't* do it in VB, which surprised me. – Dan Tao Mar 08 '10 at 16:48
  • 1
    As has been found, this is due to support for late binding...like you (Dan) I didn't realise VB had such support for late binding, I'm guessing it's a compatibility thing with VB6. As a slight aside, if you're looking into setting out some standards/training for other developers you may wish to mandate Option Strict as I feel it helps pick up a lot potential problems. YMMV of course. – Gareth Wilson Mar 08 '10 at 17:21

4 Answers4

14

You cannot extend the object type in VB.NET.

Mainly, we do not allow extension methods to be called off of any expression that is statically typed as "Object". This was necessary to prevent any existing late bound code you may have written from being broken by extension methods.

Reference:

  • Ah, that does make some sense...though I'm continually annoyed but the differences/inconsistencies between C#/VB. The key phrase to take from those linked articles is; "when you specify object, it means "take anything *other* than an object." Awesome. 8-) – Gareth Wilson Mar 08 '10 at 16:48
  • @roygbiv: As someone who doesn't code too often in VB.NET, I honestly had no idea late bound method calls such as those discussed in the link were even possible. (I would have guessed that VB.NET's late binding capabilities were significantly smaller.) So thanks for teaching me something new, and for being the first to hit on what I think is the "true" answer here. – Dan Tao Mar 08 '10 at 17:10
  • 1
    @DanTao: I wish VB.NET would allow one to declare a variable as `System.Object` and have it behave simply as a class reference whose base type happens to be `System.Object`, without the special handling associated with `Object`. – supercat Dec 06 '13 at 20:09
8

Update:

The answer below seems to be specific to the case the System.Object is extended. When extending other classes there is no NullReferenceException in VB.

This behavior is by design for the reason stated in this Connect issue:

VB allows you to call extension methods defined on Object, but only if the variable is not statically typed as Object.

The reason is VB also supports late-binding, and if we bind to an extension method when you make a call off a variable declared as Object, then it's ambigous whether or not you're trying to call an extension method or a different late-bound method with the same name.

Theoretically we could allow this with Strict On, but one of the principles of Option Strict is that it should not change the semantics of your code. If this was allowed then changing your Option Strict setting could cause a silent rebinding to a different method, resulting in totally different runtime behavior.

Example:

Imports System.Runtime.CompilerServices

Module Extensions
    <Extension()> _
    Public Function IsNull(ByVal obj As Object) As Boolean
        Return obj Is Nothing
    End Function

    <Extension()> _
    Public Function IsNull(ByVal obj As A) As Boolean
        Return obj Is Nothing
    End Function

    <Extension()> _
    Public Function IsNull(ByVal obj As String) As Boolean
        Return obj Is Nothing
    End Function

End Module

Class A
End Class

Module Module1

    Sub Main()
        ' works
        Dim someString As String = Nothing
        Dim isStringNull As Boolean = someString.IsNull()

        ' works
        Dim someA As A = Nothing
        Dim isANull As Boolean = someA.IsNull()

        Dim someObject As Object = Nothing
        ' throws NullReferenceException
        'Dim someObjectIsNull As Boolean = someObject.IsNull()

        Dim anotherObject As Object = New Object
        ' throws MissingMemberException
        Dim anotherObjectIsNull As Boolean = anotherObject.IsNull()
    End Sub

End Module

In fact, the VB compiler creates a late binding call in case your variable is statically typed as Object:

.locals init ([0] object exampleObject, [1] bool exists)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  ldnull
  IL_0004:  ldstr      "IsNull"
  IL_0009:  ldc.i4.0
  IL_000a:  newarr     [mscorlib]System.Object
  IL_000f:  ldnull
  IL_0010:  ldnull
  IL_0011:  ldnull
  IL_0012:  call       
     object [Microsoft.VisualBasic]Microsoft.VisualBasic.
       CompilerServices.NewLateBinding::LateGet(
        object,
        class [mscorlib]System.Type,
        string,
        object[],
        string[],
        class [mscorlib]System.Type[],
        bool[])
  IL_0017:  call       object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Operators::NotObject(object)
  IL_001c:  call       bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(object)
  IL_0021:  stloc.1
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • I wonder - does vb call extension methods the C# way if they are in a library written in C#? – Joel Coehoorn Mar 08 '10 at 16:35
  • and @Joel: I just tested this -- put the extension method in a C# library, called it from VB. The VB code still threw a `NullReferenceException`. It seems that Gareth is onto something: this issue only exists when the extension method is applied to `System.Object` as opposed to something more specific. – Dan Tao Mar 08 '10 at 16:45
  • Indeed, it's the type of the object you're trying to call the extension method with, not the type the function expects. That is, the IsNull( ByVal Obj as Object) works with Dim A As String ... A.IsNull just fine, even where A is Nothing. roygbiv seems to have the relevant links as to why. – Gareth Wilson Mar 08 '10 at 16:54
  • @Joel Coehoorn: My initial answer was incorrect, VB does call extension methods the same way, but it simply doesn't support extension methods in this specific case (where the compiler creates a late-binding call instead). – Dirk Vollmar Mar 08 '10 at 17:03
  • Great, informative answer. I had to give it to roygbiv, though, as I think he got to the fundamental issue (that this was a design decision specific to the `System.Object` type) first. – Dan Tao Mar 08 '10 at 17:12
  • It's too bad VB.NET didn't define a `Variant` type which would behave as `Object` does now, have `Object` behave as `System.Object` without any special behavior, and have `Variant` (rather than `Object`) be the default type in `Option Strict Off`. That would have allowed better VB6 compatibility than `Option Strict Off` presently provides, with less quirkiness. – supercat Dec 06 '13 at 20:15
3

It seems to be something quirky with Object, possibly a bug in VB or a limitation in the compiler, might need his Holiness Jon Skeet to comment!

Basically it appears to be trying to late bind the IsNull call at runtime, rather than call the extension method, which causes the NullReferenceException. If you turn on Option Strict you'll see this at design time with the red squiggles.

Changing exampleObject to something other than Object itself will allow your sample code to work, even if the value of said type is Nothing.

Gareth Wilson
  • 855
  • 6
  • 5
  • +1. The extension method allows Nothing to be used only if the type is not Object. So, it appears to be linked to the nature of Object at runtime. – Laurent Etiemble Mar 08 '10 at 16:41
0

It seems the problem is that the object is null. Also, if you try something like the following, you'll get an exception saying that String does not have an extension method called IsNull

Dim exampleObject As Object = "Test"
Dim text As String = exampleObject.IsNull()

I think whatever value you are putting into exampleObject, the framework knows what type it is. I'd personally avoid extensions methods on the Object class, not just in VB but also in CSharp

Damiano Fusco
  • 215
  • 3
  • 6