Я думаю, что разумное использование методов расширения ставит интерфейсы в более уравновешенное положение с (абстрактными) базовыми классами.
Управление версиями. Одно преимущество базовых классов по сравнению с интерфейсами состоит в том, что вы можете легко добавлять новые виртуальные элементы в более поздней версии, тогда как добавление элементов в интерфейс нарушит средства реализации, созданные для старой версии библиотеки. Вместо этого необходимо создать новую версию интерфейса с новыми членами, и библиотеке придется обходить или ограничивать доступ к устаревшим объектам, только реализующим оригинальный интерфейс.
В качестве конкретного примера, первая версия библиотеки может определять интерфейс следующим образом:
public interface INode {
INode Root { get; }
List<INode> GetChildren( );
}
Как только библиотека выпущена, мы не можем изменять интерфейс, не нарушая текущих пользователей. Вместо этого в следующем выпуске нам потребуется определить новый интерфейс для добавления дополнительной функциональности:
public interface IChildNode : INode {
INode Parent { get; }
}
Однако, только пользователи новой библиотеки смогут реализовать новый интерфейс. Чтобы работать с унаследованным кодом, нам нужно адаптировать старую реализацию, которую метод расширения может хорошо обрабатывать:
public static class NodeExtensions {
public INode GetParent( this INode node ) {
// If the node implements the new interface, call it directly.
var childNode = node as IChildNode;
if( !object.ReferenceEquals( childNode, null ) )
return childNode.Parent;
// Otherwise, fall back on a default implementation.
return FindParent( node, node.Root );
}
}
Теперь все пользователи новой библиотеки могут одинаково относиться как к устаревшим, так и к современным реализациям.
Перегрузки. Другая область, где методы расширения могут быть полезны, - это предоставление перегрузок для методов интерфейса. У вас может быть метод с несколькими параметрами для управления его действием, из которых только первые один или два важны в 90% случае. Поскольку C # не позволяет устанавливать значения по умолчанию для параметров, пользователям либо приходится каждый раз вызывать полностью параметризованный метод, либо каждая реализация должна реализовывать тривиальные перегрузки для основного метода.
Вместо этого можно использовать методы расширения для обеспечения тривиальных реализаций перегрузки:
public interface ILongMethod {
public bool LongMethod( string s, double d, int i, object o, ... );
}
...
public static LongMethodExtensions {
public bool LongMethod( this ILongMethod lm, string s, double d ) {
lm.LongMethod( s, d, 0, null );
}
...
}
Обратите внимание, что оба этих случая написаны в терминах операций, предоставляемых интерфейсами, и включают тривиальные или хорошо известные реализации по умолчанию. Тем не менее, вы можете наследовать от класса только один раз, и целевое использование методов расширения может обеспечить ценный способ справиться с некоторыми тонкостями, предоставляемыми базовыми классами, которых нет в интерфейсах:)
Редактировать: Связанный пост Джо Даффи: Методы расширения как реализации метода интерфейса по умолчанию