Saltar al contenido principal

Métodos de extensión

Aprende cómo añadir funcionalidad a APIs existentes.

Los métodos de extensión añaden funcionalidad a las bibliotecas existentes. Es posible que utilices métodos de extensión sin siquiera saberlo. Por ejemplo, cuando utilizas el autocompletado de código en un IDE, este sugiere métodos de extensión junto con los métodos normales.

Si ver videos te ayuda a aprender, echa un vistazo a esta descripción general de los métodos de extensión.

Ver en YouTube en una nueva pestaña: "Métodos de extensión en Dart"

Descripción general

#

Cuando estás utilizando la API de otra persona o cuando implementas una biblioteca que se utiliza ampliamente, a menudo es poco práctico o imposible cambiar la API. Pero es posible que aún quieras añadir alguna funcionalidad.

Por ejemplo, considera el siguiente código que analiza una cadena para convertirla en un entero:

dart
int.parse('42')

Podría ser agradable —más corto y más fácil de usar con herramientas— que esa funcionalidad estuviera en String en su lugar:

dart
'42'.parseInt()

Para habilitar ese código, puedes importar una biblioteca que contenga una extensión de la clase String:

dart
import 'string_apis.dart';

void main() {
  print('42'.parseInt()); // Use an extension method.
}

Las extensiones pueden definir no solo métodos, sino también otros miembros como getters, setters y operadores. Además, las extensiones pueden tener nombres, lo que puede ser útil si surge un conflicto de APIs. Así es como podrías implementar el método de extensión parseInt(), utilizando una extensión (llamada NumberParsing) que opera sobre cadenas:

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

La siguiente sección describe cómo usar métodos de extensión. Después hay secciones sobre cómo implementar métodos de extensión.

Usar métodos de extensión

#

Como todo el código de Dart, los métodos de extensión están en bibliotecas. Ya has visto cómo usar un método de extensión: simplemente importa la biblioteca en la que se encuentra y úsalo como un método ordinario:

dart
// Import a library that contains an extension on String.
import 'string_apis.dart';

void main() {
  print('42'.padLeft(5)); // Use a String method.
  print('42'.parseInt()); // Use an extension method.
}

Eso es todo lo que normalmente necesitas saber para usar métodos de extensión. Al escribir tu código, es posible que también necesites saber cómo los métodos de extensión dependen de los tipos estáticos (a diferencia de dynamic) y cómo resolver conflictos de APIs.

Tipos estáticos y dynamic

#

No puedes invocar métodos de extensión en variables de tipo dynamic. Por ejemplo, el siguiente código genera una excepción en tiempo de ejecución:

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

Los métodos de extensión funcionan con la inferencia de tipos de Dart. El siguiente código es correcto porque se infiere que la variable v tiene el tipo String:

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

La razón por la que dynamic no funciona es que los métodos de extensión se resuelven con respecto al tipo estático del receptor. Debido a que los métodos de extensión se resuelven de forma estática, son tan rápidos como llamar a una función estática.

Para obtener más información sobre los tipos estáticos y dynamic, consulta El sistema de tipos de Dart.

Conflictos de APIs

#

Si un miembro de extensión entra en conflicto con una interfaz o con otro miembro de extensión, tienes algunas opciones.

Una opción es cambiar la forma en que importas la extensión en conflicto, usando show o hide para limitar la API expuesta:

dart
// 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;

void main() {
  // Uses the parseInt() defined in 'string_apis.dart'.
  print('42'.parseInt());
}

Otra opción es aplicar la extensión explícitamente, lo que da como resultado un código que parece como si la extensión fuera una clase envoltura (wrapper class):

dart
// 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.

void main() {
  // print('42'.parseInt()); // Doesn't work.
  print(NumberParsing('42').parseInt());
  print(NumberParsing2('42').parseInt());
}

Si ambas extensiones tienen el mismo nombre, es posible que debas importarlas usando un prefijo:

dart
// 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;

void main() {
  // 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());
}

Como muestra el ejemplo, puedes invocar métodos de extensión implícitamente incluso si los importas usando un prefijo. El único momento en que necesitas usar el prefijo es para evitar un conflicto de nombres al invocar una extensión explícitamente.

Implementar métodos de extensión

#

Usa la siguiente sintaxis para crear una extensión:

extension <extension name>? on <type> { // <extension-name> is optional
  (<member definition>)* // Can provide one or more <member definition>.
}

Por ejemplo, así es como podrías implementar una extensión en la clase String:

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

  double parseDouble() {
    return double.parse(this);
  }

}

Los miembros de una extensión pueden ser métodos, getters, setters u operadores. Las extensiones también pueden tener campos estáticos y métodos de ayuda estáticos. Para acceder a miembros estáticos fuera de la declaración de la extensión, invócalos a través del nombre de la declaración como en variables y métodos de clase.

Extensiones sin nombre

#

Al declarar una extensión, puedes omitir el nombre. Las extensiones sin nombre son visibles solo en la biblioteca donde se declaran. Dado que no tienen nombre, no se pueden aplicar explícitamente para resolver conflictos de APIs.

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

Implementar extensiones genéricas

#

Las extensiones pueden tener parámetros de tipo genérico. Por ejemplo, aquí tienes un código que extiende el tipo incorporado List<T> con un getter, un operador y un método:

dart
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)];
}

El tipo T se vincula en función del tipo estático de la lista sobre la cual se llaman los métodos.

Recursos

#

Para obtener más información sobre los métodos de extensión, consulta lo siguiente: