Методы расширения позволяют добавлять новую функциональность к существующим типам и типам сторонних библиотек, не изменяя исходный код исходного класса. Автодополнение IDE показывает методы расширения вместе с собственными методами типа.
Вы не можете изменять предоставленные сторонними разработчиками API, но хотите добавить удобные вспомогательные методы.
Пример задачи: использовать запись '42'.parseInt() вместо int.parse('42').
Синтаксическая структура
extension ИмяРасширения? on ЦелевойТип {
// Методы, геттеры, сеттеры, операторы, статические члены
}Code language: JavaScript (javascript)
Пример
// Весь код размещайте в одном файле, разделение не требуется, функция main запускается напрямую
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
void main() {
print('42'.parseInt()); // 42
print('3.14'.parseDouble()); // 3.14
print('100'.padLeft(6)); // Сравнение со встроенным методом String
}Code language: Dart (dart)
Результат выполнения
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
42
3.14
100Code language: CSS (css)
Имя расширения необязательно; this ссылается на экземпляр, вызывающий расширение.
Поддерживаемые типы членов в расширениях
Внутри расширения можно определять: методы экземпляра, геттеры, сеттеры, операторы и статические члены.
extension StringExt on String {
// геттер
bool get isEmptyOrBlank => trim().isEmpty;
// Метод экземпляра
String repeat(int count) => List.filled(count, this).join();
// Перегрузка оператора
String operator *(int count) => repeat(count);
// Статический член (доступ через имя расширения)
static String emptyStr = '';
static void printTip() => print("Статический метод расширения строк");
}
void main() {
var str = " dart ";
print(str.isEmptyOrBlank);
print("hi" * 3);
print("hello".repeat(2));
// Доступ к статическим членам
print(StringExt.emptyStr);
StringExt.printTip();
}Code language: PHP (php)
Результат выполнения
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
false
hihihi
hellohello
Статический метод расширения строкCode language: CSS (css)
Безымянные расширения (анонимные расширения)
Если не указать имя расширения, оно будет видно только внутри текущей библиотеки. Разрешить конфликты явным вызовом невозможно.
Из внешнего кода вызвать его синтаксисом ИмяРасширения(экземпляр) нельзя.
extension on String {
bool get isBlank => trim().isEmpty;
}
void main() {
print(" ".isBlank); // true
}Code language: Dart (dart)
Статические члены внутри анонимного расширения можно использовать только внутри самого расширения.
Обобщенные расширения (Generic extensions)
Можно создавать расширения для обобщенных типов, например List<T>
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
void main() {
List<int> list = [1, 2, 3, 4];
print(list.doubleLength); // 8
print(-list); // [4,3,2,1]
print(list.split(2)); // [[1,2],[3,4]]
}Code language: Dart (dart)
Результат выполнения
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
8
[4, 3, 2, 1]
[[1, 2], [3, 4]]Code language: CSS (css)
Статический тип static и dynamic
Переменные типа dynamic не могут вызывать методы расширения
Методы расширения разрешаются статически. Тип переменной dynamic определяется только во время выполнения, поэтому расширение не найдется:
void main() {
dynamic d = '2';
// print(d.parseInt()); // Во время выполнения возникнет ошибка NoSuchMethodError
var v = '2';
print(v.parseInt()); // Тип выводится как String, код работает корректно
}
extension NumberParsing on String {
int parseInt() => int.parse(this);
}Code language: JavaScript (javascript)
Причина: привязка методов расширения происходит на этапе компиляции по статическому типу переменной, а не поиском во время выполнения.
Способы разрешения конфликтов имен API
Существует три решения при наличии одноименных членов в нескольких расширениях.
1: Скрыть конфликтующие расширения при импорте с помощью show / hide
// string_apis.dart содержит NumberParsing
// string_apis_2.dart содержит NumberParsing2 с методом parseInt
import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;
void main() {
print('42'.parseInt()); // Используется реализация из string_apis.dart
}Code language: Dart (dart)
2: Явный вызов расширения (оборачивающий синтаксис)
import 'string_apis.dart'; // NumberParsing
import 'string_apis_2.dart'; // NumberParsing2
void main() {
// print('42'.parseInt()); // Конфликт, ошибка компиляции
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
}Code language: Dart (dart)
3: Импортировать библиотеки с префиксом
Используйте префиксы для различения двух расширений с одинаковым именем:
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
void main() {
print(NumberParsing('42').parseInt());
print(rad.NumberParsing('42').parseInt());
// Методы расширения без конфликтов можно вызывать неявно
print('42'.parseNum());
}Code language: Dart (dart)
Методы расширения (Extension methods)