Erweiterungsmethoden (Extension methods)

Erweiterungsmethoden ermöglichen es, bestehenden Typen und Typen aus Drittbibliotheken neue Funktionen hinzuzufügen, ohne den Quellcode der ursprünglichen Klasse zu ändern. Die Codevervollständigung der IDE zeigt Erweiterungsmethoden gemeinsam mit nativen Methoden an.

Sie können von Dritten bereitgestellte APIs nicht ändern, möchten aber praktische Hilfsmethoden ergänzen.

Beispielanforderung: '42'.parseInt() statt int.parse('42') verwenden.

Syntaxaufbau

extension Erweiterungsname? on Zieltyp {
  // Methoden, Getter, Setter, Operatoren, statische Member
}Code-Sprache: JavaScript (javascript)

Beispiel

// Alles in einer Datei schreiben, keine Aufteilung nötig, main wird direkt ausgeführt
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));      // Vergleich mit nativer String-Methode
}Code-Sprache: Dart (dart)

Ausgabeergebnis

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
42
3.14
   100Code-Sprache: CSS (css)

Der Erweiterungsname ist optional; this steht für die Instanz, die die Erweiterung aufruft.

Unterstützte Member-Arten in Erweiterungen

Innerhalb einer Erweiterung lassen sich definieren: Instanzmethoden, Getter, Setter, Operatoren und statische Member.

extension StringExt on String {
  // Getter
  bool get isEmptyOrBlank => trim().isEmpty;

  // Instanzmethode
  String repeat(int count) => List.filled(count, this).join();

  // Operatorüberladung
  String operator *(int count) => repeat(count);

  // Statisches Member (Zugriff über Erweiterungsnamen)
  static String emptyStr = '';
  static void printTip() => print("Statische Methode der String-Erweiterung");
}

void main() {
  var str = " dart ";
  print(str.isEmptyOrBlank);
  print("hi" * 3);
  print("hello".repeat(2));

  // Zugriff auf statische Member
  print(StringExt.emptyStr);
  StringExt.printTip();
}Code-Sprache: PHP (php)

Ausgabeergebnis

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
false
hihihi
hellohello

Statische Methode der String-ErweiterungCode-Sprache: JavaScript (javascript)

Unbenannte Erweiterungen (Anonyme Erweiterungen)

Wenn Sie keinen Erweiterungsnamen angeben, ist diese nur innerhalb der aktuellen Bibliothek sichtbar. Namenskonflikte können nicht durch expliziten Aufruf aufgelöst werden.

Sie können sie von außen nicht mit der Syntax Erweiterungsname(Instanz) aufrufen.

extension on String {
  bool get isBlank => trim().isEmpty;
}

void main() {
  print("   ".isBlank); // true
}Code-Sprache: Dart (dart)

Statische Member einer anonymen Erweiterung können nur innerhalb der Erweiterung verwendet werden.

Generische Erweiterungen (Generic extensions)

Sie können Erweiterungen für generische Typen erstellen, zum Beispiel 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-Sprache: Dart (dart)

Ausgabeergebnis

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
8
[4, 3, 2, 1]
[[1, 2], [3, 4]]Code-Sprache: CSS (css)

Statischer Typ static vs dynamic

Variablen vom Typ dynamic können keine Erweiterungsmethoden aufrufen

Erweiterungsmethoden werden statisch aufgelöst. Bei dynamic-Variablen wird der Typ erst zur Laufzeit ermittelt, dadurch können Erweiterungen nicht gefunden werden:

void main() {
  dynamic d = '2';
  // print(d.parseInt()); // Zur Laufzeit tritt ein NoSuchMethodError auf

  var v = '2';
  print(v.parseInt()); // Typ wird als String abgeleitet, funktioniert einwandfrei
}

extension NumberParsing on String {
  int parseInt() => int.parse(this);
}Code-Sprache: JavaScript (javascript)

Grund: Erweiterungsmethoden werden zur Kompilierzeit anhand des statischen Typs der Variable gebunden, nicht per Suche zur Laufzeit.

Lösungen für API-Namenskonflikte

Es gibt drei Vorgehensweisen, wenn mehrere Erweiterungen Member mit identischem Namen enthalten.

1: Konfliktierende Erweiterungen beim Import mit show / hide ausblenden

// string_apis.dart enthält NumberParsing
// string_apis_2.dart enthält NumberParsing2 mit gleicher Methode parseInt
import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;

void main() {
  print('42'.parseInt()); // Nutzt die Implementierung aus string_apis.dart
}Code-Sprache: Dart (dart)

2: Expliziter Aufruf der Erweiterung (Wrapper-Syntax)

import 'string_apis.dart';    // NumberParsing
import 'string_apis_2.dart';  // NumberParsing2

void main() {
  // print('42'.parseInt()); // Namenskonflikt → Kompilierfehler
  print(NumberParsing('42').parseInt());
  print(NumberParsing2('42').parseInt());
}Code-Sprache: Dart (dart)

3: Bibliotheken mit Präfix importieren

Verwenden Sie Präfixe zur Unterscheidung zweier Erweiterungen mit identischem Namen:

import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

void main() {
  print(NumberParsing('42').parseInt());
  print(rad.NumberParsing('42').parseInt());

  // Konfliktfreie Erweiterungsmethoden lassen sich weiterhin implizit aufrufen
  print('42'.parseNum());
}Code-Sprache: Dart (dart)

Erweiterungsmethoden (Extension methods)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert