Переопределение методов расширения - PullRequest
24 голосов
/ 23 января 2009

Я думал об использовании методов расширения в качестве замены абстрактного базового класса. Методы расширения могут обеспечивать функциональность по умолчанию и могут быть «переопределены» путем помещения метода той же сигнатуры в производный класс.

По какой причине я не должен этого делать?

Кроме того, если у меня есть два метода расширения с одинаковой сигнатурой, какой из них используется? Есть ли способ установить приоритет?

Ответы [ 9 ]

12 голосов
/ 23 января 2009

В общем, вы не должны предоставлять «базовую» функциональность через методы расширения. Их следует использовать только для «расширения» функциональности класса. Если у вас есть доступ к коду базового класса, и функциональность, которую вы пытаетесь реализовать, логически является частью иерархии наследования, то вы должны поместить ее в абстрактный класс.

Суть в том, что вы можете не означает, что вы должны . Часто лучше просто придерживаться старого доброго ООП и использовать новые языковые функции, когда обычное старое ОО-программирование не дает вам разумного решения.

8 голосов
/ 23 января 2009

Это определенно плохая идея. Методы расширения связаны статически, что означает, что, если вы не вызовете переопределение для объекта, тип времени компиляции которого является подтипом, вы все равно будете продолжать вызывать метод расширения. Прощай, полиморфизм. На этой странице есть хорошее обсуждение опасностей методов расширения.

4 голосов
/ 06 февраля 2009

Во всех ответах здесь говорилось «ты не можешь», что верно, насколько это возможно. Больше всего добавлено "а ты не должен". Я хотел бы привести аргумент в пользу того, что вы сможете это сделать - небольшое удобство, как это может быть.

Возьмите болезненный пример из реальной жизни: если вам не повезло использовать новую инфраструктуру MVC, и ваш код представления везде использует какой-то метод расширения HtmlHelper, и вы хотите переопределить его поведение по умолчанию ... что то

Вы СОЛ, вот что. Даже если вы сделали «OOP вещь» - наследовать от HtmlHelper, измените класс базового представления, чтобы заменить экземпляр объекта «Html» на экземпляр вашего DerivedHtmlHelper, и определите в нем явный метод «Foo» - даже если вы все что вызов Html.Foo все же вызовет оригинальный метод расширения, а не метод.

Это удивительно! В конце концов, методы расширения должны применяться только в том случае, если у объекта еще нет метода! Что здесь происходит?

Ну, это потому, что методы расширения - это static функция. То есть при просмотре «Html.Foo» компилятор просматривает тип «Html» static . Если у него есть метод Foo, он вызывается как обычно. В противном случае, если SomeClass предоставляет метод расширения Foo, он преобразует выражение в SomeClass.Foo (Html).

Вы ожидаете, что компилятор будет учитывать динамический тип объекта. То есть, что сгенерированный (псевдо-) код будет читать 'Html.HasMethod ("Foo")? Html.Foo (): SomeClass.Foo (Html) '.

Это, конечно, повлечет за собой затраты на использование отражения в каждом вызове метода расширения. Таким образом, можно ожидать, что вы могли бы написать что-то вроде «static void Foo ( virtual this HtmlHelper html)», чтобы явно запросить компилятор вставить проверку во время выполнения. Назовите это «методом виртуального расширения».

Однако в своем ограниченном бюджете и бесконечной мудрости разработчики языка C # выбрали только более эффективную и более ограниченную альтернативу. Что оставляет меня по-прежнему SOL, когда мне нужно переопределить поведение по умолчанию HtmlHelper: - (

3 голосов
/ 23 января 2009

Я согласен с Майклом. Базовые классы должны содержать всю базовую функциональность. Методы расширения, очевидно, должны расширять базовую функциональность. В динамических языках, таких как Ruby, часто используется метод расширения для обеспечения функциональности добавления вместо использования подклассов. По сути, методы расширения существуют для замены с использованием подклассов, а не для замены с использованием базовых классов.

Единственное исключение из этого, которое я видел, состоит в том, что если у вас есть несколько типов, которые имеют разные иерархии классов (например, элементы управления winform), вы можете создать подкласс каждого из них, который все реализует и взаимодействует, а затем расширить этот интерфейс, тем самым давая «базовая» функциональность для группы различных элементов управления, не расширяя все, например, Control или Object.

Редактировать: отвечая на ваш второй вопрос

Я думаю, что компилятор поймает это за вас.

2 голосов
/ 10 октября 2012

Вы можете рассмотреть вышеизложенный полиморфизм, если вам не нужны уникальные производные классы.

Несколько общих рекомендаций из этой статьи MSDN :

  1. Как только метод определен в классе, любой метод расширения для этот класс, разделяющий это имя метода, больше не будет выполняться.

  2. Методы расширения загружаются пространством имен. Чтобы позволить пользователю другие методы расширения, вы должны избегать методы расширения в том же пространстве имен, что и расширяемый класс.

Если различные зависимые сборки используют одну и ту же библиотеку, каждая из которых требует настраиваемой функции для класса в библиотеке, вы можете объявить расширение, специфичное для зависимой сборки, в пространстве имен в этой сборке. Другие сборки, которые не зависят от этой сборки, не будут иметь этого расширения.

Хотя это не полиморфизм, вы, возможно, не захотите использовать полиморфизм, поскольку он потребует от всех производных классов реализации их переопределений.

Другими словами, если у вас есть производные со специфической, вариативной логикой, используйте полиморфизм. Если вам просто нужны пользовательские функции в определенных случаях, которые в противном случае могли бы сбить с толку или обременить, методы расширения не плохая идея.

Также см. Ответ JoshRivers, показывающий, как переопределить метод расширения, используя пространство имен внутри класса в отдельном пространстве имен.

2 голосов
/ 23 января 2009

Есть все причины, по которым вы не должны этого делать. Во-первых, вы не можете гарантировать, как будут называться ваши расширения:

MyExtensions.AMethod(myObj)

или

myObj.AMethod()

Второй - просто синтаксический сахар для первого.

То, что вы предлагаете, противоречит духу языковой особенности. Методы расширения явно не объектно-ориентированные. Тем не менее, вы пытаетесь достичь объектно-ориентированной техники. Не используйте для этого методы расширения.

2 голосов
/ 23 января 2009

Это семантически разные операции. Например, полиморфизм может работать не так, как с абстрактным базовым классом.

Как правило, используйте любой языковой инструмент для того, для чего он предназначен. Методы расширения не являются заменой наследования, они представляют собой метод расширения функциональности класса с использованием (как правило) уже видимого интерфейса.

0 голосов
/ 10 октября 2012

Добавление ссылки на другой вопрос SO, который имеет альтернативный ответ, чем "Вы не можете". Больше «ты не можешь .... если только».

Как переопределить существующий метод расширения

0 голосов
/ 20 июля 2010

Во-первых, было бы неплохо проверить [new] как модификатор метода - он должен дать тот же результат и метод разделения, но с гораздо меньшими трудностями.

Если после этого вы все еще размышляете над тем же приемом, вот основные варианты для рассмотрения - без идеологии, просто перечисление аптек, о которых вам нужно знать :-)

Для начала вам, возможно, придется ограничить все виды использования шаблонами параметров функции, чтобы гарантировать детерминированный выбор функции, поскольку этот трюк создаст ситуацию, полностью противоположную той, в которой CLR гарантирует приоритет детерминированной привязки через границы вызова функции.

Если у вас все еще будет абстрактный базовый класс, и вы владеете всем кодом, то просто "преобразование" одного базового метода в расширение не будет вам ничего стоить и просто обойдется вам потерей общего интерфейса, который все еще существует для всего остального, включая стоимость vtables.

Если вы хотите преобразовать абстрактный класс в конкретный, исключить все виртуальные методы по соображениям перфом и использовать конкретный базовый класс и все производные исключительно в шаблонах (возможно, даже конвертировать в структуры), тогда этот трюк может принести вам немного перф. Вам просто нужно знать, что вы не сможете вернуться к использованию на основе интерфейса без рефакторинга. Вам также необходимо проверить порядок вызовов в функциях, не являющихся шаблонами, с базовым классом в сигнатуре и быть особенно осторожным, если у вас есть несколько dll-ов. Попробуйте то же самое с оператором [new] и посмотрите, работает ли он лучше.

Кроме того, вам придется написать отдельную реализацию метода для каждого производного класса, даже если они имеют одинаковую функциональность, поэтому, если у вас много производных классов, вы ожидаете много дублирования кода. Эта часть поразит вас, даже если вы используете [new], если только вы не вернетесь к виртуальным методам и не введете еще один уровень наследования, что, конечно, отменит все выгоды от перфектов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...