edit4:, так как это, похоже, больше перешло в дискуссию, чем конкретный вопрос.
В программировании на C++, как правило, считается хорошей практикой "предпочитать не-членские функции, отличные от друга" вместо методов экземпляра. Это было рекомендовано Скоттом Мейерсом в этой классической статье доктора Доббса, и повторил Херб Саттер и Андрей Александреску в Стандарты кодирования С++ (пункт 44); общий аргумент состоит в том, что если функция может выполнять свою работу исключительно, полагаясь на открытый интерфейс, открытый классом, она фактически увеличивает инкапсуляцию, чтобы она была внешней. Хотя это смущает "упаковку" класса в некоторой степени, выгоды, как правило, считаются заслуживающими внимания.
Теперь, с тех пор, как я начал программировать на С#, у меня было ощущение, что здесь является окончательным выражением концепции, которую они пытаются достичь с помощью "не-членов", не связанные друг с другом функции, которые являются частью интерфейса класса ". С# добавляет два важных компонента в микс - первый из них интерфейсы, а второй методы расширения:
- Интерфейсы позволяют классу формально указать свой публичный контракт, методы и свойства, которые они раскрывают миру.
- Любой другой класс может выбрать один и тот же интерфейс и выполнить тот же контракт.
- Методы расширения могут быть определены на интерфейсе, предоставляя любую функциональность, которая может быть реализована через интерфейс для всех исполнителей автоматически.
- И лучше всего, из-за синтаксиса "синтаксиса экземпляра" и поддержки IDE, их можно вызывать так же, как и любой другой метод экземпляра, устраняя когнитивные издержки!
Таким образом, вы получаете преимущества инкапсуляции функций "не-член, не друг" с удобством членов. Кажется, лучшее из обоих миров для меня; сама библиотека .NET предоставляет яркий пример в LINQ. Однако, везде, где я смотрю, я вижу, что люди предупреждают о чрезмерном использовании метода расширения; даже сама страница MSDN заявляет:
В целом мы рекомендуем использовать методы расширения экономно и только тогда, когда вам нужно.
( edit: Даже в текущей библиотеке .NET я вижу места, где было бы полезно иметь расширения вместо методов экземпляра - например, все служебные функции List<T>
(Sort
, BinarySearch
, FindIndex
и т.д.) было бы невероятно полезно, если бы они были подняты до IList<T>
- получение бесплатных бонусных функций, как это добавляет намного больше преимуществ для реализации интерфейса.)
Так какой вердикт? Являются ли методы расширений методом инкапсуляции и повторного использования кода, или я просто обманываю себя?
( edit2: В ответ на Tomas - в то время как С# действительно начинал с менталитета OO (чрезмерно, imo) OO, он, кажется, обдумывает более многопараметрическое программирование с каждым новым выпуском; Цель этого вопроса заключается в том, полезно ли использование методов расширения для управления изменением стиля (в сторону более общего/функционального С#).)
edit3: методы расширенного расширения
Единственная реальная проблема, выявленная до сих пор при таком подходе, заключается в том, что вы не можете специализировать методы расширения, если вам нужно. Я думал о проблеме, и я думаю, что придумал решение.
Предположим, что у меня есть интерфейс MyInterface
, который я хочу расширить -
Я определяю свои методы расширения в статическом классе MyExtension
и свяжу его с другим интерфейсом, назовите его MyExtensionOverrider
. Методы MyExtension
определяются в соответствии с этим шаблоном:
public static int MyMethod(this MyInterface obj, int arg, bool attemptCast=true)
{
if (attemptCast && obj is MyExtensionOverrider)
{
return ((MyExtensionOverrider)obj).MyMethod(arg);
}
// regular implementation here
}
Интерфейс переопределения отражает все методы, определенные в MyExtension
, за исключением параметров this
или attemptCast
:
public interface MyExtensionOverrider
{
int MyMethod(int arg);
string MyOtherMethod();
}
Теперь любой класс может реализовать интерфейс и получить функциональные возможности по умолчанию:
public class MyClass : MyInterface { ... }
Любой, кто хочет переопределить его с помощью конкретных реализаций, может дополнительно реализовать интерфейс переопределения:
public class MySpecializedClass : MyInterface, MyExtensionOverrider
{
public int MyMethod(int arg)
{
//specialized implementation for one method
}
public string MyOtherMethod()
{ // fallback to default for others
MyExtension.MyOtherMethod(this, attemptCast: false);
}
}
И там мы идем: методы расширения, предоставляемые на интерфейсе, с возможностью полной расширяемости, если это необходимо. Полностью общий интерфейс сам по себе не должен знать о расширении/переопределении, а несколько пар расширения/переопределения могут быть реализованы без вмешательства друг в друга.
Я вижу три проблемы с этим подходом -
- Немного хрупкий - методы расширения и интерфейс переопределения должны синхронизироваться вручную.
- Это немного уродливо - реализация интерфейса переопределения включает в себя шаблон для каждой функции, которую вы не хотите специализировать.
- Это немного медленнее - добавлено дополнительное сравнение
bool
и попытка заливки в mainline каждого метода.
Тем не менее, несмотря на все это, я думаю, что это лучшее, что мы можем получить, пока не будет поддерживаться языковая поддержка функций интерфейса. Мысли?