← Назад к вопросам

Как работает extension под капотом?

3.0 Senior🔥 61 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает extension под капотом?

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

Что такое Extension?

Extension — это синтаксический сахар, который компилируется в статические методы. Это НЕ модификация класса, а создание вспомогательных функций, которые вызываются на объекте благодаря синтаксису с точкой.

extension NameExtension on String {
  String get firstCharacter => isEmpty ? '' : this[0];
  
  String repeatString(int times) {
    return this * times;
  }
}

void main() {
  print("Hello".firstCharacter);  // H
  print("Ha".repeatString(3));     // HaHaHa
}

Как это компилируется

Компилятор преобразует extension методы в обычные статические функции:

// Исходный код с extension
extension StringExtension on String {
  String get doubled => this + this;
}

// Как это компилируется (примерно)
class StringExtension {
  static String get doubled(String instance) => instance + instance;
}

// Использование
"Hello".doubled;  // Преобразуется в StringExtension.doubled("Hello")

Под капотом

Шаг 1: Анализ кода Когда компилятор видит extension, он анализирует тип, для которого добавляются методы (в данном случае String).

Шаг 2: Создание вспомогательного класса Компилятор создает скрытый класс с статическими методами, где первый параметр — это экземпляр расширяемого класса.

Шаг 3: Трансформация вызовов Когда вы пишите "hello".length, это компилируется в простой вызов. Но когда вы пишите "hello".doubled, компилятор преобразует это в вызов статического метода:

// Исходный код
var result = "hello".doubled;

// После компиляции
var result = StringExtension.doubled("hello");

Практический пример

// Исходный code
extension IntExtension on int {
  bool get isEven => this % 2 == 0;
  
  int squared() => this * this;
}

void main() {
  print(4.isEven);      // true
  print(5.squared());   // 25
}

// Приблизительно то, что происходит при компиляции
class IntExtension {
  static bool getIsEven(int instance) => instance % 2 == 0;
  static int squared(int instance) => instance * instance;
}

void main() {
  print(IntExtension.getIsEven(4));      // true
  print(IntExtension.squared(5));        // 25
}

Как работает method resolution

Когда Dart видит вызов метода, он ищет его в этом порядке:

  1. Методы самого класса — если String имеет метод foo(), используется он
  2. Методы суперклассов — если String наследует Object с методом foo(), используется он
  3. Extensions — только если методов нет выше в иерархии
extension on String {
  String get length => "custom";  // Перепишет настоящий length? НЕТ!
}

void main() {
  print("hello".length);  // 5, а не "custom"
  // Extensions не могут переопределить существующие члены
}

Extension на Generic типах

Ext можна работать с generic типами:

extension ListExtension<T> on List<T> {
  T? getFirstOrNull() => isEmpty ? null : first;
  
  List<T> reversed() => [...this].reversed.toList();
}

void main() {
  var numbers = [1, 2, 3];
  print(numbers.getFirstOrNull());  // 1
  
  var strings = ["a", "b", "c"];
  print(strings.getFirstOrNull());  // a
}

Компилятор корректно обрабатывает generic типы, подставляя их в статические методы.

Performance: есть ли потери?

Вопрос: Медленнее ли вызов extension метода, чем обычного?

Ответ: НЕТ! Все optimizations компилятора также применяются к extension методам. Dart VM и JIT компилятор могут inline extension методы так же, как обычные.

extension on int {
  bool get isPositive => this > 0;
}

// Это может быть скомпилировано в одну инструкцию при optimizations
print(5.isPositive);

Видимость Extensions

Extension имеют область видимости (scope):

// file1.dart
extension StringExtension on String {
  String get uppercase => toUpperCase();
}

// file2.dart
import 'file1.dart';

void main() {
  print("hello".uppercase);  // HELLO - видно благодаря импорту
}

// file3.dart (без импорта)
void main() {
  print("hello".uppercase);  // Ошибка! Extension не видна
}

Named Extensions

Ext можно называть для явного управления при конфликтах:

extension StringExt1 on String {
  String get doubled => this + this;
}

extension StringExt2 on String {
  String get doubled => "[$this]";  // Конфликт имен!
}

void main() {
  // Какой doubled используется?
  print("hello".doubled);
  
  // Можно явно указать
  print(StringExt1("hello").doubled);  // hellohello
  print(StringExt2("hello").doubled);  // [hello]
}

Extension методы vs наследование

// ❌ Неправильно (нельзя наследовать от String)
class MyString extends String {
  String get doubled => this + this;
}

// ✅ Правильно (используем extension)
extension on String {
  String get doubled => this + this;
}

Extension лучше, чем наследование для добавления функциональности, потому что:

  • String встроенный класс (immutable, нельзя наследовать)
  • Extension декларативно показывают, что это дополнительная функциональность
  • Не создаётся новый тип, расширяется существующий

Практические примеры из реальных проектов

1. DateTime Extension

extension DateTimeExtension on DateTime {
  bool get isToday => 
    year == DateTime.now().year &&
    month == DateTime.now().month &&
    day == DateTime.now().day;
  
  String get formattedTime => 
    '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
}

void main() {
  final now = DateTime.now();
  print(now.isToday);        // true
  print(now.formattedTime);  // 14:30
}

2. BuildContext Extension

extension BuildContextExtension on BuildContext {
  ThemeData get theme => Theme.of(this);
  
  MediaQueryData get mediaQuery => MediaQuery.of(this);
  
  void showSnackBar(String message) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }
}

// Использование в Widget
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => context.showSnackBar("Clicked!"),
      child: Text(
        "Tap me",
        style: TextStyle(color: context.theme.primaryColor),
      ),
    );
  }
}

Ограничения Extensions

extension on String {
  // ✅ Методы и getter - работают
  String get upper => toUpperCase();
  String reverse() => split('').reversed.join('');
  
  // ❌ Свойства с setter - НЕ работают
  // String value = ""; // ОШИБКА!
  
  // ❌ Конструкторы - НЕ работают
  // String() {} // ОШИБКА!
  
  // ❌ Статические методы - НЕ работают
  // static String create() {} // ОШИБКА!
}

Заключение

Extension в Dart — это чистая синтаксическая функция, которая компилируется в статические методы. Это позволяет:

  • Добавлять методы к встроенным типам (String, List, int)
  • Делать код более читаемым и выразительным
  • Избегать создания wrapper классов
  • Не модифицировать исходные классы

Под капотом это просто вызов статического метода с первым параметром = экземпляр, но синтаксис делает это очень элегантно и удобно. Это одна из самых сильных фич Dart для написания читаемого и поддерживаемого кода.

Как работает extension под капотом? | PrepBro