Способ расширения применяется при

Методы расширения (Руководство по программированию в C#)

Методы расширения позволяют «добавлять» методы в существующие типы без создания нового производного типа, перекомпиляции и иного изменения первоначального типа. Методы расширения представляют собой разновидность статического метода, но вызываются так же, как методы экземпляра в расширенном типе. Для клиентского кода, написанного на языках C#, F# и Visual Basic, нет видимого различия между вызовом метода расширения и вызовом методов, определенных в типе.

Самые распространенные методы расширения — стандартные операторы запросов LINQ, которые добавляют функции запросов в существующие типы System.Collections.IEnumerable и System.Collections.Generic.IEnumerable . Для использования стандартных операторов запросов их необходимо ввести в область действия с помощью директивы using System.Linq . Тогда каждый тип, реализующий тип IEnumerable , будет иметь методы экземпляра, в частности GroupBy, OrderBy, Average и т. д. Эти дополнительные методы можно видеть в завершении операторов IntelliSense при вводе точки после экземпляра типа IEnumerable , например List или Array.

Пример OrderBy

В следующем примере показано, как вызывать метод стандартного оператора запроса OrderBy для массива целых чисел. Выражение в скобках называется лямбда-выражением. Многие стандартные операторы запроса принимают лямбда-выражения в качестве параметров, но это необязательно для методов расширения. Дополнительные сведения см. в разделе Лямбда-выражения.

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

В приведенном ниже примере показан метод расширения, определенный для класса System.String. Этот метод определяется внутри невложенного, неуниверсального статического класса:

Метод расширения WordCount можно ввести в область действия с помощью следующей директивы using :

Его можно вызвать из приложения с помощью следующего синтаксиса:

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

Класс MyExtensions и метод WordCount являются static , и доступ к ним можно получить так же, как ко всем остальным элементам static . Метод WordCount может быть вызван так же, как другие методы static , следующим образом:

В приведенном выше коде C#:

  • Объявляет и назначает новый string с именем s и значением «Hello Extension Methods» .
  • Вызывает MyExtensions.WordCount с учетом аргумента s

Вообще, обычно гораздо чаще вызываются методы расширения, чем реализуются собственные методы. Так как методы расширения вызываются с помощью синтаксиса обращения к методу экземпляра, для использования их из клиентского кода специальные знания не требуются. Чтобы включить методы расширения для определенного типа, необходимо просто добавить директиву using для пространства имен, в котором эти методы определяются. Например, чтобы использовать стандартные операторы запроса, нужно добавить в код следующую директиву using :

(Также может потребоваться добавить ссылку на библиотеку System.Core.dll.) Обратите внимание, что стандартные операторы запроса теперь появляются в IntelliSense в виде дополнительных методов, доступных для большинства типов IEnumerable .

Привязка методов расширения во время компиляции

Методы расширения можно использовать для расширения класса или интерфейса, но не для их переопределения. Метод расширения, имеющий те же имя и сигнатуру, что и интерфейс или метод класса, никогда не вызывается. Во время компиляции методы расширения всегда имеют более низкий приоритет, чем методы экземпляра, определенные в самом типе. Другими словами, если тип имеет метод Process(int i) , а также есть метод расширения с такой же сигнатурой, компилятор будет всегда выполнять привязку к методу экземпляра. Если компилятор обнаруживает вызов метода, он сначала ищет совпадения с методами экземпляра типа. Если такое совпадение не найдено, компилятор выполняет поиск методов расширения, определенных для соответствующего типа, и создает привязку к первому обнаруженному методу расширения. В следующем примере кода демонстрируется, как компилятор определяет, к какому методу расширения или методу экземпляра необходимо выполнить привязку.

Пример

В следующем примере демонстрируются правила, которые компилятор C# соблюдает при определении того, к чему необходимо привязать вызов метода — к методу экземпляра типа или к методу расширения. Статический класс Extensions содержит методы расширения, определяемые для любого типа, реализующего интерфейс IMyInterface . Все три класса — A , B и C — реализуют этот интерфейс.

Метод расширения MethodB никогда не вызывается, потому что его имя и сигнатура точно совпадают с методами, уже реализованными этими классами.

Если компилятор не может найти метод экземпляра с совпадающей сигнатурой, он выполняет привязку к совпадающему методу расширения, если такой существует.

Читайте также:  Lanbena hair growth essential oil способ применения

Общие варианты использования

Функциональные возможности коллекций

В прошлом было распространено создание классов коллекций, которые реализовали интерфейс System.Collections.Generic.IEnumerable для данного типа и содержали функциональные возможности, действующие на коллекции этого типа. Хотя в создании этого типа объекта коллекции нет ничего плохого, те же функциональные возможности можно получить, используя расширение System.Collections.Generic.IEnumerable . Преимущество расширений заключается в том, что они позволяют вызывать функциональные возможности из любой коллекции, например System.Array или System.Collections.Generic.List , которая реализует System.Collections.Generic.IEnumerable для этого типа. Пример использования массива Int32 описан ранее в этой статье.

Функциональные возможности конкретного слоя

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

Расширение предопределенных типов

Если необходимо создать многократно используемые функциональные возможности, вы можете расширить существующий тип, например тип .NET или CLR, чтобы не создавать дополнительные объекты. Например, если методы расширения не используются, можно создать класс Engine или Query , чтобы выполнить запрос к SQL Server, который можно вызвать из нескольких расположений в коде. Однако вместо этого можно расширить класс System.Data.SqlClient.SqlConnection с помощью методов расширения, чтобы выполнить этот запрос из любого расположения, где установлено подключение с SQL Server. В качестве второго примера можно привести добавление общих функциональных возможностей в класс System.String, расширение возможностей обработки данных объектов System.IO.File и System.IO.Stream, а также объектов System.Exception для функциональных возможностей обработки конкретных ошибок. Сценарии использования ограничиваются только воображением и здравым смыслом.

Расширение предопределенных типов с помощью типов struct может быть сложным, так как они передаются методам по значению. Это означает, что любые изменения структуры вносятся в ее копию. Эти изменения не отображаются после выхода из метода расширения. Начиная с C# 7.2, вы можете добавить модификатор ref к первому аргументу метода расширения. Добавление модификатора ref означает, что первый аргумент передается по ссылке. Это позволяет создавать методы расширения, изменяющие состояние расширяемой структуры.

Общие рекомендации

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

Дополнительные сведения о производных типах см. в статье Наследование (Руководство по программированию на C#).

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

В случае реализации методов расширения для какого-либо типа необходимо помнить о следующих фактах:

  • Метод расширения никогда не будет вызван, если он имеет ту же сигнатуру, что и метод, определенный в типе.
  • Методы расширения вводятся в область действия на уровне пространства имен. Например, при наличии нескольких статических классов, содержащих методы расширения в единственном пространстве имен с именем Extensions , все они будут введены в область действия директивой using Extensions; .

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

Источник

Способ расширения — Extension method

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

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

СОДЕРЖАНИЕ

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

Методы расширения — это особенности многих языков, включая C # , Java через Manifold , 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 .

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

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

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

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

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

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

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

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

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

Лучше слабое сцепление

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

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

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

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

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

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

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

Представление

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

Читайте также:  Волнушки нужно вымачивать перед засолкой горячим способом

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

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

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

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

Проблема

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Оцените статью
Разные способы