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 outro Object. 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!!!!!)

Fontes

Did you find this article valuable?

Support Lucas Náiade's blog by becoming a sponsor. Any amount is appreciated!