Photo by Aleksandr Kadykov on Unsplash
Genéricos no Dart/Flutter: Arranhando a superfície 🌶
Tudo no Dart vem de Object mas nem todo Object recebe outro Object. Dynamic é tudo desde que se descubra quem ele é. - DEVaneios
Um tipo genérico é basicamente um modelo de dado que pode receber outros tipos de dados sem se importar com seu tipo.
O Dart é por padrão uma linguagem type safe
, no final sempre é garantido que o dado seja adequado ao seu tipo, aqui que entram os tipos genéricos garantindo que você possa ter um código reutilizável com menor duplicação ou segurança na tipagem caso precise.
Tudo no Dart vem de object mas nem todo
Object
recebe outroObject
.Dynamic
é tudo desde que se descubra quem ele é. Pt merd4... 🤣
Object 🤸♂️
Não é bem um tipo genérico mas a classe Odin -> pai de todos
.
A classe Object é uma superclasse onde tudo no Dart deriva(menos null), em muitos casos usa-se Object para inferir que um tipo genérico não possa ser null
.
class Catiorro<T extends Object> {} // T não pode ser null
class Bichano<T> {} // T pode ser null
Para fazer alguma operação em um tipo Object que referencia um outro tipo, só se dá através de casting.
Object inteiro = 1;
inteiro = inteiro + 1; // aqui temos um erro;
inteiro = (inteiro as int) + 1; // já aqui não chora na neneca;
print(inteiro.runtimeType.toString()); // por coincidência em tempo de execução é int, legal? 🧐
A classe Object quase não tem membros em sua composição e o casting deve ser obrigatório como por exemplo no codiguinho acima.
dynamic 🦸
Já o nosso amigo dynamic
não esquenta com nada, mas em alguns casos pode se tornar menos seguro por que ele não reflete tudo.
Por padrão genéricos são de fato dynamics, então em um Catiorro<K, V>
K e V são dynamics a não ser que tenha alguma inferência Catiorro<K extends Neneca, V extends String>
class Neneca{}
class Catiorro<K extends Neneca, V extends String>{}
dynamic inteiro = 1;
inteiro = inteiro + 1;
inteiro.chorandoNaNeneca(); // nem tudo são flores
O Object te força ao casting inevitável já o dynamic não esquenta com a cabeça e por isso é recomendável que se analise o uso em seu código - Forçar o casting ou não? Sempre vai depender das suas abordagens
Velocidade ⌛
Em algumas medições usando o package benchmark_harness fica evidente uma vantagem do dynamic
para dados em listas.
import 'package:benchmark_harness/benchmark_harness.dart';
class ListBenchmark extends BenchmarkBase {
final String listName;
final dynamic list;
const ListBenchmark(this.list, this.listName) : super(listName);
static void main(dynamic list, String listName) {
ListBenchmark(list, listName).report();
}
@override
void run() {
list.sort();
}
@override
void setup() {}
@override
void teardown() {}
@override
void exercise() => run();
}
void main() {
final listDynamic = <dynamic>[5, 9, 1, 4, -11, 10, 300, 3, 4, 5, 9, 1, 4, -11, 10, 300, 3, 4, 5, 9, 1, 4, -11, 10, 300, 3, 4];
final listInt = <int>[5, 9, 1, 4, -11, 10, 300, 3, 4, 5, 9, 1, 4, -11, 10, 300, 3, 4, 5, 9, 1, 4, -11, 10, 300, 3, 4];
final listObject = <Object>[5, 9, 1, 4, -11, 10, 300, 3, 4, 5, 9, 1, 4, -11, 10, 300, 3, 4, 5, 9, 1, 4, -11, 10, 300, 3, 4];
// Run Benchmark
ListBenchmark.main(listDynamic, 'Lista com dynamic');
ListBenchmark.main(listInt, 'Lista tipada com int');
ListBenchmark.main(listObject, 'Lista tipada com Object');
}
Resultou em algo que talvez te deixe confuso mas que não tem impacto real dependendo da sua aplicação.
Lista com dynamic(RunTime): 0.29372134156476754 us.
Lista tipada com int(RunTime): 0.3137372864007163 us.
Lista tipada com Object(RunTime): 0.3100669393288707 us.
Exited
*µs => microsegundos
Legalzinho de ver 🌶
Meu amigo kmartins.dev pediu uma ajuda para lançar uma exceção caso um campo seja null sem precisar usar o Null assertion operator
, parecido com o getOrThrow do Kotlin
.
Em conjunto chegamos em uma solução usando genéricos:
X getOrThrow<X>(X? x) => x ?? (throw Exception()); 🤔
Esquisito ou não? 😵💫
Com isso o kmartins.dev chegou na seguinte extension para Dart/Flutter
extension ObjectExt<T> on T?{
T getOrThrow() => this ?? (throw Exception());
T? getOrNull() => this;
}
Com isso temos:
final int? valor = 2;
final receber= valor.getOrThrow();
final naoReceber = valor.getOrNull();
Como diria o Xaropinho, rapaaaaaaaaaaizzzzzzzzz
🐀
(Dá like ae!!!!!)