Метод расширения
Эта статья написана как личное размышление, личное эссе или аргументативное эссе , в котором излагаются личные чувства редактора Википедии или представлен оригинальный аргумент по определенной теме. ( Май 2013 г. ) |
В объектно-ориентированном программировании метод расширения — это метод, добавляемый к объекту после компиляции исходного объекта . Модифицированный объект часто представляет собой класс, прототип или тип. Методы расширения являются особенностью некоторых объектно-ориентированных языков программирования. Нет никакой синтаксической разницы между вызовом метода расширения и вызовом метода, объявленного в определении типа. [1]
Однако не все языки реализуют методы расширения одинаково безопасно. Например, такие языки, как C#, Java (через Manifold , Lombok или Fluent ) и Kotlin, никак не изменяют расширенный класс, поскольку это может нарушить иерархию классов и помешать диспетчеризации виртуальных методов. Вот почему эти языки строго реализуют методы расширения статически и используют статическую диспетчеризацию для их вызова.
Поддержка языков программирования
[ редактировать ]Методы расширения являются функциями многих языков, включая C# , Java через Manifold или Lombok или Fluent , Gosu , JavaScript , Oxygene , Ruby , Smalltalk , Kotlin , Dart , Visual Basic.NET и Xojo . В динамических языках, таких как Python , концепция метода расширения не является необходимой, поскольку невстроенные классы могут быть расширены без какого-либо специального синтаксиса (подход, известный как « обезьянье исправление », используемый в таких библиотеках, как gevent ).
В VB.NET и Oxygene они распознаются по наличию " extension
" ключевое слово или атрибут. В Xojo " Extends
Ключевое слово «используется с глобальными методами.
В C# они реализованы как статические методы в статических классах, причем первый аргумент принадлежит расширенному классу и ему предшествует " this
"Ключевое слово.
В Java вы добавляете методы расширения через Manifold , jar-файл, который вы добавляете в путь к классам вашего проекта. Подобно C#, метод расширения Java объявляется статическим в классе @Extension , где первый аргумент имеет тот же тип, что и расширенный класс, и помечается примечанием @This
. Альтернативно, плагин Fluent позволяет вам вызывать любой статический метод в качестве метода расширения без использования аннотаций, если сигнатура метода совпадает.
В Smalltalk любой код может добавить метод в любой класс в любое время, отправив сообщение о создании метода (например, methodsFor:
) к классу, который пользователь хочет расширить. Категория метода Smalltalk традиционно названа в честь пакета, предоставляющего расширение, и окружена звездочками. Например, когда код приложения Etoys расширяет классы базовой библиотеки, добавленные методы помещаются в *etoys*
категория.
В Ruby, как и в Smalltalk, нет специальной языковой функции для расширения, поскольку Ruby позволяет повторно открывать классы в любое время с помощью class
ключевое слово, в данном случае, для добавления новых методов. Сообщество Ruby часто описывает метод расширения как своего рода обезьяний патч . Существует также новая функция для добавления безопасных/локальных расширений к объектам, называемая «Уточнения» , но известно, что она используется реже.
В Свифте extension
Ключевое слово отмечает классоподобную конструкцию, которая позволяет добавлять методы, конструкторы и поля к существующему классу, включая возможность реализовать новый интерфейс/протокол для существующего класса. [2]
Методы расширения как активирующая функция
[ редактировать ]Помимо методов расширения, позволяющих расширять код, написанный другими, как описано ниже, методы расширения также позволяют использовать шаблоны, которые полезны и сами по себе. Основной причиной введения методов расширения был языковой интегрированный запрос (LINQ). Поддержка компилятором методов расширения обеспечивает глубокую интеграцию LINQ со старым кодом так же, как и с новым кодом, а также поддержку синтаксиса запросов , который на данный момент уникален для основных языков Microsoft .NET .
Console.WriteLine(new[] { Math.PI, Math.E }.Where(d => d > 3).Select(d => Math.Sin(d / 2)).Sum());
// Output:
// 1
Централизуйте общее поведение
[ редактировать ]Однако методы расширения позволяют реализовать функции один раз таким образом, чтобы обеспечить возможность повторного использования без необходимости наследования или накладных расходов на вызовы виртуальных методов или требовать от разработчиков интерфейса реализации либо тривиальных, либо чрезвычайно сложных функций.
Особенно полезным сценарием является ситуация, когда функция работает с интерфейсом, для которого нет конкретной реализации или полезная реализация не предоставлена автором библиотеки классов, например, как это часто бывает в библиотеках, которые предоставляют разработчикам архитектуру плагинов или аналогичные функции. .
Рассмотрим следующий код и предположим, что это единственный код, содержащийся в библиотеке классов. Тем не менее, каждый разработчик интерфейса ILogger получит возможность записывать форматированную строку, просто включив оператор using MyCoolLogger , без необходимости его реализации один раз и без необходимости создания подкласса библиотеки классов, обеспечивающей реализацию ILogger.
namespace MyCoolLogger;
public interface ILogger
{
void Write(string text);
}
public static class LoggerExtensions
{
public static void Write(this ILogger logger, string format, params object[] args)
{
if (logger != null)
logger.Write(string.Format(format, args));
}
}
- использовать как:
var logger = new MyLoggerImplementation(); logger.Write("{0}: {1}", "kiddo sais", "Mam mam mam mam ..."); logger.Write("{0}: {1}", "kiddo sais", "Ma ma ma ma... "); logger.Write("{0}: {1}", "kiddo sais", "Mama mama mama mama "); logger.Write("{0}: {1}", "kiddo sais", "Mamma mamma mamma ... "); logger.Write("{0}: {1}", "kiddo sais", "Elisabeth Lizzy Liz..."); logger.Write("{0}: {1}", "mamma sais", "WHAT?!?!!!"); logger.Write("{0}: {1}", "kiddo sais", "hi.");
Лучше свободная связь
[ редактировать ]Методы расширения позволяют пользователям библиотек классов воздерживаться от объявления аргумента, переменной или чего-либо еще с типом, полученным из этой библиотеки. Построение и преобразование типов, используемых в библиотеке классов, можно реализовать как методы расширения. После тщательной реализации преобразований и фабрик переключение с одной библиотеки классов на другую может быть таким же простым, как изменение оператора using, который делает методы расширения доступными для привязки компилятора.
Свободные интерфейсы прикладного программиста
[ редактировать ]Методы расширения имеют особое применение при реализации так называемых свободных интерфейсов. Примером является API конфигурации Microsoft Entity Framework, который позволяет, например, писать код, максимально похожий на обычный английский язык.
Можно утверждать, что это вполне возможно и без методов расширения, но на практике методы расширения обеспечивают превосходный опыт, поскольку на иерархию классов накладывается меньше ограничений, чтобы заставить ее работать и читаться так, как хотелось бы.
В следующем примере используется Entity Framework и настраивается класс TodoList для хранения в списках таблиц базы данных, а также определяются первичный и внешний ключи. Код следует понимать примерно так: «TodoList имеет ключ TodoListID, имя его набора сущностей — Lists, и он имеет множество TodoItem, каждый из которых имеет обязательный TodoList».
public class TodoItemContext : DbContext
{
public DbSet<TodoItem> TodoItems { get; set; }
public DbSet<TodoList> TodoLists { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TodoList>()
.HasKey(e => e.TodoListId)
.HasEntitySetName("Lists")
.HasMany(e => e.Todos)
.WithRequired(e => e.TodoList);
}
}
Производительность
[ редактировать ]Возьмем, к примеру, IEnumerable и отметим его простоту — метод всего один, но он более или менее лежит в основе LINQ. В Microsoft .NET существует множество реализаций этого интерфейса. Тем не менее, очевидно, было бы обременительно требовать от каждой из этих реализаций реализации целого ряда методов, определенных в пространстве имен System.Linq для работы с IEnumerables, даже несмотря на то, что у Microsoft есть весь исходный код. Хуже того, это потребовало бы от всех, кроме Microsoft, рассмотрения возможности использования IEnumerable для реализации всех этих методов, что было бы очень непродуктивно, учитывая широкое использование этого очень распространенного интерфейса. Вместо этого, реализовав один метод этого интерфейса, LINQ можно будет использовать более или менее немедленно. Особенно учитывая, что практически в большинстве случаев метод GetEnumerator IEnumerable делегируется реализации GetEnumerator частной коллекции, списка или массива.
public class BankAccount : IEnumerable<decimal>
{
private List<Tuple<DateTime, decimal>> credits; // assumed all negative
private List<Tuple<DateTime, decimal>> debits; // assumed all positive
public IEnumerator<decimal> GetEnumerator()
{
var query = from dc in debits.Union(credits)
orderby dc.Item1 /* Date */
select dc.Item2; /* Amount */
foreach (var amount in query)
yield return amount;
}
}
// given an instance of BankAccount called ba and a using System.Linq on top of the current file,
// one could now write ba.Sum() to get the account balance, ba.Reverse() to see most recent transactions first,
// ba.Average() to get the average amount per transaction, etcetera - without ever writing down an arithmetic operator
Производительность
[ редактировать ]Тем не менее, дополнительные реализации функции, предоставляемые методом расширения, могут быть добавлены для повышения производительности или для работы с различными реализованными реализациями интерфейса, например предоставление компилятору реализации IEnumerable специально для массивов (в System.SZArrayHelper), которые он будет автоматически выбирать вызовы методов расширения для ссылок, типизированных в виде массива, поскольку их аргумент будет более конкретным (это значение T[]), чем метод расширения с тем же именем, который работает с экземплярами интерфейса IEnumerable (это значение IEnumerable).
Устранение необходимости в общем базовом классе
[ редактировать ]В случае универсальных классов методы расширения позволяют реализовать поведение, доступное для всех экземпляров универсального типа, не требуя, чтобы они были производными от общего базового класса, и не ограничивая параметры типа определенной ветвью наследования. Это большая победа, поскольку ситуации, когда этот аргумент имеет значение, требуют необщего базового класса только для реализации общей функции, что затем требует, чтобы универсальный подкласс выполнял упаковку и/или приведения всякий раз, когда используемый тип является одним из аргументов типа. .
Консервативное использование
[ редактировать ]Следует сделать примечание о предпочтении методов расширения другим средствам достижения повторного использования и правильного объектно-ориентированного проектирования. Методы расширения могут «загромождать» функции автоматического завершения в редакторах кода, таких как IntelliSense в Visual Studio, поэтому они должны либо находиться в собственном пространстве имен, чтобы разработчик мог выборочно импортировать их, либо они должны быть определены в типе, который достаточно специфичен для метод будет появляться в IntelliSense только тогда, когда он действительно актуален, и, учитывая вышеизложенное, учтите, что их может быть трудно найти, если разработчик ожидает их, но пропустите их из IntelliSense из-за отсутствия оператора using, поскольку разработчик, возможно, не связал метод с классом, который его определяет, или даже с пространством имен, в котором он живет, а скорее с типом, который он расширяет, и пространством имен, в котором живет этот тип.
Проблема
[ редактировать ]В программировании возникают ситуации, когда необходимо добавить функциональность существующему классу, например, добавив новый метод. Обычно программист модифицирует исходный код существующего класса , но это вынуждает программиста перекомпилировать все двоичные файлы с этими новыми изменениями и требует, чтобы программист имел возможность модифицировать класс, что не всегда возможно, например, при использовании классов из третьего класса. -партийное собрание . Обычно это решается одним из трех способов, каждый из которых несколько ограничен и неинтуитивен. [ нужна ссылка ] :
- Наследуйте класс, а затем реализуйте функциональность в методе экземпляра производного класса.
- Реализуйте функциональность в статическом методе, добавленном во вспомогательный класс.
- Используйте агрегацию вместо наследования .
Текущие решения C#
[ редактировать ]Первый вариант в принципе проще, но, к сожалению, он ограничен тем, что многие классы ограничивают наследование определенных членов или полностью запрещают его. Сюда входят запечатанные классы и различные примитивные типы данных в C#, такие как int , float и string . Второй вариант, с другой стороны, не разделяет этих ограничений, но он может быть менее интуитивным, поскольку требует ссылки на отдельный класс вместо непосредственного использования методов рассматриваемого класса.
В качестве примера рассмотрим необходимость расширения класса строк новым обратным методом, возвращаемым значением которого является строка с символами в обратном порядке. Поскольку строковый класс является закрытым типом, этот метод обычно добавляется в новый служебный класс следующим образом:
string x = "some string value";
string y = Utility.Reverse(x);
Однако по мере увеличения библиотеки служебных методов и классов ориентироваться в этом может становиться все труднее, особенно для новичков. Местоположение также менее интуитивно понятно, поскольку, в отличие от большинства строковых методов, оно не будет членом класса строк, а будет находиться в совершенно другом классе. Поэтому лучшим синтаксисом было бы следующее:
string x = "some string value";
string y = x.Reverse();
Текущие решения VB.NET
[ редактировать ]Во многом решение VB.NET похоже на решение C#, описанное выше. Однако VB.NET имеет уникальное преимущество, заключающееся в том, что он позволяет передавать элементы в расширение по ссылке (C# позволяет только по значению). Разрешение следующего;
Dim x As String = "some string value"
x.Reverse()
Поскольку Visual Basic позволяет передавать исходный объект по ссылке, можно вносить изменения в исходный объект напрямую, без необходимости создавать еще одну переменную. Он также более интуитивен, поскольку работает согласованно с существующими методами классов.
Методы расширения
[ редактировать ]Однако новая языковая функция методов расширения в C# 3.0 делает возможным использование последнего кода. Для этого подхода требуется статический класс и статический метод, как показано ниже.
public static class Utility
{
public static string Reverse(this string input)
{
char[] chars = input.ToCharArray();
Array.Reverse(chars);
return new String(chars);
}
}
В определении модификатор this перед первым аргументом указывает, что это метод расширения (в данном случае для типа string). При вызове первый аргумент не «передается», поскольку он уже известен как «вызывающий» объект (объект перед точкой).
Основное различие между вызовом методов расширения и вызовом статических вспомогательных методов состоит в том, что статические методы вызываются в префиксной нотации , тогда как методы расширения вызываются в инфиксной нотации . Последнее приводит к более читаемому коду, когда результат одной операции используется для другой операции.
- Со статическими методами
HelperClass.Operation2(HelperClass.Operation1(x, arg1), arg2)
- С методами расширения
x.Operation1(arg1).Operation2(arg2)
Конфликты именования в методах расширения и методах экземпляров
[ редактировать ]В C# 3.0 для класса могут существовать как метод экземпляра, так и метод расширения с одной и той же сигнатурой. В таком сценарии метод экземпляра предпочтительнее метода расширения. Ни компилятор, ни среда разработки Microsoft Visual Studio не предупреждают о конфликте имен. Рассмотрим этот класс C#, где GetAlphabet()
метод вызывается для экземпляра этого класса:
class AlphabetMaker
{
public void GetAlphabet()
{ //When this method is implemented,
Console.WriteLine("abc"); //it will shadow the implementation
} //in the ExtensionMethods class.
}
static class ExtensionMethods
{
public static void GetAlphabet(this AlphabetMaker am)
{ //This will only be called
Console.WriteLine("ABC"); //if there is no instance
} //method with the same signature.
}
Результат вызова GetAlphabet()
на примере AlphabetMaker
если существует только метод расширения:
ABC
Результат, если существуют и метод экземпляра, и метод расширения:
abc
См. также
[ редактировать ]- UFCS — способ использования свободных функций в качестве методов расширения, предоставляемых языком программирования D.
- Типовые классы
- Анонимные типы
- Лямбда-выражения
- Деревья выражений
- Изменение времени выполнения
- Утка печатает
Ссылки
[ редактировать ]- ^ «Методы расширения» . Майкрософт . Проверено 23 ноября 2008 г.
- ^ «Расширения — язык программирования Swift (Swift 5.7)» . docs.swift.org . Проверено 12 июня 2022 г.
Внешние ссылки
[ редактировать ]- Коллекция библиотек методов расширения C# с открытым исходным кодом . Сейчас хранится в Codeplex.
- Метод расширения в C#
- Методы расширения
- Методы расширения C# . Коллекция.
- Extensionmethod.net Большая база данных с методами расширения C#, Visual Basic, F# и Javascript.
- Пояснение и пример кода
- Определение собственных функций в jQuery
- Единый синтаксис вызова функций
- Методы расширения в C#
- Методы расширения в Java с помощью Manifold
- Методы расширения в Java с помощью Lombok
- Методы расширения в Java с Fluent
- Функции расширения в Котлине