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
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
One approach to solving this might be to write a concrete class – much like .NET’s
System.Collections.ObjectModel.Collection
Instead, let’s give INewList
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
It would be even niftier if you could add these methods straight onto INewList
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
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
This is still not perfect. If you pass the MyList
For now, let’s pretend that’s just the Clear method:
public interface IFasterList<T> : INewList<T>
{
void Clear();
}
Of course, MyList
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.