Monday, January 14, 2008

Resolving Extension Methods

Paul Stovell (author of SyncLINQ) has pointed out another issue with Extension Methods. As he writes:

The problem is the methods are *resolved* at compile time.

Suppose you had this:

void DoStuff(this IFoo foo);

class FooFactory
IFoo Create()

If your factory returns an object that provides its own instance implementation of DoStuff, it WILL NOT be invoked - the extension method will instead. This breaks many of the rules around polymorphism and good OO in general...

In other words, the compiler favours instance methods over extension methods, but only if it can "see" the instance method at compile time.

The reason is simple: currently, resolving an extension method is just a compiler trick, so compile time is the only time when it CAN be done.

But Paul's comment got me thinking. It occured to me that, if my idea about using interfaces was implemented, then it would be possible to properly solve the problem he described. It would work like this:

First, to recap: I suggest that there should be a mapping between extension methods and interfaces. Think of a set of extension methods as an "extension contract". You can make calls on the methods in that contact either by calling intstance methods (on objects which implement the interface) or by extension methods (for objects which do not implement the interface). This is to solve the versioning problem, but it can also be used to solve the problem Paul described.

How? I'll try to illustrate with an example. Imagine that our "extension contract" is called IExtensionContract. It contains two methods, Foo and Bar. We can call Foo and Bar on objects which don't support IExtensionContract, simply by calling them as extesion methods. But, when we call Foo and Bar on objects which DO support IExtensionContract, we want to call the version that is on the object even if we can't tell that the object supports them at compile time. (That's the heart of the issue, right Paul?)

That could be done if the complier worked like this:

Given this code to compile

ISomething s = ...

The compiler should compile it as if the programmer had written
ISomething = ...
IExtensionContract c = s as IExtensionContract;
if(c == null)
StaticExtender.Foo(s);  // invoke the extension method
c.Foo();                // invoke the instance method

This would be possible, if the interface-based versioning solution was adopted, because that solution introduces just enough runtime information about the extension methods - namely the "contract" to which the methods belong. Contrast that to the current situation, where there is no runtime information about extension methods.

Here is the user-authored (i.e. not compiler-generated) code for the example above:
public static class StaticExtender : IExtensionContract 
// "inheriting" from interface here is my suggested addition to the language
public static Foo(this ISomething s){...}

public static Bar(this ISomething s){...}

public interface IExtensionContract
void Foo();
void Bar();
Finally, all the suggested logic I have blogged about previously still applies. The compiler still needs to decide, at compile time, whether the call is to a method in the extension contract or to a like-named (but otherwise unrelated) instance method. All that logic still applies as I described it here.


Blogger Jason said...

Thanks John!

Tue Jan 15, 02:17:00 PM PST  
Blogger David Nelson said...

Although I agree that it would be nice if extension methods could be used in a polymorphic fashion (so Count() on a List wouldn't have to enumerate the list, for example), it should be pointed out that this is no different from having a sealed (i.e. non-virtual) method on a base class. A call to a non-virtual method is bound at compile time, and returning an instance of a derived class which happens to have another method with the same signature does not mean that method will be called. This is actually exactly in keeping with OO principles; you should only be able to override methods that the class designer has declared to be overridable (and therefore presumably has accounted for the possibility), and that the caller knows could be overridden.

Mon Jul 07, 09:33:00 AM PDT  

Links to this post:

Create a Link

<< Home