Virtual Methods vs Extension Methods

I want to follow up my last post with a clear example showing what I expected to happen and why it didn't.  What is the output of the following program?

    class Program

    {

        static void Main(string[] args)

        {

            Bar bar = new Bar();

            Foo foo = bar;

 

            foo.Say();

            bar.Say();

        }

    }

 

    class Foo

    {

        public virtual void Say() { Console.Write("Foo"); }

    }

 

    class Bar : Foo

    {

        public override void Say() { Console.Write("Bar"); }

    }

Anyone who has a decent grasp of inheritance and polymorphism should have correctly guessed that the output of the program is BarBar.  The class Bar overrides the default behaviour of Foo and replaces it with something else.  What if we use extension methods?

    class Foo {}

    class Bar : Foo {}

 

    static class Extensions

    {

        public static void Say(this Foo foo) { Console.Write("Foo"); }

        public static void Say(this Bar bar) { Console.Write("Bar"); }

    }

Because extension methods are a new addition to the framework people are more likely to get this wrong, like me.  The output of this program is FooBar.  The important difference is that the extension method on Bar is not able to override and replace the default behaviour on Foo and is only used if you refer to the object as a Foo.

If you're designing an API and want to avoid confusion, watch out for this one.  As always there's workarounds, here's something you might try:

    static class Extensions

    {

        public static void Say(this Foo foo)

        {

            if(foo is Bar)

                ((Bar)foo).Say();

            else

                Console.Write("Foo");

        }

 

        public static void Say(this Bar bar) { Console.Write("Bar"); }

    }

This produces the same result as the original virtual method based program, BarBar.

While this might make you API a little more predictable it does so at the expense of the code.  Tying Foo to it's derived classes like this starts to break down the reasons for using the Foo abstraction in the first place and will make your code difficult to modify in future.  I would suggest these issues as the reason Microsoft didn't add a special case for IQueryable<T> in IEnumerable<T>.Where, but it turns out they have already added special cases for arrays and lists so who knows.

It's the little details like this that make great interview questions for those annoying candidates that seem to know everything...

22/01/2009 04:50 PM (UTC -08:00)

LINQ to NHibernate, IEnumerable vs IQueryable

I've been taking LINQ to NHibernate (Alpha just released) for a test drive in my latest project and so far I'm pleased enough that I think I will stick with it going forward.  This post is to point out a subtle behaviour that caught me unawares.  Consider the following example:

    session.Linq<Foo>()

        .WithId(123)

        .Single();

The first line is LINQ to NHibernate speak which simply returns an IQueryable<Foo> representing all available objects of type Foo.  The important thing here is that it is just a query, the database has not been visited yet.  The second line is my own extension method which should extend the query and filter the results by Id:

    public static IEnumerable<Foo> WithId(

        this IEnumerable<Foo> foos, int id)

    {

        return foos.Where(x => x.Id == id);

    }

The third line is a standard LINQ to SQL function which makes sure that there is only one item in the list and returns it for you.  This call is the first time that the code should touch the database and the result should be that only a single row is returned.  Unfortunately one of the tests I wrote today turned up something suspicious...  my extension method was being called 23 times and by coincidence there also happened to be 23 items in my database!

Since the code actually produced the correct results and this was really a performance issue I decided it would be a good time to check out Ayende's new NHibernate Profiler.  Overall I am quite impressed as it is easy to set up, shows me everything I expect and has a reasonably intuitive interface for a developer tool.  Compared to setting up a trace on the SQL server and trying to analyse the results, using the profiler was actually fun.

Now certain that my code was eagerly requesting every row in the table then throwing them away, I set out to figure out why.  Using the traditional poke it with the debugger approach, I discovered that changing my extension method to this resolved the problem:

    public static IQueryable<Foo> WithId(

        this IQueryable<Foo> foos, int id)

    {

        return foos.Where(x => x.Id == id);

    }

Why did I think IEnumerable<T> was a good choice?  Because IQueryable<T> inherits directly from it and I was trying to use the less restrictive interface.  Why does it not work like I expect?  Because there is a Where extension method for each of the interfaces and a quick look with Reflector shows that they do very, very different things.

Extension methods can't be overridden like virtual methods which means objects will act like the name you give them, not like what they really are.  This also means that it's not LINQ to NHibernate's fault at all, in fact you will likely run into this issue with LINQ to SQL as well.

If there's a lesson to be learnt from this story it's probably to be more careful of extension methods.  I can't really call my own misunderstandings bugs in the framework so instead I'm just going to make some snarky remarks about un-intuitive behaviour and sign off...

21/01/2009 03:35 AM (UTC -08:00)