Monday, April 24, 2006

Extension Interfaces

In previous posts, I outlined the versioning problem with extension methods, and proposed a solution. The solution associates each group of extension methods with an interface – an interface which lists all the methods which appear to be "added" to objects.

For example (my suggested solution is in bold):

public class Thing
{
//...
}

public interface IFooable
{
void Foo();
void Bar();
}

public static class FooExtender: IFooable
{
public static void Foo(this Thing t)
{
//...
}

public static void Bar(this Thing t)
{
//...
}
}

I proposed this solution to solve versioning problems, but it has a convenient side effect: "extension interfaces". Instead of just making it look like an object implements additional methods, "extension interfaces" would make it look like the object implements an additional interface.

Given the above code, we can call the extension methods Foo and Bar as if they exist on type Thing. So why can’t we cast Thing to IFooable, like this:

Thing t = new Thing();
IFooable f = t;           //implicit cast
//or
IFooable f = (IFooable)t; //explicit cast
All we’d need is a little compiler magic. The compiler magic would have to generate a (hidden) class like this:

class FooThingAdapter: IFooable
{
private Thing target;

public FooThingAdapter(Thing t)
{
target = t;
}

public void Foo()
{
FooExtender.Foo(target);
}

public void Bar()
{
FooExtender.Bar(target);
}
}
Then, when we write this

IFooable f = (IFooable)t;

The compiler would compile it as if we had written:

IFooable f = new FooThingAdapter(t);

This approach is not without its problems. Jon Skeet highlighted the main one here: we actually end up with two objects: the Thing instance and an instance of FooThingAdapter. As Jon says that seems (and probably is) fundamentally wrong.

A possible solution to Jon's problem would be to leverage the existing support for TransparentProxy. With TransparentProxies we already have a situation where there are two objects pretending to be one, and there are special branches in the CLR’s logic to take care of it. So, solving the problem with extension interfaces is not out of the question. The difficulty would be in determining whether the existing CLR logic paths could be leveraged, unchanged, to support extension interfaces. Murphy’s Law suggests not, unfortunately.

Might Microsoft add support for extension interfaces? As much as I like the idea, I have to admit that, if it was up to me, I wouldn’t. I would just solve the versioning problem – which must be addressed – and consider introducing extension methods at a later date if there was enough demand for them.

If the versioning problem is solved with interfaces, adding extension interfaces later will be easy.





By the way, compared to extension methods, extension interfaces are a much better way to simulate mixins. Why? Because a mixin implies a contract for operation, as does an interface (but not an extension method).

In the code above we can act like Thing "is-a" IFooable, something we cannot do with extension methods alone.

(Update: see followup here)

2 Comments:

Blogger Kirill Osenkov said...

An excellent post John. I'll make sure this feedback reaches the C# team.

Fri Nov 16, 02:18:00 PM PST  
Blogger John Rusk said...

By the way, here's some code for existing versions of C#, which generates the "wrapper" class on the fly. It still doesn't address the issue of the wrapper being a different object from what it wraps. http://www.codeproject.com/csharp/autocaster.asp

Sat Nov 17, 12:30:00 PM PST  

Links to this post:

Create a Link

<< Home