Extension methods and late binding (Extension Methods Part 4)

This is the fourth installment in my series of posts about extension methods. You can find links to the rest of the series here.

Today I’m going to talk about extension methods and late binding. Essentially there isn’t much to say about it, other than the fact that we don’t support late bound execution of extension methods. For the most part this isn’t a big deal as one of the primary benefits of extension methods is its interaction with intellisence which doesn’t work in late bound scenarios anyways. Unfortunately, however, there is one big side effect of this decision that you need to be aware of when authoring extension methods. 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. Too see why, consider the following example:

Class C1 
   
Public Sub Method1()
       
Console.WriteLine(“Running c1.Method1”)
   
End Sub
End Class

Class C2
   
Public Sub Method1()
       
Console.WriteLine(“Running c2.Method1”)
   
End Sub
End Class

Module M1
   
Public Sub Main()
       
Dim x As Object = Nothing 
        
If (SomeCondition()) Then
           
x = New C1
       
Else
           
x = New C2
       
End If
       
x.Method1() 
    End Sub
End
Module

Here we have a program which uses late binding in order to invoke the method “Method1” on an object that is either of type “c1” or of type “c2”. It does this because the static type of the variable “x” is declared as object, which causes the compiler to resolve any calls to “unknown” methods as late bound calls that will be resolved at runtime based on the dynamic type of the object stored in “x”. Extension methods, however, are always fully resolved at compile time. As a result, if we were to add an extension method defined on Object, like so:

<Extension()> _
Public Sub Method1(ByVal x As Object)
   
Console.WriteLine(“Running Extension method m1.Method1”)
End Sub

Then the act of simply importing the namespace containing the method would cause the formerly late bound method call to Method1 to be transformed into an early bound call to the extension method. This would not be good, as it would silently change the meaning of the program, which would have the potential of making extension methods very, very dangerous. In fact, we explicitly disallow this in early bound scenarios by always having instance methods defined on a type shadow any extension methods defined on it with the same name. This enables you to import extension methods into your existing code without having to worry about things blowing up in your face.

In the case of late bound calls, however, we can’t use the same shadowing semantics that we do with early bound method calls because we don’t know the actual type of the object we will be calling the method on, so we don’t have a list of methods we can check against at compile time. As a result, the only way we could prevent extension methods from completely breaking existing late bound code was to prevent them from being used on anything typed as object.

This has the effect of slightly changing the meaning of an extension method defined on object. Consider the following non-object extension method:

<Extension()> _
Public Sub Method2(ByVal x As System.Windows.Forms.Control)
End Sub

Here the declaration of “Method2” implies that the method is an extension method applicable to the type System.Windows.Froms.Control and any class that derives from it, such as TextBox or DataGridView. In the case of “Method1”, however, the method declaration implies that it is applicable to “all types except object”. This is a little unfortunate, as it is an inconsistency in the language, but we felt it was much better in this case to be safe than it was to be consistent.

That’s all I have for today. In my next post I’ll talk about extension methods and generics.