Freigeben über


Extension Methods and ByRef targets

One of the seldom used, and often unknown, features of VB extension methods is that the argument of the extension method (the first parameter) can be passed by reference.  This gives extension methods the power to change the reference that was used to invoke the value! 

Obviously this can produce unexpected but often amusing behavior.  The following sample prints “False”.

     <Extension()> _
    Public Sub EnsureNotNull(ByRef str As String)
        If str Is Nothing Then
            str = String.Empty
        End If
    End Sub

    Sub Example(ByVal p1 As String)
        p1.EnsureNotNull()
        Console.WriteLine("{0}", p1 Is Nothing)
    End Sub

    Sub Main()
        Example(Nothing)
    End Sub

I think people will look at this example and either cringe, call it terrible code or get excited.  Personally I’m somewhere between cringing and getting excited (in fact I’m doing both).  But before people think I’ve gone off the deep end I don’t plan on checking this in any time soon (and yes I think it’s a bad idea).

Why?  The feature is fun to play with and can create some interesting samples but it can also just as easily lead to very bad and unanticipated behavior.  For instance what if instead of making sure something wasn’t Nothing, the code made the argument Nothing? 

 <Extension()> _
Sub EvilMethod(ByRef p1 As Object)
    p1 = Nothing
End Sub

Sub Example2()
    Dim s As New Student()
    s.PrintClass()
    s.EvilMethod()
    s.PrintName()   ' Causes a NullReferenceException
End Sub

The code runs and throws an exception.  A developer attaches a debugger, looks at the exception but immediately notes there are 2 other method calls just above.  How could they succeed but the last throw a NullReferenceException???  Certainly this may be a fun joke but in production code a fellow developer would not be amused. 

More importantly though the behavior when passing an extension method target by reference is flat out unexpected.  Who expects what appears to an instance method call to be able to modify “Me/this?”  Probably not too many people.  Coding is difficult enough without creating patterns that will blow away anyone who owns your code down the line.  Code should do it’s best to be clear and produce predictable results.

Still, an interesting feature to toy with.

Comments

  • Anonymous
    December 11, 2008
    A tad off-topic, and I don't know how it could be implemented in a static typed language. But I'd like a Smalltalk approach to null, where null is a real object. However, I agree with you that calling something that looks like an instance member of any kind shouldn't cause the instance itself to become null. But did you know there is a feature in .NET which behaves exactly this way? You can add a handler to an empty event, which evaluates to null, and now it's not null anymore. Then remove the same handler, and voila, it's null! Yes, I know. I have beaten that horse for a long time, but I refuse to believe it's dead :)

  • Anonymous
    December 11, 2008
    We did this in REALbasic. Few people took advantage of it, but the feature was terrific in one situation: when you want to define mutator-methods for an immutable class. For example: myString.Insert( startpos, "hello" ) Obviously this cannot actually modify the string instance, but if you define it as an extension method which accepts a byref string as its base parameter, it can simply assign a different string to the "myString" variable.

  • Anonymous
    December 11, 2008
    submit the full version on the tutorial

  • Anonymous
    December 11, 2008
    > You can add a handler to an empty event, which evaluates to null, and now it's not null anymore. Then remove the same handler, and voila, it's null! Why is it surprising? AddHandler is an operator, not a method call on the event.

  • Anonymous
    December 11, 2008
    @int19h Because something that can contain more than one handler looks like a collection. I don't know of any collections, except this one, where removing the last item also removes the instance.

  • Anonymous
    December 12, 2008
    Yes, something we noticed awhile back with a little spike was that C# cannot do this and VB can. Kind of scary if you program in both languages and forget about the little caveats of VB. :D

  • Anonymous
    December 12, 2008
    Oh, that's pretty neat. I've wanted to be able to do this in C# on occasion. But yeah, it is pretty scary.