polycopié - Rémy Malgouyres
Transcription
polycopié - Rémy Malgouyres
, Conception Objet et Programmation en C# Rémy Malgouyres LIMOS UMR 6158, IUT, département info Clermont Université B.P. 86 63172 AUBIERE cedex http ://www.malgouyres.org/ Table des matières 1 ABC de la conception et programmation objet 1.1 Conception du Package Métier . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Organisation d’une Application en packages et/ou namespace 1.1.2 Dialogue avec l’Expert Métier . . . . . . . . . . . . . . . . . . 1.1.3 Déterminer les acteurs et actions simples . . . . . . . . . . . . 1.1.4 Première ébauche du package Métier . . . . . . . . . . . . . . 1.2 Créer une classe simple . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 Modélisation Statique (diagramme de classes) . . . . . . . . . 1.2.2 Code Source . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.3 Classe de test . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Relation de Composition . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.1 Modélisation Statique . . . . . . . . . . . . . . . . . . . . . . 1.3.2 Code source . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.3 Classe de tests . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4 Pattern Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Modélisation statique . . . . . . . . . . . . . . . . . . . . . . 1.4.2 Code source . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.3 Classe de Test . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5 Diagrammes de Séquence . . . . . . . . . . . . . . . . . . . . . . . . . 1.5.1 Notion de diagramme de séquence et exemple simple . . . . . 1.5.2 Exemple de diagramme de séquence pour UrBookLTD . . . . 2 Delegates et Expressions Lambda 2.1 Fonctions de comparaison basiques . . . . . . . 2.2 Uniformisation de la signature des fonctions . . 2.3 Types delegate . . . . . . . . . . . . . . . . . 2.4 Exemple d’utilisation : méthode de tri générique 2.5 Expressions lambda . . . . . . . . . . . . . . . . 3 Collections 3.1 Listes . . . . . . . . . . . 3.2 Files . . . . . . . . . . . . 3.3 Dictionnaires . . . . . . . 3.4 Protocole de comparaison 3.5 Protocole d’égalité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 3 4 4 5 5 5 6 8 8 8 12 12 13 13 15 17 17 20 . . . . . 22 22 25 27 30 31 . . . . . 33 34 35 36 38 39 TABLE DES MATIÈRES 4 Requêtes LINQ 4.1 Types délégués anonymes . . . . . . . . . . 4.2 Méthodes d’extension . . . . . . . . . . . . . 4.3 Interface IEnumerable<T> . . . . . . . . . . 4.4 Quelques méthodes de LINQ . . . . . . . . . 4.4.1 Filtre Where . . . . . . . . . . . . . . 4.4.2 Méthode de tri OrderBy . . . . . . . 4.4.3 Filtre Where sur un dictionnaire . . . 4.4.4 Méthode de regrouppement GroupBy 4.4.5 Exemples . . . . . . . . . . . . . . . 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 42 43 44 45 45 45 46 46 47 Chapitre 1 ABC de la conception et programmation objet 1.1 Conception du Package Métier 1.1.1 Organisation d’une Application en packages et/ou namespace Une application va regrouper plusieurs unités logiques, qui chacune comportent une ou (généralement) plusieurs classes. Une petite application peut comporter, par exemple : • Le package Métier (qui dépend de l’activité du ”client” pour qui on développe l’application) ; • Un ou plusieurs packages de mise en forme dans une Interface Homme Machine (IHM ) ; • Un package de persistance (permet de gérer l’enregistrement des données de manière permanente, par exemple dans une base de données) ; • etc. Nous développons dans la suite l’exemple d’une application pour un grossiste de livres. 1.1.2 Dialogue avec l’Expert Métier Supposons que nous développons une application de gestion pour un grossiste de livres Ur Books LTD. Le but ici n’est pas de développer complètement cette aplication mais de donner les grandes lignes de l’organisation de notre application, de la démarche, de la conception et des outils de programmation. a) Description du métier par l’expert Nous intérogeons l’Expert Métier, c’est à dire la personne de Ur Books LTD avec qui nous dialogons pour établir le cahier des charges ou valider le fonctionnement de notre logiciel. Cet expert métier nous dit : “Nous vendons des livres. Un livre possède un ou plusieurs auteurs et un éditeur, plus éventuellement un numéro d’édition. Il y a deux formats de livres : les 3 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# formats poches et les formats brochés. Lorsqu’un client nous commande un (ou plusieurs livres), nous devons, si le livre n’est pas en stock, commander nous même des exemplaires du livre chez notre fournisseur. Lorsque nous avons reçu tous les livres de la commande du client, nous envoyons les livres à l’adresse du client. Le client peut être un professionnel (libraire) ou un particulier Si le client est un professionnel, nous lui envoyons régulièrement automatiquement des informations sur les nouveautés, qui nous sont données par les éditeurs.” 1.1.3 Déterminer les acteurs et actions simples Toujours dans un dialogue avec l’expert métier et notre client, nous proposons ici une interface de type ”console”. L’interface comporte différentes vues : 1. La vue client, correspondant à l’acteur Client. L’accueil propose les choix suivants : • S’enregistrer ou modifier ses données personnelles (saisir son nom et ses coordonnées) • Afficher la liste des livres du catalogue (avec pagination) ; • Commander un livre en saisissant sa référence. 2. La vue du Back Office, correspondant à l’acteur Manager. L’accueil propose les choix suivants : • Consulter la liste des commandes et la liste des commandes en attente ; • Voir l’état d’une commande et gérer une commande en saisissant son numéro de commande. 1.1.4 Première ébauche du package Métier Nous poposons de faire un package Métier qui contient : • Une classe Livre ; • Une classe Editeur ; • Une classe Fournisseur ; • Une classe Client ; • Une classe Adresse pour les adresses des clients, des éditeurs et des fournisseurs ; • Une classe Commande. Ces classes sont destinées à représenter les entités manipulées dans le métier de notre client. On commence par mettre en évidence les plus grosses classes. Nous pourrons toujours rajouter des classes plus tard. 4 Chapitre 1 : ABC de la conception et programmation objet 1.2 Créer une classe simple 1.2.1 Modélisation Statique (diagramme de classes) Voyons tout d’abord comment créer une classe pour représenter un numéro de téléphone. Nous verrons ensuite comment les information concernant l’adresse d’une personne ou d’une institution (entreprise ou administration) pourra “contenir” des numéros de téléphone. La classe Telephone est immuable. Cela signifie qu’une fois l’instance du téléphone créée, on ne peut plus la modifier. Le seul moyen de changer le numéro de téléphone serait de détruire (supprimer toute référence à) l’instance de la classe Telephone correspondante, et de construire une nouvelle instance. Pour celà, comme tous les attributs sont eux même immuables, il suffit d’interdire l’écriture sur les attributs (attributs et leurs Setters privés). Dans les setters, nous réaliserons des tests sur la forme des chaînes de caractères pour assurer la cohérence des données (et la sécurité de l’application). On utilise pour celà des expressions régulières (Regex). Dans la classe Adresse, nous avons cette fois défini des propriétés, ce qui permet d’écrire les Getters et les Setters de manière plus compacte. Diag 1. Diagramme de Classes La propriété Telephones, qui est de type Array avec relation sous forme d’attribut. de Telephone, est en lecture seule. Cela ne signifie pas qu’une autre classe ne puisse pas modifier les numéros de téléphone. En effet, l’accès à la référence de l’Array permet de modifier les éléments de cet Array, et donc par exemple d’ajouter ou supprimer un numéro de téléphone. On voit donc que les Getters peuvent parfois permettre de modifier les objets référencés. Ça ne serait pas le cas avec une structure, qui serait intégralement recopiée lors du return du Getter, interdisant la modification de ses données internes. 1.2.2 Code Source UrBooksLTD/Metier/Telephone.cs 1 2 3 4 5 6 7 8 9 using System ; using System . Text ; using System . Text . R e g u l a r E x p r e s s i o n s ; namespace M e t i e r { public c l a s s Telephone { 5 Rémy Malgouyres, www.malgouyres.org 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Conception Objet et Programmation en C# private string m _ l i b e l l e ; private string m_numero ; public string G e t L i b e l l e ( ) { return m _ l i b e l l e ; } public string GetNumero ( ) { return m_numero ; } private void S e t L i b e l l e ( string l i b e l l e ) { Regex myRegex = new Regex ( ” ^ [ a−zA−Z ] { 4 , 1 6 } $ ” ) ; i f ( myRegex . IsMatch ( l i b e l l e ) ) { m_libelle = l i b e l l e ; } else { throw new ArgumentException ( ”Le l i b e l l é e s t i n v a l i d e ” ) ; } } private void SetNumero ( string numero ) { Regex myRegex = new Regex ( ” ^[0 −9]{10} $ ” ) ; i f ( myRegex . IsMatch ( numero ) ) { m_numero = numero ; } else { throw new ArgumentException ( ”Le numéro e s t i n v a l i d e ” ) ; } } public Telephone ( string l i b e l l e , string numero ) { SetLibelle ( l i b e l l e ) ; SetNumero ( numero ) ; } public override string To S tr i n g ( ) { S t r i n g B u i l d e r r e t o u r = new S t r i n g B u i l d e r ( ) ; r e t o u r . Append ( m _ l i b e l l e + ” : ” ) ; r e t o u r . AppendLine ( ” ” + m_numero) ; return r e t o u r . T o St r i n g ( ) ; } } } 1.2.3 Classe de test 6 Chapitre 1 : ABC de la conception et programmation objet UrBooksLTD/UrBooksLTD/TestMetierTelephone.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; using M e t i e r ; namespace UrBooksLTD { class TestMetierTelephone : I T e s t I n t e r f a c e { public void Test ( ) { C o n s o le . Write ( ” Merci de s a i s i r l e l i b e l l e : ” ) ; string l i b e l l e = C on s o le . ReadLine ( ) ; C o n s o le . Write ( ” Merci de s a i s i r l e numero : ” ) ; string numero = C o n so l e . ReadLine ( ) ; try { Telephone t e l = new Telephone ( l i b e l l e , numero ) ; C on s o l e . Write ( t e l ) ; } catch ( E x c e p t i o n e ) { C on s o l e . WriteLine ( ” Erreur ; } } } } 7 : ” + e . Message + ” \n” + e . StackTrace ) Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# 1.3 Relation de Composition 1.3.1 Modélisation Statique Une autre représentation (pas tout à fait équivalente !) de la relation entre la classe Adresse et la classe Telephone est la Composition. On voit sur ce schéma, par le losange plein représentant une composition : • que la classe Adresse va contenir un ensemble de Telephone. • De plus, les cycles de vie sont liés. Si une instance de la classe Adresse est détruite, toutes les instances de la classe Telephone correspondantes seront aussi détruite. • En particulier, une instance de Telephone ne peut pas être partagée par plusieurs instances de la classe Adresse. Diag 2. Diagramme de Classes avec relation Notons en outre que nous avons représenté les sous forme de Composition données de la classe téléphone sous forme de propriétés enn lecture seule (typique de C#). Notons enfin que nous avons défini la propriété Telephones de la classe adresse en privé, interdisant ainsi aux autres classes d’accéder à la collection des téléphones. Cela garantit que la caractéristique de la composition que les téléphones ne sont pas partagés ni modifiables par d’autres instance que leur instance unique d’adresse. En contrepartie, on n’accède plus aux téléphones que sous forme de string pour affichage. 1.3.2 Code source Voici le code C#de la classe Adresse : UrBooksLTD/Metier/Adresse.cs 1 2 3 4 5 6 7 8 9 10 11 using System ; using System . Text ; using System . Text . R e g u l a r E x p r e s s i o n s ; namespace M e t i e r { public c l a s s A d r e s s e { public A d r e s s e ( string numeroRue , string rue , string c o d e P o s t a l , string ville ) { 8 Chapitre 1 : ABC de la conception et programmation objet 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 NumeroRue = numeroRue ; Rue = r u e ; CodePostal = c o d e P o s t a l ; Ville = ville ; T e l e p h on es = new Telephone [MAX_TELEPHONES ] ; } private s t a t i c void checkRegularExp ( string p a t t e r n , string c h a i n e , string errorMsg ) { Regex myRegex = new Regex ( p a t t e r n ) ; i f ( ! myRegex . IsMatch ( c h a i n e ) ) { throw new ArgumentException ( ” Erreur : ” + errorMsg ) ; } } private string m_numeroRue ; public string NumeroRue { g e t { return m_numeroRue ; } set { checkRegularExp (@” ^ [ a−zA−Z0−9\−\ ] { 0 , 1 6 } $ ” , value , ”Numéro de rue i n v a l i d e ” ) ; m_numeroRue = v a l u e ; } } private string m_rue ; public string Rue { g e t { return m_rue ; } set { checkRegularExp (@” ^ [ \w\ s \−\ ]+$ ” , va lue , ”Nom de rue / p l a c e invalide ”) ; m_rue = v a l u e ; } } private string m _ v i l l e ; public string V i l l e { g e t { return m _ v i l l e ; } set { checkRegularExp (@” ^ [ \w\ s \−\ ]+$ ” , va lue , ”Nom de v i l l e i n v a l i d e ” ); m_ville = value ; } } private string m_codePostal ; public string CodePostal { g e t { return m_codePostal ; } 9 Rémy Malgouyres, www.malgouyres.org 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 Conception Objet et Programmation en C# set { Regex myRegex = new Regex ( ” ^[0 −9]{5} $ ” ) ; i f ( ! myRegex . IsMatch ( v a l u e ) ) { throw new ArgumentException ( ”Code P o s t a l i n v a l i d e ” ) ; } m_codePostal = v a l u e ; } } // Nombre Maximum de Numéros de T é l é p h o n e s private s t a t i c readonly int MAX_TELEPHONES = 1 0 ; // p r o p r i é t é en l e c t u r e s e u l e private Telephone [ ] m_telephones ; public Telephone [ ] Te l ep ho n e s { get { return m_telephones ; } // s e t t e r p r i v é ! ! ! private s e t { m_telephones = v a l u e ; } } // / <summary> // / A j o u t e un t é l é p h o n e // / </summary> // / <r e t u r n s >r e n v o i e t r u e s i i l r e s t e de l a p l a c e </r e t u r n s > public bool AddTelephone ( string l i b e l l e , string numero ) { int i n d e x A j o u t = Array . IndexOf ( Telephones , null ) ; i f ( i n d e x A j o u t < MAX_TELEPHONES) { Te l e p h o n e s [ i n d e x A j o u t ] = new Telephone ( l i b e l l e , numero ) ; return true ; } else { return f a l s e ; } } public bool RemoveTelephone ( string l i b e l l e ) { // On c h e r c h e l e t é l é p h o n e a v e c ce l i b e l l e foreach ( Telephone t e l in Te le p h o n e s ) { i f ( t e l . G e t L i b e l l e ( ) == l i b e l l e ) { // On c r é e un t r o u dans l e t a b l e a u T e le ph o ne s [ Array . IndexOf ( Telephones , t e l ) ] = null ; return true ; 10 Chapitre 1 : ABC de la conception et programmation objet 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 } } return f a l s e ; } public override string To S tr i n g ( ) { S t r i n g B u i l d e r r e t o u r = new S t r i n g B u i l d e r ( ) ; r e t o u r . AppendFormat ( ” {0} , {1} ,\ n{2} {3}\ nTéléphone ( s ) NumeroRue , Rue , CodePostal , V i l l e ) ; foreach ( Telephone t e l in Te le p h o n e s ) { i f ( t e l != null ) r e t o u r . Append ( t e l ) ; } return r e t o u r . To S t ri n g ( ) ; } } } public c l a s s Telephone { private string m _ l i b e l l e ; public string L i b e l l e { get { return m _ l i b e l l e ; } private s e t { Regex myRegex = new Regex ( ” ^ [ a−zA−Z ] { 4 , 1 6 } $ ” ) ; i f ( myRegex . IsMatch ( l i b e l l e ) ) { m_libelle = l i b e l l e ; } else { throw new ArgumentException ( ”Le l i b e l l é e s t i n v a l i d e ” ) ; } } private string m_numero ; public string Numero { get { return m _ l i b e l l e ; } private s e t { Regex myRegex = new Regex ( ” ^[0 −9]{10} $ ” ) ; i f ( myRegex . IsMatch ( numero ) ) { m_numero = numero ; } else { throw new ArgumentException ( ”Le numéro e s t i n v a l i d e ” ) ; 11 :\n” , Rémy Malgouyres, www.malgouyres.org 36 37 38 39 Conception Objet et Programmation en C# } } } } 1.3.3 Classe de tests Voici la calsse de tests correspondant à la classe Adresse : UrBooksLTD/UrBooksLTD/TestMetierAdresse.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using System ; using System . Text ; using M e t i e r ; namespace UrBooksLTD { class TestMetierAdresse : I T e s t I n t e r f a c e { public void Test ( ) { try { A d r e s s e a d r e s s e = new A d r e s s e ( ” 12 b i s ” , ” P l a c e de l a Bourse ” , ” 63000 ” , ” Clermont−Ferrand ” ) ; a d r e s s e . AddTelephone ( ” f i x e ” , ” 0123456789 ” ) ; a d r e s s e . AddTelephone ( ” asupprimer ” , ” 9876543210 ” ) ; a d r e s s e . AddTelephone ( ” m o b i l e ” , ” 0623456789 ” ) ; a d r e s s e . RemoveTelephone ( ” asuppr imer ” ) ; C on s o l e . WriteLine ( a d r e s s e ) ; } catch ( E x c e p t i o n e ) { C on s o l e . WriteLine ( ” Erreur : ” + e . Message + ” \n” + e . StackTrace ) ; } } } } 1.4 Pattern Strategy Comme nous l’avons vu dans la partie 1.1.4, nous devons représenter des clients, des fournisseurs et des éditeurs, qui peuvent être, selon les cas, des personnes morales (entreprises) ou des personnes physiques (particuliers). Les informations sur une personne morale (raison sociale, personne à contacter, etc.) ne sont pas forcément les mêmes que les informations sur une personne physique (nom, prénom, civilité, etc.). On souhaite cependant factoriser les données et opérations communues aux deux types de personnes (comme les données et opérations liées à l’adresse de la personne/entreprise). 12 Chapitre 1 : ABC de la conception et programmation objet 1.4.1 Modélisation statique On se propose de créer trois classes, une classe PersonnePhysiqueOuMorale, générique, qui factorisera les traits communs à tous les types de personnes, une classe PersonnePhysique, et une classe PersonneMorale. Ces classes sont liées par des relations d’héritage, aussi appelées des relations de généralisation (ou de spécialisation, selon le point de vue). Ainsi, une PersonnePhysique est un cas particulier de PersonnePhysiqueOuMorale. Une PersonneMorale est aussi un cas particulier de PersonnePhysiqueOuMorale. Diag 3. e pattern Strategy pour les types de personnes. 1.4.2 Code source Voici le code de la classe PersonnePhysiqueOuMorale, suivi du code de la classe PersonneMorale et enfin de la classe PersonnePhysique. Notez le chaînage des constructeur par lequel le constructeur d’une classe dérivée (par exemple le constructeur de PersonnePhysique fait appel (avant même le corps du constructeur) au constructeur de la classe de base PersonnePhysiqueOuMorale pour initialiser les propriétés de la claase de base. UrBooksLTD/Metier/PersonnePhysiqueOuMorale.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 using System ; using System . Text ; namespace M e t i e r { public c l a s s PersonnePhysiqueOuMorale { protected string D e s c r i p t i o n { g e t ; s e t ; } // A j o u t e r d e s t e s t s par Regex s i b e s o i n protected A d r e s s e m_adresse ; public PersonnePhysiqueOuMorale ( string d e s c r i p t i o n , A d r e s s e a d r e s s e ) 13 Rémy Malgouyres, www.malgouyres.org 14 15 16 17 18 19 20 21 22 23 24 25 Conception Objet et Programmation en C# { Description = description ; m_adresse = a d r e s s e ; } public override string To S tr i n g ( ) { return ” Personne p h y s i q u e ou p e rs o n n e morale + D e s c r i p t i o n + ” ,\ n” + m_adresse ; } :\n” } } UrBooksLTD/Metier/PersonneMorale.cs 1 2 3 4 5 6 7 8 9 10 using System ; using System . Text ; namespace M e t i e r { public c l a s s PersonneMorale : PersonnePhysiqueOuMorale { public string R a i s o n S o c i a l e { g e t ; s e t ; } // A j o u t e r d e s t e s t s par Regex si besoin public string ContactName { g e t ; s e t ; } // A j o u t e r d e s t e s t s par Regex si besoin 11 12 public PersonneMorale ( string r a i s o n S o c i a l e , string contactName , A d r e s s e adresse ) // Appel du c o n s t r u c t e u r de l a c l a s s e de b a s e PersonnePhysiqueOuMorale : base ( r a i s o n S o c i a l e + ” , c o n t a c t e r ” + contactName , a d r e s s e ) { RaisonSociale = raisonSociale ; ContactName = contactName ; } 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public override string To S tr i n g ( ) { return ” Personne Morale :\n” + R a i s o n S o c i a l e + ” \nà l ’ a t t e n t i o n de ” + ContactName + ” ,\ n” + m_adresse ; } } } UrBooksLTD/Metier/PersonnePhysique.cs 1 2 3 4 5 6 7 using System ; using System . Text ; namespace M e t i e r { public c l a s s PersonnePhysique : PersonnePhysiqueOuMorale 14 Chapitre 1 : ABC de la conception et programmation objet 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { public enum C i v i l i t e {MR, MME, MLLE} private s t a t i c string G e t C i v i l i t e S t r i n g ( C i v i l i t e c i v i l i t e ) { switch ( c i v i l i t e ) { case C i v i l i t e .MLLE : return ” M a d e m o i s e l l e ” ; case C i v i l i t e .MME : return ”Madame” ; case C i v i l i t e .MR : return ” Monsieur ” ; default } public string Nom { g e t ; s e t ; } // A j o u t e r d e s t e s t s par Regex s i b e s o i n public string Prenom { g e t ; s e t ; } // A j o u t e r d e s t e s t s par Regex s i besoin public C i v i l i t e m _ c i v i l i t e ; 24 25 26 public PersonnePhysique ( string nom , string prenom , C i v i l i t e c i v i l i t e , Adresse a d r e s s e ) // Appel du c o n s t r u c t e u r de l a c l a s s e de b a s e PersonnePhysiqueOuMorale : base ( G e t C i v i l i t e S t r i n g ( c i v i l i t e ) + ” ” + prenom + ” ” + nom , adresse ) { 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 : return ” ” ; } Nom = nom ; Prenom = prenom ; m_civilite = c i v i l i t e ; } public string G e t C i v i l i t e ( ) { return G e t C i v i l i t e S t r i n g ( m _ c i v i l i t e ) ; } public override string To S tr i n g ( ) { return ” Personne P h y s i q u e \n” + base . D e s c r i p t i o n ; } } } 1.4.3 Classe de Test Voici la calsse de tests correspondant à l’ensempble du pattern Strategy pour représenter les personnes : UrBooksLTD/UrBooksLTD/TestMetierPersonne.cs 1 2 3 4 5 using System ; using System . Text ; using M e t i e r ; 15 Rémy Malgouyres, www.malgouyres.org 6 7 8 9 10 11 12 13 14 15 namespace UrBooksLTD { class TestMetierPersonne : I T e s t I n t e r f a c e { public void Test ( ) { try { A d r e s s e a d r e s s e = new A d r e s s e ( ” 12 b i s ” , ” P l a c e de l a Bourse ” , ” 63000 ” , ” Clermont−Ferrand ” ) ; 16 17 18 // Test de per son n e morale PersonnePhysiqueOuMorale p e r s o n n e = new PersonneMorale ( ” L i b r a i r i e Les Bouts qun ” , ” C h r i s t i n e Kafka ” , adresse ) ; C on s o l e . WriteLine ( p e r s o n n e ) ; PersonneMorale p e r s o n n e M o r a l e = ( p e r s o n n e as PersonneMorale ) ; i f ( p e r s o n n e M o r a l e != null ) { C on s o le . WriteLine ( ” Notre c o n t a c t c h e z \” ” + p e r s o n n e M o r a l e . RaisonSociale + ” \” e s t : ” + p e r s o n n e M o r a l e . ContactName + ” \n” ) ; } 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // Test de per son n e p h y s i q u e p e r s o n n e = new PersonnePhysique ( ” Kafka ” , ” C h r i s t i n e ” , PersonnePhysique . C i v i l i t e .MME, a d r e s s e ) ; C on s o l e . WriteLine ( p e r s o n n e ) ; PersonnePhysique p e r s o n n e P h y s i q u e = ( p e r s o n n e as PersonnePhysique ) ; i f ( p e r s o n n e P h y s i q u e != null ) { Co n s o l e . WriteLine ( ” \nOn s ’ a d r e s s e à ” + p e r s o n n e P h y s i q u e . Prenom + ” en d i s a n t : ” + p e r s o n n e P h y s i q u e . GetCivilite () ) ; } 34 35 36 37 38 39 40 41 42 43 44 45 46 47 Conception Objet et Programmation en C# } catch ( E x c e p t i o n e ) { C on s o l e . WriteLine ( ” Erreur ; } } } } 16 : ” + e . Message + ” \n” + e . StackTrace ) Chapitre 1 : ABC de la conception et programmation objet 1.5 Diagrammes de Séquence 1.5.1 Notion de diagramme de séquence et exemple simple Les diagrammes de séquences permettent d’avoir une vue dynamique des intéractions entre classes et/ou des algorithmes. Généralement, lorsqu’un acteur lance une action (comme un click de bouton) dans l’application, un certain nombre de classes interviennent dans la mise en oeuvre de l’action par le programme. Les méthodes de ces classes s’appellent les unes les autres au cours du temps. De plus, à l’intérieur de ces méthodes, des boucles (des loop correspondant à des while ou for), des branchements conditionnels (opt pour un if ou alt pour un if...else ou un switch) implémentent des algotithmes. En bref, les diagrammes de séquences schématisent l’algorithmique inter classes et parfois l’algorithmique intra classe. Nous voyons ici un exemple simple. Une classe MaximumTableau, possède un constructeur qui crée un tableau aléatoire d’entiers, dont la référence est mémorisée dans un attribut. Une méthode CalculeMaximum retourne la plus grande des valeurs contenues dans le tableau. Le programme principal crée une instance de MaximumTableau, appelle la méthode de MaximumTableau qui calcule le maximum, et affiche le résultat. Voici tout d’abord le diagramme de conception statique (diagramme de classes) : Diag 4. Diagramme de Classes pour le programme créant un tableau aléatoire et calculant son maximum. Voici maintenant le diagramme de séquence de l’algorithme grossier (inter classes) pour le même programme, qui schématise les appels de méthodes entre la classe Program et la classe MaximumTableau. Dans ce diagramme, l’axe vertical représente le temps. Les lignes verticales représentent des cycles de vies de classes ou d’instances de classes. La flèche avec le stéréotype create est un appel du constructeur de MaximumTableau, lequel génère le tableau aléatoire. La flèche continue est un appel de la méthode CalculeMaximum dans la fonction Program.Main. Les rectangles verticaux représentent des méthodes (appelés messages) et les flèches en pointillés qui en sont issues des valeurs retournées par ces méthodes (appelés messages de retour). 17 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# Diag 5. Diagramme de Séquences “vu de loin” (algorithmique inter classe) pour le programme créant un tableau aléatoire et calculant son maximum. Voici enfin l’algorithme détaillé pour le même programme, qui inclut l’algorithme de calcul du maximum à l’intérieur de la méthode MaximumTableau.CalculeMaximum Diag 6. Diagramme de Séquences “vu de près” (incluant l’algorithmique intra classe) pour le programme créant un tableau aléatoire et calculant son maximum. Voici maintenant le code source C#de la classe MaximumTableau, puis de la classe Program. ExemplesDiagSeq/ExemplesDiagSeq/Program.cs 1 2 3 4 5 using System ; using System . Text ; namespace ExemplesDiagSeq 18 Chapitre 1 : ABC de la conception et programmation objet 6 7 8 9 10 11 12 13 14 15 16 17 { c l a s s Program { s t a t i c void Main ( string [ ] a r g s ) { MaximumTableau myTableau = new MaximumTableau ( 5 0 ) ; int max = myTableau . CalculeMaximum ( ) ; C o n s o le . WriteLine ( ”Maximum du Tableau : {0} ” , max) ; C o n s o le . ReadKey ( ) ; } } } ExemplesDiagSeq/ExemplesDiagSeq/MaximumTableau.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 using System ; using System . Text ; namespace ExemplesDiagSeq { c l a s s MaximumTableau { // Générateur a l é a t o i r e , OBLIGATOIREMENT s t a t i q u e , // I n i t i a l i s é au d é b u t du programme par l e c o n s t r u c t e u r private s t a t i c Random random = new Random ( ) ; private int [ ] m_tableau ; public MaximumTableau ( int nbElements ) { m_tableau = new int [ nbElements ] ; fo r ( int i = 0 ; i < nbElements ; i ++) { m_tableau [ i ] = random . Next ( 0 , 1 0 0 ) ; } } public int CalculeMaximum ( ) { int max = −1 ; foreach ( int x in m_tableau ) { i f ( x > max) { max = x ; } } return max ; } } } 19 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# 1.5.2 Exemple de diagramme de séquence pour UrBookLTD Un diagramme de séquence correspond à une action (avec éventuellement des hypothèses sur les branchements). Recenser les acteurs et les actions fait partie de l’analyse fonctionnelle de l’application. Nous avons recencé dans la partie 1.1.3 les principales actions pour l’acteur Client et pour l’acteur Manager. Voyons un exemple de diagramme de séquence pour l’action modifier ses données personnelles d’un Client. Deux cas sont possibles : le client est une personne physique ou une personne morale. On fait saisir les données au client, y compris concernant son adresse et son numéro de téléphone. On affiche enfin le résultat. 20 Chapitre 1 : ABC de la conception et programmation objet Diag 7. Diagramme de Séquences de la saisie des données personnelles par une personne (physique ou morale). 21 Chapitre 2 Delegates et Expressions Lambda 2.1 Fonctions de comparaison basiques Considérons trois types qui admettent la comparaison (un ordre) entre les éléments : • Le type int avec la relations d’ordre usuelle sur les nombres entiers ; • Le type string avec l’ordre alphabétique ; • Le type MyDate défini ci-dessous avec l’ordre chronologique sur les dates. Voici la définition de la classe MyDate : CoursLinq/ExemplesDelgate/ex02_MyDate.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; System . Text . R e g u l a r E x p r e s s i o n s ; namespace ExemplesDelegate { public p a r t i a l c l a s s MyDate { public int Annee { g e t ; private s e t ; } public int Mois { g e t ; private s e t ; } public int Jour { g e t ; private s e t ; } public MyDate ( string dateJJMMAAA) { string [ ] e l e m e n t s D a t e = dateJJMMAAA . S p l i t (new [ ] { ’ / ’ } ) ; Annee = int . Parse ( e l e m e n t s D a t e [ 2 ] ) ; Mois = int . Parse ( e l e m e n t s D a t e [ 1 ] ) ; Jour = int . Parse ( e l e m e n t s D a t e [ 0 ] ) ; } public override string To S tr i n g ( ) { return Jour . T o S tr i n g ( ) . PadLeft ( 2 , ’ 0 ’ ) 22 Chapitre 2 : Delegates et Expressions Lambda 28 29 30 31 32 33 34 35 36 37 38 39 + ”/” + Mois . T o S t ri n g ( ) . PadLeft ( 2 , ’ 0 ’ ) + ”/ ” + Annee . T oS t r i ng ( ) . PadLeft ( 2 , ’ 0 ’ ) ; } public string T o S t r i n g R e v e r s e ( ) { return Annee . T o S t ri n g ( ) . PadLeft ( 2 , ’ 0 ’ ) + ”/” + Mois . T o S t ri n g ( ) . PadLeft ( 2 , + Jour . T oS t r i ng ( ) . PadLeft ( 2 , ’ 0 ’ ) ; } ’ 0 ’ ) + ”/ ” } } Nous pouvons définir trois fonctions différentes pour coparer respectivement ces trois types de données. Le fonctionnement est à peu près le même, chaque méthode renvoie une valeur négative, nulle ou positive suivant les cas sur l’ordre ou l’égalité des deux paramètres (comme la fonction strcmp pour le langage C). CoursLinq/ExemplesDelgate/ex01_ExCompareBasic.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s ExCompareBasic { // Fo nc t i on de comparaison de deux e n t i e r s // Retourne 0 s i l e s a==b , p o s i t i f s i a<b , n é g a t i f s i b<a public s t a t i c int ? CompareInt ( int ? a , int ? b ) { return a − b ; } // Fo nc t i on de comparaison de deux c h a î n e par o r d r e a l p h a b é t i q u e // Retourne 0 s i l e s a==b , p o s i t i f s i a<b , n é g a t i f s i b<a public s t a t i c int CompareString ( string a , string b ) { return S t r i n g . Compare ( a , b ) ; } // Fo nc t i on de comparaison de deux d a t e s par o r d r e a l p h a b é t i q u e // Retourne 0 s i l e s a==b , p o s i t i f s i a<b , n é g a t i f s i b<a public s t a t i c int CompareDate ( MyDate a , MyDate b ) { return S t r i n g . Compare ( a . T o S t r i n g R e v e r s e ( ) , b . T o S t r i n g R e v e r s e ( ) ) ; } } } Ces méthodes s’utilisent comme dans les méthodes de test suivantes : CoursLinq/ExemplesDelgate/ex03_TestCompareBasic.cs 23 Rémy Malgouyres, www.malgouyres.org 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 using using using using using Conception Objet et Programmation en C# System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s TestCompareBasic { // Test de l a comparaison de deux e n t i e r s public s t a t i c void TestCompareInt ( ) { C o n s o le . Write ( ” Merci d ’ e n t r e r deux e n t i e r s s é p a r é s pas un / : ” ) ; string [ ] chainesAB = C o ns o l e . ReadLine ( ) . S p l i t (new [ ] { ’ / ’ } ) ; int a = int . Parse ( chainesAB [ 0 ] ) ; int b = int . Parse ( chainesAB [ 1 ] ) ; i f ( ExCompareBasic . CompareInt ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( ExCompareBasic . CompareInt ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } // Test de l a comparaison a l p h a b é t i q u e de deux c h a î n e s public s t a t i c void TestCompareString ( ) { C o n s o le . Write ( ” Merci d ’ e n t r e r deux c h a î n e s a l a p h a b é t i q u e s s é p a r é e s pas un / : ” ) ; string [ ] chainesAB = C o ns o l e . ReadLine ( ) . S p l i t (new [ ] { ’ / ’ } ) ; string a = chainesAB [ 0 ] ; string b = chainesAB [ 1 ] ; i f ( ExCompareBasic . CompareString ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( ExCompareBasic . CompareString ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } // Test de l a comparaison c h r o n o l o g i q u e de deux d a t e s public s t a t i c void TestCompareDate ( ) { C o n s o le . Write ( ” Merci d ’ e n t r e r une d a t e au f o r ma t j j /mm/ aaaa : ” ) ; MyDate a = new MyDate ( C o n s o l e . ReadLine ( ) ) ; C o n s o le . Write ( ” Merci d ’ e n t r e r une a u t r e d a t e au f o r ma t j j /mm/ aaaa ”) ; MyDate b = new MyDate ( C o n s o l e . ReadLine ( ) ) ; i f ( ExCompareBasic . CompareDate ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else 24 : Chapitre 2 : Delegates et Expressions Lambda 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 i f ( ExCompareBasic . CompareDate ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } // Méthode q u i e x é c u t e l e s t e s t s d e s t r o i s méthodes de comparaison public s t a t i c void TestAllCompareMethods ( ) { TestCompareInt ( ) ; TestCompareString ( ) ; TestCompareDate ( ) ; } } } 2.2 Uniformisation de la signature des fonctions Pour pouvoir utiliser la comparaison sur des types génériques (par exemple, comme nous le verrons ci-dessous, pour écrire des algorithmes de tris qui fonctionneront indépendament du type), nous allons créer des méthodes de comparaison dont la signature est générique. Autrement dit, la signature des trois méthodes de comparaison est la même, ainsi que la signature des trois méthodes de lecture dans la console. On s’appuie pour celà sur la classe Objet, qui est ancètre de toutes les classes en C#. Dans la définition des méthodes, on réalise un downcasting pour utiliser la comparaison sur des types plus spécialisés que la classe Objet (ici les types int, string et MyDate). CoursLinq/ExemplesDelgate/ex04_ExUniformSignature.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s ExUniformSignature { public s t a t i c Object ReadInt ( ) { C o n s o le . Write ( ” Merci d ’ e n t r e r un nombre e n t i e r int a = int . Parse ( C on s o l e . ReadLine ( ) ) ; return a ; } : ”) ; public s t a t i c Object Read String ( ) { C o n s o le . Write ( ” Merci d ’ e n t r e r une c h a î n e a l p h a b é t i q u e string a = C o n so l e . ReadLine ( ) ; return a ; } 25 : ”) ; Rémy Malgouyres, www.malgouyres.org 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 Conception Objet et Programmation en C# public s t a t i c Object ReadDate ( ) { C o n s o le . Write ( ” Merci d ’ e n t r e r une d a t e au f o r ma t j j /mm/ aaaa MyDate a = new MyDate ( C o n s o l e . ReadLine ( ) ) ; return a ; } : ”) ; // Fo nc t i on de comparaison de deux e n t i e r s // Retourne 0 s i l e s a==b , p o s i t i f s i a<b , n é g a t i f s i b<a public s t a t i c int CompareInt ( Object a , Object b ) { int ? aa = a as int ? ; int ? bb = b as int ? ; return aa == bb ? 0 : ( aa < bb ? −1 : 1 ) ; } // Fo nc t i on de comparaison de deux c h a î n e par o r d r e a l p h a b é t i q u e // Retourne 0 s i l e s a==b , p o s i t i f s i a<b , n é g a t i f s i b<a public s t a t i c int CompareString ( Object a , Object b ) { string aa = a as string ; string bb = b as string ; return S t r i n g . Compare ( aa , bb ) ; } // Fo nc t i on de comparaison de deux d a t e s par o r d r e c h r o n o l o g i q u e // Retourne 0 s i l e s a==b , p o s i t i f s i a<b , n é g a t i f s i b<a public s t a t i c int CompareDate ( Object a , Object b ) { MyDate aa = a as MyDate ; MyDate bb = b as MyDate ; return S t r i n g . Compare ( aa . T o S t r i n g R e v e r s e ( ) , bb . T o S t r i n g R e v e r s e ( ) ) ; } } } Dans les trois fonctions de test suivantes, on constate que le code est exactement le même, sauf le nom des méthodes et les downcasts. Comme nous allons le voir dans la partie suivante, on peut en fait factoriser ce code en passant les fonctions de lecture dans la console et de comparaison en paramètre. CoursLinq/ExemplesDelgate/ex05_TestUniformSignature.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s T e s t U n i f o r m S i g n a t u r e { public s t a t i c void TestCompareInt ( ) { int ? a = ExUniformSignature . ReadInt ( ) as int ? ; 26 Chapitre 2 : Delegates et Expressions Lambda 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 int ? b = ExUniformSignature . ReadInt ( ) as int ? ; i f ( ExUniformSignature . CompareInt ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( ExUniformSignature . CompareInt ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } public s t a t i c void TestCompareString ( ) { string a = ExUniformSignature . Read Str ing ( ) as string ; string b = ExUniformSignature . R ead Str ing ( ) as string ; i f ( ExUniformSignature . CompareString ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( ExUniformSignature . CompareString ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } public s t a t i c void TestCompareDate ( ) { Object a = ExUniformSignature . ReadDate ( ) ; Object b = ExUniformSignature . ReadDate ( ) ; i f ( ExUniformSignature . CompareDate ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( ExUniformSignature . CompareDate ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } // Méthode q u i e x é c u t e l e s t e s t s d e s t r o i s méthodes de comparaison public s t a t i c void TestAllCompareMethods ( ) { TestCompareInt ( ) ; TestCompareString ( ) ; TestCompareDate ( ) ; } } } 2.3 Types delegate Définition. Un type délégué est un type de donnée pouvant contenir des méthodes C#. Pour un type délégué donné, la signature des méthodes est fixée, dans la définition du type délégué. 27 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# Les types délégués sont analogues aux types pointeurs de fonctions en langage C : ils peuvent contenir des fonctions. Exemple. Voici la déclaration d’un type délégué qui peut être utilisé pour des méthodes de comparaison. 1 2 3 4 5 6 7 // Type d é l é g u é pour l e s f o n c t i o n s de comparaison // s i g n a t u r e : i n t f o n c t i o n ( O b j e c t , O b j e c t ) public delegate int MonTypeFonctionComparaison ( Object a , Object b ) ; // Ajout d ’ une é t h o d e dans une i n s t a n c e de d é l é g u é ( abonnement ) . // La méthode d o i t a v o i r l a même s i g n a t u r e que l e t y p e d é l é g u é MonTypeFonctionLecture r e a d F u n c t i o n = ExUniformSignature . ReadInt ; En fait, nous verrons plus loin que les instances de types délégués en C#peuvent contenir plusieurs méthodes de même signature. On parle alors de délégués multicast. Voici maintenant des exemples d’utilisation des délégués pour créer des méthodes qui peuvent s’appliquer à la comparaison d’objets de différents types. La première méthode, plus simple, montre comment on affecte une méthode (ici ExUniformSignature.R ou ExUniformSignature.CompareInt). Dans la deuxième méthodes, les instances de types déléguées sont en paramètre de la fonction, ce qui permet, lors de l’appel de la fonction, de passer la méthode correspondant au type (int, string et MyDate) utilisé (voir troisième méthode ci-dessous). CoursLinq/ExemplesDelgate/ex06_BasicDelegateExemples.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s B a s i c D e l e g a t e E x e m p l e s { // ////////////////////////////////////////////////////////////// // D é c l a r a t i o n de t y p e s d é l é g u é s // Type d é l é g u é pour l e s f o n c t i o n s de l e c t u r e // s i g n a t u r e : O b j e c t f o n c t i o n ( v o i d ) public delegate Object MonTypeFonctionLecture ( ) ; // Type d é l é g u é pour l e s f o n c t i o n s de comparaison // s i g n a t u r e : i n t f o n c t i o n ( O b j e c t , O b j e c t ) public delegate int MonTypeFonctionComparaison ( Object a , Object b ) ; // ///////////////////////////////////////////////////////////// // Exemples d ’ u t i l i s a t i o n de d é l é g u é s // Exemple de méthode u t i l i s a n t d e s v a r i a b l e s l o c a l e s de t y p e d é l é g u é public s t a t i c void ExempleDelegatesUnicastDeBase ( ) { // Ajout d ’ une é t h o d e dans une i n s t a n c e de d é l é g u é ( abonnement ) . 28 Chapitre 2 : Delegates et Expressions Lambda 30 31 32 33 34 35 // La méthode d o i t a v o i r l a même s i g n a t u r e que l e t y p e d é l é g u é MonTypeFonctionLecture r e a d F u n c t i o n = ExUniformSignature . ReadInt ; // Ajout d ’ une é t h o d e dans une i n s t a n c e de d é l é g u é ( abonnement ) . // La méthode d o i t a v o i r l a même s i g n a t u r e que l e t y p e d é l é g u é MonTypeFonctionComparaison compareFunction = ExUniformSignature . CompareInt ; 36 37 // E x é c u t i o n de l a méthode c o n t e n u e dans l ’ i n s t a n c e de d é l é g u é readFunction // Syntaxiquement , ça f o n c t i o n n e comme un a p p e l de f o n c t i o n // Le t y p e Object a = r e a d F u n c t i o n ( ) ; Object b = r e a d F u n c t i o n ( ) ; 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 // E x é c u t i o n de l a méthode c o n t e n u e dans l ’ i n s t a n c e de d é l é g u é compareFunction i f ( compareFunction ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( compareFunction ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } // Passage de p a r a m è t r e s de t y p e d é l é g u é // La méthode i c i d é f i n i e marche pour nos t r o i s t y p e s i n t , s t r i n g e t MyDate // Les méthodes de l e c t u r e e t de comparaison s o n t p a s s é e s en paramètre !!!! public s t a t i c void TestCompareGeneric ( MonTypeFonctionLecture readFunction , MonTypeFonctionComparaison compareFunction ) { Object a = r e a d F u n c t i o n ( ) ; Object b = r e a d F u n c t i o n ( ) ; i f ( compareFunction ( a , b ) == 0 ) C on s o l e . WriteLine ( ” {0} é g a l e {1} ” , a , b ) ; else i f ( compareFunction ( a , b ) < 0 ) C on s o le . WriteLine ( ” {0} i n f é r i e u r à {1} ” , a , b ) ; else C on s o le . WriteLine ( ” {0} s u p é r i e u r à {1} ” , a , b ) ; } // U t i l i s a t i o n d ’ une méthode a v e c p a r a m è t r e s de t y p e d é l é g u é public s t a t i c void t e s t P a s s a g e P a r a m e t r e D e l e g u e ( ) { TestCompareGeneric ( ExUniformSignature . ReadInt , ExUniformSignature . CompareInt ) ; TestCompareGeneric ( ExUniformSignature . ReadString , ExUniformSignature . CompareString ) ; 29 Rémy Malgouyres, www.malgouyres.org 77 78 79 80 Conception Objet et Programmation en C# TestCompareGeneric ( ExUniformSignature . ReadDate , ExUniformSignature . CompareDate ) ; } } } 2.4 Exemple d’utilisation : méthode de tri générique Voici une méthode de tri, implémentation de l’algorithme du tri par sélection, qui peut fonctionner pour nos trois types de données : int, string et MyDate. La liste des objets à trier, ainsi que la méthode de comparaison à utiliser pour comparer les éléments de la liste, sont passées en paramètre. CoursLinq/ExemplesDelgate/ex07_ExTriGenerique.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s ExTriGenerique { // Type d é l é g u é pour l e s f o n c t i o n s de comparaison // s i g n a t u r e : i n t f o n c t i o n ( O b j e c t , O b j e c t ) public delegate int MonTypeFonctionComparaison ( Object a , Object b ) ; // Méthode de t r i q u i f o n c t i o n n e pour t o u s nos t y p e s de données : i n t ; s t r i n g ou MyDate // La f o n c t i o n p e r m e t t a n t de comparer l e s é l é m e n t s à t r i e r e s t p a s s é e en paramètre // La l i s t e e s t p a s s é e par r é f é r e n c e e t l a r é f é r e n c e e s t m o d i f i é e à l a f i n de l a f o n c t i o n // // L ’ a l g o r i t h m e u t i l i s é pour ce t r i ”à l a main” e s t l e t r i par s é l e c t i o n : // t a n t que l a l i s t e e s t non v i d e // c h e r c h e r l e p l u s p e t i t é l é m e n t min de l a l i s t e // a j o u t e r min en queue de l i s t e au r é s u l t a t // sup p r im e r min de l a l i s t e public s t a t i c void T r i S e l e c t i o n ( r e f L i s t <Object> l i s t e , MonTypeFonctionComparaison fonctionCompare ) { L i s t <Object> l i s t e T r i e e = new L i s t <Object >() ; // t a n t que l a l i s t e e s t non v i d e while ( l i s t e . Count ( ) != 0 ) { // Recherche du minimum de l a l i s t e r e s t a n t e : Object min = l i s t e . F i r s t ( ) ; foreach ( Object e l e m e n t in l i s t e ) { i f ( fonctionCompare ( element , min ) < 0 ) 30 Chapitre 2 : Delegates et Expressions Lambda 36 37 38 39 40 { min = e l e m e n t ; } } l i s t e T r i e e . Add( min ) ; résultat l i s t e . Remove ( min ) ; 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 // a j o u t e r min en queue de l i s t e au // s up p r i m e r min de l a l i s t e } // Retour de l a l i s t e t r i é e par r é f é r e n c e liste = listeTriee ; } // Gé nér a tion d ’ une l i s t e ( non ordonnée ) de MyDate public s t a t i c L i s t <Object> g e n e r a t e I n i t i a l L i s t ( ) { L i s t <Object> l i s t e D a t e s = new L i s t <Object >() ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2012 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2015 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/10/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 10/09/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2013 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/08/2015 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 12/09/2013 ” ) ) ; return l i s t e D a t e s ; } // Méthode de t e s t de l a méthode de t r i g é n é r i q u e , a p p l i q u é e à une l i s t e de d a t e s . public s t a t i c void T e s t T r i B u l l e D e l e g a t e ( ) { // Gé nér a tion de l a l i s t e de MyDate L i s t <Object> l i s t e D a t e s = g e n e r a t e I n i t i a l L i s t ( ) ; // Appel de l a méthode de t r i a v e c paramètre d é l é g u é pour // l a méthode de comaraison T r i S e l e c t i o n ( r e f l i s t e D a t e s , ExUniformSignature . CompareDate ) ; // A f f i c h a g e de l a l i s t e t r i é e foreach ( MyDate d a t e in l i s t e D a t e s ) C on s o l e . Write ( d a t e + ” , ” ) ; C o n s o le . WriteLine ( ) ; } } } 2.5 Expressions lambda Les Expressions lambda sont un moyen de définir des méthodes anonymes en C#. Voici un exemple de méthode de comparaison défini par une expression lambda, que nous passerons en paramètre à la méthode ExTriGenerique.TriSelection. CoursLinq/ExemplesDelgate/ex08_ExempleExpressionLambda.cs 31 Rémy Malgouyres, www.malgouyres.org 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s ExempleExpressionLambda { public s t a t i c void TestTriBulleExprssionLambda ( ) { L i s t <Object> l i s t e D a t e s = ExTriGenerique . g e n e r a t e I n i t i a l L i s t ( ) ; // U t i l i s a t i o n d ’ une e x p r e s s i o n lambda ( méthode anonyme ) // Paramètres de l a méthode anonyme : a e t b de t y p e O b j e c t // Retour de l a méthode anonyme : i n t ( t y p e d é t e r m i n é dynamiquement par l e r e t u r n ) ExTriGenerique . T r i S e l e c t i o n ( r e f l i s t e D a t e s , ( Object a , Object b ) => { MyDate aa = a as MyDate ; MyDate bb = b as MyDate ; return S t r i n g . Compare ( aa . T o S t r i n g R e v e r s e ( ) , bb . T o S t r i n g R e v e r s e ( ) ); }) ; 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Conception Objet et Programmation en C# // A f f i c h a g e de l a l i s t e t r i é e foreach ( MyDate d a t e in l i s t e D a t e s ) C on s o l e . Write ( d a t e + ” , ” ) ; C o n s o le . WriteLine ( ) ; } } } 32 Chapitre 3 Collections Les Collections sont des classes qui gèrent des structures de données représentant des ensembles, listes, tableaux, etc. d’objets. Les opérations de base des collections sont typiquement l’ajout, la suppression d’éléments, le tri suivant un certain ordre (utilisant une fonction de comparaison), etc. Certaines collections, comme les files (gestion First In First Out ou encore FIFO) et les piles (gestion Last In First Out LIFO), ont un ensemble de primitives spécifiques, qui permettent une gestion particulières des éléments qui ont de bonnes propriétés algorithmiques. Les dictionnaires permettent, eux, de représenter des applications (au sens mathématique, associant à un élément d’un ensemble un élément d’un autre ensemble). Un dictionnaire associe à différentes clefs des valeurs. Chaque clef est unique (pas de doublon) et sa valeur est données par le dictionnaire. Nous abordons ici les collections appelées génériques, parce qu’elles dépendent d’un template, à savoir, le type de données qui constitue les éléments de la collection (ou encore les clefs et les valeurs dans le cas d’un dictionnaire). Elles se trouvent dans le namespace suivant : System.Collections.Generic Dont la documentation peut être consultée via la MSDN en ligne ou dans la visionneuse d’aire de Visual Studio. Figure 3.1 : Capture de la Visionneuse d’Aide de Visual Studio 33 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# 3.1 Listes CoursLinq/ExemplesCollections/ex01_ExempleList.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace E x e m p l e s C o l l e c t i o n s { public c l a s s ExempleList { public s t a t i c void Ex em pl e L is t e ( ) { // D é c l a r a t i o n d ’ une r é f é r e n c e s u r d e s L i s t <i n t > L i s t <int> l i s t e ; // A l l o c a t i o n OBLIGATOIRE : c r é a t i o n d ’ une l i s t e v i d e // ( par exemple dans un c o n s t r u c t e u r , pour i n i t i a l i s e r un a t t r i b u t liste ) l i s t e = new L i s t <int >() ; // La méthode Add a j o u t e un é l é m e n t en queue de l i s t e l i s t e . Add ( 4 ) ; // é l é m e n t s : 4 l i s t e . Add ( 7 ) ; // é l é m e n t s : 4 , 7 l i s t e . Add ( 9 ) ; // é l é m e n t s : 4 , 7 , 9 l i s t e . Add ( 7 ) ; // é l é m e n t s : 4 , 7 , 9 , 7 // I n s e r t i o n de 34 en t ê t e de l i s t e l i s t e . I n s e r t ( 0 , 3 4 ) ; // é l é m e n t s : 34 , 4 , 7 , 9 , 7 l i s t e . I n s e r t ( 2 , 1 2 ) ; // é l é m e n t s : 34 , 4 , 12 , 7 , 9 , 7 // S u p p r e s s i o n de l a p r e m i è r e o c c u r e n c e d ’ un é l é m e n t : l i s t e . Remove ( 7 ) ; // é l é m e n t s : 34 , 4 , 12 , 9 , 7 // Nombre d ’ é l é m e n t s de l a l i s t e : C o n s o le . WriteLine ( ”Nombre d ’ é l é m e n t s : {0} ” , l i s t e . Count ) ; // Parcours de l a l i s t e par f o r e a c h : a f f i c h e ”34 , 4 , 12 , 9 , 7 ,” foreach ( int e l e m e n t in l i s t e ) { C on s o l e . Write ( e l e m e n t + ” , ” ) ; } l i s t e . S o r t ( ) ; // Tri de l a l i s t e C o n s o le . WriteLine ( ” \ n L i s t e T r i é e :” ) ; foreach ( int e l e m e n t in l i s t e ) { C on s o l e . Write ( e l e m e n t + ” , ” ) ; } } } } 34 Chapitre 3 : Collections 3.2 Files Une file est une structures de données dans laquelle on peut ajouter et supprimer des éléments suivant la règle du premier arrivé premier sorti, ou encore F IF O (First In First Out). Le nom de file vient d’une analogie avec une file d’attente à un guichet, dans laquelle le premier arrivé sera la premier servi. Les usagers arrivent en queue de file, et sortent de la file à sa tête. Les files correspondent à la classe C#Queue<T> Les principales primitives de gestion des files sont les suivantes : • Constructeur : cette fonction crée une file vide. • EstVide : renvoie 1 si la file est vide, 0 sinon. • EstPleine : renvoie 1 si la file est pleine, 0 sinon. • AccederTete : cette fonction permet l’accès à l’information contenue dans la tête de file. • Enfiler : cette fonction permet d’ajouter un élément en queue de file. La fonction renvoie un code d’erreur si besoin en cas de manque de mémoire. • Defiler : cette fonction supprime la tête de file. L’élément supprimé est retourné par la fonction Defiler pour pouvoir être utilisé. • Vider : cette fonction vide la file. • Detruire : cette fonction permet de détruire la file. CoursLinq/ExemplesCollections/ex02_ExempleQueue.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace E x e m p l e s C o l l e c t i o n s { public c l a s s ExempleQueue { public s t a t i c void ExempleFile ( ) { // d é c l a r a t i o n d ’ une v a r i a b l e r é f é r e n c e v e r s d e s Queues<i n t > Queue<int> f i l e ; // A l l o c a t i o n OBLIGATOIRE : c r é a t i o n d ’ une f i l e v i d e // ( par exemple dans un c o n s t r u c t e u r , pour i n i t i a l i s e r un a t t r i b u t file ) f i l e = new Queue<int >() ; // La méthode Enqueue a j o u t e un é l é m e n t en queue de f i l e (FIFO) f i l e . Enqueue ( 4 ) ; // é l é m e n t s : 4 f i l e . Enqueue ( 7 ) ; // é l é m e n t s : 4 , 7 f i l e . Enqueue ( 9 ) ; // é l é m e n t s : 4 , 7 , 9 // S u p p r e s s i o n de l a t ê t e de f i l e ( l ’ é l é m e n t supprimé e s t r e t o u r n é ) 35 Rémy Malgouyres, www.malgouyres.org 24 25 26 27 28 29 30 31 32 33 34 35 36 37 Conception Objet et Programmation en C# int t e t e = f i l e . Dequeue ( ) ; // é l é m e n t supprimé : 4 ( g e s t i o n FIFO) C o n s o le . WriteLine ( ”La t ê t e de f i l e {0} a é t é supprimée ” , t e t e ) ; C o n s o le . WriteLine ( ”La t ê t e de f i l e e s t maintenant {0} ” , f i l e . Peek ( ) ) ; // Parcours de l a l i s t e par f o r e a c h foreach ( int e l e m e n t in f i l e ) { C on s o l e . Write ( e l e m e n t + ” , ” ) ; } : a f f i c h e ”7 , 9” } } } 3.3 Dictionnaires Comme nous l’avons dit, un dictionnaire permet d’associer à des clefs d’un certain type des valeurs d’un autre type. Voici un exemple dans lequel notre dictionnaire associe à une chaîne de caractère (type string) représentant le nom d’un aliment, un nombre représentant le poid de cet aliment en grammes. CoursLinq/ExemplesCollections/ex03_ExempleDictionnaire.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace E x e m p l e s C o l l e c t i o n s { public c l a s s E x e m p l e D i c t i o n n a i r e { public s t a t i c void ExempleDico ( ) { // d é c l a r a t i o n d ’ une v a r i a b l e r é f é r e n c e v e r s d e s D i c t i o n a r y <s t r i n g , int> D i c t i o n a r y <string , int> d i c o P o i d s ; // A l l o c a t i o n OBLIGATOIRE : c r é a t i o n d ’ une d i c t i o n n a i r e v i d e // ( par exemple dans un c o n s t r u c t e u r , pour i n i t i a l i s e r un a t t r i b u t dicoPoids ) d i c o P o i d s = new D i c t i o n a r y <string , int >() ; // p o i d s en gramme d ’ un aliment try { d i c o P o i d s . Add( ” B a r q u e t t e de f r i t e s ” , 1 0 0 ) ; d i c o P o i d s . Add( ” Yoghurt n a t u r e ” , 1 2 5 ) ; d i c o P o i d s . Add( ” Yoghurt aux f r u i t ” , 1 4 0 ) ; d i c o P o i d s . Add( ” L i t r e de l a i t ” , 1 0 0 0 ) ; } catch ( E x c e p t i o n e ) 36 Chapitre 3 : Collections 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 { C on s o l e . WriteLine ( ” Erreur : ” + e . StackTrace ) ; } i f ( d i c o P o i d s . Remove ( ” Yoghurt aux f r u i t ” ) ) { C on s o l e . WriteLine ( ” S u p p r e s s i o n de Yoghurt aux f r u i t . . . ” ) ; } else { C on s o l e . WriteLine ( ” Yoghurt aux f r u i t : c l e f i n t r o u v a b l e ” ) ; } // Parcours de l ’ e n s e m b l e d e s c o u p l e s ( c l e f s , v a l e u r s ) // 1) R é c u p é r a t i o n de l a c o l l e c t i o n d e s c l e f s : D i c t i o n a r y <string , int >. K e y C o l l e c t i o n l e s C l e f s = d i c o P o i d s . Keys ; // 2) Parcours de l a c o l l e c t i o n d e s c l e f s e t a f f i c h a g e d e s v a l e u r s : foreach ( string c l e f in l e s C l e f s ) { C on s o l e . WriteLine ( ”Le p o i d s de {0} e s t de {1} grammes” , clef , dicoPoids [ c l e f ] ) ; } // Parcour de l ’ e n s e m b l e d e s c l e f s v a l e u r s D i c t i o n a r y <string , int >. V a l u e C o l l e c t i o n l e s V a l e u r s = d i c o P o i d s . Values ; C o n s o le . WriteLine ( ” Les p o i d s p o s s i b l e s pour l e s a l i m e n t s p r é s e n t s s o n t :” ) ; foreach ( int v a l in l e s V a l e u r s ) { C on s o l e . WriteLine ( ” {0} grammes , ” , v a l ) ; } try { C on s o l e . WriteLine ( ” V o i c i l e p o i d s de l a b o î t e d ’ o e u f s d i c o P o i d s [ ” B o î t e de 6 o e u f s ” ] ) ; : ”, } catch ( KeyNotFoundException ) { C on s o l e . WriteLine ( ”Euh . . . En f a i t , nous n ’ avons pas d ’ o e u f s . . . ” ) ; } try { // Les d o u b l o n s ne s o n t pas permis : chaque c l e f e s t u n i q u e // e t e s t a s s o c i é e à une s e u l e v a l e u r d i c o P o i d s . Add( ” Yoghurt n a t u r e ” , 1 5 0 ) ; } catch ( E x c e p t i o n e ) { C on s o l e . WriteLine ( ” Erreur : chaque c l e f e s t u n i q u e ! ! ! ” ) ; C on s o l e . WriteLine ( e . StackTrace ) ; } 37 Rémy Malgouyres, www.malgouyres.org 80 81 82 try { // Recherche du pr emier é l é m e n t s a t i s f a i s a n t un p r é d i c a t ( p o i d s >= 150 grammes ) // Le p r é d i c a t e s t une e x p r e s s i o n lambda p r e n a n t une p a i r e c l e f valeur // e t r e t o u r n a n t un b o o l é e n . KeyValuePair<string , int> c l e f V a l e u r P o i d s S u p e r i e u r A 1 5 0 = d i c o P o i d s . F i r s t ( p a i r e C l e f V a l e u r => p a i r e C l e f V a l e u r . Value >= 150) ; C on s o l e . WriteLine ( ”Le pr emier p o i d s s u p é r i e u r à 150 grammes dans le dictionnaire est ”) ; C on s o l e . WriteLine ( ” l e p o i d s de {0} , s o i t {1} grammes” , c l e f V a l e u r P o i d s S u p e r i e u r A 1 5 0 . Key , c l e f V a l e u r P o i d s S u p e r i e u r A 1 5 0 . Value ) ; 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 Conception Objet et Programmation en C# } catch { C on s o l e . WriteLine ( ”Aucune é l é m e n t d ’ a son p o i d s s u p é r i e u r à 150 grammes . ” ) ; } } } } 3.4 Protocole de comparaison Le protocole de comparaison permet de définir l’ordre sur les éléments d’une collection pour certaines opérations comme, typiquement, le tri des éléments. La définition d’une classe fille de Comparer permet la comparaison d’instances, par exemple pour trier les objets. Cette classe Comparer contient une méthode abstraite : la méthode de comparaison, qui doit être implémentée. La classe Comparer est générique (elle s’applique à un template). Dans l’exemple suivant, on compare des dates (de type MyDate) pour les trier dans l’ordre chronologique. CoursLinq/ExemplesDelgate/ex09_MyDateComparer.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using using using using using using System ; System . C o l l e c t i o n s ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { // L ’ i m p l é m e n t a t i o n de l ’ i n t e r f a c e IComparer permet // l a comparaison d ’ i n s t a n c e s , par exemple pour t r i e r l e s o b j e t s public c l a s s MyDateComparer : Comparer<MyDate> { public override int Compare ( MyDate a , MyDate b ) { 38 Chapitre 3 : Collections 17 18 19 20 return S t r i n g . Compare ( a . T o S t r i n g R e v e r s e ( ) , b . T o S t r i n g R e v e r s e ( ) ) ; } } } Voici un exemple d’utilisation pour trier une liste de dates avec la méthode List<T>.Sort. CoursLinq/ExemplesDelgate/ex10_TestComparerClass.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s TestComparerClass { public s t a t i c void T e s t S o r t D a t e s ( ) { L i s t <MyDate> l i s t e D a t e s = new L i s t <MyDate>() ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2012 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2015 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/10/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 10/09/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2013 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/08/2015 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 20/09/2014 ” ) ) ; l i s t e D a t e s . Add(new MyDate ( ” 12/09/2013 ” ) ) ; l i s t e D a t e s . S o r t (new MyDateComparer ( ) ) ; foreach ( MyDate d a t e in l i s t e D a t e s ) C on s o l e . Write ( d a t e + ” , ” ) ; C o n s o le . WriteLine ( ) ; } } } 3.5 Protocole d’égalité Le protocole d’égalité permet de définir les critères sur les éléments d’une collection pour les considérer comme égaux, ou plus exactement, comme équivalents. En effet, par défaut, les références des instances de classes sont utilisées pour tester l’égalité. Ça n’est pas toujours pertinent. Si l’on reprend l’exemple de notre classe MyDate de la partie 2.1, deux instances différentes (donc avec des références différentes) de la classe MyDate pourraient représenter la même date si les ont même année, même mois, et même jour. Le programmeur doit ainsi spécifier les critères à prendre en compte pour l’égalité, ce qui se définit par une fonction booléenne. L’implémentation d’une classe dérivée de la classe EqualityComparer permet la comparaison d’instances pour tester l’égalité (ou l’équivalence) des objets. Celà nécessite l’implémentation 39 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# de deux méthodes (qui sont virtuelles au nniveau de la classe EqualityComparer) : la méthode booléenne d’égalité Equals, et la méthode GetHashCode qui donne le code de hachage. La classe est générique (elle s’applique à un template). Dans l’exemple suivant, on compare des dates (de type MyDate) pour définir s’il s’agit “d’une même date”. CoursLinq/ExemplesDelgate/ex11_MyDateEqualityComparer.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { c l a s s MyDateEqualityComparer : EqualityComparer<MyDate> { public override bool Equals ( MyDate d1 , MyDate d2 ) { return d1 . Annee == d2 . Annee && d1 . Mois == d2 . Mois && d1 . Jour == d2 . Jour ; } public override int GetHashCode ( MyDate d ) { // Chaque d a t e c o r r e s p o n d r a à un e n t i e r u n i q u e ( c ’ e s t l e mieux ) return int . Parse ( d . Annee . T o S tr i n g ( ) . PadLeft ( 4 , ’ 0 ’ ) *10000 + d . Mois . T o S tr i n g ( ) . PadLeft ( 2 , ’ 0 ’ ) *100 + d . Jour . T o S t r i n g ( ) . PadLeft ( 2 ) ) ; } } } Dans l’exemple suivant, on voit comment utiliser ce protocole d’égalité pour gérer l’interdiction des doublons dans la collection des clefs d’un dictionnaire (chaque clef est unique, mais on ne parle pas forcément de l’égalité des références). Dans notre cas des dates, si on veut associer à chaque date une liste d’événements (ou autre), il s’agit que les événements d’une même date soient regroupés dans le dictionnaire et acessible avec la clef correspondant à cette date. CoursLinq/ExemplesDelgate/ex12_TestEqualityComparer.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 using using using using using System ; System . C o l l e c t i o n s . G e n e r i c ; System . Linq ; System . Text ; System . Threading . Tasks ; namespace ExemplesDelegate { public c l a s s T e s t E q u a l i t y C o m p a r e r C l a s s { s t a t i c void TryAdd ( D i c t i o n a r y <MyDate , string> d i c o , MyDate date , string chaine ) { 40 Chapitre 3 : Collections 14 15 16 17 18 19 20 try { d i c o . Add( date , c h a i n e ) ; } catch { i f ( c h a i n e != null ) C on s o le . E r r o r . WriteLine ( ” T e n t a t i v e d ’ a j o u t d ’ une c l é e x i s t a n t e dans l e d i c o . ” ) ; } 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 } public s t a t i c void T e s t I n s e r t D a t e s ( ) { D i c t i o n a r y <MyDate , string> d i c o = new D i c t i o n a r y <MyDate , string >(new MyDateEqualityComparer ( ) ) ; TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o TryAdd ( d i c o , , , , , , , , , new new new new new new new new new MyDate ( ” 20/09/2014 ” ) MyDate ( ” 20/09/2012 ” ) MyDate ( ” 20/09/2015 ” ) MyDate ( ” 20/10/2014 ” ) MyDate ( ” 10/09/2014 ” ) MyDate ( ” 20/09/2015 ” ) MyDate ( ” 20/08/2015 ” ) MyDate ( ” 20/09/2014 ” ) MyDate ( ” 12/09/2013 ” ) , , , , , , , , , foreach ( string v a l e u r in d i c o . Values ) C on s o l e . Write ( v a l e u r + ” , ” ) ; C o n s o le . WriteLine ( ) ; } } } 41 ”1 è r e ”2ème ”3ème ”4ème ”5ème ”6ème ”7ème ”8ème ”9ème date date date date date date date date date 20/09/2014 ” ) ; 20/09/2012 ” ) ; 20/09/2015 ” ) ; 22/10/2014 ” ) ; 10/09/2014 ” ) ; 20/09/2013 ” ) ; 20/08/2015 ” ) ; 20/09/2014 ” ) ; 12/09/2013 ” ) ; Chapitre 4 Requêtes LINQ 4.1 Types délégués anonymes Comme nous l’avons vu dans le chapitre 2, les types délégués sont des types dont les instances peuvent contenir des méthodes. Par exemple, si l’on considère un type délégué pour un prédicat qui, pour une personne, renvoie un booléen. On pourra par exemple affecter dans une instance de ce type délégué une méthode qui renvoie vrai si l’âge de la personne est supérieur ou égal à 18 ans. Le code correspondant serait, en supposant que l’on dispose d’une méthode Personne.getAge() qui renvoie une nombre d’années révolues depuis la date de naissance de la personne (instance d’une classe Personne). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 c l a s s DemoDelegate { // Type d é l é g u é pour d e s p r é d i c a t s s u r d e s p e r s o n n e s : public delegate bool MonTypePredicatSurPersonne ( Personne p e r s o n n e ) ; // Méthode q u i t e s t e une p r é d i c a t s u r une Personne public s t a t i c void T e s t P r e d i c a t P e r s o n n e ( MonTypePerdicatSurPersonne p r e d i c a t ) { // C r é a t i o n d ’ une i n s t a n c e de Personne ( a p p e l de f a b r i q u e ) Personne p e r s o n n e = Personne . f a b r i q u e D e P e r s o n n e ( ) ; i f ( pr e d i c a t ( personne ) ) { C o n s o l e . WriteLine ( ”La pe rs o n n e v é r i f i e l e p r é d i c a t . ” ) ; } else { C o n s o l e . WriteLine ( ”La pe rs o n n e v é r i f i e l e p r é d i c a t . ” ) ; } } public s t a t i c A p p e l l e T e s t P r e d i c a t P e r s o n n e ( ) { T e s t P r e d i c a t P e r s o n n e ( ( Personne p e r s o n n e ) => { return p e r s o n n e . GetAge ( ) >= 1 8 ; }) ; } } Avec cette syntaxe pour définir des types délégués, nous devons obligatoirement définir notre type délégué et lui donner un nom avant de l’utiliser pour définir des fonctions. En fait, 42 Chapitre 4 : Requêtes LINQ il existe en C#une syntaxe pour définir des types délégués à la volée. Reprenons notre exemple, ci-dessus, du prédicat qui, pour une personne, renvoie un booléen. Nous aurions pu définir notre méthode TestPredicatPersonne directement sans définir auparavant un type MonTypePredicatSurPersonne : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 c l a s s DemoDelegate { // Méthode q u i t e s t e une p r é d i c a t s u r une Personne public s t a t i c void T e s t P r e d i c a t P e r s o n n e ( Fonc<Personne , bool> p r e d i c a t ) { // C r é a t i o n d ’ une i n s t a n c e de Personne ( a p p e l de f a b r i q u e ) Personne p e r s o n n e = Personne . f a b r i q u e D e P e r s o n n e ( ) ; i f ( pr e d i c a t ( personne ) ) { Co n s o l e . WriteLine ( ”La pe r so n n e v é r i f i e l e p r é d i c a t . ” ) ; } else { C o n s o l e . WriteLine ( ”La pe rs o n n e v é r i f i e l e p r é d i c a t . ” ) ; } } public s t a t i c A p p e l l e T e s t P r e d i c a t P e r s o n n e ( ) { T e s t P r e d i c a t P e r s o n n e ( ( Personne p e r s o n n e ) => { return p e r s o n n e . GetAge ( ) >= 1 8 ; }) ; } } Ainsi, Fonc<Personne,bool> désigne un type déléggué qui à une Personne associe un booléen (prédicat sur une Personne). De même, Fonc<Personne,int> désigne un type déléggué qui à une Personne associe un entier. 4.2 Méthodes d’extension Les méthodes d’extension permettent d’ajouter des méthodes à une classe existante sans retoucher le code de la classe proprement dite. Les méthodes de LINQ sont définies comme des méthodes d’extension sur les différentes classes de collections. Ça n’est pas difficile à utiliser, mais pour compreendre les prototypes des méthodes de LINQ, il faut savoir lire le prototype d’une méythode d”extension. Par exemple, supposons que nous souhaitions ajouter à la classe string une méthode qui calcule le nombre d’occurences d’un caractère passé en paramètre. Voici comme on peut faire : 1 2 3 4 5 6 7 8 using System ; using System . Linq ; using System . Text ; namespace DemoExtension { // Les méthodes d ’ e x t e n s i o n d o i v e n t ê t r e d é f i n i e s dans une c l a s s e s t a t i q u e public s t a t i c c l a s s S t r i n g E x t e n s i o n 43 Rémy Malgouyres, www.malgouyres.org 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Conception Objet et Programmation en C# { // Ceci e s t l a méthode d ’ e x t e n s i o n . // Le p r em ie r paramètre e s t d é c l a r é a v e c l e m o d i f i c c a t e u r t h i s // La méthode s ’ a p p l i q u e r a à d e s i n s t a n c e s de l a c l a s s e // c o r r e s p o n d a n t au premie r paramètre ( i c i S t r i n g ) . public s t a t i c int CompteOccurences ( t h i s S t r i n g c h a i n e , Char c a r a c t e r e ) { // La méthode Where s é l e c t i o n n e l e s c a r a c t è r e s s u i v a n t un p r e d i c a t // ( v o i r d é t a i l s de l a méthode c i −d e s s o u s return c h a i n e . Where ( ( Char c ) => { return c==c a r a c t e r e ; } ) . ToList ( ) . Count ( ) ; } } c l a s s Program { s t a t i c void Main ( string [ ] a r g s ) { string s = ”C# p e u t r e p r é s e n t e r un paradigme du l a n g a g e o b j e t ” ; // Appel de l a méthode d ’ e x t e n s i o n q u i compte l e s // o c c u r e n c e s de l a l e t t r e ’ a ’ dans l a c h a i n e s int nOccurences = s . CompteOccurences ( ’ a ’ ) ; C o n s o le . WriteLine ( ” I l y a {0} c a r a c t è r e s {1} dans l a c h a i n e {2} ” , nOccurences , ’ a ’ , s ) ; } } } Notez que le premier paramètre dans la définition de la méthode d’extention, qui est modifié par le mot clé this, ne fait pas vraiment partie des paramètres de la méthode, mais il représente l’instance de classe sur laquellem s’appliquera la méthode. 4.3 Interface IEnumerable<T> L’interface IEnumerable<T> est l’interface de base de toutes les classes de collecctions génériques. Elle ne contient qu’une seule méthode, qui renvoie un énumérateur (aussi appelé itérateur). 1 IEnumerator<T> GetEnumerator ( ) L’énumérateur possède les méthodes qui permettent de parcourir une collection (se positionner au début, obtenir l’élément suivant, etc.). L’interface IEnumerable<T> esst ainsi le strict nécesssaire pour parcourir une collection. La plupart des méthodes de LINQ, qui s’appliquent à une collection renvoient une collection en sortie, sont des méthodes d’extension de l’interface IEnumerable<T>. Elles auront donc un premier paramètre this IEnumerable<T> : 1 IEnumerable<T> MaMethodeDeLINQ ( t h i s IEnumerable<T>, e t c . . . ) 44 Chapitre 4 : Requêtes LINQ 4.4 Quelques méthodes de LINQ 4.4.1 Filtre Where La méthode d’extension Where, qui est définie sur les principaux types de collections en C#. 1 2 3 4 public s t a t i c IEnumerable<TSource> Where<TSource >( t h i s IEnumerable<TSource> s o u r c e , Func<TSource , bool> p r e d i c a t e ) Paramètres : • source : une collection générique qui implémente IEnumerable<TSource>, dont les éléments sont d’un type TSource. • predicate : Prédicat sur le type TSource des éléments de la collection source (qui à une instance de TSource associe un booléen. Valeur retournée : La sous collection (du type IEnumerable<TSource>) de la collection source formée des éléments qui satisfont le prédicat (le prédicat renvoie vrai sur ces éléments). 4.4.2 Méthode de tri OrderBy La méthode d’extension OrderBy, qui est définie sur les principaux types de collections en C#, permet de trier les éléments suivant un critère de tri (implémentation de IComparer (voir partie 3.4). 1 2 3 4 5 public s t a t i c IOrderedEnumerable<TSource> OrderBy<TSource , TKey>( t h i s IEnumerable<TSource> s o u r c e , Func<TSource , TKey> k e y S e l e c t o r , IComparer<TKey> comparer ) Paramètres : • source : une collection générique qui implémente IEnumerable<TSource>, dont les éléments sont d’un type TSource. • keySelector : Sélection de clef sur le type TSource des éléments de la collection source (qui à une instance de TSource associe une clef de type TKey. • comparer : instance d’une classe de comparaison des clefs (qui implémente l’interface IComparer). Si le paramètre comparer est omis, le comparateur par défaut sur les clefs, s’il existe, est utilisé. Valeur retournée : La collection (du type IEnumerable<TSource>) qui comprend les mêmes éléments que la collection source, mais triés suivant l’ordre défini par la méthode Compare du comparer. 45 Rémy Malgouyres, www.malgouyres.org Conception Objet et Programmation en C# 4.4.3 Filtre Where sur un dictionnaire La méthode d’extension Where, qui est définie sur les principaux types de collections en C#. 1 2 3 4 public s t a t i c IEnumerable<KeyValuePair<TKey , TValue>> Where<KeyValuePair<TKey , TValue>>( t h i s IEnumerable<KeyValuePair<TKey , TValue>> s o u r c e , Func<KeyValuePair<TKey , TValue >, Boolean> p r e d i c a t e ) Paramètres : • source : un dictionnaire qui implémente IDictionnary<TKey,TValue>, dont les clef sont d’un type TKey et les valeurs d’un type TValue, • predicate : Prédicat sur le type KeyValuePair<TKey, TValue> des éléments de la collection source (qui à une instance de KeyValuePair<TKey, TValue> associe un booléen. Valeur retournée : La sous collection (du type KeyValuePair<TKey, TValue>) des couple clef/valeur du dictionnaire source formée des couples qui satisfont le prédicat (le prédicat renvoie vrai sur ces éléments). 4.4.4 Méthode de regrouppement GroupBy La méthode d’extension GroupBy, qui est définie sur les principaux types de collections en C#, permet de regroupe les éléments suivant un critère de projection et de comparaison (implémentation de IEqualityComparer (voir partie 3.5). 1 2 3 4 5 6 7 8 public s t a t i c IEnumerable<IGrouping<TKey , TSource>> GroupBy<TSource , TKey>( t h i s IEnumerable<TSource> s o u r c e , Func<TSource , TKey> k e y S e l e c t o r , IEqualityComparer<TKey> comparer ) public i n t e r f a c e IGrouping<out TKey , out TElement> : IEnumerable<TElement >, IEnumerable Paramètres : • source : une collection générique qui implémente IEnumerable<TSource>, dont les éléments sont d’un type TSource. • keySelector : Sélection de clef sur le type TSource des éléments de la collection source (qui à une instance de TSource associe une clef de type TKey). • comparer : instance d’une classe de comparaison des clefs (qui implémente l’interface IEqualityComparer) (voir partie 3.5). Si le paramètre comparer est omis, le comparateur d’égalité par défaut sur les clefs, s’il existe, est utilisé. Valeur retournée : L’interface IGrouping<out TKey, out TElement>, similaire à un dictionnaire, représente une association qui à chaque clef unique (suivant le protocole d’égalité défini par comparer) associe la collection (du type IEnumerable<TSource>) qui comprend les éléments que la collection source qui possèdent cette clef. On peut parcourir la collection regroupée par une double boucl foreach imbriquée. 46 Chapitre 4 : Requêtes LINQ 4.4.5 Exemples Voici le diagramme d’une classe personne, avec un nom, un prénom et une date de naissance. Une méthode GetAge permet d’obtenir l’âge de la personne en années révolues (au format int). Diag 8. Diagramme de Classes : la classe Personne. Voici maintenant des exemples sélectionnant, dans une liste de personnes, les personnes majeures, triées et/ou regroupées suivant différents critères. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using System ; using System . C o l l e c t i o n s . G e n e r i c ; using System . Linq ; namespace ExemplesLINQ { public c l a s s ExemplesPersonnesLINQ { // L ’ i m p l é m e n t a t i o n de l ’ i n t e r f a c e IComparer permet // l a comparaison d ’ i n s t a n c e s , par exemple pour t r i e r l e s o b j e t s public c l a s s MyDateComparer : Comparer<MyDate> { public override int Compare ( MyDate a , MyDate b ) { return S t r i n g . Compare ( a . T o S t r i n g R e v e r s e ( ) , b . T o S t r i n g R e v e r s e ( ) ) ; } } // L ’ h é r i t a g e de l a c l a s s e EqualityComparer<MyDate> permet // l a comparaison d ’ i n s t a n c e s pour t e s t e r l ’ é g a l i t é // Ou p l u t ô t , l ’ é q u i v a l e n c e , s u i v a n t un c e r t a i n c r i t è r e , i c i l ’ année . c l a s s MyDateYearEquality : EqualityComparer<MyDate> { public override bool Equals ( MyDate d1 , MyDate d2 ) { return d1 . Annee == d2 . Annee ; } public override int GetHashCode ( MyDate d ) { return int . Parse ( d . Annee ) ; } } 47 Rémy Malgouyres, www.malgouyres.org 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // Méthode de d é m o n s t r a t i o n d ’ e x e m p l e s LINQ s u r l e s Personne public s t a t i c L i s t <Personne> DemoLINQ_Personne1 ( L i s t <Personne> l i s t e ) { // Exemple a v e c comparat eu rs de s t r i n g par d é f a u t // ( o r d r e a l p h a b é t i q u e ) return l i s t e . Where ( ( Personne p ) => { return ( p . GetAge ( ) >= 1 8 ; } ) ) . OrderBy ( ( Personne p ) => { return p .Nom; } ) . ThenBy ( ( Personne p ) => { return p . Prenom ; } ) . ToList ( ) ; } // Méthode de d é m o n s t r a t i o n d ’ e x e m p l e s LINQ s u r l e s Personne public s t a t i c L i s t <Personne> DemoLINQ_Personne2 ( L i s t <Personne> l i s t e ) { // Même exemple a v e c s y n t a x e d ’ e x p r e s s i o n lambda p l u s l é g è r e // Types d e s p a r a m è t r e s i m p l i c i t e s , c o r p s de f o n c t i o n r é d u i t return l i s t e . Where ( p => ( p . GetAge ( ) >= 1 8 ) ) . OrderBy ( p => p .Nom) . ThenBy ( p => p . Prenom ) . ToList ( ) ; } // Méthode de d é m o n s t r a t i o n d ’ e x e m p l e s LINQ s u r l e s Personne public s t a t i c L i s t <Personne> DemoLINQ_Personne3 ( L i s t <Personne> l i s t e ) { // Exemple a v e c c omp ar a te u r s de MyDate par o r d r e chronologique // Voir p a r t i e 3 . 4 du p o l y ( p r o t o c o l e de comparaison ) return l i s t e . Where ( p => { return ( p . GetAge ( ) >= 1 8 ; } ) ) . OrderBy ( p => p . DateNaissance , new MyDateComparer ( ) ) ; } 58 59 60 61 62 63 64 65 // Méthode de d é m o n s t r a t i o n d ’ e x e m p l e s LINQ s u r l e s Personne public s t a t i c L i s t <Personne> DemoLINQ_Personne4 ( L i s t <Personne> l i s t e ) { // Exemple a v e c c omp ar a te u r s de MyDate par o r d r e chronologique // Exemple a v e c comparat eu rs de MyDate par o r d r e c h r o n o l o g i q u e // Voir p a r t i e 3 . 4 du p o l y ( p r o t o c o l e de comparaison ) return l i s t e . Where ( p => { return ( p . GetAge ( ) >= 1 8 ; } ) ) . OrderBy ( p => p . DateNaissance , new MyDateComparer ( ) ) ; . GroupBy ( p => p . DateNaissance , new MyDateYearEquality ( ) ) ; } 66 67 68 69 70 71 72 73 Conception Objet et Programmation en C# } } 48