RSS 2.0

Personal Info:

Joe Send mail to the author(s) is a lead architect on an OS incubation project at Microsoft, and was the architect for Parallel Extensions to .NET. He is an author and frequent speaker.

Blogroll:
Other
News
 C|Net
 Kuro5hin
 The Register
Technology
 <?xmlhack?>
 Daily WTF
 DevX
 Hacknot
 Java Today
 Microsoft Top 10 Downloads
 MSDN
 MSDN: "Longhorn"
 MSDN: XML Developer Center
 Slashdot
 Techdirt
 theserverside.com
 W3C
 Web Pages That Suck
 XML Cover Pages
 XML Journal
 xml.com
Technology Blogs
 Aaron Skonnard [PluralSight]
 Adam Bosworth [Google]
 Andy Rich [MS/C++]
 Arpan Desai [MS/XML]
 BCL Team [MS]
 Bill Clementson [Lisp]
 Bill de hÓra
 Bruce Eckel [J]
 Bruce Tate [J]
 Casey Chestnut
 Cedric Beust [Google]
 Chris Anderson [MS/Avalon]
 Chris Lyon [MS]
 Christian Weyer
 Clemens Vasters [newtelligence]
 Craig Andera [PluralSight]
 Dan Sugalski [Parrot]
 Daniel Cazzulino
 Dave Chappel
 Dave Roberts [Lisp]
 Dave Thomas [PragProg]
 Dave Winer
 Dion Almaer [J]
 Don Demsak
 Doug Purdy [MS/Indigo]
 Drew Marsh
 Eric Gunnerson [MS]
 Eric Rudder [MS]
 Eric Sink
 Fritz Onion [PluaralSight]
 Gavin King [J/Hibernate]
 Grady Booch [IBM]
 Hervey Wilson [MS/Indigo]
 Hillel Cooperman [MS/Shell]
 Howard Lewis Ship [J/Apache]
 Ingo Rammer [PluralSight]
 James Gosling [J/Sun]
 James Strachan [J/Groovy]
 Jason Matusow [MS/OSS]
 Jeffrey Schlimmer [MS/Indigo]
 Joe Beda [Google]
 Joel Spoelsky
 Jon Udell
 Josh Ledgard [MS/Evang]
 Joshua Allen [MS]
 Lambda
 Larry Osterman [MS]
 Maoni Stephens [MS/CLR]
 Mark Fussell [MS/XML]
 Martin Fowler
 Martin Gudgin [MS/Indigo]
 Me
 Michael Howard [MS]
 Miguel de Icaza [Mono]
 Mike Clark
 Omri Gazitt [MS/Indigo]
 Pat Helland [MS/PAG]
 Pinku Surana
 Raymond Chen [MS]
 Rich Lander [MS/CLR]
 Rob Howard
 Rob Relyea [MS/Avalon]
 Robert Cringely
 S. Somasegar [MS/DevDiv]
 Sam Gentile
 Scoble [MS/Evang]
 Scott Guthrie [MS/WebNet]
 Scott Hanselman
 Sean McGrath [J]
 Simon Fell
 Stanley Lippman [MS/C++]
 Steve Maine
 Steve Swartz [MS/Indigo]
 Steve Vinoski
 Steven Clarke [MS/Usability]
 Stuart Halloway
 Ted Leung
 Ted Neward [DM]
 Tim Bray [Sun]
 Tim Ewald [Mindreef]
 Tim O'Reilly
 Werner Vogels [Amazon]
 Wintellect
 Yasser Shohoud [MS/Indigo]
Top 20
 Brad Abrams [MS/CLR]
 Chris Brumme [MS/CLR]
 Chris Sells [MS/Ultra]
 Cyrus Najmabadi [MS/C#]
 Dominic Cooney [MS/XAF]
 Don Box [MS/Ultra]
 Don Syme [MS/R]
 Guido van Rossum [Python]
 Herb Sutter [MS/C++]
 Ian Griffiths
 Jason Zander [MS/CLR]
 Jim Hugunin [MS/CLR]
 Joel Pobar [MS/CLR]
 Krzysztof Cwalina [MS/CLR]
 Patrick Logan
 Paul Graham
 Rico Mariani [MS/CLR]
 Rory Blyth [MS/DN]
 Sam Ruby
 Wesner Moise
VC/Business Blogs
 Ed Sim
 Fred Wilson
 Jonathan Schwartz [J/Sun]
 Lawrence Lessig [Stanford]
 Mark Cuban
 Michael Hyatt
 Pierre Omidyar
 Ross Mayfield
 VentureBlog
 Weekly Read
Wine, Food & Tea
 The Silk Road of Wine
 Vinography: a wine blog
 Wine Whys

Disclaimer:
The content of this site are my own personal opinions and do not represent my employer's view in anyway.

© 2010, Joe Duffy

 
 Tuesday, February 09, 2010

One of my comments in the 2nd edition of the .NET Framework design guidelines (on page 164) was that you can use extension methods as a way of getting default implementations for interface methods.  We've actually begun using these techniques here on my team.  To illustrate this trick, let's rewind the clock and imagine we were designing new collections APIs from day one.

Let's say we gave the core interfaces the most general methods possible.  These may neither be the most user friendly overloads nor the ones that most people use all the time.  They would, however, be those from which all the other convenience methods could be implemented.  An INewList<T> interface that was designed with these principles in mind may look like this:

public interface INewList<T> : IEnumerable<T>

{

    int Count { get; }

    T this[int index] { get; set; }

 

    void InsertAt(int index, T item);

    void RemoveAt(int index);

}

This interface is missing all the nice convenience methods you will find on .NET's IList<T>, like Add, Clear, Contains, CopyTo, IndexOf, and Remove.  So it's not really as nice to use.  You can't write an API that takes in an INewList<T> and performs an Add against it, for example, like you can with IList<T>.

One approach to solving this might be to write a concrete class -- much like .NET's System.Collections.ObjectModel.Collection<T> -- that provides concrete implementations of all of these methods, and then other lists can simply subclass that.  But we can do better.

Instead, let's give INewList<T> default implementations of all of these methods.  How do we do this?  That's right: with extension methods.  Voila!

public static class NewListExtensions

{

    public static void Add<T>(this INewList<T> lst, T item)

    {

        lst.InsertAt(lst.Count, item);

    }

 

    public static void Clear<T>(this INewList<T> lst)

    {

        int count;

        while ((count = lst.Count) > 0) {

            lst.RemoveAt(count - 1);

        }

    }

 

    public static bool Contains<T>(this INewList<T> lst, T item)

    {

        return lst.IndexOf(item) != -1;

    }

 

    public static void CopyTo<T>(this INewList<T> lst, T[] array, int arrayIndex)

    {

        for (int i = 0; i < lst.Count; i++) {

            array[arrayIndex + i] = lst[i];

        }

    }

 

    public static int IndexOf<T>(this INewList<T> lst, T item)

    {

        var eq = EqualityComparer<T>.Default;

        for (int i = 0; i < lst.Count; i++) {

            if (eq.Equals(item, lst[i])) {

                return i;

            }

        }

        return -1;

    }

 

    public static bool Remove<T>(this INewList<T> lst, T item)

    {

        int index = lst.IndexOf(item);

        if (index == -1) {

            return false;

        }

 

        lst.RemoveAt(index);

        return true;

    }

}

Well isn't that neat.  We've now given any INewList<T> implementations all these common methods without dirtying their class hierarchies, built atop a tiny core of extensibility.  This is much like .NET's Collection<T> which exposes the core as abstract methods.  Indeed, we can go even further.  Any convenience overloads, like the multitude of CopyTos on List<T> in .NET, can be given to all INewList<T>'s also.  And yet implementing INewList<T> remains as braindead simple as it was before: two properties and two methods.  In fact, it's simpler than doing a more feature-rich IList<T>, because the convenience methods come for free.

It would be even niftier if you could add these methods straight onto INewList<T>, and have the C# compiler emit the extension methods silently for you.  In other words:

public interface INewList<T> : IEnumerable<T>

{

    ... interface methods (as above) ...

 

    void Add(T item)

    {

        InsertAt(Count, item);

    }

 

    void Clear()

    {

        int count;

        while ((count = Count) > 0) {

            RemoveAt(count - 1);

        }

    }

 

    ... and so on ...
}

Although this would just be sugar for the NewListExtensions class shown earlier, it sure saves some typing and makes it the pattern more apparent and first class.

Though cool, this whole idea is certainly not perfect.

For one, there are no extension properties.  So you can't use this trick for properties.

But the more obvious and severe downside to this approach that these methods are not specialized for the given concrete type.  For example, the Clear method is potentially far less efficient than a hand-rolled List<T>, because it does O(N) RemoveAts rather than a single O(1) fixup of the count.

Recall now that the compiler binds more tightly to instance methods than extension methods.  So we could implement our own little list class with a faster Clear method if we'd like:

class MyList : INewList<T>

{

    ... the two properties and two methods from INewList<T> ...

 

    public void Clear()

    {

         .. efficient! ...
    }

}

Now when someone calls Clear on a MyList<T> directly, the compiler will bind to the efficient Clear.

This is still not perfect.  If you pass the MyList<T> to an API that takes in an INewList<T>, any calls to Clear will fall back to the extension method.  Extension methods are not virtual in any way.  You can try to simulate virtual dispatch, but it gets messy quick.  For example, say we defined an IFasterList<T> that includes all those convenience methods that lists frequently want to make faster; we can then do a typecheck plus virtual dispatch in the extension method.

For now, let's pretend that's just the Clear method:

public interface IFasterList<T> : INewList<T>

{

    void Clear();

}

Of course, MyList<T> above would now implement IFasterList<T>.  Invocations through IFasterList<T> will automatically bind to the faster variant; but if objects that implement IFasterList<T> get passed around as IList<T>s, you lose this ability.  So the Clear extension method can now do a typecheck:

    public static void Clear<T>(this INewList<T> lst)

    {

        IFasterList<T> fstLst = lst as IFasterList<T>;

        if (fstLst != null) {

            fstLst.Clear();

            return;

        }

 

        int count;

        while ((count = lst.Count) > 0) {

            lst.RemoveAt(count - 1);

        }

    }

This works but is obviously a tedious and hard-to-maintain solution.  It would be neat if someday C# figured out a way to "magically" reconcile virtual dispatch and extension methods.  I don't know if there is a clever solution out there.  I am skeptical.  Nevertheless, despite this flaw, the above techniques are certainly thought provoking and interesting enough to play around with and consider for your own projects.  And at the very least, it's fun.  Enjoy.

2/9/2010 6:21:49 PM (Pacific Standard Time, UTC-08:00)  #    Comments [6]

 

Recent Entries:

Search:

Browse by Date:
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Browse by Category:

Notables:

Currently Up To:

Reading...

Listening...

Watching...