Découverte du Langage Dart

Ce chapitre vous montre comment utiliser les fonctionnalités majeures de Dart, des variables et opérateurs jusqu’aux classes et bibliothèques, en supposant que vous savez déjà programmer dans un autre langage.

Consultez les spécifications Dart lorsque vous souhaitez en savoir plus sur une des fonctionnalités du langages.

Un simple programme Dart

Le programme suivant utilise quelques unes des fonctionnalités de base de Dart :

// Définition d'une fonction.
afficheNombre(num unNombre) {
  print('Le nombre est $unNombre.'); // Affiche dans la console
}

// C’est ici que l’application commence son exécution.
main() {
  var nombre = 42; // Déclare et initialise une variable.
  afficheNombre(nombre); // Appel d'une fonction.
}

Voici ce qu’utilise ce programme et qu’utilisent toutes (ou presque toutes) les applications Dart :

// Ceci est un commentaire.

Utilisez // pour indiquer que le reste de la ligne est un commentaire. Vous pouvez également utiliser /* … */. Pour plus de détails, voir Commentaires.

num

Un type. Quelques un des autres types natifs sont String, int, and bool.

42

Un nombre littéral. Les littéraux sont une sorte de constante à la compilation.

print()

Un moyen pratique d’afficher dans la sortie.

'...' (ou "...")

Une chaine de caratère.

$nomDeVariable (ou ${expression})

L’interpolation: permet l’évaluation de variables ou d’expressions à l’intérieur d’une chaîne de caractères littérale. Pour plus d’informations, Voir Strings.

main()

La fonction spéciale, nécessaire et de premier niveau où l’application démarre. Pour plus d’information, Voir La fonction main().

var

Une façon de déclarer une variable sans avoir à préciser son type.

Concepts importants

Pendant que vous apprenez le langage Dart, gardez ces principes et concepts en mémoire :

  • Tout ce que vous pouvez placer dans une variable est un objet, et tout objet est une instance d’une classe. Y compris les nombres, les fonctions et null sont des objets. Tous les objets héritent de la classe Object.

  • Le typage statique (comme num dans l’exemple précédent) clarifie votre intention et permet aux outils de détecter les erreurs de types, mais il reste optionnel. (Pendant que vous debuggez votre code, vous pourrez remarquer que les variables dont le type n’a pas été spécifié ont un type spécial : dynamic.)

  • Dart analyse tout votre code avant de le lancer. Vous pouvez fournir des indications à Dart, par exemple, en précisant les types ou les constantes de compilation, pour détecter les erreurs ou encore pour accélérer l’exécution de votre code.

  • Dart supporte les fonctions de premier niveau (tel que main()), tout comme elles peuvent être attachées à une classe ou un objet (respectivement, fonctions statiques et méthodes d’instance). Vous pouvez aussi créer des fonctions à l’intérieur d’une fonction (fonctions imbriquées ou fonctions internes).

  • De la même façon, Dart supporte les variables de premier niveau, tout comme elles peuvent être attachées à une classe ou un objet (variables statiques et variables d’instance). Les variables d’instance sont aussi nommées champs ou propriétés.

  • Contrairement à Java, Dart n’a pas les mots-clés public, protected et private. Si un identifiant commence avec un souligner (_), il est privé à sa librairie. Pour plus de détails, voir Bibliothèques et visibilité.

  • Les identifiants peuvent commencer par une lettre ou _, suivi de n’importe quelle combinaison de ces caractères ou chiffres.

  • Il est parfois important de distinguer expression et instruction, nous allons préciser la différence entre ces deux mots.

  • Les outils Dart peuvent rapporter deux types de problèmes : des alertes et des erreurs. Les alertes indiquent juste que votre code peut ne pas fonctionner, mais n’empêche pas l’exécution de votre programme. Une erreur de compilation empêche totalement l’exécution de votre programme; une erreur à l’exécution résulte d’une exception qui est remontée lorsque le code s’exécute.

  • Dart a deux modes d’exécution : production et checked. Nous recommandons de développer et debugger en mode checked, et déployer en mode production.

    Le mode production est le mode d’exécution par défaut d’un programme Dart, optimisé pour la vitesse. Le mode production ignore les instructions d’assertion et le typage statique.

    Le mode checked est un mode pour les développeurs, qui aide à détecter certaines erreurs de type à l’exécution. Par exemple, si on affecte une chaîne de caractères à une variable déclarée comme num, alors le mode checked lance une exception.

Mots clés

Le tableau suivant liste les mots clés que le langage Dart traite spécialement.

abstract 1 continue false new this
as 1 default final null throw
assert deferred 1 finally operator 1 true
async 2 do for part 1 try
async* 2 dynamic 1 get 1 rethrow typedef 1
await 2 else if return var
break enum implements 1 set 1 void
case export 1 import 1 static 1 while
catch external 1 in super with
class extends is switch yield 2
const factory 1 library 1 sync* 2 yield* 2

1 Les mots annotés 1 sont des identifiants primitifs. Evitez d’utiliser les identifants primitifs en tant qu’identifiant, et ne les utilisez jamais en nom de classe ou de type. Les identifiants primitifs existent pour faciliter le portage de JavaScript à Dart. Par exemple, si du code JavaScript a une variable nommée factory, il n’est pas nécessaire de la renommer lorsque vous portez le code en Dart.

2 Les mots annotés 2 sont nouveaux, ces mots réservés sont liés au support de l’asynchronisme qui a été ajouté après la sortie de la version 1.0 de Dart. Vous ne pouvez pas utiliser async, await, ou yield en tant qu’identifiant dans le corps d’une fonction marquée avec async, async*, or sync*. Pour plus d’information, voir Support de l’asynchronisme.

Tous les autres mots de ce tableau sont des mots réservés. Vous ne pouvez pas utiliser les mots réservés en tant qu’identifiant.

Variables

Ici un exemple de création de variables et d’affectation de valeur:

var name = 'Bob';

Les variables sont des références. La variable name contient une référence vers une chaine de caractères qui a pour valeur “Bob”.

Valeur par défaut

Les variables non initialisées ont null pour valeur initiale, même les variables numériques car les nombres sont des objets.

int lineCount;
assert(lineCount == null);
// Les variables (même si numériques) sont initialisées à null.

Types optionnels

Vous avez la possibilité d’assigner un type statique à une variable lors de sa déclaration:

String name = 'Bob';

Mettre des types est un bon moyen de spécifier son intention. Les outils comme les compilateurs et éditeurs peuvent utiliser ces types pour vous aider, en fournissant de la l’auto-complétion de code ou des alertes préventives sur des bugs.

Final et const

Si vous n’avez pas l’intention de changer une variable, utiliser final ou const, plutôt que var ou en complément à un type. Une variable final peut seulement être affectée une fois; une variable const peut être une constante de compilation.

Une variable de premier niveau ou de classe déclarée comme final est initialisée à sa première utilisation:

final nom = 'Bob';  // Ou: final String nom = 'Bob';
// nom = 'Alice';   // Décommenter cette ligne produit une erreur

Utilisez const pour les variables que vous souhaitez utiliser comme constantes de compilation. Si la variable const est au niveau de la classe, mettez la en static const (les variables d’instances ne peuvent pas être const). Là où vous déclarez la variable, mettez la valeur en constante de compilation comme un litéral, une variable const, ou le résultat d’une opération arithmétique sur une constante numérique.

const bar = 1000000;       // Unité de pression (en dynes/cm2)
const atm = 1.01325 * bar; // Atmosphere standard

Types natifs

Les types suivants sont nativement supportés par le langage Dart :

  • les nombres
  • les chaînes de caractères
  • les booléens
  • les listes (aussi connues comme tableaux)
  • les dictionnaires (aussi appelés tableaux associatifs)
  • les symboles

Vous pouvez initialiser un objet de n’importe quel de ces types en utisant une valeur littérale. Par exemple, 'ceci est une chaîne de caractères' est une chaîne de caractères littérale, et true est un booléen littéral.

Comme toute variable en Dart fait référence à un objet, c’est-à-dire, à une instance d’une classe, vous pouvez utiliser des constructeurs pour initialiser ces variables. Certains de ces types natifs ont leurs propres constructeurs. Par exemple, vous pouvez utiliser le constructeur Map() pour créer un dictionnaire, en utilisant le code tel que new Map().

Nombres

Il existe deux types de nombre en Dart :

int

Les valeurs entières, qui doivent être généralement comprise entre -253 et 253

double

Les nombres à virgule flottante 64-bit (double précision), tel que spécifié dans le standart IEEE 754

Les deux types int et double sont des sous-types de num. Le type num inclut des opérateurs de base tel que +, -, /, et *, mais aussi les opérateurs bit à bit tel que >>. Vous trouverez aussi dans le type num, abs(), ceil(), et floor(), parmi d’autres méthodes. Si num et ses sous-types n’ont pas ce que vous recherchez, la librairie Math peut vous aider.

Les entiers sont des nombres sans décimale. Voici quelques exemples de nombres entiers littéraux :

var x = 1;
var hex = 0xDEADBEEF;
var grosEntier = 34653465834652437659238476592374958739845729;

Si un nombre inclut une décimale, c’est un double. Voici quelques exemples de nombres réels littéraux :

var y = 1.1;
var dixPuissance = 1.42e5;

Voici comment transformer une chaîne de caractères en nombre, ou vice versa :

// String -> int
var un = int.parse('1');
assert(un == 1);

// String -> double
var unVirguleUn = double.parse('1.1');
assert(unVirguleUn == 1.1);

// int -> String
String unEnString = 1.toString();
assert(unEnString == '1');

// double -> String
String piEnString = 3.14159.toStringAsFixed(2);
assert(piEnString == '3.14');

Le type int définit le traditionnel décalage de bit (<<, >>), les opérateurs ET (&), et OU (|). Par exemple :

assert((3 << 1) == 6);  // 0011 << 1 == 0110
assert((3 >> 1) == 1);  // 0011 >> 1 == 0001
assert((3 | 4)  == 7);  // 0011 | 0100 == 0111

Chaînes de caractères

Une chaîne de caractères en Dart est une suite de codes UTF-16. Vous pouvez utiliser des guillemets simples ou des guillemets doubles pour créer une chaîne de caractères :

var s1 = 'Les guillemets simples fonctionnent pour les chaînes de caractères littérales.';
var s2 = "Les guillemets doubles fonctionnent tout aussi bien.";
var s3 = 'C\'est facile d\'échapper les délimiteurs d\'une chaîne de caractères.';
var s4 = "C'est encore plus facile d'utiliser l'autre délimiteur.";

Vous pouvez mettre la valeur d’une expression à l’intérieur d’une chaine de caractères en utilisant ${expression}. Si l’expression est un identifiant, vous pouvez omettre les {}. Pour obtenir la chaîne de caractères correspondant à un objet, Dart appelle la méthode toString() de l’objet.

var s = 'interpolation';

assert("Dart a l'$s, ce qui est très pratique." ==
       "Dart a l'interpolation, " + 
       "ce qui est très pratique.");
assert("Le même en majuscule. " + 
       "L'${s.toUpperCase()} est très pratique !" ==
       "Le même en majuscule. " +
       "L'INTERPOLATION est très pratique !");

Vous pouvez concatener des chaînes de caractères en utilisant des chaînes littérales adjacentes ou l’opérateur + :

var s1 = 'La concatenation ' ' de chaînes de caractères'
         " fonctionne même avec des retours à la ligne.";
assert(s1 == 'La concatenation de chaînes de caractères fonctionne même avec ' +
         'des retours à la ligne.');

var s2 = "L'opérateur + "
         + 'fonctionne également.';
assert(s2 == "L'opérateur + fonctionne également.");

Une autre façon de créer des chaînes de caractères multiligne est d’utiliser un triplet de guillements qu’ils soient simple ou double :

var s1 = '''
Vous pouvez créer
des chaînes de caractères multiligne comme celle-ci.
''';

var s2 = """Celle-là est aussi une
chaîne de caractères multiligne.""";

Vous pouvez créer une chaîne de caractères “brute” en la préfixant avec r :

var s = r"Dans une chaîne de caractères brute, même \n n'est pas spécial.";

Vous pouvez utiliser l’échappement Unicode dans une chaîne de caractères :

// L'échapement Unicode fonctionne : [heart]
print("L'échapement Unicode fonctionne : \u2665");

Pour plus d’information sur comment utiliser les chaînes de caractères, voir Chaînes de caractères et expressions régulières.

Booléens

Pour représenter les valeurs booléennes, Dart a un type nommé bool. Seulement deux objets ont le type booléen: les valeurs littérales true et false, respectivement vrai et faux.

Lorsque Dart s’attend à une valeur booléenne, seulement la valeur true est considérée comme vraie. Toutes les autres valeurs sont considérées comme fausses. Contrairement à JavaScript, les valeurs telles que 1, "uneChaineDeCaractères", et unObjet sont toutes considérées comme fausses.

Par exemple, considérons le code suivant qui est valide en JavaScript et en Dart :

var nom = 'Bob';
if (nom) {
  // S'affiche en JavaScript, mais pas en Dart.
  print('Tu as un nom !');
}

Si vous lancez ce code en JavaScript, il affiche “Tu as un nom !” car nom est un objet non null. Par contre, en Dart tournant dans un mode production, le code précédent n’affiche rien car nom est converti en false (car nom != true). En Dart tournant dans un mode de vérification le code précédent lance une exception car la variable nom n’est pas un booléen.

Voici un autre exemple de code qui se comporte différement entre JavaScript et Dart :

if (1) {
  print('JS affiche cette ligne.');
} else {
  print('Dart en mode production affiche cette ligne.');
  // Cependant, en mode vérification, if (1) lance une 
  // exception car 1 n'est pas un booléen.
}

Le traitement des booléens en Dart a été conçu de façon à éviter des comportements étranges qui peuvent arriver lorsque de nombreuses valeurs sont considérées comme vraie. Ce qui signifie pour vous c’est que, au lieu d’utiliser du code comme if (valeurNonBooleene), il faut plutôt vérifier explicitement les valeurs. Par exemple :

// Vérifie que la chaîne de caractères est vide.
var nom = '';
assert(nom.isEmpty);

// Vérifie si la valeur est zero.
var pointDeVie = 0;
assert(pointDeVie <= 0);

// Vérifie si la valeur est null.
var unicorne;
assert(unicorne == null);

// Vérifie si la valeur n'est pas un nombre.
var jeVoulaisLeFaire = 0 / 0;
assert(jeVoulaisLeFaire.isNaN);

Listes

Peut-être la collection la plus commune dans presque tous les langages de programmation est le tableau, ou un groupe ordonné d’objets. Dans Dart, les tableaux sont des objets List, donc nous les appelons juste listes.

Les listes littérales Dart ressemblent aux tableaux littéraux de JavaScript. Voici un exemple d’une liste Dart :

var liste = [1, 2, 3];

Les listes utilisent une indexation à partir de zero, c’est-à-dire que l’index 0 est l’index du premier élément et list.length - 1 est l’index du dernier élément. Vous pouvez obtenir la longueur de la liste et faire référence aux éléments de la liste comme vous le feriez en JavaScript :

var liste = [1, 2, 3];
assert(liste.length == 3);
assert(liste[1] == 2);

Le type List a plein de méthodes pratiques pour manipuler les listes. Pour plus d’information sur les listes, voir les Génériques et les Collections.

Dictionnaires

En général, un dictionnaire est un objet qui associe des clés et des valeurs. Clés et valeurs peuvent être de n’importe quel type d’objet. Chaque clé n’apparait qu’une seule fois, mais vous pouvez utiliser une même valeur plusieurs fois. Le support des dictionnaires par Dart est fait grâce aux dictionnaires en valeurs littérales et au type Map.

Voici quelques exemples simples de dictionnaire en Dart, créés en utilisant des valeurs littérales :

var cadeaux = {
// Clés          Valeurs
  'premier'   : 'perdrix',
  'deuxième'  : 'tourterelle',
  'cinquième' : 'faisan'
};

var gazNobles = {
// Clés  Valeurs
  2 :   'hélium',
  10:   'néon',
  18:   'argon',
};

Vous pouvez créer les mêmes objets en utilisant le constructeur Map :

var cadeaux = new Map();
cadeaux['premier'] = 'perdrix';
cadeaux['deuxième'] = 'tourterelle';
cadeaux['cinquième'] = 'anneaux d\'or';

var gazNobles = new Map();
gazNobles[2] = 'hélium';
gazNobles[10] = 'néon';
gazNobles[18] = 'argon';

Ajoutez une nouvelle paire clé-valeur à un dictionnaire existant comme vous le feriez en JavaScript :

var cadeaux = {'premier': 'perdrix'};
cadeaux['quatrième'] = 'merles noirs'; // Ajoute une paire clé-valeur

Récupérez une valeur d’un dictionnaire de la même façon que vous le feriez en JavaScript :

var cadeaux = {'premier': 'perdrix'};
assert(cadeaux['premier'] == 'perdrix');

Si vous cherchez une clé qui n’est pas dans le dictionnaire, vous obtenez un null en retour :

var cadeaux = {'premier': 'perdrix'};
assert(cadeaux['cinquième'] == null);

Utilisez .length pour obtenir le nombre de paires clé-valeur dans le dictionnaire :

var cadeaux = {'premier': 'perdrix'};
cadeaux['quatrième'] = 'merles noirs';
assert(cadeaux.length == 2);

Pour plus d’information sur les dictionnaires, voir les Génériques et Maps.

Symboles

Un objet Symbol représente un opérateur ou un identifiant déclaré dans un programme Dart. Vous n’aurait peut être jamais le besoin d’utiliser les symboles, mais ils sont indispensables pour les APIs qui font référence à un identifiant par son nom, car la minification change les noms des identifiants mais pas ceux des symboles.

Pour obtenir le symbole pour un identifiant, utilisez un symbole littéral, qui est juste un # suivi de l’identifiant :

#radix
#bar

Pour plus d’information sur les symboles, voir dart:mirrors - reflection.

Fonctions

Voici un exemple d’implémentation d’une fonction:

void afficherNombre(num nombre) {
  print('Le nombre est $nombre.');
}

Bien que les conventions recommandent de spécifier les types de paramètre et de retour, ce n’est pas obligatoire :

afficherNombre(nombre) { // Ne pas mettre le type est accepté
  print('Le nombre est $nombre.');
}

Pour les fonctions qui contiennent seulement une expression, vous pouvez utiliser la syntaxe abrégée:

afficherNombre(nombre) => 
    print('Le nombre est $nombre.');

La syntaxe => expr; est un raccourci pour { return expr;}. Dans la fonction afficherNombre(), l’expression est l’appel à la fonction de premier niveau print().

Voici un exemple d’appel à une fonction:

afficherNombre(123);

Une fonction peut avoir deux types de paramètres: requis ou optionnel. Les paramètres requis sont placés en premier, suivi par les paramètres optionnels.

Paramètres optionnels

Les paramètres optionnels peuvent être soit positionné ou nommé, mais pas les deux en même temps.

Les deux peuvent avoir une valeur par défaut. Elle doit être une constante de compilation telle qu’une valeur littérale. Si aucune valeur par défaut n’est fournie, la valeur est null.

Paramètres positionnés ou nommés

Lors de l’appel à une fonction, vous pouvez spécifier un paramètre positionnés ou nommés par nomParametre: valeur. Par exemple:

activerFlag(bold: true, hidden: false);

Lors de la définition d’une fonction: {param1, param2, …} Pour spéficier les paramètres nommés:

/// Affecte les flags [bold] et [hidden] aux valeurs 
/// indiquées.
activerFlags({bool bold, bool hidden}) {
  // ...
}

Utilisez deux points (:) pour indiquer la valeur par défaut:

/// Affecte les flags [bold] et [hidden] aux valeurs
/// indiquées, par défaut à false
activerFlags({bool bold: false, bool hidden: false}) {
  // ...
}

// bold sera à true, hidden à faux.
activerFlags(bold: true);

Paramètres optionnels positionnés

Placer un ensemble de paramètres de fonctions entre des [] les indiquent comme des paramètres optionnels positionnés :

String dire(String de, String message, [String support]) {
  var resultat = '$de dit $message';
  if (support != null) {
    resultat = '$resultat avec un $support';
  }
  return resultat;
}

Voici un exemple d’appel à cette fonction sans paramètre optionnel :

assert(dire('Bob', 'Salut') == 'Bob dit Salut');

Et ici, un exemple d’appel à cette fonction avec un troisième paramètre:

assert(dire('Bob', 'Salut', 'signal de fumée') ==
    'Bob dit Salut avec un signal de fumée');

Utilisez = pour indiquer la valeur par défaut :

String dire(String de, String message,
    [String support = 'pigeon voyageur', String humeur]) {
  var resultat = '$de dit $message';
  if (support != null) {
    resultat = '$resultat avec un $support';
  }
  if (mood != null) {
    resultat = '$resultat (dans une humeur $humeur)';
  }
  return resultat;
}

assert(dire('Bob', 'Salut') == 
    'Bob dit Salut avec un pigeon voyageur');

La fonction main()

Toute application doit avoir une fonction de premier niveau main() qui sert de point d’entrée. Elle retourne void et a un paramètre optionnel List<String> comme argument.

Voici un exemple de la fonction main() pour une application web:

void main() {
  querySelector("#sample_text_id")
    ..text = "Clique moi!"
    ..onClick.listen(inverserTexte);
}

Voici un exemple de la fonction main() pour une application en ligne de commandes qui prend des arguments :

// Lancer le code comme cela: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

Vous pouvez utiliser la bibliothèque args pour définir et analyser des lignes de commandes.

Fonctions comme des objets

Vous pouvez passer une fonction comme paramètre à une autre fonction. Par exemple :

afficherElement(element) {
  print(element);
}

var liste = [1, 2, 3];

// Passe afficherElement comme un paramètre.
liste.forEach(afficherElement);

Vous pouvez aussi affecter une fonction à une variable comme ceci :

var masjusculer = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(masjusculer('hello') == '!!! HELLO !!!');

Portée lexicale

Dart est un langage à portée lexicale, ce qui signifie que la portée d’une variable est déterminée statiquement, simplement par la mise en page du code. Vous pouvez “scruter les accolades limitrophes” pour voir si une variable est dans la portée.

Voici un exemple de test d’égalité sur des fonctions de premier niveau, des méthodes statiques, et des méthodes d’instance :

var premierNiveau = true;

main() {
  var dansLeMain = true;

  maFonction() {
    var dansLaFonction = true;

    fonctionImbriquee() {
      var dansLaFonctionImbriquee = true;

      assert(premierNiveau);
      assert(dansLeMain);
      assert(dansLaFonction);
      assert(dansLaFonctionImbriquee);
    }
  }
}

Notez comment fonctionImbriquee() peut utiliser des variables de chaque niveau, , en remontant jusqu’au premier niveau.

Closures lexicales

Une closure est un objet fonction qui a un accès aux variables de sa portée lexicale, même si la fonction est utilisée en dehors de sa portée d’origine.

Les fonctions peuvent se refermer sur les variables définies dans des portées environnantes. Dans l’exemple suivant, creeAdditionneur() capture la variable ajouterPar. Partout où la fonction retounée est utilisée, il rapelle ajouterPar.

/// Retourne une fonction qui ajoute [ajouterPar] 
/// à l'argument de la fonction.
Function creeAdditionneur(num ajouterPar) {
  return (num i) => ajouterPar + i;
}

main() {
  // Crée une méthode qui ajoute 2.
  var add2 = creeAdditionneur(2);
  
  // Crée une méthode qui ajoute 2.
  var add4 = creeAdditionneur(4);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

Tests d’égalité de méthodes

Voici un exemple de test d’égalité de fonctions de premier niveau, de méthodes statiques et de méthodes d’instance.

foo() {}               // Une fonction de premier niveau

class SomeClass {
  static void bar() {} // Une méthode statique
  void baz() {}        // Une méthode d'instance
}

main() {
  var x;

  // Comparaison de fonctions de premier niveau.
  x = foo;
  assert(foo == x);

  // Comparaison de méthodes statiques.
  x = A.bar;
  assert(A.bar == x);

  // Comparaison de méthodes d'instance.
  var v = new A(); // Instance #1 de A
  var w = new A(); // Instance #2 de A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

Valeurs de retour

Toutes les fonctions retournent une valeur. Si aucune valeur n’est retournée, l’instruction return null; est implicitement ajoutée au corps de la fonction.

Opérateurs

Les opérateurs définis par Dart sont présentés dans le tableau suivant. Vous pouvez surcharger certains de ces opérateurs, comme décrit dans Surcharge d’opérateurs.

Description Opérateur
post-fixé unaire expr++    expr--    ()    []    .
pre-fixé unaire -expr    !expr    ~expr    ++expr    --expr   
multificatif *    /    %  ~/
additif +    -
décalage <<    >>
ET binaire &
OU exclusif binaire ^
OU binaire |
comparateur et test de type >=    >    <=    <    as    is    is!
égalité ==    !=   
ET logique &&
OU logique ||
conditionnel expr1 ? expr2 : expr3
cascade ..
assignation =    *=    /=    ~/=    %=    +=    -=    <<=    >>=    &=    ^=    |=   

Quand vous utilisez les opérateurs, vous créez des expressions. Voici quelques exemples d’expressions utilisant des opérateurs :

a++
a + b
a = b
a == b
a ? b: c
a is T

Dans le précédent tableau des opérateurs, chaque opérateur a une plus haute priorité que les opérateurs dans des lignes suivantes.

Par exemple, l’opérateur multiplicatif % a une priorité plus importante que (et de ce fait s’éxecute avant) l’opérateur ==, qui a lui même une priorité plus importante que l’opérateur logique AND &&. Cette priorié signifie que les deux lignes de code suivantes s’exécutent de la même façon :

// 1: Les parenthèses améliorent la lisibilité.
if ((n % i == 0) && (d % i == 0))

// 2: Plus difficile à lire mais identique.
if (n % i == 0 && d % i == 0)

Les opérateurs arithmétiques

Dart supporte les opérateurs arithmétiques usuels, comme montré dans le tableau suivant.

Operateur Signification
+ Ajoute
Soustrait
-expr Moins unaire, aussi connu comme négation (inverse le signe de l’expression)
* Multiplie
/ Divise
~/ Divise, retourne un résultat entier
% Donne le reste d’une division entière (modulo)

Example:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5);   // Le résultat est un double
assert(5 ~/ 2 == 2);    // Le résultat est un entier
assert(5 % 2 == 1);     // Reste

print('5/2 = ${5~/2} reste ${5%2}'); // 5/2 = 2 reste 1

Dart supporte également les opérateurs de pré et post incrémentation et décrémentation

Opérateur Signification
++var var = var + 1 (la valeur de l’expression est var + 1)
var++ var = var + 1 (la valeur de l’expression est var)
--var var = var – 1 (la valeur de l’expression est var – 1)
var-- var = var – 1 (la valeur de l’expression est var)

Exemple:

var a, b;

a = 0;
b = ++a;        // Incrémente a avant d'attribuer la valeur à b.
assert(a == b); // 1 == 1

a = 0;
b = a++;        // Incrémente a après avoir attribué sa valeur à b.
assert(a != b); // 1 != 0

a = 0;
b = --a;        // Decrémente a avant d'attribuer sa valeur à b.
assert(a == b); // -1 == -1

a = 0;
b = a--;        // Decrémente a après avoir attribué sa valeur à b.
assert(a != b); // -1 != 0

Opérateurs d’égalité et de comparaison

Le tableau suivant liste les significations des opérateurs d’égalité et de comparaison.

Opérateur Signification
== Egal; voir plus bas
!= Différent
> Plus grand que
< Plus petit que
>= Plus grand ou égal
<= Plus petit ou égal

Pour tester si deux objets x et y sont identiques, utilisez l’opérateur ==. (Dans les rares cas où vous avez besoin de savoir si deux objets sont éxactement le même, utilisez la fonction identical() à la place.) Voici comment fonctionne l’opérateur ==:

  1. Si x ou y est nul, renvoie vrai si les deux sont nuls, et faux si seulement l’un d’eux est nul.

  2. Retourne le résultat de l’invocation x.==(y). (C’est ça, les opérateurs tels que == sont des méthodes invoquées sur leur premier opérande. Vous pouvez surcharger grand nombre de ces opérateurs, y compris ==, comme vous pouvez le voir dans Surcharge d’opérateurs.)

Voilà un exemple d’utilisation de chacun des opérateurs d’égalité et de comparaison :

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

Opérateurs de test sur les types

Les opérateurs as, is, and is! sont très utiles pour tester les types durant l’éxecution.

Opérateur Signification
as Conversion de type
is Vrai si l’objet a le même type
is! Faux si l’objet n’a pas le même type

Le résultat de obj is T est vrai si obj implémente l’interface précisée par T. Par exemple, obj is Object est toujours vrai.

Utilisez l’opérateur as pour convertir un objet dans un type spécifique. En général, vous devez l’utiliser comme raccourci pour un test is sur un objet suivi d’une expression utilisant cet objet. Par exemple, dans le code suivant :

if (emp is Person) { // Vérification de type
  emp.firstName = 'Bob';
}

Vous pouvez simplifier le code en utilisant l’opérateur as :

(emp as Person).firstName = 'Bob';

Opérateurs d’assignation

Comme nous l’avons déjà vu, vous pouvez assigner des valeurs en utilisant l’opérateur =. Vous pouvez également utiliser des opérateurs composés tels que +=, qui combine une opération avec une assignation.

= –= /= %= >>= ^=
+= *= ~/= <<= &= |=

Voici comment fonctionnent les opérateurs d’assignation :

  Assignation composée Expression équivalente
Pour un opérateur op: a op= b a = a op b
Exemple: a += b a = a + b

L’exemple suivant utilise à la fois une assignation et une assignation composeé :

var a = 2;           // Assignation utilisant =
a *= 3;              // Assignation et multiplication : a = a * 3
assert(a == 6);

Opérateurs logiques

Vous pouvez inverser ou combiner des expressions booléenes en utilisant des opérateurs logiques.

Operateur Signification
!expr Inverse l’expression qui le suit (change faux en vrai, et vice versa)
|| OU logique
&& ET logique

Voici un exemple d’utilisation des opérateurs logiques :

if (!done && (col == 0 || col == 3)) {
  // ...Fait quelque chose...
}

Opérateurs binaires et de décalage de bits

Vous pouvez manipuler individuellement les bits des nombres en Dart. Généralement, vous utiliserez ces opérateurs binaires et de décalage de bits avec des entiers.

Opérateur Signification
& ET
| OU
^ OU Exclusif
~expr Complément binaire à 1 (les 0 deviennent des 1; les 1 deviennent des 0)
<< Décalage à gauche
>> Décalage à droite

Voici un exemple d’utilisation des opérateurs binaires et de décalage :

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask)  == 0x02);  // ET
assert((value & ~bitmask) == 0x20);  // ET NON
assert((value | bitmask)  == 0x2f);  // OU
assert((value ^ bitmask)  == 0x2d);  // OU Exclusif
assert((value << 4)       == 0x220); // Décalage à gauche
assert((value >> 4)       == 0x02);  // Décalage à droite

Autres opérateurs

Il reste quelques opérateurs, vous en avez déjà vu la plupart dans les autres exemples.

Opérateur Nom Signification
() Applique une fonction Représente un appel de fonction
[] Accède à une liste Réfère à la valeur dans la liste à l’index spécifié
expr1 ? expr2 : expr3 Conditionnel Si expr1 est vraie, exécute expr2; sinon, exécute expr3
. Accède un membre Réfère à une propriété d’une expression; exemple: foo.bar sélectionne la propriété bar de l’expression foo
.. Cascade Vous permet d’effectuer plusieurs opérations sur les membres d’un même objet; décrit dans Classes

Structures de contrôle

Vous pouvez contrôler le flux d’exécution de votre code Dart en utilisant :

  • if et else

  • boucles for

  • boucles while et do-while

  • break et continue

  • switch et case

  • assert

Vous pouvez aussi modifier le flux en utilisant try-catch et throw, comme expliqué dans Exceptions.

If et else

Dart permet les instructions if avec des instructions else optionnelles, comme le montre l’exemple suivant. Voir aussi les expressions conditionnelles, qui sont abordées dans Autres opérateurs.

if (estPluvieux()) {
  vous.prendreImpermeable();
} else if (estNeigeux()) {
  vous.porterBlouson();
} else {
  voiture.conduireDecapotable();
}

N’oubliez pas, contrairement à JavaScript, Dart considère toutes les valeurs autre que true comme false. Voir Booléens pour plus d’informations.

Boucles for

Vous pouvez itérer avec une boucle for classique. Par exemple :

var message = new StringBuffer("Dart est fun");
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Les closures à l’intérieur d’une boucle capturent la valeur de l’index, évitant un piège classique existant en Javascript. Par exemple, prenons :

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

La sortie est 0 puis 1 comme voulu. A l’inverse, l’exemple afficherait 2 puis 2 en JavaScript.

Si l’objet sur lequel on itére est un Iterable, vous pouvez utiliser la méthode forEach(). Utiliser forEach() est une bonne solution si vous n’avez pas besoin de connaître la valeur courante du compteur incrémental:

candidats.forEach((candidat) => candidat.entretien());

Les classes Iterable tel que List et Set autorisent aussi les itérations for-in :

var collection = [0, 1, 2];
for (var x in collection) {
  print(x);
}

While et do-while

Une boucle while évalue la condition avant la boucle :

while (!estFait()) {
  faireQuelqueChose();
}

Une boucle do-while évalue la condition après la boucle :

do {
  afficherLigne();
} while (!aLaFinDePage());

Break et continue

Utiliser break pour stopper une boucle :

while (true) {
  if (extinctionDemandee()) break;
  traitementDesRequetesEntrantes();
}

Utiliser continue pour passer à la prochaine itération de la boucle:

for (int i = 0; i < candidats.length; i++) {
  var candidat = candidats[i];
  if (candidat.anneeExperience < 5) {
    continue;
  }
  candidat.entretien();
}

Vous pourriez écrire cet exemple différement si vous utilisez un Iterable comme une liste ou un set :

candidats.where((c) => c.anneeExperience >= 5)
          .forEach((c) => c.entretien());

Switch et case

Les instructions switch en Dart comparent les entiers, chaines de caractères, ou constantes de compilations en utilisant ==. Les objets comparés doivent tous être des instances de la même classe (et non pas l’un de ses sous types), et la classe doit surcharger ==. Les types énumérés fonctionnent également dans les instructions switch.

Chaque case non vide prend fin avec une instruction break, c’est une règle. Une autre façon possible de fermer un case non vide et une instruction continue, throw ou return.

Utilisez une clause default pour exécuter du code lorsque aucune clause ne correspond :

var commande = 'OUVRIR';
switch (commande) {
  case 'FERME':
    executeFerme();
    break;
  case 'EN_ATTENTE':
    executeEnAttente();
    break;
  case 'APPOUVE':
    executeApprouve();
    break;
  case 'INTERDIT':
    executeInterdit();
    break;
  case 'OUVERT':
    executeOuvert();
    break;
  default:
    executeInconnu();
}

L’exemple suivant omet l’instruction break de la clause case, ce qui génère une erreur :

var commande = 'OUVERT';
switch (commande) {
  case 'OUVERT':
    executeOuvert();
    // ERREUR: Le break manquant provoque une exception.

  case 'FERME':
    executeFerme();
    break;
}

Cependant, Dart permet une clause case vide, autorisant un bloc passant au travers :

var commande = 'FERME';
switch (commande) {
  case 'FERME': // Cas vide, passe au travers
  case 'FERME_IMMEDIAT':
    // Executé à la fois pour FERME et FERME_IMMEDIAT.
    executeFermeImmediat();
    break;
}

Si vous voulez vraiment passer au travers, vous pouvez utiliser une instruction continue et une étiquette :

var commande = 'FERME';
switch (commande) {
  case 'CLOSED':
    executeFerme();
    continue maintenantFerme; 
    // Continue l'exécution à l'étiquette maintenantFerme

maintenantFerme:
  case 'MAINTENANT_FERME':
    // Executé à la fois pour FERME et MAINTENANT_FERME.
    executeMaintenantFerme();
    break;
}

Une clause case peut avoir des variables locales, qui peuvent être visible seulement à l’intérieur de la portée de cette clause.

Assert

Utiliser une instruction assert pour empécher l’exécution normale si une condition booléene est fausse. Vous pouvez trouver des exemples de déclarations assert tout au long de cet ouvrage. En voici un de plus :

// S'assure que la variable a une valeur non nulle.
assert(text != null);

// S'assure que la valeur est inférieure à 100.
assert(number < 100);

// S'assure que la valeur est une URL https.
assert(urlString.startsWith('https'));

A l’intérieur des parenthèses, après assert, vous pouvez placer une expression qui renvoit une valeur booléene ou une fonction. Si la valeur d’une expression ou de la fonction retournée est vraie, l’assertion réussit et l’exécution continue. Si elle est fausse, l’assertion échoue et une exception (une AssertionError) est levée.

Exceptions

Votre code Dart peut lever et attraper des exceptions. Les exceptions sont des erreurs indiquant que quelque chose d’inattendu est survenu. Si l’exception n’est pas attrapée, l’isolate qui a levé l’exception est suspendu, et l’isolate et son programme sont interrompus.

Contrairement à Java, toutes les exceptions Dart sont des exceptions incontrôlées. Les méthodes ne déclarents par quelles exceptions elles sont susceptible de lever, et il ne vous est pas demandé de toutes les attraper.

Dart founit les types Exception et Error , ainsi que de nombreux sous types prédéfinis. Vous pouvez bien entendu définir vos propre exception. Cependant, les programmes Dart peuvent lever n’importe quel objet non nul comme une exception, pas seulement des objets Exception et Error.

Lever une exception

Voici un exemple de comment lever une exception :

throw new FormatException('Au moins 1 section attendue');

Vous pouvez également lever n’importe quel type d’objet :

throw 'A cours de lamas!';

Lever une exception étant une expression, vous pouvez lever des exceptions dans les instructions =>, ainsi que dans tout autre endroit permettant les expressions :

distanceJusqua(Point autre) => 
    throw new UnimplementedError();

Attraper une exception

Attraper, ou capturer une exception interromp la propagation de l’exception. Attraper une exception vous offre la possibilité de la traiter :

try {
  eleverPlusDeLamas();
} on PlusDeLamaException {
  acheterPlusDeLamas();
}

Pour traiter du code qui peut lever plus d’un type d’exception, vous pouvez spécifier de multiples clauses catch. La première clause catch qui correspond au type de l’exception levée traite l’exception. Si la clause catch ne spécifie pas de type, cette clause peut traiter n’importe quel type d’objet levé :

try {
  eleverPlusDeLamas();
} on PlusDeLamaException { 
  // Une exception spécifique
  acheterPlusDeLamas();
} on Exception catch (e) {  
  // N'importe quoi d'autre qui est uen exception
  print('Exception inconnue : $e');
} catch (e) {               
  // Pas de type spécifié, traite tout
  print('Quelque chose de vraiment pas connu : $e');
}

Comme le montre le code précédent, vous pouvez utiliser au choix on ou catch ou les deux. Utilisez on quand vous avez besoin de spécifier le type de l’exception. Utilisez catch lorque traitement de l’exception a besoin de l’objet exception.

Clause finally

Pour s’assurer qu’une partie de code s’exécute qu’une exception est été levée ou pas, utilisez la clause finally. Si aucune clause catch ne correspond à l’exception, cette dernière est propagée après que la clause finally n’ait été lancée :

try {
  eleverPlusDeLamas();
} finally {
  // Toujours nettoyer, même si une exception est levée.
  nettoyerLesStalles();
}

La clause finally s’exécute après n’importe quelle clause catch :

try {
  eleverPlusDeLamas();
} catch(e) {
  print('Erreur: $e');  // Traiter l'exception en premier.
} finally {
  nettoyerLesStalles();  // Ensuite nettoyer.
}

Apprenez en davantage en lisant la section Exceptions.

Classes

Dart est un langage orienté objet avec des classes et un héritage à base de mixin. Chaque objet est une instance d’une classe, et toutes les classes descendent d’Object. L’héritage à base de mixin signifie qu’un corps d’une classe peut être réutilisé dans un héritage multiple, même si toute classe (excepté pour Object) a une et une seule classe mère (superclass).

Pour créer un objet, vous pouvez utiliser le mot-clé new avec le constructeur d’une classe. Les noms des constructeurs peuvent être soit NomClasse soit NomClasse.identifiant. Par exemple :

var jsonData = JSON.decode('{"x":1, "y":2}');

// Créer un Point en utilisant Point().
var p1 = new Point(2, 2);

// Créer un Point en utilisant Point.fromJson().
var p2 = new Point.fromJson(jsonData);

Les objets ont des membres constitués de fonctions et de données (respectivement méthodes et variables d’instance). Lorsque vous appelez une méthode, vous l’invoquez sur un objet : la méthode a accès aux fonctions et données de l’objet.

Utilisez un point (.) pour faire référence à une variable d’instance ou une méthode :

var p = new Point(2, 2);

// Défini la valeur de la variable d'instance y.
p.y = 3;

// Obtient la valeur de y.
assert(p.y == 3);

// Invoque distanceTo() sur p.
num distance = p.distanceTo(new Point(4, 4));

Utilisez l’opérateur en cascade (..) lorsque vous voulez enchainer des opérations sur les membres d’un même objet :

querySelector('#button') // Récupère un objet.
    ..text = 'Cliquez pour confirmer' // Utilise ses membres.
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmé !'));

Certaines classes fournissent des constructeurs de constantes. Pour créer une constante à la compilation en utilisant constructeur de constantes, utilisez const au lieu de new :

var p = const PointImmuable(2, 2);

La construction de deux constantes de compilation identiques résulte en une seule et unique instance :

var a = const PointImmuable(1, 1);
var b = const PointImmuable(1, 1);

assert(identical(a, b)); // Ce sont les mêmes instances !

Les sections suivantes montrent comment implémenter des classes.

Variables d’instance

Voici comment déclarer des variables d’instance :

class Point {
  num x; // Déclare une variable d'instance x, initialisée à null.
  num y; // Déclare y, initialisée à null.
  num z = 0; // Déclare z, initialisée à 0.
}

Toutes les variables d’instance non initialisée ont la valeur null.

Toutes les variables d’instance génèrent une méthode getter implicite. Les variables d’instance non-finales génèrent également une méthode setter implicite. Pour plus de détails, voir Getters et setters.

class Point {
  num x;
  num y;
}

main() {
  var point = new Point();
  point.x = 4;             // Utilise la méthode setter pour x.
  assert(point.x == 4);    // Utilise la méthode getter pour x.
  assert(point.y == null); // Les valeurs par défaut sont nulles.
}

Si vous initialisez une variable d’instance là où elle est déclarée (plutôt que dans un constructeur ou dans une méthode), la valeur est définie quand l’instance est créée, c’est-à-dire, avant l’exécution du constructeur et de sa liste d’initialisation.

Constructeurs

Déclarez un constructeur en créant une fonction de même nom que sa classe (plus, éventuellement, un identifiant comme décrit dans Constructeurs nommés). La forme la plus commune de constructeur, le constructeur générateur, créé une nouvelle instance d’une classe :

class Point {
  num x;
  num y;

  Point(num x, num y) {
    // Il y a une meilleure façon de faire ça, restez à l'écoute.
    this.x = x;
    this.y = y;
  }
}

Le mot clé this réfère à l’instance courante.

Le modèle consistant à assigner un argument du constructeur à une variable d’instance est tellement commun, Dart possède un sucre syntaxique pour le rendre facile :

class Point {
  num x;
  num y;

  // Sucre syntaxique pour assigner x et y 
  // avant que le contenu du constructeur s'éxécute.
  Point(this.x, this.y);
}

Constructeurs par défaut

Si vous ne déclarez pas de constructeur, un constructeur par défaut est fourni pour vous. Le constructeur par défaut n’a pas d’argument et invoque le constructeur sans arguement de la classe parente.

Les constructeurs ne sont pas hérités

Les sous classes n’héritent pas des constructeurs de leur classes parentes. Une sous classe qui ne déclare pas de constructeur possède seulement le constructeur par défaut (pas d’argument, pas de nom).

Constructeurs nommés

Utilisez un constructeur nommé pour implémenter plusieurs constructeurs pour une classe ou pour plus de clareté :

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Constructeur nommé
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

Souvenez vous que les constructeurs ne sont pas hérités, ce qui sigifie qu’un constructeur nommé d’une classe parente n’est pas hérité par sa sous classe. Si vous désirez qu’une sous classe soit créé avec un constructeur nommé défini par une super classe, vous devez implémenter ce constructeur dans la sous classe.

Invoquer un constructeur non par défaut d’une superclasse

Par défaut, un constructeur d’une sous classe appelle le constructeur non nommé et à zéro paramètre de la superclasse. Si la superclasse n’a pas ce type de constructeur, alors, vous devez appeler manuellement un des constructeur de la superclasse. Indiquez le constructeur de la superclasse après le symbole deux points (:), juste avant le corps du constructeur (s’il y en a un).

class Personne {
  Personne.fromJson(Map info) {
    print('dans Personne');
  }
}

class Employe extends Personne {
  // Personne n'a pas de constructeur par défaut;
  // vous devez appeler super.fromJson(info).
  Employe.fromJson(Map info) : super.fromJson(info) {
    print('dans Employe');
  }
}

main() {
  var emp = new Employe.fromJson({});

  // Affiche:
  // dans Personne
  // dans Employe
}

Du fait que les paramètres du constructeur de la superclasse sont évalués avant d’invoquer le constructeur, un paramètre peut être une expression telle qu’un appel de fonction :

class Employe extends Personne {
  // ...
  Employe() : super.fromJson(trouveInfoParDefaut());
}

Liste de paramètres d’initialisation

Hormis l’invocation du constructeur de la classe parente, vous pouvez également initialiser les variables d’instance avant que le corps du constructeur ne soit exécuté. Séparez les paramètres d’initialisation par des virgules.

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // La liste de paramètres d'initialisation renseigne les variables d'instance
  // avant que le corps du constructeur ne s'exécute.
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('Dans Point.fromJson(): ($x, $y)');
  }
}

Redirection de constructeur

Parfois, un constructeur a pour seul objectif de rediriger vers un autre constructeur de la même classe. Le corps d’un constructeur de redirection est vide avec l’appel de constructeur cible apparaissant après le symbole (:).

class Point {
  num x;
  num y;

  // Le constructeur principal de cette classe.
  Point(this.x, this.y);

  // Délègue au constructeur principal de la classe.
  Point.surLaxeDesX(num x) : this(x, 0);
}

Constructeurs de constantes

Si votre classe produit des objets qui ne changent jamais, vous faites de ces objets des constantes de compilation. Pour ce faire, définissez un constructeur const et assurez vous que toutes les variables d’instance sont final.

class ImmutablePoint {
  final num x;
  final num y;
  const PointImmutable(this.x, this.y);
  static final PointImmutable origine = 
      const PointImmutable(0, 0);
}

Constructeurs de type fabrique

Utilisez le mot clé factory lorsque vous implémentez un constructeur qui ne crée pas toujours une nouvelle instance de sa classe. Par exemple, un constructeur de type fabrique peut retourner une instance à partir d’un cache, ou il pourrait encore retourner une instance d’un sous-type.

L’exemple suivant présente un constructeur de type fabrique qui retourne un objet depuis un cache :

class Logger {
  final String nom;
  bool silencieux = false;

  // _cache est privé à la librairie, gràce au _ qui 
  // précède son nom.
  static final Map<String, Logger> _cache = 
      <String, Logger>{};

  factory Logger(String nom) {
    if (_cache.containsKey(nom)) {
      return _cache[nom];
    } else {
      final logger = new Logger._internal(nom);
      _cache[nom] = logger;
      return logger;
    }
  }

  Logger._internal(this.nom);

  void log(String msg) {
    if (!silencieux) {
      print(msg);
    }
  }
}

Pour invoquer un constructeur de type fabrique, utilisez le mot clé new :

var logger = new Logger('IHM');
logger.log('Bouton cliqué');

Méthodes

Les méthodes sont des fonctions qui fournissent un comportement pour un objet.

Méthodes d’instance

Les méthodes d’instance sur des objets peuvent accéder aux variables d’instance et this. La méthode distanceA() dans le code suivant suivant est un exemple de méthode d’instance.

import 'dart:math';

class Point {
  num x;
  num y;
  Point(this.x, this.y);

  num distanceA(Point autre) {
    var dx = x - autre.x;
    var dy = y - autre.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters et setters

Les getters et setters sont des méthodes spéciales qui fournissent des accès en lecture et écriture aux propriétés d’un objet. Rappelons que chaque variable d’instance a un getter implicite, et un setter si elle est affectable. Vous pouvez créer des propriétés additionelles en implémentant des getters et setters, en utilisant les mots clés get et set :

class Rectangle {
  num gauche;
  num haut;
  num largeur;
  num hauteur;

  Rectangle(this.gauche, this.haut, this.largeur, this.hauteur);

  // Définie deux propriétés calculées : droite et bas.
  num get droite             => gauche + largeur;
      set droite(num valeur)  => gauche = valeur - largeur;
  num get bas            => haut + hauteur;
      set bas(num valeur) => haut = valeur - hauteur;
}

main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.gauche == 3);
  rect.droite = 12;
  assert(rect.gauche == -8);
}

Avec les getters et setters, vous pouvez commencer avec des variables d’instances, et plus tard les encapsuler avec des méthodes, tout cela sans changer le code client.

Méthodes abstraites

Les méthodes d’instance, de getters et setters peuvent être abstrait, définissant une interface mais laissant l’implémentation à d’autres classes. Pour rendre une méthode abstraite, utiliser le point virgule (;) au lieu d’un corps de méthode :

abstract class Actif {
  // ... Définie des variables et méthodes d'instances...

  void faireQuelqueChose(); // Définie une méthode abstraite.
}

class ActionneurVeritable extends Actif {
  void faireQuelqueChose() {
    // ... Fournit une implémentation, donc la méthode ici n'est pas abstraite...
  }
}

Appeler une méthode abstraitre produit une erreur d’exécution.

Voir aussi Classes abstraites.

Surcharge d’opérateurs

Vous pouvez surcharger les opérateurs présents dans le tableau suivant. Par exemple, si vous définissez une classe Vecteur, vous pourriez définir une méthode + pour additionner deux vecteurs.

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>  

Voici un exemple d’une classe qui surcharge les opérateurs + et - :

class Vecteur {
  final int x;
  final int y;
  const Vecteur(this.x, this.y);

  Vecteur operator +(Vecteur v) { // Surcharge + (a + b).
    return new Vecteur(x + v.x, y + v.y);
  }

  Vecteur operator -(Vecteur v) { // Surcharge - (a - b).
    return new Vecteur(x - v.x, y - v.y);
  }
}

main() {
  final v = new Vecteur(2, 3);
  final w = new Vecteur(2, 2);

  // v == (2, 3)
  assert(v.x == 2 && v.y == 3);

  // v + w == (4, 5)
  assert((v + w).x == 4 && (v + w).y == 5);

  // v - w == (0, 1)
  assert((v - w).x == 0 && (v - w).y == 1);
}

Si vous surchargez ==, vous devriez aussi surcharger le getter hashCode de Object. Pour un exemple de surcharge de == et hashCode, voir Implémentation des clés d’un dictionnaire.

Pour plus d’information sur la surcharge, de façon générale, voir Etendre une classe.

Classes abstraites

Utilisez le modificateur abstract pour définir une classe abstraite, c’est-à-dire, une classe qui ne peut pas être instanciée. Les classes abstraites sont utiles pour définir des interfaces, souvent avec quelques implémentations. Si vous voulez que votre classe abstraite apparaisse comme instanciable, définissez un Constructeur de type fabrique.

Les classes abstraites ont souvent des méthodes abstraites. Voici un exemple de déclaration d’une classe abstraite qui a une méthode abstraite :

// Cette classe est déclarée comme abstraite et 
// ne peut pas être instanciée.
abstract class ConteneurAbstrait {
  // ...Définition des constructeurs, champs, méthodes...

  void rafraichirEnfants(); // Abstract method.
}

La classe suivante n’est pas abstraite, et elle peut être instanciée, même si elle définit une méthode abstraite :

class ConteneurSpecialise extends ConteneurAbstrait {
  // ...Définition d'autres constructeurs, champs, méthodes...

  void rafraichirEnfants() {
    // ...Implémentation de rafraichirEnfants()...
  }

  // La méthode abstraite cause une alerte mais
  // n'empêche pas l'instanciation.
  void faireQuelqueChose();
}

Interfaces implicites

Toute classe définie implicitement une interface contenant tous les membres de la classe ainsi que toutes interfaces qu’elle implémente. Si vous créez une classe A qui supporte l’API de la classe B sans hériter de l’implémentation de B, la classe A doit implémenter l’interface B.

Une classe implémente une ou plusieurs interfaces en les déclarant dans la clause implements et en fournissant l’API requise par ces interfaces. Par exemple :

// Une personne. L'interface implicite contient la méthode saluer().
class Personne {
  // Dans l'interface, mais visible uniquement dans cette librairie,
  final _nom;
  
  // Pas dans l'interface étant donné qu'il s'agit d'un constructeur.
  Personne(this._nom);
  
  // Dans l'interface.
  String saluer(qui) => 'Salut, $qui. Je suis $_nom.';
}

// Une implémentation de l'interface Personne.
class Imposteur implements Personne {
  // Nous avons besoin de la définir, mais ne l'utilisons pas.
  final _nom = "";
  
  String saluer(qui) => 'Hé $qui. Tu sais qui je suis ?';
}

saluerBob(Personne personne) => personne.saluer('bob');

main() {
  print(saluerBob(new Personne('kathy')));
  print(saluerBob(new Imposteur()));
}

Voici un exemple de classe implémentant plusieurs interfaces :

class Point implements Comparable, Location {
  // ...
}

Etendre une classe

Utilisez extends pour créer une sous-classe, et super pour référer à la super-classe :

class Television {
  void allumer() {
    _eclairerLecran();
    _activerCapteurInfrarouge();
  }
  // ...
}

class TelevisionIntelligente extends Television {
  void allumer() {
    super.allumer();
    _demarrerInterfaceReseau();
    _initialiserMemoire();
    _mettreAJourApplications();
  }
  // ...
}

Les sous-classes peuvent étendre les méthodes d’instance, les getters et setters. Voici un exemple de surcharge de la méthode noSuchMethod() de la classe Object. Celle-ci est appelée par n’importe quel code qui tente d’utiliser une méthode ou une variable d’instance inexistante :

class A {
  // A moins que vous ne surchargiez noSuchMethod, utiliser un 
  // membre inexistant se terminera par une NoSuchMethodError.
  void noSuchMethod(Invocation mirror) {
    print('Vous avez essayer d'utiliser un membre inexistant : ' +
          '${mirror.memberName}');
  }
}

Vous pouvez utiliser l’annotation @override pour indiquer que vous surchargez intentionnellement un membre :

class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

Si vous utilisez noSuchMethod() pour implémenter tous les getters, setters, méthodes pour une classe alors vous pouvez utiliser l’annotation @proxy pour éviter les avertissements :

@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

Pour plus d’information sur les annotations, jetez un oeil à Metadata.

Types énumerés

Enumerated types, often called enumerations or enums, are a special kind of class used to represent a fixed number of constant values.

Utiliser les enums

Declare an enumerated type using the enum keyword:

enum Color {
  red,
  green,
  blue
}

Each value in an enum has an index getter, which returns the zero-based position of the value in the enum declaration. For example, the first value has index 0, and the second value has index 1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

To get a list of all of the values in the enum, use the enum’s values constant.

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

You can use enums in switch statements. If the e in switch (e) is explicitly typed as an enum, then you’re warned if you don’t handle all of the enum’s values:

enum Color {
  red,
  green,
  blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor);  // 'Color.blue'
}

Enumerated types have the following limits:

  • You can’t subclass, mix in, or implement an enum.
  • You can’t explicitly instantiate an enum.

For more information, see the Dart Language Specification.

Ajout de fonctionnalités à une classe: mixins

Les mixins sont une façon de réutiliser le code d’une classe dans des héritages multiples.

Pour utiliser un mixin, utilisez le mot-clé with suivi par un ou plusieurs noms de mixin. L’exemple suivant montre deux classes qui utilisent les mixins :

class Musicien extends Interprete with Musical {
  // ...
}

class Maestro extends Personne
    with Musical, Aggressif, Dement {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

Pour implémenter un mixin, créez une classe qui étend Object, ne déclare pas de constructeurs, et n’a aucun appels à super. Par exemple:

abstract class Musical {
  bool peutJouerDuPiano = false;
  bool peutComposer = false;
  bool peutConduire = false;

  void divertisMoi() {
    if (peutJouerDuPiano) {
      print('Joue du piano');
    } else if (peutComposer) {
      print('Bouge les mains');
    } else {
      print('Fredonne');
    }
  }
}

Pour plus d’information, voir l’article Mixins dans Dart.

Variables de classes et méthodes

Utilisez le mot clé static pour implémenter des variables et méthodes de classes.

Variables statiques

Les variables statiques (variables de classes) sont utiles un état et des constantes communs à la classe :

class Couleur {
  static const rouge = const Couleur('rouge'); // Une variable statique constante.
  final String nom;                            // Une variable d'instance.
  const Couleur(this.nom);                     // Un constructeur de constante.
}

main() {
  assert(Couleur.rouge.name == 'rouge');
}

Les variables statiques sont initialisées lorsqu’elles sont utilisées.

Méthodes statiques

Les méthodes statiques (méthodes de classes) n’opèrent pas sur une instance, et ainsi n’ont pas accès à this. Par exemple :

import 'dart:math';

class Point {
  num x;
  num y;
  Point(this.x, this.y);

  static num distanceEntre(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceEntre(a, b);
  assert(distance < 2.9 && distance > 2.8);
}

Vous pouvez utiliser des méthodes statiques comme constantes de compilation. Par exemple, vous pouvez passer une méthode statique comme un paramètre à un constructeur constant.

Génériques

Si vous regardez sur la documentation de l’API pour le type tableau de base, List, vous verrez que le type est en fait List<E>. La notation <…> marque la liste comme un type générique (ou paramétré), c’est-à-dire, un type qui prend des types en paramètre. Par convention, les variables de type ont des noms d’une seule lettre, tels que E, T, S, K, et V.

Pourquoi utiliser les génériques ?

Etant donné que les types sont optionnels en Dart, vous n’êtes jamais obligé d’utiliser les génériques. Il se peut que vous vouliez l’utiliser, pour la même raison que vous voudriez utiliser d’autres types dans votre code : les types (génériques ou non) vous permettent de documenter et d’annoter votre code, rendant votre intention plus claire.

Par exemple, si votre intention est qu’une liste ne contienne que des chaînes de caractères, vous pouvez la déclarer comme List<String> (le lire comme “liste de string”). De cette façon, vous, vos co-programmeurs, et vos outils (comme votre IDE et la VM Dart en mode vérification) peuvent détecter qu’assigner autre chose qu’une chaîne de caractères à la liste est probablement une erreur. Voici un exemple :

var noms = new List<String>();
noms.addAll(['Seth', 'Kathy', 'Lars']);
// ...
noms.add(42); // Échoue en mode vérification (passe en mode production).

Une autre raison pour utiliser les génériques est de réduire la duplication de code. Les génériques vous permettent de partager une seule interface et implémentation entre de nombreux types, tout en tirant avantage du mode vérification et des alertes préventives de l’analyse statique. Par exemple, disons que vous créez une interface pour mettre en cache un objet :

abstract class ObjectCache {
  Object getByKey(String key);
  setByKey(String key, Object value);
}

Vous découvrez que vous voulez une version spécifique de cette interface pour les chaînes de caractères, donc vous créez une autre interface :

abstract class StringCache {
  String getByKey(String key);
  setByKey(String key, String value);
}

Plus tard, vous décidez que vous voulez une version spécifique de cette interface pour les nombres… et ainsi de suite.

Les types génériques peuvent vous sauver de la difficulté de créer toutes ces interfaces. A la place, vous pouvez créer une interface unique qui prend un type en paramètre :

abstract class Cache<T> {
  T getByKey(String key);
  setByKey(String key, T value);
}

Dance ce code, T est un type de remplacement. C’est un paramètre fictif que vous pensez comme un type qu’un développeur définira plus tard.

En utilisant les collections littérales

Les listes et les dictionnaires peut être paramétrés. Les valeurs littérales paramétrées sont comme les valeurs littérales que vous avez déjà vues, excepté que vous ajoutez <type> pour les listes avant le crochet ouvrant ou <typeClé, typeValeur> pour les dictionnaires avant l’accolade ouvrante. Vous pouvez utiliser les valeurs littérales paramétrées lorsque vous voulez des alertes de type en mode vérification. Voici un exemple d’utilisation de valeurs littérales typées :

var noms = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
  'index.html': 'Accueil',
  'robots.txt': 'Trucs pour les robots',
  'humans.txt': 'Nous sommes des personnes, pas des machines'
};

En utilisant les types paramétrés avec les constructeurs

Pour spécifier un ou plusieurs types en utilisant un constructeur, mettre les types entre chevrons (<...>) juste après le nom de la classe. Par exemple :

var noms = new List<String>();
noms.addAll(['Seth', 'Kathy', 'Lars']);
var ensembleNoms = new Set<String>.from(noms);

Le code suivant crée un dictionnaire qui a des entiers pour clé et des types Vue pour valeur :

var vues = new Map<int, Vue>();

Les collections génériques et les types qu’elles contiennent

Les types génériques Dart sont réifiés, c’est-à-dire qu’ils portent l’information de leur type à l’exécution. Par exemple, vous pouvez tester le type d’une collection, même en mode production :

var noms = new List<String>();
noms.addAll(['Seth', 'Kathy', 'Lars']);
print(noms is List<String>); // true

Cependant, l’expression is vérifie le type de la collection seulement, pas ceux des objets à l’intérieur. En mode production, une List<String> peut avoir des éléments autres que des chaînes de caractères. La solution est de, soit vérifier le type de chaque élément ou soit d’entourer le code manipulant les éléments dans un gestionnaire d’exception (voir Exceptions).

Pour plus d’information sur les génériques, voir Optional Types in Dart.

Bibliothèques et visibilité

Les instructions import, part, et library peuvent vous aider à créer une base de code modulaire et partageable. Les bibliothèques fournissent non seulement des APIs, mais sont une unité de cloisonement : les identifiants qui commencent par un underscore (_) ne sont visibles qu’au sein de la bibliothèque. Toute application Dart est une bibliothèque, même si elle n’utilise pas une directive library.

Les bibliothèques peuvent être distribuées comme paquet. Voir Pub Package and Asset Manager pour davantage d’informations sur pub, un gestionnaire de paquets inclu dans le SDK.

Utiliser les bibliothèques

Utilisez import pour spécifier comment un espace de nommage d’une bibliothèque est utilisé dans le cadre d’une autre bibliothèque.

Par exemple, les applications Web Dart utilisent généralement la bibliothèque dart:html, qu’elles peuvent importer ainsi :

import 'dart:html';

Le seul argument nécessaire à import est une URI spécifiant la bibliothèque. Pour les bibliothèques natives, les URI ont le préfixe spécial dart:. Pour les autres bibliothèques, vous pouvez utilisez un chemin de votre système de fichier ou le préfixe package:. Le préfixe package: désigne les bibliothèques fournies par un gestionnaire de paquets tel que l’outil pub. Par exemple :

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

Spécifier un préfixe de bibliothèque

Si vous importez deux bibliothèques qui ont des identifiants en conflit, vous avez la possibilité de spécifier un préfixe pour l’une ou les deux de ces bibliothèques. Par exemple, si bibliothèque1 et bibliothèque2 possèdent toutes deux une classe Element, vous pouvez avoir le code suivant :

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
var element1 = new Element();      // Utilise Element de lib1.
var element2 = 
    new lib2.Element(); // Utilise Element de lib2.

Importer une partie d’une bibliothèque

Si vous ne désirez utiliser qu’une partie d’une bibliothèque, vous avez la possibilité de choisir quoi importer de celle-ci. Par exemple :

// Importe uniquement foo.
import 'package:lib1/lib1.dart' show foo;

// Importe tout SAUF foo.
import 'package:lib2/lib2.dart' hide foo;

Chargement différé d’une bibliothèque

Deferred loading (also called lazy loading) allows an application to load a library on demand, if and when it’s needed. Here are some cases when you might use deferred loading:

  • To reduce an app’s initial startup time.
  • To perform A/B testing—trying out alternative implementations of an algorithm, for example.
  • To load rarely used functionality, such as optional screens and dialogs.

To lazily load a library, you must first import it using deferred as.

import 'package:deferred/hello.dart' deferred as hello;

When you need the library, invoke loadLibrary() using the library’s identifier.

greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

In the preceding code, the await keyword pauses execution until the library is loaded. For more information about async and await, see Support de l’asynchronisme.

You can invoke loadLibrary() multiple times on a library without problems. The library is loaded only once.

Keep in mind the following when you use deferred loading:

  • A deferred library’s constants aren’t constants in the importing file. Remember, these constants don’t exist until the deferred library is loaded.
  • You can’t use types from a deferred library in the importing file. Instead, consider moving interface types to a library imported by both the deferred library and the importing file.
  • Dart implicitly inserts loadLibrary() into the namespace that you define using deferred as namespace. The loadLibrary() function returns a Future.

Créer une bibliothèque

Utilisez library pour nommer une bibliothèque, et part pour spécifier des fichiers additionnels dans la bibliothèque.

Déclarer une bibliothèque

Utilisez l'identifiant library pour spécifier le nom de la bibliothèque courante :

// Déclare qu'il s'agit d'une bibliothèque nommée jeudeballe.
library jeudeballe;

// Cette application utilise la bibliothèque HTML.
import 'dart:html';

// ... Le code va ici ...

Associer un fichier à une bibliothèque

Pour ajouter un fichier d’implémentation, ajoutez part uriDuFichier dans le fichier qui a la déclaration library, où uriDuFichier est le chemin vers le fichier d’implémentation. Indiquez alors part of identifiant, où identifiant est le nom de la bibliothèque. L’exemple suivant utilise part et part of pour implémenter une bibliothèque de trois fichiers.

Le premier fichier jeudeballe.dart, déclare la bibliothèque jeudeballe, importe les bibliothèques qui lui sont nécessaires, et indique que balle.dart et util.dart font partie de la bibliothèque :

library jeudeballe;

import 'dart:html';
// ... Les autres imports viennent ici ...

part 'balle.dart';
part 'util.dart';

// ... Le code peut venir ici ...

Le deuxième fichier, balle.dart, implémente une partie de la bibliothèque jeudeballe :

part of jeudeballe;

// ... Le code vient ici ...

Le troisième fichier util.dart, implémente le reste de la bibliothèque jeudeballe :

part of jeudeballe;

// ... Le code vient ici ...

Ré-exporter des bibliothèques

Vous pouvez combiner et ré-empaqueter des bibliothèques en ré-exportant tout ou partie de celles-ci. Par exemple, vous pouvez avec une énorme bibliothèque que vous implémentez sous forme de plus petites bibliothèques. Ou alors vous pouvez créer un bibliothèque qui fournit un sous ensemble de méthodes d’une autre bibliothèque.

// Dans francais.dart:
library francais;

bonjour() => print('Bonjour!');
auRevoir() => print('Au Revoir!');

// In togo.dart:
library togo;

import 'francais.dart';
export 'francais.dart' show bonjour;

// Dans un autre fichier .dart :
import 'togo.dart';

void main() {
  bonjour();   //affiche bonjour
  auRevoir(); //ERREUR
}

Support de l’asynchronisme

Dart has added new language features to support asynchronous programming. The most commonly used of these features are async functions and await expressions.

Dart libraries are full of functions that return Future or Stream objects. These functions are asynchronous: they return after setting up a possibly time-consuming operation (such as I/O), without waiting for that operation to complete.

When you need to use a value represented by a Future, you have two options:

Similarly, when you need to get values from a Stream, you have two options:

  • Use async and an asynchronous for loop (await for)
  • Use the Stream API

Code that uses async and await is asynchronous, but it looks a lot like synchronous code. For example, here’s some code that uses await to wait for the result of an asynchronous function:

await lookUpVersion()

To use await, code must be in a function marked as async:

checkVersion() async {
  var version = await lookUpVersion();
  if (version == expectedVersion) {
    // Do something.
  } else {
    // Do something else.
  }
}

You can use try, catch, and finally to handle errors and cleanup in code that uses await:

try {
  server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
  // React to inability to bind to the port...
}

Déclaration de fonctions asynchrone

An async function is a function whose body is marked with the async modifier. Although an async function might perform time-consuming operations, it returns immediately—before any of its body executes.

checkVersion() async {
  // ...
}

lookUpVersion() async => /* ... */;

Adding the async keyword to a function makes it return a Future. For example, consider this synchronous function, which returns a String:

String lookUpVersionSync() => '1.0.0';

If you change it to be an async function—for example, because a future implementation will be time consuming—the returned value is a Future:

Future<String> lookUpVersion() async => '1.0.0';

Note that the function’s body doesn’t need to use the Future API. Dart creates the Future object if necessary.

Utilisation d’expressions await avec les Futures

An await expression has the following form:

await expression

You can use await multiple times in an async function. For example, the following code waits three times for the results of functions:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

In await expression, the value of expression is usually a Future; if it isn’t, then the value is automatically wrapped in a Future. This Future object indicates a promise to return an object. The value of await expression is that returned object. The await expression makes execution pause until that object is available.

If await doesn’t work, make sure it’s in an async function. For example, to use await in your app’s main() function, the body of main() must be marked as async:

main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

Utilisation de boucles asynchrones avec les Streams

An asynchronous for loop has the following form:

await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

The value of expression must have type Stream. Execution proceeds as follows:

  1. Wait until the stream emits a value.
  2. Execute the body of the for loop, with the variable set to that emitted value.
  3. Repeat 1 and 2 until the stream is closed.

To stop listening to the stream, you can use a break or return statement, which breaks out of the for loop and unsubscribes from the stream.

If an asynchronous for loop doesn’t work, make sure it’s in an async function. For example, to use an asynchronous for loop in your app’s main() function, the body of main() must be marked as async:

main() async {
  ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  ...
}

For more information about asynchronous programming, see the dart:async section of the library tour. Also see the articles Dart Language Asynchrony Support: Phase 1 and Dart Language Asynchrony Support: Phase 2, and the Dart language specification.

Isolates

Les navigateurs modernes, même sur les plateformes mobiles, fonctionnent sur des processeurs multi-coeurs. Pour tirer avantage de ces coeurs, les développeurs utilisent traditionnellement des treads à mémoire partagée s’éxcutant en parrallèle. Cependant, le parallélisme à état partagé est source d’erreur et peut ammener à du code complexe.

Au lieu d’utiliser les threads, tout code Dart tourne au sein d’un isolates. Chaque isolate possède sa propre mémoire tas, permettant de garantir qu’aucun état d’isolate n’est accessible depuis un autre isolate.

Typedefs

En Dart, les fonctions sont des objets, tout comme les chaines de caractères et les nombres sont des objets. Un typedef, ou alias de type de fonction, donne un nom à un type de fonction que vous pouvez utiliser pour déclarer un champ ou un type de retour. Un typedef garde les informations de type lorsqu’un type de fonction est assigné à une variable.

Voici un exemple de code qui n’utilise pas de typedef:

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Implémentation initiale erronée.
int sort(Object a, Object b) => ... ;

main() {
  SortedCollection coll = new SortedCollection(sort);

  // Tout ce que nous savons est que compare est une fonction, 
  // mais quel type de fonction ?
  assert(coll.compare is Function);
}

L’information de type est perdue lors de l’assignation de f à compare. Le type de f est (Object, Object)int (où → signigie retourne), le type de compare est également Function. Si nous adaptons le code pour utiliser des noms explicites et garder l’information, les développeurs ainsi que les outils peuvent utiliser cette information.

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// Implémentation initial erronée.
int sort(Object a, Object b) => ... ;

main() {
  SortedCollection coll = new SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

Du fait que les typedefs sont des alias, ils offrent un moyen de vérifier le type de n’importe quelle fonction. Par exemple :

typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare); // Vrai!
}

Metadata

Utilisez les metadata pour donner de l’information complémentaire à votre code. Une annotation metadata commence par le caratère @, suivi par soit une référence à une constante de compilation (telle que deprecated) soit un appel à un constructeur constant.

Trois annotations sont disponibles pour tout code Dart : @deprecated, @override, et @proxy. Pour des exemples d’utilisation de @override et @proxy, voir Etendre une classe. Voici un exemple d’utilisation de l’annotation @deprecated :

class Television {
  /// _Deprecated: Utiliser [allumer] à la place._
  @deprecated
  void activer() {
    allumer();
  }

  /// Allume la télé.
  void allumer() {
    print('On!');
  }
}

Vous pouvez définir vos propres annotations metadata. Voici un exemple de définition de l’annotation @todo qui prend deux arguments :

library todo;

class todo {
  final String qui;
  final String quoi;

  const todo(this.qui, this.quoi);
}

Et voici un exemple d’utilisation de cette annotation @todo :

import 'todo.dart';

@todo('seth', 'Fait faire quelque chose à cette fonction')
void faitQuelqueChose() {
  print('Fait quelque chose');
}

Les metadatas peuvent apparaitre avant une déclaration de librairie, classe, typedef, paramètre de type, constructeur, factory, fonction, champ, paramètre, ou variable et avant un import ou export de directive. Vous pouvez récupérer les metadatas durant l’execution en utilisant la réflexion.

Commentaires

Dart supporte les commentaires sur une ou plusieurs lignes et les commentaires de documentation.

Commentaires d’une ligne

Un commentaire d’une ligne commence par //. Tout entre // et la fin de la ligne est ignoré par le compilateur Dart.

main() {
  // TODO: refactorer en AbstractLlamaGreetingFactory?
  print('Bienvenue à ma ferme de lamas !');
}

Commentaires de plusieurs lignes

Un commentaire de plusieurs lignes commence par /* et se termine par */. Tout entre /* et */ est ignoré par le compilateur Dart (à moins que le commentaire ne soit un commentaire de documentation; voir la section suivante). Les commentaires de plusieurs lignes peuvent s’imbriquer.

main() {
  /*
   * C'est beaucoup de travail. Envisager l'élevage de poulets.

  Llama larry = new Llama();
  larry.nourrir();
  larry.preparer();
  larry.nettoyer();
   */
}

Commentaires de documentation

Les commentaires de documentation sont des commentaires d’une ou plusieurs lignes qui commencent avec /// ou /**. Utiliser /// sur plusieurs lignes consécutives a le même effet qu’un commentaire de documentation sur plusieurs lignes.

Dans un commentaire de documentation, le compilateur Dart ignore tout text sauf si celui-ci est entouré par des crochets. En utilisant des crochets, vous pouvez référer à une classe, une méthode, un champ, une variable globale, une fonction et des paramètres. Les noms entre crochets sont résolues dans le champ lexical de l’élément documenté.

Voici un exemple de commentaires de documentation qui référencient d’autres classes et arguments:

/// Une camélidé domestiqué d'amérique du sud (Lama glama).
///
/// Les cultures Andines utilisent les lamas comme nouriture et animaux de portage
/// depuis les temps préhistoriques.
class Lama {
  String nom;

  /// Nourris ton lama [Nourriture].
  ///
  /// Un lama typique mange une botte de foin par semaine.
  void nourrir(Nourriture nourriture) {
    // ...
  }

  /// Fait travailler ton lama sur une [activité] pour
  /// [limiteDeTemps] minutes.
  void travaille(Activité activité, int limiteDeTemps) {
    // ...
  }
}

Dans la documentation générée, [Nourriture] devient un lien pour la documentation d’API pour la classe Nourriture.

Pour parser du code Dart et générer de la documentation HTML, vous pouvez utiliser l’outil de génération de documentation. du SDK. Pour un exemple de documentation générée, voir la Documentation de l’API Dart.. Pour des conseils sur la structuration des commentaires, voir le Guide pour les commentaires de documentation Dart.

Résumé

Ce chapitre résume les fonctionnalités les plus communes dans le langage Dart. D’autres fonctionnalités sont en train d’être implémentées, et nous espérons qu’elles ne casseront pas le code existant. Pour plus d’information, voir la Spécification du Langage Dart et les articles tel que Idiomatic Dart.