Métodos de extensão permitem adicionar novas funcionalidades aos tipos existentes e tipos de bibliotecas de terceiros sem alterar o código-fonte da classe original. O preenchimento automático da IDE exibe os métodos de extensão juntamente com os métodos nativos.
Você não consegue alterar APIs fornecidas por outras pessoas, mas deseja criar métodos auxiliares práticos.
Exemplo de requisito: utilizar '42'.parseInt() em vez de int.parse('42').
Estrutura da sintaxe
extension NomeDaExtensão? on TipoAlvo {
// Métodos, getters, setters, operadores, membros estáticos
}Code language: JavaScript (javascript)
Exemplo
// Deixe todo o código em um único arquivo, não é necessário dividir; main executa diretamente
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)); // Comparação com método nativo da String
}Code language: Dart (dart)
Resultado da execução
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
42
3.14
100Code language: CSS (css)
O nome da extensão é opcional; this representa a instância que invoca a extensão.
Tipos de membros suportados por extensões
Dentro de uma extensão é possível definir: métodos de instância, getters, setters, operadores e membros estáticos.
extension StringExt on String {
// getter
bool get isEmptyOrBlank => trim().isEmpty;
// Método de instância
String repeat(int count) => List.filled(count, this).join();
// Sobrecarga de operador
String operator *(int count) => repeat(count);
// Membro estático (acessível pelo nome da extensão)
static String emptyStr = '';
static void printTip() => print("Método estático da extensão de string");
}
void main() {
var str = " dart ";
print(str.isEmptyOrBlank);
print("hi" * 3);
print("hello".repeat(2));
// Acessar membros estáticos
print(StringExt.emptyStr);
StringExt.printTip();
}Code language: PHP (php)
Resultado da execução
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
false
hihihi
hellohello
Método estático da extensão de stringCode language: CSS (css)
Extensões sem nome (extensões anônimas)
Ao omitir o nome da extensão, ela ficará visível apenas na biblioteca atual. Não é possível resolver conflitos por meio de chamada explícita.
Não é possível chamá-la externamente com a sintaxe NomeDaExtensão(instância).
extension on String {
bool get isBlank => trim().isEmpty;
}
void main() {
print(" ".isBlank); // true
}Code language: Dart (dart)
Membros estáticos em extensões anônimas só podem ser usados dentro da própria extensão.
Extensões genéricas (Generic extensions)
Você pode criar extensões para tipos genéricos, como 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)
Resultado da execução
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
8
[4, 3, 2, 1]
[[1, 2], [3, 4]]Code language: CSS (css)
Tipo estático static vs dynamic
Variáveis do tipo dynamic não conseguem invocar métodos de extensão
Métodos de extensão são resolvidos de forma estática. O tipo de uma variável dynamic só é definido em tempo de execução, logo a extensão não é localizada:
void main() {
dynamic d = '2';
// print(d.parseInt()); // Erro NoSuchMethodError em tempo de execução
var v = '2';
print(v.parseInt()); // O tipo é inferido como String e funciona normalmente
}
extension NumberParsing on String {
int parseInt() => int.parse(this);
}Code language: JavaScript (javascript)
Motivo: Os métodos de extensão são vinculados durante a compilação com base no tipo estático da variável, não por busca em tempo de execução.
Soluções para conflitos de nomes em APIs
Existem três abordagens quando múltiplas extensões possuem membros com nomes idênticos.
1: Ocultar extensões conflitantes com show / hide na importação
// string_apis.dart contém NumberParsing
// string_apis_2.dart contém NumberParsing2 que também implementa parseInt
import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;
void main() {
print('42'.parseInt()); // Usa a implementação de string_apis.dart
}Code language: Dart (dart)
2: Chamada explícita da extensão (sintaxe de empacotamento)
import 'string_apis.dart'; // NumberParsing
import 'string_apis_2.dart'; // NumberParsing2
void main() {
// print('42'.parseInt()); // Conflito, erro de compilação
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
}Code language: Dart (dart)
3: Importar bibliotecas com prefixo
Use prefixos para diferenciar duas extensões com nomes iguais:
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
void main() {
print(NumberParsing('42').parseInt());
print(rad.NumberParsing('42').parseInt());
// Métodos de extensão sem conflito ainda podem ser chamados implicitamente
print('42'.parseNum());
}Code language: Dart (dart)
Métodos de extensão (Extension methods)