Методы расширения, представленные в Dart 2.7, позволяют добавить функциональность в существующие библиотеки. Вы можете использовать методы расширения, даже не подозревая об этом. Например, когда вы используете автозавершение кода в среде IDE, оно предлагает методы расширения наряду с обычными методами.

Обзор

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

Например, рассмотрим следующий код, который преобразует строку в целое число:

int.parse('42')

Было бы неплохо - короче и проще в использовании с инструментами - вместо этого включить эту функцию String:

'42'.parseInt()

Чтобы включить этот код, вы можете импортировать библиотеку, которая содержит расширение класса String:

import 'string_apis.dart';
// ···
print('42'.parseInt()); // Use an extension method.

Расширения могут определять не только методы, но и другие члены, такие как методы получения, установки и операторы. Кроме того, у расширений есть имена, которые могут быть полезны при возникновении конфликта API. Вот как можно реализовать метод расширения parseInt(), используя расширение (с именем NumberParsing), которое работает со строками:

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
  // ···
}

В следующем разделе описывается, как использовать методы расширения. После этого идут разделы о реализации методов расширения.

Использование методов расширения

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

// Import a library that contains an extension on String.
import 'string_apis.dart';
// ···
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

Это все, что вам обычно нужно знать, чтобы использовать методы расширения. При написании кода вам также может потребоваться знать, как методы расширения зависят от статических типов (в отличие от dynamic) и как разрешать конфликты API.

Статические типы и динамические

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

dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError

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

var v = '2';
print(v.parseInt()); // Output: 2

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

Для получения дополнительной информации о статических типах и dynamic см. Система типов Dart.

Конфликты API

Если член расширения конфликтует с интерфейсом или с другим членом расширения, у вас есть несколько вариантов.

Один из вариантов - изменить способ импорта конфликтующего расширения, используя show или hide, чтобы ограничить доступный API:

// Defines the String extension method parseInt().
import 'string_apis.dart';
// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;
// ···
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());

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

// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.
// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());

Если оба расширения имеют одинаковое имя, вам может потребоваться импорт с использованием префикса:

// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
// ···
// print('42'.parseInt()); // Doesn't work.
// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());
// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());
// Only string_apis_3.dart has parseNum().
print('42'.parseNum());

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

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

Используйте следующий синтаксис для создания расширения:

extension <extension name> on <type> {
  (<member definition>)*
}

Например, вот как можно реализовать расширение в классе String:

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
  double parseDouble() {
    return double.parse(this);
  }
}

lib / string_apis.dart

Чтобы создать локальное расширение, которое будет отображаться только в той библиотеке, в которой оно объявлено, опустите имя расширения или дайте ему имя, начинающееся с символа подчеркивания (_).

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

Реализация универсальных расширений

Расширения могут иметь параметры универсального типа. Например, вот код, расширяющий встроенный тип List<T> с помощью геттера, оператора и метода:

extension MyFancyList<T> on List<T> {
  int get doubleLength => length * 2;
  List<T> operator -() => reversed.toList();
  List<List<T>> split(int at) => <List<T>>[sublist(0, at), sublist(at)];
}

Тип T связан на основе статического типа списка, для которого вызываются методы.

Узнать больше о методах расширения Dart 2.7