I’m clearing out some old mail and I came across a reference to a blog entry by Jon Skeet about extension methods that I saved a while back. He says:
One of the things I don’t like about the proposed extension methods is the way the compiler is made aware of them – on a namespace basis. “Using” directives are very common to add for any namespace used in a class, and quite often I wouldn’t want the extension methods within that namespace to be applied. I propose using
using static System.Query.Sequence;instead, in a way that is analogous to the static imports of Java 1.5 (except without importing single members or requiring the “.*” part. This would make it clearer what you were actually trying to do.
The interesting thing is, if you look at the document that we released on LINQ at the PDC, you’ll see that our design for extension methods incorporates this suggestion: extension methods in VB are brought into scope by directly importing the containing type, not the namespace.
Imports System.Query isn’t sufficient to get LINQ methods in scope; you have to say
Imports System.Query.Sequence. I agree with Jon that it’s clearer to do it this way, but that’s not the whole reason we did it. You see, the real problem is late binding.
Yes, late binding. We still support that, you know? And if you think for a minute about late binding and extension methods the way C# does them, you’ll quickly see that the two things don’t go together very well. When we go to late bind a member “foo” on an instance of a type “bar” today things are relatively simple — we gather all the members of “bar” with the name “foo” and then apply our regular binding rules to determine which, if any, of them fit the bill. All we need to know at run-time is the type of the instance we’re late binding on. With extension methods, though, this breaks down. Now we need to know not just the type of the instance, but also all of the extension methods in scope at the point of invocation. That’s because if “bar” doesn’t have a member “foo,” then in the early-bound case the compiler is going to go looking for extensions method. And the late-binder needs to do this, too!
If you look at how C# does extension methods, they go out and start looking in all the enclosing namespaces for extension methods, then look in all the imported namespaces for extension methods, etc. Replicating this at run-time would be difficult, at best — at every late-bound invocation point you would have to capture the complete binding context. And this binding context would change from method to method. What a nightmare! Our design is more friendly to late-binding. Our current design says that if “bar” doesn’t have a member “foo,” then we’ll look only at types whose members have been imported for extension methods. This collapses down the search space hugely and also means that the binding context is per-file (since we only allow file-level imports, unlike C#). While still a bit bulky, this seems much more manageable. Although we’ll see — although we’ve implemented early-bound extension methods, we haven’t gotten to the late bound stuff yet. <g>
Although we felt we had to do extension methods this way because of late-binding, we also believe there are some other advantages to the scheme. Jon lists one: it becomes much easier to be clear about the extensions you are using. You won’t import some useful namespace and then, whoops!, you just added a whole bunch of extension methods that you didn’t want. It’s also fairly congruent with the fact that VB (again, unlike C#) allows you to import a type directly, allowing access to its shared members without qualification.
Of course, now that I think of it, I’m not sure how this works with standard modules and their special binding rules. Hmmmm. I’m going to have to look at that…
On the other hand, you could make things a lot easier for everybody by just getting rid of late-binding. 🙂 I’m inclined to think that Late Binding plus _anything_ equals trouble.