Thursday, August 16, 2012

Return of the Extension Method

Recently a small team of us branched the code to start work on a new feature for a future release while work continued in the mainline for the current upcoming release. As I was working on our new feature I realized that I wanted to extend some common core classes with new functionality. The classes have their own hierarchy tree and normally I would have created a base method for the shared functionality and overridden it for some of the subclasses. However, since I knew that other developers were working on these same classes I looked for ways that I could minimize merging code later. I've written before about extension methods (here and here) and thought this might be a good time to use them.

I quickly ran into an issue where extension methods only work with the same class that the object is cast to. Say for example you have a Shape superclass and Circle subclass. Normally we would pass around Circle objects as Shapes and rely on virtual or abstract methods to provide a common interface. With extension methods there is no inheritance so you have to either explicitly cast our objects or do some type of switching in a common method.

I've created a simple program in LINQPad to demonstrate this behavior:

void Main() {
    // Create a new Circle but cast it as a Shape
    Shape obj = new Circle();
    // Call the virtual method
    Console.WriteLine(obj.WhoAmI());
    // Call the extension method
    Console.WriteLine(obj.WhoAmIEx());
}

public class Shape {
    public virtual string WhoAmI() {
        return "Virtual Shape";
    }
}
public class Circle : Shape {
    public override string WhoAmI() {
        return "Overriden Circle";
    }
}

public static class Extensions {
    public static string WhoAmIEx(this Shape obj) {
        return "Shape Extension";
    }
    public static string WhoAmIEx(this Circle obj) {
        return "Circle Extension";
    }
}

Running this produces:
 Overriden Circle
Shape Extension

Took me a little while to figure out this behavior (although I should have realized it from the start). I still ended up using an extension method to add one new call but after we merge the code I think I will change that back to a virtual method.