You are on page 1of 31

Chapitre

22
Dicult :

La POO et le C#

ans ce chapitre, vous allez vous immerger un peu plus dans les subtilits de la POO en utilisant le C#. Il est temps un peu de tourmenter nos objets et de voir ce quils ont dans le ventre. Ainsi, nous allons voir comment les objets hritent les uns des autres ou comment fonctionnent les dirents polymorphismes. Nous allons galement voir comment tous ces concepts se retrouvent dans le quotidien dun dveloppeur C#.

191

CHAPITRE 22. LA POO ET LE C#

Des types, des objets, type valeur et type rfrence


Ok, je sais maintenant crer des objets, mais je me rappelle quau dbut du livre, nous avons manipul des int et des string et que tu as appel a des types ; et aprs, tu nous dis que tout est objet. . . Tu serais pas en train de raconter nimporte quoi par hasard ? ! Eh bien non, perspicace lecteur ! Prcisons un peu, maintenant que vous avez de meilleures connaissances. Jai bien dit que tout tait objet, je le maintiens, mme sous la torture ! Cest--dire que mme les types simples comme les entiers int ou les chanes de caractres sont des objets. Jen veux pour preuve ce simple exemple :
1 2 3 4 5 6

int a = 10 ; string chaine = a . ToString () ; chaine = " abc " + chaine ; string chaineEnMajuscule = chaine . ToUpper () ; Console . WriteLine ( chaineEnMajuscule ) ; Console . WriteLine ( chaineEnMajuscule . Length ) ;

La variable a est un entier. Nous appelons la mthode ToString() sur cet entier. Mme si nous navons pas encore vu quoi elle servait, nous pouvons supposer quelle eectue une action qui consiste transformer lentier en chane de caractres. Nous concatnons ensuite la chane abc cette chane et nous eectuons une action qui, travers la mthode ToUpper(), met la chane en majuscule. Enn, la mthode Console.WriteLine nous ache ABC10 puis nous ache la proprit Length de la chane de caractres qui correspond bien sr sa taille. Pour crer une chane de caractres, nous utilisons le mot-cl string. Sachez que ce mot-cl est quivalent la classe String (notez la dirence de casse). En crant une chane de caractres, nous avons instanci un objet dni par la classe String. Mais alors, pourquoi utiliser string et non pas String ?

En fait, le mot-cl string est ce quon appelle un alias de la classe String qui se situe dans lespace de nom System. De mme, le mot-cl int est un alias de la structure Int32 qui se situe galement dans lespace de nom System (nous verrons un peu plus loin ce quest vraiment une structure). Ce qui fait que les instructions suivantes :
1 2

int a = 10 ; string chaine = " abc " ;

sont quivalentes celles-ci :


1

System . Int32 a = 10 ;

192

DES TYPES, DES OBJETS, TYPE VALEUR ET TYPE RFRENCE


2

System . String chaine = " abc " ;

En pratique, comme on la dj fait, on utilise plutt les alias que les classes quils reprsentent. Cependant, les entiers, les boolens et autres types simples sont ce quon appelle des types intgrs. Et mme si ce sont des objets part entire (mthodes, proprits,. . . ), ils ont des particularits, notamment dans la faon dont ils sont grs par le framework .NET. On les appelle des types valeur, car les variables de ce type possdent la vraie valeur de ce quon leur aecte a contrario des classes qui sont des types rfrence dont les variables possdent simplement un lien vers un objet en mmoire. Par exemple :
1

int entier = 5 ;

Ici, la variable contient vraiment lentier 5. Alors que pour linstanciation suivante :
1

Voiture voitureNicolas = new Voiture () ;

La variable voitureNicolas contient une rfrence vers lobjet en mmoire. On peut imaginer que le type rfrence est un peu comme si on disait que ma maison se situe au 9 rue des bois . Ladresse a t crite sur un bout de papier et rfrence ma maison qui ne se situe bien sr pas au mme endroit que le bout de papier. Si je veux vraiment voir lobjet maison, il va falloir que jaille voir o cest indiqu sur le bout de papier. Cest ce que fait le type rfrence, il va voir en mmoire ce quil y a vraiment dans lobjet. Alors que le type valeur pourrait ressembler un billet de banque par exemple. Je peux me balader avec, il est marqu 500 dessus (oui, je suis riche !) et je peux payer directement avec sans que le fait de donner le billet implique daller chercher le contenu la banque. Le type valeur contient la vraie valeur qui en gnral est assez petite et facile stocker. Le type rfrence ne contient quun lien vers un plus gros objet stock ailleurs. Cette manire dirente de grer les types et les objets implique plusieurs choses. Dans la mesure o les types valeur possdent vraiment la valeur de ce quon y stocke, une copie de la valeur est eectue chaque fois que lon fait une aectation. Cest possible car ces types sont relativement petits et optimiss. Cela savre impossible pour un objet qui est trop gros et trop complexe. Cest un peu compliqu de copier toute ma maison alors que cest un peu plus simple de recopier ce quil y a sur le bout de papier. Ainsi, lexemple suivant :
1 2 3 4

int a = 5 ; int b = a ; b = 6; Console . WriteLine ( a ) ;

193

CHAPITRE 22. LA POO ET LE C#


5

Console . WriteLine ( b ) ;

achera les valeurs 5 puis 6 ; ce qui est le rsultat que lon attend. En eet, la variable a a t initialise 5. On a ensuite aect a b. La valeur 5 sest copie (duplique) dans la variable b. Puis nous avons aect 6 b. Ce qui parat tout fait logique ! Par contre, lexemple suivant :
1 2 3 4 5 6

Voiture voitureNicolas = new Voiture () ; voitureNicolas . Couleur = " Bleue " ; Voiture voitureJeremie = voitureNicolas ; voitureJeremie . Couleur = " Verte " ; Console . WriteLine ( voitureNicolas . Couleur ) ; Console . WriteLine ( voitureJeremie . Couleur ) ;

achera verte et verte. Quoi ? Nous indiquons que la voiture de Nicolas est bleue. Puis nous disons que celle de Jrmie est verte et quand on demande dacher la couleur des deux voitures, on nous dit quelles sont vertes toutes les deux alors quon croyait que celle de Nicolas tait bleue ? Tout lheure, le fait de changer b navait pas chang la valeur de a. . . Eh oui, ceci illustre le fait que les classes (comme Voiture) sont des types rfrence et ne possdent quune rfrence vers une instance de Voiture. Quand nous aectons voitureNicolas voitureJeremie, nous disons en fait que la voiture de Jrmie rfrence la mme chose que celle de Nicolas. Concrtement, le C# copie la rfrence de lobjet Voiture qui est contenue dans la variable voitureNicolas dans la variable voitureJeremie. Ce sont donc deux variables direntes qui possdent toutes les deux une rfrence vers lobjet Voiture, qui est la voiture de Nicolas. Cest--dire que les deux variables rfrencent le mme objet. Ainsi, la modication des proprits de lun aectera forcment lautre. Inattendu au premier abord, mais nalement, cest trs logique. Comprendre cette dirence entre les types valeur et les types rfrence est important, nous verrons dans les chapitres suivants quels sont les autres impacts de cette dirence. noter galement quil est impossible de driver dun type intgr alors que cest possible, et facile, de driver dune classe. Dailleurs, si nous parlions un peu dhritage ?

Hritage
Nous avons vu pour linstant la thorie de lhritage. Que les objets chiens hritaient des comportements des objets Animaux, que les labradors hritaient des comportements des chiens, etc. 194

HRITAGE Passons maintenant la pratique et crons une classe Animal et une classe Chien qui en hrite. Nous allons crer des classes relativement courtes et nous nous limiterons dans le nombre dactions ou de proprits de celles-ci. Par exemple, nous pourrions imaginer que la classe Animal possde une proprit NombreDePattes qui est un entier et une mthode Respirer qui ache le dtail de laction. Ce qui donne :
1 2 3 4 5 6 7 8 9

public class Animal { public int NombreDePattes { get ; set ; } public void Respirer () { Console . WriteLine ( " Je respire " ) ; }

La classe Chien drive de la classe Animal et peut donc hriter de certains de ses comportements. En loccurrence, la classe Chien hritera de tout ce qui est public ou protg, identis comme vous le savez dsormais par les mots-cls public et protected. Le chien sait galement faire quelque chose qui lui est propre, savoir aboyer. Il possdera donc une mthode supplmentaire. Ce qui donne :
1 2 3 4 5 6 7

public class Chien : Animal { public void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; } }

On reprsente la notion dhritage en ajoutant aprs la classe le caractre : suivi de la classe mre. Ici, nous avons dni une classe publique Chien qui hrite de la classe Animal. Nous pouvons ds prsent crer des objets Animal et des objets Chien, par exemple :
1 2 3 4 5 6 7

Animal animal = new Animal { NombreDePattes = 4 }; animal . Respirer () ; Console . WriteLine () ; Chien chien = new Chien { NombreDePattes = 4 }; chien . Respirer () ; chien . Aboyer () ;

Si nous excutons ce code, nous nous rendons bien compte que lobjet Chien, bien que nayant pas dni la proprit NombreDePattes ou la mthode Respirer() dans le corps de sa classe, est capable davoir des pattes et de faire laction respirer :
Je respire Je respire Wouaf !

195

CHAPITRE 22. LA POO ET LE C# Il a hrit ces comportements de lobjet Animal, en tout cas, ceux qui sont publics. Rajoutons deux variables membres de la classe Animal :
1 2 3 4 5 6 7 8 9 10 11 12

public class Animal { private bool estVivant ; public int age ; public int NombreDePattes { get ; set ; } public void Respirer () { Console . WriteLine ( " Je respire " ) ; }

Lentier age est public alors que le boolen estVivant est priv. Si nous tentons de les utiliser depuis la classe lle Chien, comme ci-dessous :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

public class Chien : Animal { public void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; } public void Vieillir () { age ++; } public void Naissance () { age = 0 ; estVivant = true ; /* Erreur > ' MaPr emier eAppli catio n . Animal . estVivant ' est inaccessible en raison de son niveau de protection */ }

17 18

nous voyons quil est tout fait possible dutiliser la variable age depuis la mthode Vieillir() alors que lutilisation du boolen estVivant provoque une erreur de compilation. Vous avez bien compris que celui-ci tait inaccessible car il est dni comme membre priv. Pour lutiliser, on pourra le rendre public par exemple. Il existe par contre un autre mot-cl qui permet de rendre des variables, proprits ou mthodes inaccessibles depuis un autre objet tout en les rendant accessibles depuis des classes lles. Il sagit du mot-cl protected. Si nous lutilisons la place de private pour dnir la visibilit du boolen estVivant, nous pourrons nous rendre compte que la classe Chien peut dsormais compiler : 196

HRITAGE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

public class Animal { protected bool estVivant ; [... Extrait de code supprim ...] } public class Chien : Animal { [... Extrait de code supprim ...] public void Naissance () { age = 0 ; estVivant = true ; // compilation OK }

Par contre, cette variable est toujours inaccessible depuis dautres classes, comme lest galement une variable prive. Dans notre classe Program, linstruction suivante :
1

chien . estVivant = true ;

provoquera lerreur de compilation que dsormais nous connaissons bien :


Ma P r e m i e r e A p p licati on . Animal . estVivant est inaccessible en raison de son niveau de protection

Le mot-cl protected prend tout son intrt ds que nous avons faire avec lhritage. Nous verrons un peu plus loin dautres exemples de ce mot-cl. Nous avons dit dans lintroduction quun objet B qui drive de lobjet A est une sorte dobjet A. Dans notre exemple du dessus, le Chien est une sorte dAnimal. Cela veut dire que nous pouvons utiliser un chien en tant quanimal. Par exemple, le code suivant :
1

Animal animal = new Chien { NombreDePattes = 4 };

est tout fait correct. Nous disons que notre variable animal, de type Animal est une instance de Chien. Avec cette faon dcrire, nous avons rellement instanci un objet Chien mais celuici sera trait en tant quAnimal. Cela veut dire quil sera capable de Respirer() et davoir des pattes. Par contre, mme si en vrai, notre objet est capable daboyer, le fait quil soit manipul en tant quAnimal nous empche de pouvoir le faire Aboyer. Cela veut dire que le code suivant :
1 2 3

Animal animal = new Chien { NombreDePattes = 4 }; animal . Respirer () ; animal . Aboyer () ; // erreur de compilation

provoquera une erreur de compilation pour indiquer que la classe Animal ne contient aucune dnition pour la mthode Aboyer(). Ce qui est normal, car un animal ne sait pas forcment aboyer. . . 197

CHAPITRE 22. LA POO ET LE C#

Quel est lintrt alors dutiliser le chien en tant quanimal ?

Bonne question. Pour y rpondre, nous allons enrichir notre classe Animal, garder notre classe Chien et crer une classe Chat qui hrite galement dAnimal. Ce pourrait tre :
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

public class Animal { protected string prenom ; public void Respirer () { Console . WriteLine ( " Je suis " + prenom + " et je respire "); }

public class Chien : Animal { public Chien ( string prenomDuChien ) { prenom = prenomDuChien ; } public void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; }

public class Chat : Animal { public Chat ( string prenomDuChat ) { prenom = prenomDuChat ; } public void Miauler () { Console . WriteLine ( " Miaou " ) ; }

Nous forons les chiens et les chats avoir un nom, hrit de la classe Animal, grce au constructeur an de pouvoir les identier facilement. Le chat garde le mme principe que le chien, sauf que nous avons une mthode Miauler() la place de la mthode Aboyer(). . . Ce qui est, somme toute, logique ! Lide est de pouvoir utiliser nos chiens et nos chats ensemble comme des animaux, par exemple en utilisant une liste. 198

HRITAGE Pour illustrer ce fonctionnement, donnons vie quelques chiens et quelques chats grce nos pouvoirs de dveloppeur et mettons-les dans une liste :
1 2 3 4 5 6 7 8 9 10 11 12

List < Animal > animaux = new List < Animal >() ; Animal milou = new Chien ( " Milou " ) ; Animal dingo = new Chien ( " Dingo " ) ; Animal idefix = new Chien ( " Id fix " ) ; Animal tom = new Chat ( " Tom " ) ; Animal felix = new Chat ( " F lix " ) ; animaux . Add ( milou ) ; animaux . Add ( dingo ) ; animaux . Add ( idefix ) ; animaux . Add ( tom ) ; animaux . Add ( felix ) ;

Nous avons dans un premier temps instanci une liste danimaux laquelle nous avons rajout 3 chiens et 2 chats, chacun tant considr comme un animal puisquils sont tous des sortes danimaux, grce lhritage. Maintenant, nous navons plus que des animaux dans la liste. Il sera donc possible de les faire tous respirer en une simple boucle :
1 2 3 4

foreach ( Animal animal in animaux ) { animal . Respirer () ; }

Ce qui donne :
Je Je Je Je Je suis suis suis suis suis Milou et je respire Dingo et je respire Id fix et je respire Tom et je respire F lix et je respire

Et voil, cest super simple ! Imaginez le bonheur de No sur son arche quand il a compris que grce la POO, il pouvait faire respirer tous les animaux en une seule boucle ! Quel travail conomis. Peu importe ce quil y a dans la liste, des chiens, des chats, des hamsters, nous savons que ce sont tous des animaux et quils savent tous respirer. Vous avez sans doute remarqu que nous faisons la mme chose dans le constructeur de la classe Chien et dans celui de la classe Chat. Deux fois la mme chose. . . Ce nest pas terrible. Peut-tre y a-t-il un moyen de factoriser tout a ? Eectivement, il est possible galement dcrire nos classes de cette faon :
1 2 3 4 5

public class Animal { protected string prenom ; public Animal ( string prenomAnimal )

199

CHAPITRE 22. LA POO ET LE C#


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

{ }

prenom = prenomAnimal ;

public void Respirer () { Console . WriteLine ( " Je suis " + prenom + " et je respire "); }

public class Chien : Animal { public Chien ( string prenomDuChien ) : base ( prenomDuChien ) { } public void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; }

public class Chat : Animal { public Chat ( string prenomDuChat ) : base ( prenomDuChat ) { } public void Miauler () { Console . WriteLine ( " Miaou " ) ; }

Quest-ce qui change ? Eh bien la classe Animal possde un constructeur qui prend en paramtre un prnom et qui le stocke dans sa variable prive. Cest elle qui fait le travail dinitialisation. Il devient alors possible pour les constructeurs des classes lles dappeler le constructeur de la classe mre an de faire laectation du prnom. Pour cela, on utilise les deux points suivis du mot-cl base qui signie appelle-moi le constructeur de la classe du dessus auquel nous passons la variable en paramtre. Avec cette criture un peu barbare, il devient possible de factoriser des initialisations qui ont un sens pour toutes les classes lles. Dans notre cas, je veux que tous les objets qui drivent dAnimal puissent facilement dnir un prnom. Il faut aussi savoir que si nous appelons le constructeur par dfaut dune classe qui nappelle pas explicitement un constructeur spcialis dune classe mre, alors celuici appellera automatiquement le constructeur par dfaut de la classe dont il hrite. 200

HRITAGE Modions nouveau nos classes pour avoir :


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

public class Animal { protected string prenom ; public Animal () { prenom = " Marcel " ; } public void Respirer () { Console . WriteLine ( " Je suis " + prenom + " et je respire "); }

public class Chien : Animal { public void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; } } public class Chat : Animal { public Chat ( string prenomDuChat ) { prenom = prenomDuChat ; } public void Miauler () { Console . WriteLine ( " Miaou " ) ; }

Ici, la classe Animal met un prnom par dfaut dans son constructeur. Le chien na pas de constructeur et le chat en a un qui accepte un paramtre. Il est donc possible de crer un Chien sans quil ait de prnom mais il est obligatoire den dnir un pour le chat. Sauf que lorsque nous instancierons notre objet chien, il appellera automatiquement le constructeur de la classe mre et tous nos chiens sappelleront Marcel :
1 2 3 4 5

static void Main ( string [] args ) { List < Animal > animaux = new List < Animal >() ; Animal chien = new Chien () ; Animal tom = new Chat ( " Tom " ) ;

201

CHAPITRE 22. LA POO ET LE C#


6 7 8 9 10 11 12 13 14 15 16

Animal felix = new Chat ( " F lix " ) ; animaux . Add ( chien ) ; animaux . Add ( tom ) ; animaux . Add ( felix ) ; foreach ( Animal animal in animaux ) { animal . Respirer () ; }

Ce qui achera :
Je suis Marcel et je respire Je suis Tom et je respire Je suis F lix et je respire

Il est galement possible dappeler un constructeur partir dun autre constructeur. Prenons lexemple suivant :
1 2 3 4 5 6 7 8 9

public class Voiture { private int vitesse ; public Voiture ( int vitesseVoiture ) { vitesse = vitesseVoiture ; }

Si nous souhaitons rajouter un constructeur par dfaut qui initialise la vitesse 10 par exemple, nous pourrons faire :
1 2 3 4 5 6 7 8 9 10 11 12 13 14

public class Voiture { private int vitesse ; public Voiture () { vitesse = 10 ; } public Voiture ( int vitesseVoiture ) { vitesse = vitesseVoiture ; }

Ou encore : 202

HRITAGE
1 2 3 4 5 6 7 8 9 10 11 12 13

public class Voiture { private int vitesse ; public Voiture () : this ( 10 ) { } public Voiture ( int vitesseVoiture ) { vitesse = vitesseVoiture ; }

Ici, lutilisation du mot-cl this, suivi dun entier permet dappeler le constructeur qui possde un paramtre entier au dbut du constructeur par dfaut. Inversement, nous pouvons appeler le constructeur par dfaut dune classe depuis un constructeur possdant des paramtres an de pouvoir bncier des initialisations de celui-ci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

public class Voiture { private int vitesse ; private string couleur ; public Voiture () { vitesse = 10 ; } public Voiture ( string couleurVoiture ) : this () { couleur = couleurVoiture ; }

Puisque nous parlons dhritage, il faut savoir que tous les objets que nous crons ou qui sont disponibles dans le framework .NET hritent dun objet de base. On parle en gnral dun super-objet . Lintrt de driver dun tel objet est de permettre tous les objets davoir certains comportements en commun, mais galement de pouvoir ventuellement tous les traiter en tant quobjet. Notre super-objet est reprsent par la classe Object qui dnit plusieurs mthodes. Vous les avez dj vues si vous avez regard dans la compltion automatique aprs avoir cr un objet. Prenons une classe toute vide, par exemple :
1 2 3

public class ObjetVide { }

Si nous instancions cet objet et que nous souhaitons lutiliser, nous verrons que la compltion automatique nous propose des mthodes que nous navons jamais cres (voir gure 22.1). 203

CHAPITRE 22. LA POO ET LE C#

Figure 22.1 La compltion automatique montre des mthodes de la classe de base Object Nous voyons plusieurs mthodes, comme Equals ou GetHashCode ou GetType ou encore ToString. Comme vous lavez compris, ce sont des mthodes qui sont dnies dans la classe Object. La mthode ToString par exemple permet dobtenir une reprsentation de lobjet sous la forme dune chane de caractres. Cest une mthode qui va souvent nous servir, nous y reviendrons un peu plus tard. Ce super-objet est du type Object, mais on utilise gnralement son alias object. Ainsi, il est possible dutiliser tous nos objets comme des object et ainsi utiliser les mthodes qui sont dnies sur la classe Object. Ce qui nous permet de faire :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

static void Main ( string [] args ) { ObjetVide monObjetVide = new ObjetVide () ; Chien chien = new Chien () ; int age = 30 ; string prenom = " Nicolas " ; A f f i c he rRe pr ese nt ati on ( monObjetVide ) ; A f f i c he rRe pr ese nt ati on ( chien ) ; A f f i c he rRe pr ese nt ati on ( age ) ; A f f i c he rRe pr ese nt ati on ( prenom ) ;

private static void A ffi ch erR ep res en tat ion ( object monObjetVide ) { Console . WriteLine ( monObjetVide . ToString () ) ; }

Ce qui ache :
M a P r e m i e r e Appli catio n . ObjetVide M a P r e m i e r e Appli catio n . Chien 30 Nicolas

204

HRITAGE Comme indiqu, la mthode ToString() permet dacher la reprsentation par dfaut dun objet. Vous aurez remarqu quil y a une dirence suivant ce que nous passons. En eet, la reprsentation par dfaut des types rfrence correspond au nom du type, savoir son espace de nom suivi du nom de sa classe. Pour ce qui est des types valeur, la reprsentation contient en gnral la valeur du type, lexception des structures que nous navons pas encore vues et que nous aborderons un peu plus loin. Lintrt dans cet exemple de code est de voir que nous pouvons tout manipuler comme un object. Dune manire gnrale, vous aurez peu loccasion de traiter vos objets en tant quobject car il est vraiment plus intressant de proter pleinement du type, lobject tant peu utilisable. Notez que lhritage de object est automatique. Nul besoin dutiliser la syntaxe dhritage que nous avons dj vue. Jen prote maintenant que vous connaissez la mthode ToString() pour parler dun point qui a peut-tre titill vos cerveaux. Dans la premire partie, nous avions fait quelque chose du genre :
1 2

int vitesse = 20 ; string chaine = " La vitesse est " + vitesse + " km / h " ;

La variable vitesse est un entier. La chane La vitesse est est une chane de caractres. Nous essayons dajouter un entier une chane alors que jai dit quils ntaient pas compatibles entre eux ! Et pourtant cela fonctionne. Eectivement, cest bizarre ! Nous concatnons une chane un entier avec loprateur + et nous concatnons encore une chane. Et si je fais linverse :
1

int vitesse = 20 + " 40 " ;

cela provoque une erreur de compilation. Cest logique, on ne peut pas ajouter un entier et une chane de caractres. Alors pourquoi cela fonctionne dans lautre sens ? Ce qui se passe en fait dans linstruction :
1

string chaine = " La vitesse est " + vitesse + " km / h " ;

cest que le compilateur se rend compte que nous concatnons une chane avec un autre objet, peu importe que ce soit un entier ou un objet complexe. Alors, pour que a fonctionne, il demande une reprsentation de lobjet sous la forme dune chane de caractres. Nous avons vu que ceci se faisait en appelant la mthode ToString() qui est hrite de lobjet racine Object. Linstruction est donc quivalente :
1

string chaine = " La vitesse est " + vitesse . ToString () + " km / h ";

205

CHAPITRE 22. LA POO ET LE C# Dans le cas dun type valeur comme un entier, la mthode ToString() renvoie la reprsentation interne de la valeur, savoir 20 . Dans le cas dun objet complexe, elle aurait renvoy le nom du type de lobjet. Avant de terminer, il est important dindiquer que le C# nautorise pas lhritage multiple. Ainsi, si nous possdons une classe Carnivore et une classe EtreVivant, il ne sera pas possible de faire hriter directement un objet Homme de lobjet Carnivore et de lobjet EtreVivant. Ainsi, le code suivant :
1 2 3 4 5 6 7 8 9 10

public class Carnivore { } public class EtreVivant { } public class Homme : Carnivore , EtreVivant { }

provoquera lerreur de compilation suivante :


La classe M aPrem iereAp plica tion . Homme ne peut pas avoir plusieurs classes de base : MaPre miere Applic ation . Carnivore et EtreVivant

Il est impossible de driver de deux objets en mme temps.

En revanche, et cest pertinent, nous pourrons faire un hritage en cascade an que Carnivore drive de EtreVivant et que Homme drive de Carnivore :
1 2 3 4 5 6 7 8 9 10

public class Carnivore : EtreVivant { } public class EtreVivant { } public class Homme : Carnivore { }

Cependant, il nest pas toujours pertinent doprer de la sorte. Notre Homme pourrait tre la fois Carnivore et Frugivore, cependant cela na pas de sens quun carnivore soit galement frugivore, ou linverse. 206

SUBSTITUTION Tu avais pourtant dit que chaque objet drivait du super-objet Object, mais sil drive dune autre classe comme un chien drive dun animal, a fait bien deux classes dont il drive. . . Eectivement, mais dans ce cas-l, ce nest pas pareil. Comme il est automatique de driver de object, cest comme si on avait le chien qui hrite de animal qui hrite lui-mme de object. Le C# est assez malin pour a !

Substitution
Nous avons vu juste avant lutilisation de la mthode ToString() qui permet dobtenir la reprsentation dun objet sous forme de chane de caractres. En loccurrence, vous conviendrez avec moi que la reprsentation de notre classe Chien nest pas particulirement exploitable. Le nom du type cest bien, mais ce nest pas trs parlant. a serait pas mal que, quand nous demandons dacher un chien, nous obtenions le nom du chien, vous ne trouvez pas ? Cest l quintervient la substitution. Nous en avons parl dans lintroduction la POO, la substitution permet de rednir un comportement dont lobjet a hrit an quil corresponde aux besoins de lobjet ls. Typiquement, ici, la mthode ToString() du super-objet ne nous convient pas et dans le cas de notre chien, nous souhaitons la rednir, en crire une nouvelle version. Pour cet exemple, simplions notre classe Chien an quelle nait quune proprit pour stocker son prnom :
1 2 3 4

public class Chien { public string Prenom { get ; set ; } }

Pour rednir la mthode ToString() nous allons devoir utiliser le mot-cl override qui signie que nous souhaitons substituer la mthode existante an de remplacer son comportement, ce que nous pourrons crire en C# avec :
1 2 3 4 5 6 7 8 9

public class Chien { public string Prenom { get ; set ; } public override string ToString () { return " Je suis un chien et je m ' appelle " + Prenom ; }

Le mot-cl override se met avant le type de retour de la mthode, comme on peut le voir ci-dessus. Si nous appelons dsormais la mthode ToString de notre objet Chien :
1 2

Chien chien = new Chien { Prenom = " Max " }; Console . WriteLine ( chien . ToString () ) ;

207

CHAPITRE 22. LA POO ET LE C# notre programme va utiliser la nouvelle version de la mthode ToString() :
Je suis un chien et je m appelle Max

Et voil un bon moyen dutiliser la substitution, la reprsentation de notre objet est quand mme plus parlante ! Adaptons dsormais cet exemple nos classes. Pour montrer comment faire, reprenons notre classe Chien qui possde une mthode Aboyer() :
1 2 3 4 5 6 7

public class Chien { public void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; } }

Nous pourrions imaginer de crer une classe ChienMuet qui drive de la classe Chien et qui hrite donc de ses comportements. Mais, que penser dun chien muet qui serait capable daboyer ? Cela na pas de sens ! Il faut donc rednir cette chue mthode. Utilisons alors le mot-cl override comme nous lavons vu pour obtenir :
1 2 3 4 5 6 7

public class ChienMuet : Chien { public override void Aboyer () { Console . WriteLine ( " ... " ) ; } }

Crons un chien muet puis faisons-le aboyer, cela donne :


1 2

ChienMuet pauvreChien = new ChienMuet () ; pauvreChien . Aboyer () ;

Sauf que nous rencontrons un problme. Si nous tentons de compiler ce code, Visual C# Express nous gnre une erreur de compilation :
M a P r e m i e r eAppl icati on . Program . ChienMuet . Aboyer () : ne peut pas substituer le membre h rit MaPr emier eAppli catio n . Program . Chien . Aboyer () , car il n est pas marqu comme virtual , abstract ou override .

En ralit, pour pouvoir crer une mthode qui remplace une autre, il faut quune condition supplmentaire soit vrie : il faut que la mthode remplacer sannonce comme candidate la substitution. Cela veut dire que lon ne peut pas substituer nimporte quelle mthode, mais seulement celles qui acceptent de ltre. Cest le cas pour la mthode ToString que nous avons vue prcdemment. Les concepteurs du framework .NET ont autoris cette ventualit. Heureusement, sinon, nous serions bien embts ! 208

SUBSTITUTION Pour marquer notre mthode Aboyer de la classe Chien comme candidate ventuelle la substitution, il faut la prxer du mot-cl virtual. Ainsi, elle annonce ses futures lles que si elles le souhaitent, elles peuvent rednir cette mthode. Cela se traduit ainsi dans le code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

public class Chien { public virtual void Aboyer () { Console . WriteLine ( " Wouaf ! " ) ; } } public class ChienMuet : Chien { public override void Aboyer () { Console . WriteLine ( " ... " ) ; } }

Dsormais, linstanciation de lobjet est possible et nous pourrons avoir notre code :
1 2

ChienMuet pauvreChien = new ChienMuet () ; pauvreChien . Aboyer () ;

Ce code achera :
...

Parfait ! Tout est rentr dans lordre. Le message derreur, quoique peu explicite, nous mettait quand mme sur la bonne voie. Visual C# Express nous disait quil fallait que la mthode soit marque comme virtual, ce que nous avons fait. Il proposait galement quelle soit marque abstract, nous verrons un peu plus loin ce que a veut dire. Visual C# Express indiquait enn que la mthode pouvait tre marque override. Cela veut dire quune classe lle de ChienMuet peut galement rednir la mthode Aboyer() an quelle colle ses besoins. Elle nest pas marque virtual mais elle est marque override. Par exemple :
1 2 3 4 5 6 7

public class C h i e n M u e t A v e c S y n t h e s e V o c a l e : ChienMuet { public override void Aboyer () { Console . WriteLine ( " bwarf ! " ) ; } }

Il y a encore un dernier point que nous navons pas abord. Il sagit de la capacit pour une classe lle de rednir une mthode tout en conservant la fonctionnalit de 209

CHAPITRE 22. LA POO ET LE C# la mthode de la classe mre. Imaginons notre classe Animal qui possde une mthode Manger() :
1 2 3 4 5 6 7 8 9 10

public class Animal { public virtual void Manger () { Console . WriteLine ( " Mettre les aliments dans la bouche " ) ; Console . WriteLine ( " Mastiquer " ) ; Console . WriteLine ( " Avaler " ) ; Console . WriteLine ( " ... " ) ; } }

Notre classe Chien pourra sappuyer sur le comportement de la mthode Manger() de la classe Animal pour crer sa propre action personnelle. Cela se passe en utilisant nouveau le mot-cl base qui reprsente la classe mre. Nous pourrons par exemple appeler la mthode Manger() de la classe mre an de rutiliser son fonctionnement. Cela donne :
1 2 3 4 5 6 7 8 9

public class Chien : Animal { public override void Manger () { Console . WriteLine ( " R clamer manger au ma tre " ) ; base . Manger () ; Console . WriteLine ( " Remuer la queue " ) ; } }

Dans cet exemple, je fais quelque chose avant dappeler la mthode de la classe mre, puis je fais quelque chose dautre aprs. Maintenant, si nous faisons manger notre chien :
1 2

Chien chien = new Chien () ; chien . Manger () ;

sachera dans la console toute la srie des actions dnies comme tant manger :
R clamer manger au ma tre Mettre les aliments dans la bouche Mastiquer Avaler ... Remuer la queue

Nous voyons bien avec cet exemple comment la classe lle peut rutiliser les mthodes de sa classe mre. noter quon peut galement parler de spcialisation ou de rednition la place de la substitution. 210

POLYMORPHISME

Polymorphisme
Nous avons dit quune manifestation du polymorphisme tait la capacit pour une classe deectuer la mme action sur dirents types dintervenants. Il sagit de la surcharge, appele aussi polymorphisme ad hoc. Concrtement, cela veut dire quil est possible de dnir la mme mthode avec des paramtres en entre dirents. Si vous vous rappelez bien, cest quelque chose que nous avons dj fait sans le savoir. Devinez . . . Oui, cest a, avec la mthode Console.WriteLine. Nous avons pu acher des chanes de caractres, mais aussi des entiers, mme des types double, et plus rcemment des objets. Comment ceci est possible alors que nous avons dj vu quil tait impossible de passer des types en paramtres dune mthode qui ne correspondent pas sa signature ? ! Ainsi, lexemple suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

public class Program { static void Main ( string [] args ) { Math math = new Math () ; int a = 5 ; int b = 6 ; int resultat = math . Addition (a , b ) ; double c = 1 . 5 ; double d = 5 . 0 ; resultat = math . Addition (c , d ) ; /* erreur de compilation */

public class Math { public int Addition ( int a , int b ) { return a + b ; } }

provoquera une erreur de compilation lorsque nous allons essayer de passer des variables du type double notre mthode qui prend des entiers en paramtres. Pour que ceci fonctionne, nous allons rendre polymorphe cette mthode en dnissant nouveau cette mme mthode mais en lui faisant prendre des paramtres dentre dirents :
1 2 3 4 5

public class Program { static void Main ( string [] args ) { Math math = new Math () ;

211

CHAPITRE 22. LA POO ET LE C#


6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

int a = 5 ; int b = 6 ; int resultat = math . Addition (a , b ) ; double c = 1 . 5 ; double d = 5 . 0 ; double resultatDouble = math . Addition (c , d ) ; /* a compile , youpi */

public class Math { public int Addition ( int a , int b ) { return a + b ; } public double Addition ( double a , double b ) { return a + b ; }

Nous avons ainsi crit deux formes direntes de la mme mthode. Une qui accepte des entiers et lautre qui accepte des double. Ce code fonctionne dsormais correctement. Il est bien sr possible dcrire cette mthode avec beaucoup de paramtres de types dirents, mme une classe Chien, en imaginant que le fait dadditionner deux chiens correspond au fait dadditionner leurs nombres de pattes :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

public class Math { public int Addition ( int a , int b ) { return a + b ; } public double Addition ( double a , double b ) { return a + b ; } public int Addition ( Chien c1 , Chien c2 ) { return c1 . NombreDePattes + c2 . NombreDePattes ; }

Attention, jai toujours indiqu quil tait possible dajouter une nouvelle forme la mme mthode en changeant les paramtres dentres. Vous ne pourrez pas le faire en 212

POLYMORPHISME changeant uniquement le paramtre de retour. Ce qui fait que cet exemple ne pourra pas compiler :
1 2 3 4 5 6 7 8 9 10 11 12

public class Math { public int Addition ( int a , int b ) { return a + b ; } public double Addition ( int a , int b ) { return a + b ; }

Les deux mthodes acceptent deux entiers en paramtres et renvoient soit un entier, soit un double. Le compilateur ne sera pas capable de choisir quelle mthode utiliser lorsque nous essayerons dappeler cette mthode. Les mthodes doivent se direncier avec les paramtres dentres. Lorsque nous avons plusieurs signatures possibles pour la mme mthode, vous remarquerez que la compltion automatique nous propose alors plusieurs possibilits (voir gure 22.2).

Figure 22.2 La compltion automatique propose plusieurs signatures de la mme mthode Visual C# indique quil a trois mthodes possibles qui sappellent Math.Addition. Pour voir la signature des autres mthodes, il sut de cliquer sur les ches, ou dutiliser les ches du clavier, comme indiqu la gure 22.3. Cest ce qui se passe dans la mthode Console.WriteLine (voir gure 22.4). Nous voyons ici quil existe 19 critures de la mthode WriteLine, la cinquime prenant en paramtre un dcimal. Notez que pour crire plusieurs formes de cette mthode, nous pouvons galement jouer sur le nombre de paramtres. La mthode :
1 2 3 4

public int Addition ( int a , int b , int c ) { return a + b + c ; }

213

CHAPITRE 22. LA POO ET LE C#

Figure 22.3 La compltion automatique prsente plusieurs signatures de la mthode Addition

Figure 22.4 La compltion automatique prsente plusieurs signatures de la mthode Console.WriteLine sera bien une nouvelle forme de la mthode Addition. Nous avons galement vu dans le chapitre sur les constructeurs dune classe quil tait possible de cumuler les constructeurs avec des paramtres dirents. Il sagit nouveau du polymorphisme. Il nous permet de dnir dirents constructeurs sur nos objets.

La conversion entre les objets avec le casting


Nous avons dj vu dans la partie prcdente quil tait possible de convertir les types qui se ressemblent entre eux. Cela fonctionne galement avec les objets. Plus prcisment, cela veut dire que nous pouvons convertir un objet en un autre seulement sil est une sorte de lautre objet. Nous avons vu dans les chapitres prcdents quil sagissait de la notion dhritage. Ainsi, si nous avons dni une classe Animal et que nous dnissons une classe Chien qui hrite de cette classe Animal :
1 2 3 4 5 6 7

public class Animal { } public class Chien : Animal { }

nous pourrons alors convertir le chien en animal dans la mesure o le chien est une 214

LA CONVERSION ENTRE LES OBJETS AVEC LE CASTING sorte danimal :


1 2

Chien medor = new Chien () ; Animal animal = ( Animal ) medor ;

Nous utilisons pour ce faire un cast, comme nous lavons dj fait pour les types intgrs (int, bool, etc.). Il sut de prxer la variable convertir du type entre parenthses dans lequel nous souhaitons le convertir. Ici, nous pouvons convertir facilement notre Chien en Animal. Par contre, il est impossible de convertir un chien en voiture, car il ny a pas de relation dhritage entre les deux. Ainsi les instructions suivantes :
1 2

Chien medor = new Chien () ; Voiture voiture = ( Voiture ) medor ;

provoqueront une erreur de compilation. Nous avons prcdemment utilis lhritage an de mettre des chiens et des chats dans une liste danimaux. Nous avions fait quelque chose du genre :
1 2 3 4 5 6

List < Animal > animaux = new List < Animal >() ; Animal chien = new Chien () ; Animal chat = new Chat () ; animaux . Add ( chien ) ; animaux . Add ( chat ) ;

Il serait plus logique en fait dcrire les instructions suivantes :


1 2 3 4 5 6

List < Animal > animaux = new List < Animal >() ; Chien chien = new Chien () ; Chat chat = new Chat () ; animaux . Add (( Animal ) chien ) ; animaux . Add (( Animal ) chat ) ;

Dans ce cas, nous crons un objet Chien et un objet Chat que nous mettons dans une liste dobjets Animal grce une conversion utilisant un cast. En fait, ce cast est inutile et nous pouvons simplement crire :
1 2 3 4 5 6

List < Animal > animaux = new List < Animal >() ; Chien chien = new Chien () ; Chat chat = new Chat () ; animaux . Add ( chien ) ; animaux . Add ( chat ) ;

La conversion est implicite, comme lorsque nous avions utilis un object en paramtres dune mthode et que nous pouvions lui passer tous les types qui drivent dobject. Nous avions galement vu que nous pouvions traiter les chiens et les chats comme des animaux partir du moment o nous les mettions dans une liste. Avec les objets suivants : 215

CHAPITRE 22. LA POO ET LE C#


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

public class Animal { public void Respirer () { Console . WriteLine ( " Je respire " ) ; } } public class Chien : Animal { public void Aboyer () { Console . WriteLine ( " Waouf " ) ; } } public class Chat : Animal { public void Miauler () { Console . WriteLine ( " Miaou " ) ; } }

Nous pouvions utiliser une boucle pour faire respirer tous nos animaux :
1 2 3 4 5 6 7 8 9 10 11

List < Animal > animaux = new List < Animal >() ; Chien chien = new Chien () ; Chat chat = new Chat () ; animaux . Add ( chien ) ; animaux . Add ( chat ) ; foreach ( Animal animal in animaux ) { animal . Respirer () ; }

Mais impossible de faire aboyer le chien, ni miauler le chat. Si vous tentez de remplacer dans la boucle Animal par Chien, avec :
1 2 3 4

foreach ( Chien c in animaux ) { c . Aboyer () ; }

Vous pourrez faire aboyer le premier lment de la liste qui est eectivement un chien, par contre il y aura un plantage au deuxime lment de la liste car il sagit dun chat :
Waouf

216

LA CONVERSION ENTRE LES OBJETS AVEC LE CASTING


Exception non g r e : System . InvalidC astException : Impossible d effectuer un cast d un objet de type Ma Premie reApp licati on . Chat en type Ma Premie reApp licati on . Chien . M a P r e m i e r e A p plica tion . Program . Main ( String [] args ) dans C :\ Users \ Nico \ Documents \ Visual Studio 2010\ Projects \ C #\ M a P r e m i e r e Appl icatio n \ MaP remier eAppli catio n \ Program . cs : ligne 19

Lorsque notre programme a tent de convertir un animal qui est un chat en chien, il nous a fait comprendre quil napprciait que moyennement. Les chiens naiment pas trop les chats dune manire gnrale, alors en plus, un chat qui essaie de se faire passer pour un chien : cest une dclaration de guerre ! Voil pourquoi notre programme a lev une exception. Il lui tait impossible de convertir un Chat en Chien. Il est cependant possible de tester si une variable correspond un objet grce au motcl is. Ce qui nous permettra de faire la conversion adquate et de nous viter une erreur lexcution :
1 2 3 4 5 6 7 8 9 10 11 12 13

foreach ( Animal animal in animaux ) { if ( animal is Chien ) { Chien c = ( Chien ) animal ; c . Aboyer () ; } if ( animal is Chat ) { Chat c = ( Chat ) animal ; c . Miauler () ; } }

Nous testons avec le mot-cl is si lanimal est une instance dun chien ou dun chat. Le code du dessus nous permettra dutiliser dans la boucle lanimal courant comme un chien ou un chat en fonction de ce quil est vraiment, grce au test :
Waouf Miaou

Le fait de tester ce quest vraiment lanimal avant de le convertir est une scurit indispensable pour viter ce genre derreur. Cest linconvnient du cast explicite. Il convient trs bien si nous sommes certains du type dans lequel nous souhaitons en convertir un autre. Par contre, si la conversion nest pas possible, alors nous aurons une erreur. Lorsque nous ne sommes pas certains du rsultat du cast, mieux vaut tester si linstance dun objet correspond bien lobjet lui-mme. Cela peut se faire comme nous lavons vu avec le mot-cl is, mais galement avec un autre cast qui sappelle le cast dynamique. Il se fait en employant le mot-cl as. Ce cast dynamique vrie que lobjet est bien convertible. Si cest le cas, alors il fait un 217

CHAPITRE 22. LA POO ET LE C# cast explicite pour renvoyer le rsultat de la conversion, sinon, il renvoie une rfrence nulle. Le code du dessus peut donc scrire :
1 2 3 4 5 6 7 8 9 10 11 12 13

foreach ( Animal animal in animaux ) { Chien c1 = animal as Chien ; if ( c1 != null ) { c1 . Aboyer () ; } Chat c2 = animal as Chat ; if ( c2 != null ) { c2 . Miauler () ; } }

On utilise le mot-cl as en le faisant prcder de la valeur tenter de convertir et en le faisant suivre du type dans lequel nous souhaitons la convertir. Fonctionnellement, nous faisons la mme chose dans les deux codes. Vous pouvez choisir lcriture que vous prfrez, mais sachez que cest ce dernier qui est en gnral utilis, car il est prconis par Microsoft. De plus, il est un tout petit peu plus performant. Un petit dtail encore. Il est possible de convertir un type valeur, comme un int ou un string en type rfrence en utilisant ce quon appelle le boxing. Rien voir avec le fait de taper sur les types valeur ! Comme nous lavons vu, les types valeur et les types rfrence sont grs diremment par .NET. Aussi, si nous convertissons un type valeur en type rfrence, .NET fait une opration spciale automatiquement. Ainsi le code suivant :
1 2

int i = 5 ; object o = i ; // boxing

eectue un boxing automatique de lentier en type rfrence. Cest ce boxing automatique qui nous permet de manipuler les types valeur comme des object. Cest aussi ce qui nous a permis plus haut de passer un entier en paramtre une mthode qui acceptait un object. En interne, ce qui se passe cest que object se voit attribuer une rfrence vers une copie de la valeur de i. Ainsi, modier o ne modiera pas i. Ce code :
1 2 3 4 5

int i = 5 ; object o = i ; // boxing o = 6; Console . WriteLine ( i ) ; Console . WriteLine ( o ) ;

ache 5 puis 6. Le contraire est galement possible, ce quon appelle lunboxing. Seulement, celui-ci a besoin dun cast explicite an de pouvoir compiler. Cest--dire :
1 2 3

int i = 5 ; object o = i ; // boxing int j = ( int ) o ; // unboxing

218

LA CONVERSION ENTRE LES OBJETS AVEC LE CASTING ici nous reconvertissons la rfrence vers la valeur de o en entier et nous eectuons nouveau une copie de cette valeur pour la mettre dans j. Ainsi le code suivant :
1 2 3 4 5 6 7 8 9

int i = 5 ; object o = i ; // boxing o = 6; int j = ( int ) o ; // unboxing j = 7; Console . WriteLine ( i ) ; Console . WriteLine ( o ) ; Console . WriteLine ( j ) ;

achera en toute logique 5 puis 6 puis 7. noter que ces oprations sont consommatrices de temps, elles sont donc faire le moins possible.

En rsum
Les objets peuvent tre des types valeur ou des types rfrence. Les variables de type valeur possdent la valeur de lobjet, comme un entier. Les variables de type rfrence possdent une rfrence vers lobjet en mmoire. Tous les objets drivent de la classe de base Object. On peut substituer une mthode grce au mot-cl override si elle sest dclare candidate la substitution grce au mot-cl virtual. La surcharge est le polymorphisme permettant de faire varier les types des paramtres dune mme mthode ou leurs nombres. Il est possible grce au cast de convertir un type en un autre type, sils ont une relation dhritage.

219

You might also like