edit4: wikified, поскольку кажется, что это переросло в обсуждение больше, чем в конкретный вопрос.
В программировании на C ++ обычно считается хорошей практикой«предпочесть не являющиеся членами не дружественные функции» вместо методов экземпляра.Это было рекомендовано Скоттом Мейерсом в этой классической статье доктора Доббса и повторено Хербом Саттером и Андреем Александреску в C ++ Стандарты кодирования (пункт 44);общий аргумент состоит в том, что если функция может выполнять свою работу исключительно, полагаясь на открытый интерфейс, предоставляемый классом, она фактически увеличивает инкапсуляцию , чтобы сделать ее внешней.Хотя это в некоторой степени смущает «упаковку» класса, преимущества, как правило, стоят того.
Теперь, с тех пор, как я начал программировать на C #, у меня было ощущение, что здесь является окончательным выражением концепции , которую они пытаютсядостичь с помощью «не принадлежащих, не дружественных функций, которые являются частью интерфейса класса».C # добавляет два важных компонента в микс - первый - интерфейсы , а второй методы расширения :
- Интерфейсы позволяют классу формально определять свои открытыеконтракт, методы и свойства, которые они выставляют миру.
- Любой другой класс может выбрать реализацию того же интерфейса и выполнить тот же контракт.
- Методы расширения могут быть определены на интерфейсе, предоставляя любые функциональные возможности, которые могут быть реализованы через интерфейс для всех исполнителей автоматически .
- И, что самое приятное, из-за поддержки сахара и IDE «синтаксиса экземпляра» их можно вызывать так же, как и любой другой метод экземпляра, , исключая когнитивные издержки!
Таким образом, вы получаете преимущества инкапсуляции функций «не член, не друг» с удобством членов.Похоже, лучшее из обоих миров для меня;библиотека .NET сама по себе является ярким примером в LINQ.Однако везде, где я смотрю, я вижу людей, предупреждающих против чрезмерного использования метода расширения;даже сама страница MSDN гласит:
В общем, мы рекомендуем применять методы расширения экономно и только тогда, когда это необходимо.
( edit: Даже в текущей библиотеке .NET я могу видеть места, где было бы полезно иметь расширения вместо методов экземпляра - например, всефункции полезности List<T>
(Sort
, BinarySearch
, FindIndex
и т. д.) были бы невероятно полезными, если бы их число было увеличено до IList<T>
- получение бесплатной бонусной функциональности, подобной этой, добавляет гораздо больше преимуществ для реализацииинтерфейс.)
Так какой вердикт?Являются ли методы расширения высшей точкой инкапсуляции и повторного использования кода, или я просто обманываю себя?
( edit2: В ответ на Томаса - в то время как C # начинался с Java (чрезмерно, imo)ОО-менталитет, кажется, с каждым новым выпуском охватывает все больше парадигмального программирования, и основной вопрос в этом вопросе: полезны или целесообразны ли использование методов расширения для изменения стиля (в сторону более общего / функционального C #) ...)
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
сравнение и попытку приведения.
Тем не менее, несмотря на все это, я думаю, что это лучшее, что мы можем получить, пока не будет языковой поддержки для функций интерфейса. Мысли?