Skip to content

Latest commit

 

History

History
3519 lines (2704 loc) · 111 KB

ch02.markdown

File metadata and controls

3519 lines (2704 loc) · 111 KB
layout title subsite description prev-chapter prev-chapter-title next-chapter next-chapter-title
book
Découverte du Langage Dart
Dart Up and Running
Read Chapter 2, A Tour of the Dart Language of Dart (from Dart: Up and Running, published by O'Reilly).
ch01.html
Quick Start
ch03.html
Library Tour

{% include toc.html %} {% include book-nav.html %}

{{ page.title }}

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.

**Note:** Vous pouvez essayer la plupart des fonctionnalités présentées dans [Up and running](ch01.html#up-and-running) en utilisant DartPad.

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

Un simple programme Dart {#a-basic-dart-program}

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

{% highlight 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. } {% endhighlight %}

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.

**Note:** Notre code suit la convention décrite dans le [Dart Style Guide.](https://www.dartlang.org/articles/style-guide/). Par exemple, nous utilisons une indentation à deux espaces.

Concepts importants {#important-concepts}

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 {#keywords}

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

{% assign bii = ' 1' %} {% assign lrw = ' 2' %}

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

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 {#variables}

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

{% highlight dart %} var name = 'Bob'; {% endhighlight %}

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 {#default-value}

{:.no_toc}

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

{% highlight dart %} int lineCount; assert(lineCount == null); // Les variables (même si numériques) sont initialisées à null. {% endhighlight %}

**Note:** L'appel à `assert()` est ignoré en mode production. Par contre en mode vérification, assert(condition) lance une exception si *condition* est fausse. Pour plus d'informations, voir la section [Assert](#assert).

Types optionnels {#optional-types}

{:.no_toc}

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

{% highlight dart %} String name = 'Bob'; {% endhighlight %}

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.

**Note:** Ce chapitre suit la [convention de code](https://www.dartlang.org/articles/style-guide/#type-annotations) d'utiliser `var` plutôt que des statiques pour des variables locales.

Final et const {#final-and-const}

{:.no_toc}

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:

{% highlight dart %} final nom = 'Bob'; // Ou: final String nom = 'Bob'; // nom = 'Alice'; // Décommenter cette ligne produit une erreur {% endhighlight %}

**Note:** L'initialisation paresseuse d'une variable final aide à rendre le démarrage d'une application plus rapide.

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.

{% highlight dart %} const bar = 1000000; // Unité de pression (en dynes/cm2) const atm = 1.01325 * bar; // Atmosphere standard {% endhighlight %}

Types natifs {#built-in-types}

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 {#numbers}

{:.no_toc}

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.

**Attention :** Actuellement, les nombres entiers en dehors de l'intervalle -253 et 253 se comporte différement dans le JavaScript généré à partir du code Dart comparé au même code Dart tournant sous la machine virtuelle Dart. La raison est que Dart a été spécifié pour avoir des entiers multiprécisions, ce qui n'est pas le cas de JavaScript. Voir [issue 1533](http://dartbug.com/1533) pour plus de détails.

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

{% highlight dart %} var x = 1; var hex = 0xDEADBEEF; var grosEntier = 34653465834652437659238476592374958739845729; {% endhighlight %}

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

{% highlight dart %} var y = 1.1; var dixPuissance = 1.42e5; {% endhighlight %}

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

{% highlight dart %} // 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'); {% endhighlight %}

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

{% highlight dart %} assert((3 << 1) == 6); // 0011 << 1 == 0110 assert((3 >> 1) == 1); // 0011 >> 1 == 0001 assert((3 | 4) == 7); // 0011 | 0100 == 0111 {% endhighlight %}

Chaînes de caractères {#strings}

{:.no_toc}

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 :

{% highlight dart %} 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."; {% endhighlight %}

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.

{% highlight dart %} 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 !"); {% endhighlight %}

**Note:** L'opérateur `==` teste si deux objets sont équivalents. Deux chaînes de caractères sont équivalentes s'elles contiennent la même suite de codes de caractère.

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

{% highlight dart %} 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."); {% endhighlight %}

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 :

{% highlight dart %} 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."""; {% endhighlight %}

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

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

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

{% highlight dart %} // L'échapement Unicode fonctionne : [heart] print("L'échapement Unicode fonctionne : \u2665"); {% endhighlight %}

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 {#booleans}

{:.no_toc}

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 :

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

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 :

{% highlight 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. } {% endhighlight %}

**Note:** Les deux examples précédents fonctionnent seulement en mode production, pas en mode vérification. En mode vérification, une exception est lancée si un non-booléen est utilisé alors qu'une valeur booléenne est attendue.

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 :

{% highlight dart %} // 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); {% endhighlight %}

Listes {#lists}

{:.no_toc}

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 :

{% highlight dart %} var liste = [1, 2, 3]; {% endhighlight %}

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 :

{% highlight dart %} var liste = [1, 2, 3]; assert(liste.length == 3); assert(liste[1] == 2); {% endhighlight %}

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 {#maps}

{:.no_toc}

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 :

{% highlight dart %} 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', }; {% endhighlight %}

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

{% highlight dart %} 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'; {% endhighlight %}

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

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

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

{% highlight dart %} var cadeaux = {'premier': 'perdrix'}; assert(cadeaux['premier'] == 'perdrix'); {% endhighlight %}

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

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

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

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

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

Symboles {#symbols}

{:.no_toc}

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 :

{% highlight dart %} #radix #bar {% endhighlight %}

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

Fonctions {#functions}

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

{% highlight dart %} void afficherNombre(num nombre) { print('Le nombre est $nombre.'); } {% endhighlight %}

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

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

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

{% highlight dart %} afficherNombre(nombre) => print('Le nombre est $nombre.'); {% endhighlight %}

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

**Note:** Seul une *expression* et non pas une *instruction* peut être placée entre la fléche (=\>) et le point virgule (;). Par exemple, vous ne pouvez pas y mettre une [instruction if](#if-and-else), mais vous pouvez utiliser une [expression conditionnelle (`?:`)](#other-operators).

Voici un exemple d'appel à une fonction:

{% highlight dart %} afficherNombre(123); {% endhighlight %}

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 {#optional-parameters}

{:.no_toc}

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 {#optional-named-parameters}

{:.no_toc}

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

{% highlight dart %} activerFlag(bold: true, hidden: false); {% endhighlight %}

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

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

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

{% highlight dart %} /// 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); {% endhighlight %}

Paramètres optionnels positionnés {#optional-positional-parameters}

{:.no_toc}

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

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

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

{% highlight dart %} assert(dire('Bob', 'Salut') == 'Bob dit Salut'); {% endhighlight %}

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

{% highlight dart %} assert(dire('Bob', 'Salut', 'signal de fumée') == 'Bob dit Salut avec un signal de fumée'); {% endhighlight %}

Utilisez = pour indiquer la valeur par défaut :

{% highlight dart %} 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'); {% endhighlight %}

La fonction main() {#the-main-function}

{:.no_toc}

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:

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

**Note:** L'opérateur `..` dans le code précédent est un opérateur en cascade qui permet d'effectuer des opérations multiples sur le même objet. Vous en saurez plus avec les [Classes](#classes).

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

{% highlight dart %} // Lancer le code comme cela: dart args.dart 1 test void main(List arguments) { print(arguments);

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

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

Fonctions comme des objets {#functions-as-first-class-objects}

{:.no_toc}

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

{% highlight dart %} afficherElement(element) { print(element); }

var liste = [1, 2, 3];

// Passe afficherElement comme un paramètre. liste.forEach(afficherElement); {% endhighlight %}

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

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

Portée lexicale {#lexical-scope}

{:.no_toc}

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 :

{% highlight dart %} var premierNiveau = true;

main() { var dansLeMain = true;

maFonction() { var dansLaFonction = true;

fonctionImbriquee() {
  var dansLaFonctionImbriquee = true;

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

} } {% endhighlight %}

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

Closures lexicales {#lexical-closures}

{:.no_toc}

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.

{% highlight dart %} /// 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); } {% endhighlight %}

Tests d'égalité de méthodes {#testing-functions-for-equality}

{:.no_toc}

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

{% highlight dart %} 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); } {% endhighlight %}

Valeurs de retour {#return-values}

{:.no_toc}

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 {#operators}

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 =    *=    /=    ~/=    %=    +=    -=    <<=    >>=    &=    ^=    `
{:.table .table-striped}

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

{% highlight dart %} a++ a + b a = b a == b a ? b: c a is T {% endhighlight %}

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 :

{% highlight dart %} // 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) {% endhighlight %}

**Attention:** Pour les opérateurs qui travaillent sur deux opérandes, c'est l'opérande la plus à gauche qui détermine la version de l'opérateur utilisé. Par exemple, si vous avez un objet Vecteur et un objet Point, `unVecteur + unPoint` utilise l'opérateur + de l'objet Vecteur.

Les opérateurs arithmétiques {#arithmetic-operators}

{:.no_toc}

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) {:.table .table-striped}

Example:

{% highlight dart %} 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 {% endhighlight %}

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) {:.table .table-striped}

Exemple:

{% highlight dart %} 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 {% endhighlight %}

Opérateurs d'égalité et de comparaison {#equality-and-relational-operators}

{:.no_toc}

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 {:.table .table-striped}

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 :

{% highlight dart %} assert(2 == 2); assert(2 != 3); assert(3 > 2); assert(2 < 3); assert(3 >= 3); assert(2 <= 3); {% endhighlight %}

Opérateurs de test sur les types {#type-test-operators}

{:.no_toc}

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 {:.table .table-striped}

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 :

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

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

{% highlight dart %} (emp as Person).firstName = 'Bob'; {% endhighlight %}

**Note:** Le code n'est pas équivalent. Si `emp` est nul ou n'est pas une instance de Person, le premier exemple (avec `is`) ne fait rien; le second (avec `as`) lève une exception.

Opérateurs d'assignation {#assignment-operators}

{:.no_toc}

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.

| = | –= | /= | %= | >>= | ^= | += | *= | ~/= | <<= | &= | |= {:.table}

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 {:.table}

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

{% highlight dart %} var a = 2; // Assignation utilisant = a *= 3; // Assignation et multiplication : a = a * 3 assert(a == 6); {% endhighlight %}

Opérateurs logiques {#logical-operators}

{:.no_toc}

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 {:.table .table-striped}

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

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

Opérateurs binaires et de décalage de bits {#bitwise-and-shift-operators}

{:.no_toc}

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 {:.table .table-striped}

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

{% highlight dart %} 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 {% endhighlight %}

Autres opérateurs {#other-operators}

{:.no_toc}

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 {:.table .table-striped}

Structures de contrôle {#control-flow-statements}

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 {#if-and-else}

{:.no_toc}

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.

{% highlight dart %} if (estPluvieux()) { vous.prendreImpermeable(); } else if (estNeigeux()) { vous.porterBlouson(); } else { voiture.conduireDecapotable(); } {% endhighlight %}

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 {#for-loops}

{:.no_toc}

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

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

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 :

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

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:

{% highlight dart %} candidats.forEach((candidat) => candidat.entretien()); {% endhighlight %}

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

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

While et do-while {#while-and-do-while}

{:.no_toc}

Une boucle while évalue la condition avant la boucle :

{% highlight dart %} while (!estFait()) { faireQuelqueChose(); } {% endhighlight %}

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

{% highlight dart %} do { afficherLigne(); } while (!aLaFinDePage()); {% endhighlight %}

Break et continue {#break-and-continue}

{:.no_toc}

Utiliser break pour stopper une boucle :

{% highlight dart %} while (true) { if (extinctionDemandee()) break; traitementDesRequetesEntrantes(); } {% endhighlight %}

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

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

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

{% highlight dart %} candidats.where((c) => c.anneeExperience >= 5) .forEach((c) => c.entretien()); {% endhighlight %}

Switch et case {#switch-and-case}

{:.no_toc}

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.

**Note:** Les instructions switch en Dart sont utiles dans des circonstances limitées, tel que les interpréteurs ou les scanners.

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 :

{% highlight dart %} 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(); } {% endhighlight %}

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

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

case 'FERME': executeFerme(); break; } {% endhighlight %}

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

{% highlight dart %} 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; } {% endhighlight %}

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

{% highlight dart %} 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; } {% endhighlight %}

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

Assert {#assert}

{:.no_toc}

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 :

{% highlight dart %} // 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')); {% endhighlight %}

**Note:** Les instructions assert fonctionnent seulement en mode vérification. Elles n'ont aucun effet en mode production.

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 {#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 {#throw}

{:.no_toc}

Voici un exemple de comment lever une exception :

{% highlight dart %} throw new FormatException('Au moins 1 section attendue'); {% endhighlight %}

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

{% highlight dart %} throw 'A cours de lamas!'; {% endhighlight %}

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

{% highlight dart %} distanceJusqua(Point autre) => throw new UnimplementedError(); {% endhighlight %}

Attraper une exception {#catch}

{:.no_toc}

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

{% highlight dart %} try { eleverPlusDeLamas(); } on PlusDeLamaException { acheterPlusDeLamas(); } {% endhighlight %}

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é :

{% highlight dart %} 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'); } {% endhighlight %}

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 {#finally}

{:.no_toc}

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 :

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

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

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

Apprenez en davantage en lisant la section Exceptions.

Classes {#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 :

{% highlight dart %} 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); {% endhighlight %}

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 :

{% highlight dart %} 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)); {% endhighlight %}

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

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

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 :

{% highlight dart %} var p = const PointImmuable(2, 2); {% endhighlight %}

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

{% highlight dart %} var a = const PointImmuable(1, 1); var b = const PointImmuable(1, 1);

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

Les sections suivantes montrent comment implémenter des classes.

Variables d'instance {#instance-variables}

{:.no_toc}

Voici comment déclarer des variables d'instance :

{% highlight dart %} 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. } {% endhighlight %}

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.

{% highlight dart %} 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. } {% endhighlight %}

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 {#constructors}

{:.no_toc}

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 :

{% highlight dart %} 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; } } {% endhighlight %}

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

**Note:** Utilisez `this` seulement lorsqu'il y a un conflit de nom. Autrement, le style Dart néglige le `this`.

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 :

{% highlight dart %} 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); } {% endhighlight %}

Constructeurs par défaut {#default-constructors}

{:.no_toc}

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 {#constructors-arent-inherited}

{:.no_toc}

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 {#named-constructors}

{:.no_toc}

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

{% highlight dart %} class Point { num x; num y;

Point(this.x, this.y);

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

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 {#invoking-a-non-default-superclass-constructor}

{:.no_toc}

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).

{% highlight dart %} 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 } {% endhighlight %}

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 :

{% highlight dart %} class Employe extends Personne { // ... Employe() : super.fromJson(trouveInfoParDefaut()); } {% endhighlight %}

**Attention:** Les paramètres pour le constructeur de la superclasse n'ont pas accès à `this`. Par exemple, les arguments peuvent appeler des méthodes statiques mais pas des méthodes d'instance.

Liste de paramètres d'initialisation {#initializer-list}

{:.no_toc}

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.

{% highlight dart %} 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)'); } } {% endhighlight %}

**Attention:** La valeur de l'assignation n'a pas accès à `this`.

Redirection de constructeur {#redirecting-constructors}

{:.no_toc}

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 (:).

{% highlight dart %} 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); } {% endhighlight %}

Constructeurs de constantes {#constant-constructors}

{:.no_toc}

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.

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

Constructeurs de type fabrique {#factory-constructors}

{:.no_toc}

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 :

{% highlight dart %} 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); } } } {% endhighlight %}

**Note:** Les constructeurs de type fabrique n'ont pas accès à `this`.

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

{% highlight dart %} var logger = new Logger('IHM'); logger.log('Bouton cliqué'); {% endhighlight %}

Méthodes {#methods}

{:.no_toc}

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

Méthodes d'instance {#instance-methods}

{:.no_toc}

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.

{% highlight dart %} 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); } } {% endhighlight %}

Getters et setters {#getters-and-setters}

{:.no_toc}

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 :

{% highlight dart %} 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); } {% endhighlight %}

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.

**Note:** Les opérateurs comme l'incrémentation (++) fonctionnent de la façon prévue, même si un getter est explicitement défini ou non. Pour éviter des effets imprévus, l'opérateur appelle le getter exactement une fois, sauvant la valeur dans une variable temporaire.

Méthodes abstraites {#abstract-methods}

{:.no_toc}

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 :

{% highlight dart %} 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... } } {% endhighlight %}

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

Voir aussi Classes abstraites.

Surcharge d'opérateurs {#overridable-operators}

{:.no_toc}

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.

< | + | | | [] > | / | ^ | []= <= | ~/ | & | ~ >= | * | << | == | % | >> {:.table}

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

{% highlight dart %} 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); } {% endhighlight %}

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 {#abstract-classes}

{:.no_toc}

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 :

{% highlight dart %} // 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. } {% endhighlight %}

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

{% highlight dart %} 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(); } {% endhighlight %}

Interfaces implicites {#implicit-interfaces}

{:.no_toc}

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 :

{% highlight dart %} // 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())); } {% endhighlight %}

Voici un exemple de classe implémentant plusieurs interfaces :

{% highlight dart %} class Point implements Comparable, Location { // ... } {% endhighlight %}

Etendre une classe {#extending-a-class}

{:.no_toc}

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

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

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

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 :

{% highlight dart %} 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}'); } } {% endhighlight %}

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

{% highlight dart %} class A { @override void noSuchMethod(Invocation mirror) { // ... } } {% endhighlight %}

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 :

{% highlight dart %} @proxy class A { void noSuchMethod(Invocation mirror) { // ... } } {% endhighlight %}

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

Types énumerés {#enums}

{:.no_toc}

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 {#using-enums}

{:.no_toc}

Declare an enumerated type using the enum keyword:

{% highlight dart %} enum Color { red, green, blue } {% endhighlight %}

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.

{% highlight dart %} assert(Color.red.index == 0); assert(Color.green.index == 1); assert(Color.blue.index == 2); {% endhighlight %}

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

{% highlight dart %} List colors = Color.values; assert(colors[2] == Color.blue); {% endhighlight %}

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:

{% highlight dart %} 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' } {% endhighlight %}

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 {#adding-features-to-a-class-mixins}

{:.no_toc}

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 :

{% highlight dart %} class Musicien extends Interprete with Musical { // ... }

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

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:

{% highlight dart %} 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'); } } } {% endhighlight %}

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

Variables de classes et méthodes {#class-variables-and-methods}

{:.no_toc}

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

Variables statiques {#static-variables}

{:.no_toc}

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

{% highlight dart %} 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'); } {% endhighlight %}

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

**Note:** Ce chapitre suit la recommandation du guide de style [style guide– recommendation](https://www.dartlang.org/articles/style-guide/#prefer-using-lowercamelcase-for-constant-names) qui préfère `lowerCamelCase` comme nom de constantes.

Méthodes statiques {#static-methods}

{:.no_toc}

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 :

{% highlight dart %} 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); } {% endhighlight %}

**Note:** Préférez l'utilisation de fonctions de premier niveau à des méthodes statiques, pour des fonctionnalités communes ou largement utilisées.

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 {#generics}

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 ? {#why-use-generics}

{:.no_toc}

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 :

{% highlight dart %} var noms = new List(); noms.addAll(['Seth', 'Kathy', 'Lars']); // ... noms.add(42); // Échoue en mode vérification (passe en mode production). {% endhighlight %}

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 :

{% highlight dart %} abstract class ObjectCache { Object getByKey(String key); setByKey(String key, Object value); } {% endhighlight %}

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 :

{% highlight dart %} abstract class StringCache { String getByKey(String key); setByKey(String key, String value); } {% endhighlight %}

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 :

{% highlight dart %} abstract class Cache { T getByKey(String key); setByKey(String key, T value); } {% endhighlight %}

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 {#using-collection-literals}

{:.no_toc}

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 :

{% highlight dart %} var noms = ['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' }; {% endhighlight %}

En utilisant les types paramétrés avec les constructeurs {#using-parameterized-types-with-constructors}

{:.no_toc}

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 :

{% highlight dart %} var noms = new List(); noms.addAll(['Seth', 'Kathy', 'Lars']); var ensembleNoms = new Set.from(noms); {% endhighlight %}

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

{% highlight dart %} var vues = new Map<int, Vue>(); {% endhighlight %}

Les collections génériques et les types qu'elles contiennent {#generic-collections-and-the-types-they-contain}

{:.no_toc}

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 :

{% highlight dart %} var noms = new List(); noms.addAll(['Seth', 'Kathy', 'Lars']); print(noms is List); // true {% endhighlight %}

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).

**Note:** En comparaison, les génériques en Java utilisent l'*effacement*, c'est-à-dire, que les paramètres d'un type générique sont enlevés à l'exécution. En Java, vous pouvez tester si un objet est une List, mais vous ne pouvez pas tester si c'est une `List`.

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

Bibliothèques et visibilité {#libraries-and-visibility}

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 {#using-libraries}

{:.no_toc}

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 :

{% highlight dart %} import 'dart:html'; {% endhighlight %}

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 :

{% highlight dart %} import 'dart:io'; import 'package:mylib/mylib.dart'; import 'package:utils/utils.dart'; {% endhighlight %}

**Note:** *URI* est l'acronyme de uniform resource identifier. *URLs* (uniform resource locators) est un type commun d'URI.

Spécifier un préfixe de bibliothèque {#specifying-a-library-prefix}

{:.no_toc}

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 :

{% highlight dart %} 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. {% endhighlight %}

Importer une partie d'une bibliothèque {#importing-only-part-of-a-library}

{:.no_toc}

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 :

{% highlight dart %} // Importe uniquement foo. import 'package:lib1/lib1.dart' show foo;

// Importe tout SAUF foo. import 'package:lib2/lib2.dart' hide foo; {% endhighlight %}

Chargement différé d'une bibliothèque {#deferred-loading}

{:.no_toc}

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.
**Warning:** Deferred loading is a new feature first implemented in 1.6. If you use it, [let us know](http://dartbug.com) about any implementation issues you discover.

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

{% highlight dart %} import 'package:deferred/hello.dart' deferred as hello; {% endhighlight %}

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

{% highlight dart %} greet() async { await hello.loadLibrary(); hello.printGreeting(); } {% endhighlight %}

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 {#implementing-libraries}

{:.no_toc}

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

**Note:** Il n'est pas nécessaire d'utiliser `library` dans une application (un fichier qui a une fonction de haut niveau main()), mais le faire vous permet d'implémenter votre application dans plusieurs fichiers.

Déclarer une bibliothèque {#declaring-a-library}

{:.no_toc}

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

{% highlight dart %} // 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 ... {% endhighlight %}

Associer un fichier à une bibliothèque {#associating-a-file-with-a-library}

{:.no_toc}

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 :

{% highlight dart %} library jeudeballe;

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

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

// ... Le code peut venir ici ... {% endhighlight %}

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

{% highlight dart %} part of jeudeballe;

// ... Le code vient ici ... {% endhighlight %}

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

{% highlight dart %} part of jeudeballe;

// ... Le code vient ici ... {% endhighlight %}

Ré-exporter des bibliothèques {#re-exporting-libraries}

{:.no_toc}

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.

{% highlight dart %} // 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 } {% endhighlight %}

Support de l'asynchronisme {#asynchrony}

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:

{% highlight dart %} await lookUpVersion() {% endhighlight %}

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

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

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

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

Déclaration de fonctions asynchrone {#async}

{:.no_toc}

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.

{% highlight dart %} checkVersion() async { // ... }

lookUpVersion() async => /* ... */; {% endhighlight %}

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

{% highlight dart %} String lookUpVersionSync() => '1.0.0'; {% endhighlight %}

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:

{% highlight dart %} Future lookUpVersion() async => '1.0.0'; {% endhighlight %}

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 {#await}

{:.no_toc}

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:

{% highlight dart %} var entrypoint = await findEntrypoint(); var exitCode = await runExecutable(entrypoint, args); await flushThenExit(exitCode); {% endhighlight %}

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:

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

Utilisation de boucles asynchrones avec les Streams {#await}

{:.no_toc}

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:

{% highlight dart %} main() async { ... await for (var request in requestServer) { handleRequest(request); } ... } {% endhighlight %}

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 {#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 {#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:

{% highlight dart %} 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); } {% endhighlight %}

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.

{% highlight dart %} 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); } {% endhighlight %}

**Note:** Pour le moment, les typedefs sont cantonnés aux types de fonction. Cela va certainement évoluer.

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

{% highlight dart %} typedef int Compare(int a, int b);

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

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

Metadata {#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 :

{% highlight dart %} class Television { /// Deprecated: Utiliser [allumer] à la place. @deprecated void activer() { allumer(); }

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

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

{% highlight dart %} library todo;

class todo { final String qui; final String quoi;

const todo(this.qui, this.quoi); } {% endhighlight %}

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

{% highlight dart %} import 'todo.dart';

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

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 {#comments}

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

Commentaires d'une ligne {#single-line-comments}

{:.no_toc}

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

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

Commentaires de plusieurs lignes {#multi-line-comments}

{:.no_toc}

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.

{% highlight dart %} main() { /*

  • C'est beaucoup de travail. Envisager l'élevage de poulets.

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

Commentaires de documentation {#documentation-comments}

{:.no_toc}

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:

{% highlight dart %} /// 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) { // ... } } {% endhighlight %}

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é {#summary}

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.


{% include book-nav.html %}