Extension Methods and inheritance

Remember that extension methods are static methods !

Since their introduction in the Framework, extension methods have been a very simple mean to add functionality into class or on top of interfaces. Without extension methods, there would be no LINQ-style syntax allowing us to chain easily calls such as :

words
    .Where(w => w.StartsWith("M"))
    .Select(w => w.ToUpper());

The key to this pattern has been described many times and is built on the principle that the extension methods “Where” and “Select” take an IEnumerable<T> (T is string here) as their first input parameter, and return an other IEnumerable<T>. This way, the next call in the chain will use the previous output as its input.

But when implementing an using extension methods, when it comes to inheritance you have to remember that these methods are static ! What I mean here is that the method resolution will occur at compile time and not at runtime. This leads to extension methods having quite the same behaviour as instance methods written using the “new” keyword (as opposed to “overrides”).

Let me explain that with an example. I first define here two interfaces, the second being a specialization of the first.

public interface ISoundMaker
{
    string MakeSound(bool loud);
}

public interface ITalker : ISoundMaker
{
    string[] MakeSentence(int wordCount);
}

I then define a class Baby which implements the interface ITalker :

public class Baby : ITalker
{
    /* Hidden implementation */

    public string MakeSound(bool loud)
    {
        string sound = InfiniteSoundsSequence.First();
        return loud ? sound.ToUpper() : sound.ToLower();
    }

    public string[] MakeSentence(int wordCount)
    {
        return InfiniteWordsSequence.Take(wordCount).ToArray();
    }
}

In the hidden implementation, two (statefull) infinite sequences are defined, in order to yield sounds or words as needed. These sequences are independent and yield different values.

I now define two “Sing” extension methods, respectively on top of the ISoundMaker and ITalker interfaces :

public static class SoundTalkExtensions
{
    public static string Sing(this ISoundMaker soundMaker,
                                int wordCount)
    {
        IEnumerable<string> words = Enumerable
            .Range(0, wordCount)
            .Select(i => soundMaker.MakeSound(i % 2 == 0));

        return String.Join(" ", words);
    }

    public static string Sing(this ITalker talker,
                                int wordCount)
    {
        IEnumerable<string> words = talker
            .MakeSentence(wordCount)
            .Select((w, i) => i % 2 == 0 ? w.ToUpper() : w);

        return String.Join(" ", words);
    }
}

Basically, the two methods do the same work : they take either sounds or words and alternate lowercase and uppercase items.

And now if we write the following unit test…

ISoundMaker soundMaker = new Baby();
ITalker talker = new Baby();

string soundSong = soundMaker.Sing(15);
string talkSong = talker.Sing(15);

?? Assert.AreEqual(soundSong, talkSong) ??

It fails !

The two instances of the Baby class are identical, but the extension methods calls are resolved based on the type of the declared variables into which theses instances are stored. One of these instances is a ISoundMaker, and the other one is a ITalker.

Even if the Baby class implements ITalker, when an instance is stored in a variable of type ISoundMaker, the extension method will be the ISoundMaker’s one.

So here for instance the variables soundSong and talkSong have the following values :

soundSong = “MAMA dada AAA gaga BUBU mamma DADA aaa GAGA bubu MAMA dada AAA gaga BUBU

talkSong = “MAMA life HAD just BEGUN But NOW I’ve GONE and THROWN it ALL away MAMA

In the next post, we will see why you must understand that clearly before using LINQ Providers (LINQ to something) or PLINQ (Parallel LINQ to objects).

This entry was posted in LINQ and tagged , , . Bookmark the permalink.

Comments are closed.