Метод расширения - Википедия - Extension method

В объектно-ориентированное компьютерное программирование, метод расширения это метод добавлен к объекту после того, как исходный объект был составлен. Измененный объект часто является классом, прототипом или типом. Методы расширения являются особенностями некоторых объектно-ориентированных языков программирования. Синтаксической разницы между вызовом метода расширения и вызовом метода, объявленного в определении типа, нет.[1]

Однако не все языки реализуют методы расширения одинаково безопасно. Например, такие языки, как C #, Java (через Manifold) и Kotlin, никоим образом не изменяют расширенный класс, потому что это может нарушить иерархию классов и помешать диспетчеризации виртуальных методов. Вот почему эти языки строго статически реализуют методы расширения и используют статическую диспетчеризацию для их вызова.

Поддержка языков программирования

Методы расширения - это особенности многих языков, включая C #, Ява через Многообразие, Госу, JavaScript, Кислород, Рубин, Болтовня, Котлин, Дротик, Visual Basic.NET и Xojo. В динамических языках, таких как Python, концепция метода расширения не нужна, потому что классы могут быть расширены без какого-либо специального синтаксиса (подход, известный как «исправление обезьяны», используемый в таких библиотеках, как Gevent ).

В VB.NET и Oxygene они распознаются по наличию символа "расширение"ключевое слово или атрибут. В Xojo"Расширяется"ключевое слово используется с глобальными методами.

В C # они реализованы как статические методы в статических классах с первым аргументом расширенного класса, которому предшествует "это"ключевое слово.

В Java вы добавляете методы расширения через Многообразие, файл jar, который вы добавляете в путь к классам вашего проекта. Подобно C #, метод расширения Java объявляется статическим в @Расширение класс, где первый аргумент имеет тот же тип, что и расширенный класс, и аннотирован @Этот.

В Smalltalk любой код может добавить метод к любому классу в любое время, отправив сообщение о создании метода (например, методы для:) в класс, который пользователь хочет расширить. Категория методов Smalltalk условно названа в честь пакета, который предоставляет расширение, и заключена в звездочки. Например, когда код приложения Etoys расширяет классы в основной библиотеке, добавленные методы помещаются в * etoys * категория.

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

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

Методы расширения как функция включения

Наряду с методами расширения, позволяющими расширять код, написанный другими, как описано ниже, методы расширения включают шаблоны, которые также полезны сами по себе. Основная причина, по которой были введены методы расширения, заключалась в Интегрированный языковой запрос (LINQ). Поддержка компилятором методов расширения позволяет осуществлять глубокую интеграцию LINQ со старым кодом так же, как и с новым кодом, а также поддерживает синтаксис запроса который на данный момент является уникальным для первичного Microsoft .NET языков.

Консоль.WriteLine(новый[] { Математика.ЧИСЛО ПИ, Математика.E }.Где(d => d > 3).Выбирать(d => Математика.Грех(d / 2)).Сумма());// Выход:// 1

Централизовать общее поведение

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

Особенно полезный сценарий - если функция работает с интерфейсом, для которого нет конкретной реализации или полезная реализация не предоставлена ​​автором библиотеки классов, например как это часто бывает в библиотеках, которые предоставляют разработчикам архитектуру плагина или аналогичные функции.

Рассмотрим следующий код и предположим, что это единственный код, содержащийся в библиотеке классов. Тем не менее каждый разработчик интерфейса ILogger получит возможность писать отформатированную строку, просто включив используя MyCoolLogger оператор, без необходимости реализовывать его один раз и без необходимости создавать подкласс библиотеки классов, обеспечивающий реализацию ILogger.

пространство имен MyCoolLogger {    общественный интерфейс ILogger { пустота Написать(нить текст); }    общественный статический учебный класс LoggerExtensions {        общественный статический пустота Написать(это ILogger регистратор, нить формат, параметры объект[] аргументы) {             если (регистратор != ноль)                регистратор.Написать(нить.Формат(формат, аргументы));        }    }}
  • использовать как :
    вар регистратор = новый MyLoggerImplementation();регистратор.Написать("{0}: {1}", "kiddo sais", "Мам мам мам мам ...");регистратор.Написать("{0}: {1}", "kiddo sais", "Ма ма ма ма ...");регистратор.Написать("{0}: {1}", "kiddo sais", "Мама мама мама мама");регистратор.Написать("{0}: {1}", "kiddo sais", «Мама, мама, мама ...»);регистратор.Написать("{0}: {1}", "kiddo sais", "Элизабет Лиззи Лиз ...");регистратор.Написать("{0}: {1}", "мама саис", "КАКИЕ?!?!!!");регистратор.Написать("{0}: {1}", "kiddo sais", "Здравствуй.");

Лучшая слабая связь

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

Свободный интерфейс прикладного программиста

Методы расширения особенно используются при реализации так называемых свободных интерфейсов. Примером может служить API конфигурации Microsoft Entity Framework, который позволяет, например, писать код, максимально похожий на обычный английский.

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

В следующем примере используется Entity Framework и настраивается класс TodoList для хранения в таблице базы данных Lists и определяется первичный и внешний ключи. Код следует понимать примерно так: «У TodoList есть ключевой TodoListID, имя его набора сущностей - Списки, и у него много TodoItem, каждый из которых имеет обязательный TodoList».

общественный учебный класс TodoItemContext : DbContext {    общественный DbSet<TodoItem> TodoItems { получать; набор; }    общественный DbSet<Список дел> TodoLists { получать; набор; }    защищенный отменять пустота OnModelCreating(DbModelBuilder modelBuilder)     {        основание.OnModelCreating(modelBuilder);        modelBuilder.Юридическое лицо<Список дел>()                    .HasKey(е => е.TodoListId)                    .HasEntitySetName("Списки")                    .Имеет много(е => е.Todos)                    .Требуется(е => е.Список дел);    }}

Продуктивность

Рассмотрим, например, IEnumerable и обратите внимание на его простоту - есть только один метод, но он более или менее является основой LINQ. В Microsoft .NET существует множество реализаций этого интерфейса. Тем не менее, очевидно, было бы обременительным требовать, чтобы каждая из этих реализаций реализовывала всю серию методов, которые определены в System.Linq пространство имен для работы с IEnumerables, даже если у Microsoft есть весь исходный код. Хуже того, это потребовало бы от всех, кроме Microsoft, рассматривающих возможность использования IEnumerable, также для реализации всех этих методов, что было бы очень непродуктивно, учитывая широкое использование этого очень распространенного интерфейса. Вместо этого, реализуя один метод этого интерфейса, LINQ можно использовать более или менее немедленно. Особенно заметно, что практически в большинстве случаев метод IEnumerable GetEnumerator делегируется частной коллекции, списку или реализации GetEnumerator массива.

общественный учебный класс Банковский счет : IEnumerable<десятичный> {    частный Список<Кортеж<DateTime, десятичный>> кредиты; // предполагаем все отрицательные    частный Список<Кортеж<DateTime, десятичный>> дебет; // предполагаем все положительные    общественный IEnumerator<десятичный> GetEnumerator()     {        вар запрос = из Округ Колумбия в дебет.Союз(кредиты)                     Сортировать по Округ Колумбия.Item1 /* Дата */                     Выбрать Округ Колумбия.Item2; /* Количество */            для каждого (вар количество в запрос)            урожай возвращаться количество;    }}// учитывая экземпляр BankAccount с именем ba и использование System.Linq поверх текущего файла,// теперь можно написать ba.Sum (), чтобы получить баланс счета, ba.Reverse (), чтобы сначала увидеть самые последние транзакции,// ba.Average () для получения средней суммы за транзакцию и т. д. - без записи арифметического оператора

Спектакль

Тем не менее, дополнительные реализации функции, предоставляемой методом расширения, могут быть добавлены для повышения производительности или для работы с реализованными по-другому реализациями интерфейса, такими как предоставление компилятору реализации IEnumerable специально для массивов (в System.SZArrayHelper), которые он будет автоматически выбирать для вызовов методов расширения ссылки на типизированные массивы, поскольку их аргумент будет более конкретным (это значение T []), чем метод расширения с тем же именем, который работает с экземплярами интерфейса IEnumerable (это значение IEnumerable).

Устранение необходимости в общем базовом классе

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

Консервативное использование

Следует отметить, что методы расширения предпочтительнее других средств достижения повторного использования и правильного объектно-ориентированного дизайна. Методы расширения могут `` загромождать '' функции автоматического завершения редакторов кода, таких как IntelliSense Visual Studio, поэтому они должны находиться в своем собственном пространстве имен. чтобы разработчик мог выборочно импортировать их, или они должны быть определены в типе, который достаточно специфичен, чтобы метод отображался в IntelliSense только тогда, когда это действительно актуально и с учетом вышеизложенного, учтите, что их может быть трудно найти, если разработчик их ожидает, но пропустить их из IntelliSense из-за отсутствия оператора using, поскольку разработчик мог связать метод не с классом, который его определяет, или даже с пространством имен, в котором он живет, а скорее с типом, который он расширяет, и пространством имен этого типа живет в.

Проблема

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

  1. Наследуйте класс, а затем реализуйте функции в методе экземпляра в производном классе.
  2. Реализуйте функциональность в статическом методе, добавленном во вспомогательный класс.
  3. Использовать агрегирование вместо наследование.

Текущие решения C #

Первый вариант в принципе проще, но, к сожалению, он ограничен тем фактом, что многие классы ограничивают наследование определенных членов или полностью запрещают его. Это включает запечатанный класс и различные примитивные типы данных в C #, такие как int, плавать и нить. Второй вариант, с другой стороны, не разделяет эти ограничения, но он может быть менее интуитивным, поскольку требует ссылки на отдельный класс вместо прямого использования методов рассматриваемого класса.

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

нить Икс = "какое-то строковое значение";нить у = Полезность.Обеспечить регресс(Икс);

Однако по мере увеличения библиотеки служебных методов и классов в этом может стать все труднее ориентироваться, особенно для новичков. Местоположение также менее интуитивно понятно, потому что, в отличие от большинства строковых методов, оно будет не членом строкового класса, а совсем другим классом. Следовательно, лучший синтаксис будет следующим:

нить Икс = "какое-то строковое значение";нить у = Икс.Обеспечить регресс();

Текущие решения VB.NET

Во многих отношениях решение VB.NET похоже на решение C # выше. Однако VB.NET имеет уникальное преимущество в том, что он позволяет передавать члены в расширение по ссылке (C # допускает только по значению). Учитывая следующее;

Тусклый Икс В качестве Нить = "какое-то строковое значение"Икс.Обеспечить регресс()

Поскольку Visual Basic позволяет передавать исходный объект по ссылке, можно вносить изменения в исходный объект напрямую, без необходимости создания другой переменной. Он также более интуитивен, поскольку работает согласованно с существующими методами классов.

Методы расширения

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

общественный статический учебный класс Полезность{    общественный статический нить Обеспечить регресс(это нить Вход)    {        char[] символы = Вход.ToCharArray();        Множество.Обеспечить регресс(символы);        возвращаться новый Нить(символы);    }}

В определении модификатор this перед первым аргументом указывает, что это метод расширения (в данном случае для типа string). При вызове первый аргумент не «передается», потому что он уже известен как «вызывающий» объект (объект перед точкой).

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

Статическими методами
HelperClass.Операция2(HelperClass.Операция1(Икс, arg1), arg2)
С помощью методов расширения
Икс.Операция1(arg1).Операция2(arg2)

Конфликты имен в методах расширения и методах экземпляра

В C # 3.0 для класса могут существовать как метод экземпляра, так и метод расширения с одинаковой сигнатурой. В таком сценарии метод экземпляра предпочтительнее метода расширения. Ни компилятор, ни Microsoft Visual Studio IDE предупреждает о конфликте имен. Рассмотрим этот класс C #, где GetAlphabet () метод вызывается для экземпляра этого класса:

учебный класс AlphabetMaker {    общественный пустота GetAlphabet()           {                               // Когда этот метод реализован,        Консоль.WriteLine("abc");   // он затеняет реализацию    }                               // в классе ExtensionMethods.}статический учебный класс ExtensionMethods{    общественный статический пустота GetAlphabet(это AlphabetMaker являюсь)       {                               // Это будет только вызываться         Консоль.WriteLine("АВС");   // если экземпляра нет    }                               // метод с такой же сигнатурой. }

Результат вызова GetAlphabet () на примере AlphabetMaker если существует только метод расширения:

ABC

Результат, если существуют и метод экземпляра, и метод расширения:

abc

Смотрите также

Рекомендации

  1. ^ «Методы расширения». Microsoft. Получено 2008-11-23.

внешняя ссылка