Экстеншн что это такое в программировании
Перейти к содержимому

Экстеншн что это такое в программировании

  • автор:

Экстеншн что это такое в программировании

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

Например, нам надо добавить для типа string новый метод:

string s = "Привет мир"; char c = 'и'; int i = s.CharCount(c); Console.WriteLine(i); public static class StringExtension < public static int CharCount(this string str, char c) < int counter = 0; for (int i = 0; i < str.Length; i++) < if (str[i] == c) counter++; >return counter; > >

Для того, чтобы создать метод расширения, вначале надо создать статический класс, который и будет содержать этот метод. В данном случае это класс StringExtension . Затем объявляем статический метод. Суть нашего метода расширения — подсчет количества определенных символов в строке.

Собственно метод расширения — это обычный статический метод, который в качестве первого параметра всегда принимает такую конструкцию: this имя_типа название_параметра , то есть в нашем случае this string str . Так как наш метод будет относиться к типу string, то мы и используем данный тип.

Затем у всех строк мы можем вызвать данный метод:

int i = s.CharCount(c);

Причем нам уже не надо указывать первый параметр. Значения для остальных параметров передаются в обычном порядке.

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

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

Функции-расширения (Extension functions)

Java содержит множество классов с замечательными методами. Но разработчику всегда не хватает ещё одного метода, который ему нужен для собственного проекта. Функции-расширения позволяют создавать видимость внедрения в существующий класс нового метода. Это позволяет более элегантно решить проблему с классами Utils, которые создают разработчики для подобных целей. Кроме того, созданные функции будут выводиться в подсказках при наборе кода вместе с стандартными методами класса.

Рассмотрим пример на Java. Как узнать, является ли указанная дата субкотой (иногда люди используют слово «суббота»)? У класса Date нет соответствующего метода и мы можем определить субботу только по методу getDay(), который возвращает число 6 для субботы.

Создадим класс Utils.java и поместим свой метод.

 static boolean isSaturday(Date date)

Вызываем метод по щелчку кнопки.

 public void onClick(View view)

Теперь рассмотрим, как это можно сделать на Kotlin.

Добавим функцию в активность и вызовем её по щелчку кнопки.

 fun Date.isSaturday(): Boolean < return getDay() == 6 >button_choose.setOnClickListener

Теперь, если смотреть на код, то создаётся ощущение, что isSaturday() является частью класса Date и мы вызываем его прямо из класса.

Предыдущий пример с функцией был написан в Java-стиле. В Kotlin можно заменить метод getDay() на свойство day.

 fun Date.isSaturday(): Boolean

Впрочем и это не предел. Подобную функцию можно переписать в одну строку.

 fun Date.isSaturday(): Boolean = day == 6 

Если у вас будет несколько подобных функций-расширений, то удобнее их хранить в отдельном файле (не обязательно в классе). Создадим дополнительный пакет utils с файлом Utils.kt.

 package ru.alexanderklimov.counter.utils import java.util.* fun Date.isSaturday() = day == 6 // другие функции-расширения 

В активности при вызове функции следует импортировать её (возможно это сделает сама студия автоматически).

 import ru.alexanderklimov.counter.utils.isSaturday // код для кнопки останется прежним 

Можно переопределить имя функции при помощи as:

 import ru.alexanderklimov.counter.utils.isSaturday as caturday val saturday = now.caturday() 

Другие примеры из различных докладов и презентаций.

Функция для получения последнего символа из строки.

 fun String.lastChar() = get(length - 1) // Применяем к строке val cat = "Мурзик" val c: Char = cat.lastChar() textview_info.text = cat.lastChar().toString() // длинный вариант 

Знакомый нам пример. Мы добавляем в класс Context новую функцию toast(), которая вызывает метод Toast.makeText() с использованием параметров по умолчанию.

 fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) < Toast.makeText(this, message, duration).show() >// вызываем в активности toast("Hello Kitty") 

JetBrains добавила свой набор расширений для различных классов Java и Android. Самый показательный случай — набор расширений для коллекций — функции filter(), map(), count() и т.д. в функциональном стиле, даже используя старую Java 6.

Помните, что функции-расширения класса не могут обращаться к его членам с модификаторами protected или private.

Ключевое слово inflix

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

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

 fun Int.isGreater(value: Int): Boolean < return this >value > // вызываем функцию 3.isGreater(9) // false 

Перепишем функцию с ключевым словом infix.

 infix fun Int.isGreater(value: Int): Boolean < return this >value > // вызываем функцию 12 isGreater 9 // true 

Расширения (extensions)

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

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

Функции-расширения

Для того чтобы объявить функцию-расширение, укажите в качестве префикса расширяемый тип, то есть тип, который мы расширяем. Следующий пример добавляет функцию swap к MutableList :

fun MutableList.swap(index1: Int, index2: Int) < val tmp = this[index1] // 'this' даёт ссылку на список this[index1] = this[index2] this[index2] = tmp >

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

val list = mutableListOf(1, 2, 3) list.swap(0, 2) // 'this' внутри 'swap()' будет содержать значение 'list' 

`, and you can make it generic: —>

Следующая функция имеет смысл для любого MutableList , и вы можете сделать её обобщённой:

fun MutableList.swap(index1: Int, index2: Int) < val tmp = this[index1] // 'this' относится к списку this[index1] = this[index2] this[index2] = tmp >

Вам нужно объявлять обобщённый тип-параметр перед именем функции для того, чтобы он был доступен в получаемом типе-выражении. См. Обобщения.

Расширения вычисляются статически

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

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

open class Shape class Rectangle: Shape() fun Shape.getName() = "Shape" fun Rectangle.getName() = "Rectangle" fun printClassName(s: Shape) < println(s.getName()) >printClassName(Rectangle()) 

Этот пример выведет нам Shape на экран потому, что вызванная функция-расширение зависит только от объявленного параметризованного типа s , который является Shape классом.

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

class Example < fun printFunctionType() < println("Class method") >> fun Example.printFunctionType() < println("Extension function") >Example().printFunctionType() 

Этот код выведет Class method.

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

class Example < fun printFunctionType() < println("Class method") >> fun Example.printFunctionType(i: Int) < println("Extension function #$i") >Example().printFunctionType(1) 

Обращение к Example().printFunctionType(1) выведет на экран надпись Extension function #1.

Расширение null-допустимых типов

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

Благодаря этому метод toString() в Kotlin вызывается без проверки на null : она проходит внутри функции-расширения.

fun Any?.toString(): String < if (this == null) return "null" // после проверки на null, `this` автоматически приводится к не-null типу, // поэтому toString() обращается (ориг.: resolves) к функции-члену класса Any return toString() >

Свойства-расширения

Аналогично функциям, Kotlin поддерживает расширения свойств.

val List.lastIndex: Int get() = size - 1 

Since extensions do not actually insert members into classes, there’s no efficient way for an extension > property to have a [backing field](properties.md#backing-fields). This is why _initializers are not allowed for > extension properties_. Their behavior can only be defined by explicitly providing getters/setters. —>

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

val House.number = 1 // ошибка: запрещено инициализировать значения // в свойствах-расширениях 

Расширения для вспомогательных объектов (ориг.: companion object extensions)

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

class MyClass < companion object < >// называется "Companion" > fun MyClass.Companion.printCompanion()

Область видимости расширений

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

package org.example.declarations fun List.getLongestString() < /*. */>

Для того, чтобы использовать такое расширение вне пакета, в котором оно было объявлено, импортируйте его на месте вызова.

package org.example.usage import org.example.declarations.getLongestString fun main()

См. Импорт для более подробной информации.

Объявление расширений в качестве членов класса

Внутри класса вы можете объявить расширение для другого класса. Внутри такого объявления существует несколько неявных объектов-приёмников (ориг.: implicit receivers), доступ к членам которых может быть произведён без квалификатора. Экземпляр класса, в котором расширение объявлено, называется диспетчером приёмников (ориг.: dispatch receiver), а экземпляр класса, для которого вызывается расширение, называется приёмником расширения (ориг.: extension receiver).

class Host(val hostname: String) < fun printHostname() < print(hostname) >> class Connection(val host: Host, val port: Int) < fun printPort() < print(port) >fun Host.printConnectionString() < printHostname() // вызывает Host.printHostname() print(":") printPort() // вызывает Connection.printPort() >fun connect() < /*. */ host.printConnectionString() // вызов функции-расширения >> fun main() < Connection(Host("kotl.in"), 443).connect() // Host("kotl.in").printConnectionString() // ошибка, функция расширения недоступна вне подключения >

В случае конфликта имён между членами классов диспетчера приёмников и приёмников расширения, приоритет имеет приёмник расширения. Чтобы обратиться к члену класса диспетчера приёмников, можно использовать синтаксис this с квалификатором.

class Connection < fun Host.getConnectionString() < toString() // вызывает Host.toString() this@Connection.toString() // вызывает Connection.toString() >> 

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

open class Base < >class Derived : Base() < >open class BaseCaller < open fun Base.printFunctionInfo() < println("Base extension function in BaseCaller") >open fun Derived.printFunctionInfo() < println("Derived extension function in BaseCaller") >fun call(b: Base) < b.printFunctionInfo() // вызов функции расширения >> class DerivedCaller: BaseCaller() < override fun Base.printFunctionInfo() < println("Base extension function in DerivedCaller") >override fun Derived.printFunctionInfo() < println("Derived extension function in DerivedCaller") >> fun main() < BaseCaller().call(Base()) // "Base extension function in BaseCaller" DerivedCaller().call(Base()) // "Base extension function in DerivedCaller" - приемник отправки является виртуальным DerivedCaller().call(Derived()) // "Base extension function in DerivedCaller" - приемник расширения является статическим >

Примечание о видимости

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

  • Расширение, объявленное на верхнем уровне файла, имеет доступ к другим private объявлениям верхнего уровня в том же файле;
  • Если расширение объявлено вне своего типа приёмника, оно не может получить доступ к private или protected членам приёмника.

© 2015—2024 Open Source Community

С# extension methods: простой, но полезный пример

Что делает данный код? Он расширяет все классы реализующие интерфейс IList методом ListToString, который позволяет получить перечисление элементов списка через запятую в виде строки. Для расширения функционала стандартного интерфейса всего-то и нужно теперь подключить пространство имен SampleNS. (Комментарий: благодаря весьма полезному замечанию easyman, пример переписан с использованием класса StringBuilder)

Вот пример использования:

var _list = DataContextORM.ExecuteQuery("Select name from products").ToList(); string result = _list.ListToString();

Этот пример получит в _list список названий всех продуктов, а затем присвоит строку с перечислением названий через запятую переменной result.

PS: перенес из персонального блога

  • C sharp
  • extension methods

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *