Vous êtes sur la page 1sur 1068

Pratique de .NET 2.

0
et de C  2.0

patrick smacchia

Pratique de .NET 2.0



et de C 2.0

ditions OREILLY
18 rue Sguier
75006 PARIS
http://www.oreilly.fr

Cambridge Cologne Farnham Paris Pkin Sbastopol Tape Tokyo

Couverture conue et ralise par Hanna Dyer & Marcia Friedman.

dition : Xavier Cazin.


Les programmes figurant dans ce livre ont pour but dillustrer les sujets traits. Il nest donn
aucune garantie quant leur fonctionnement une fois compils, assembls ou interprts dans
le cadre dune utilisation professionnelle ou commerciale.

c ditions OReilly, Paris, 2005



ISBN 2-84177-339-6

Toute reprsentation ou reproduction, intgrale ou partielle, faite sans le consentement de lauteur, de ses ayants droit, ou ayants cause, est illicite (loi du 11 mars 1957, alina 1er de larticle 40).
Cette reprsentation ou reproduction, par quelque procd que ce soit, constituerait une contrefaon sanctionne par les articles 425 et suivants du Code pnal. La loi du 11 mars 1957 autorise
uniquement, aux termes des alinas 2 et 3 de larticle 41, les copies ou reproductions strictement rserves lusage priv du copiste et non destines une utilisation collective dune part et, dautre
part, les analyses et les courtes citations dans un but dexemple et dillustration.

Table des matires

propos de ce livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Lorganisation de ce livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

qui sadresse ce livre et comment lexploiter . . . . . . . . . . . . . . . . . . . . .

Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Remerciements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Aborder la plateforme .NET

Quest ce que .NET ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.NET hors du monde Microsoft / Windows . . . . . . . . . . . . . . . . . . . . . .

Liens sur .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

Assemblages, modules, langage IL

15

Assemblages, modules et fichiers de ressource . . . . . . . . . . . . . . . . . . . . .

15

Anatomie des modules

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

Analyse dun assemblage avec ildasm.exe et Reflector . . . . . . . . . . . . . . . . .

20

Attributs dassemblage et versionning . . . . . . . . . . . . . . . . . . . . . . . . .

25

Assemblage nom fort (strong naming) . . . . . . . . . . . . . . . . . . . . . . . .

28

Internationalisation et assemblages satellites . . . . . . . . . . . . . . . . . . . . .

34

Introduction au langage IL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

41

Construction, configuration et dploiement des applications .NET

49

Construire vos applications avec MSBuild . . . . . . . . . . . . . . . . . . . . . . .

49

MSBuild : Cibles, tches, proprits, items et conditions . . . . . . . . . . . . . . .

50

Concepts avancs de MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

54

Fichiers de configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

vi

Table des matires


Dploiement des assemblages : XCopy vs. Rpertoire GAC . . . . . . . . . . . . . .

63

Assemblage de stratgie dditeur (Publisher policy) . . . . . . . . . . . . . . . . .

66

Introduction au dploiement dapplications .NET . . . . . . . . . . . . . . . . . .

69

Dployer une application avec un fichier cab . . . . . . . . . . . . . . . . . . . . .

71

Dployer une application avec la technologie MSI . . . . . . . . . . . . . . . . . .

73

Dployer une application avec la technologie ClickOnce . . . . . . . . . . . . . . .

75

Dployer une application avec la technologie No Touch Deployment . . . . . . .

84

Et si .NET nest pas install sur la machine cible ? . . . . . . . . . . . . . . . . . . .

85

Le CLR (le moteur dexcution des applications .NET)


Les domaines dapplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87

Chargement du CLR dans un processus grce lhte du moteur dexcution . . .

94

Profiler vos applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

102

Localisation et chargement des assemblages lexcution . . . . . . . . . . . . . .

103

Rsolution des types lexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . .

108

La compilation Juste temps (JIT Just In Time)

. . . . . . . . . . . . . . . . .

111

. . . . . . . . . . . . . . . . . . . . . . . . .

116

Facilits fournies par le CLR pour rendre votre code plus fiable . . . . . . . . . . .

125

CLI et CLS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

129

Gestion du tas par le ramasse-miettes

87

Processus, threads et gestion de la synchronisation

133

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

133

Les processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

134

Les threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

136

Introduction la synchronisation des accs aux ressources . . . . . . . . . . . . . .

143

Synchronisation avec les champs volatiles et la classe Interlocked . . . . . . . . . .

145

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock . . . .

147

Synchronisation avec des mutex, des vnements et des smaphores . . . . . . . .

153

Synchronisation avec la classe System.Threading.ReaderWriterLock . . . . . . . .

158

Synchronisation avec lattribut System...SynchronizationAttribute . . . . . . . . .

160

Le pool de threads du CLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

167

Timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

169

Appel asynchrone dune mthode . . . . . . . . . . . . . . . . . . . . . . . . . . .

171

Anit entre threads et ressources . . . . . . . . . . . . . . . . . . . . . . . . . . .

176

Contexte dexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

180

Table des matires

La gestion de la scurit

vii

185

Introduction Code Access Security (CAS) . . . . . . . . . . . . . . . . . . . . . .

185

CAS : Preuves et permissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

187

CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

193

CAS : La permission FullTrust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

198

CAS : Vrifier les permissions imprativement partir du code source . . . . . . .

199

CAS : Vrifier les permissions dclarativement partir du code source . . . . . . .

203

CAS : Facilits pour tester et dboguer votre code mobile . . . . . . . . . . . . . .

205

CAS : La permission de faire du stockage isol . . . . . . . . . . . . . . . . . . . . .

205

Support .NET pour les utilisateurs et rles Windows . . . . . . . . . . . . . . . . .

206

Support .NET pour les contrles des accs aux ressources Windows . . . . . . . . .

211

.NET et la notion de rle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

216

.NET et les algorithmes symtriques de cryptographie . . . . . . . . . . . . . . . .

219

.NET et les algorithmes asymtriques de cryptographie (cl publique/cl prive) .

221

LAPI de protection des donnes (Data Protection API) . . . . . . . . . . . . . . . .

225

Authentifier vos assemblages avec la technologie Authenticode . . . . . . . . . . .

230

Rflexion, liens tardifs, attributs

233

Le mcanisme de rflexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

233

Les liens tardifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

238

Les attributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

248

Construction et utilisation dynamique dun assemblage . . . . . . . . . . . . . . .

255

Interoprabilit .NET code natif / COM / COM+

265

Le mcanisme P/Invoke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

265

Introduction linteroprabilit avec C++/CLI . . . . . . . . . . . . . . . . . . . . .

272

.NET et les Handles win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

277

Utilisation de COM partir de .NET . . . . . . . . . . . . . . . . . . . . . . . . . .

278

Encapsuler une classe .NET dans une classe COM . . . . . . . . . . . . . . . . . . .

288

Introduction COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

295

Prsentation des services dentreprise COM+ . . . . . . . . . . . . . . . . . . . . . .

296

Utiliser les services COM+ dans des classes .NET . . . . . . . . . . . . . . . . . . .

299

viii

Table des matires

Les concepts fondamentaux du langage

309

Organisation du code source

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

309

Les tapes de la compilation

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

312

Le prprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

313

Le compilateur csc.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

317

Les alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

320

Commentaires et documentation automatique . . . . . . . . . . . . . . . . . . . .

323

Les identificateurs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

326

Les structures de contrle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

327

La mthode Main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

334

10 Le systme de types
Stockage des objets en mmoire

337
. . . . . . . . . . . . . . . . . . . . . . . . . . . .

337

Type valeur et type rfrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

339

Le CTS (Common Type System) . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

342

La classe System.Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

344

Comparer des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

345

Cloner des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

347

Boxing et UnBoxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

350

Les types primitifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

353

Oprations sur les types primitifs . . . . . . . . . . . . . . . . . . . . . . . . . . . .

358

Les structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

364

Les numrations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

366

Les chanes de caractres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

370

Les dlgations et les dlgus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

378

Les types nullables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

385

Dfinir un type sur plusieurs fichiers sources . . . . . . . . . . . . . . . . . . . . .

392

11 Notions de classe et dobjet

395

Remarques sur la programmation objet . . . . . . . . . . . . . . . . . . . . . . . .

395

Notions et vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

395

Dfinition dune classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

396

Les champs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

397

Les mthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

400

Les proprits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

407

Table des matires

ix

Les indexeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

409

Les vnements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

411

Les types encapsuls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

416

Encapsulation et niveaux de visibilit . . . . . . . . . . . . . . . . . . . . . . . . .

417

Le mot-cl this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

420

Construction des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

421

Destruction des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

423

Les membres statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

430

Surcharge des oprateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

433

12 Hritage/drivation polymorphisme et abstraction

443

Objectif : rutilisation de code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

443

Lhritage dimplmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

445

Mthodes virtuelles et polymorphisme

. . . . . . . . . . . . . . . . . . . . . . . .

448

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

452

Les interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

456

Proprits, vnements et indexeurs virtuels et abstraits . . . . . . . . . . . . . . .

462

Les oprateurs is et as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

464

Techniques de rutilisation de code . . . . . . . . . . . . . . . . . . . . . . . . . . .

466

Labstraction

13 La gnricit

467

Un problme de C  1 et sa rsolution par les types gnriques de C  2 . . . . . . . .


Vue densemble de la gnricit de C  2 . . . . . . . . . . . . . . . . . . . . . . . .

471

Possibilit de contraindre un type paramtre . . . . . . . . . . . . . . . . . . . . .

474

Les membres dun type gnrique . . . . . . . . . . . . . . . . . . . . . . . . . . .

477

Les oprateurs et les types gnriques . . . . . . . . . . . . . . . . . . . . . . . . . .

481

Le transtypage (casting) et la gnricit . . . . . . . . . . . . . . . . . . . . . . . . .

484

Lhritage et la gnricit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

486

Les mthodes gnriques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

487

Les dlgus, les vnements et la gnricit . . . . . . . . . . . . . . . . . . . . . .

491

Rflexion, attribut, IL et gnricit . . . . . . . . . . . . . . . . . . . . . . . . . . .

493

La gnricit et le framework .NET . . . . . . . . . . . . . . . . . . . . . . . . . . .

499

467

Table des matires

14 Les mcanismes utilisables dans C 

501

Les pointeurs et les zones de code non vrifiable . . . . . . . . . . . . . . . . . . .


Manipulation des pointeurs en C  . . . . . . . . . . . . . . . . . . . . . . . . . . .

501
503

Les exceptions et le traitement des erreurs . . . . . . . . . . . . . . . . . . . . . . .

509

Objet associ une exception et lancement de vos propres exceptions . . . . . . .

511

Le gestionnaire dexceptions et la clause finally . . . . . . . . . . . . . . . . . . . .

515

Exceptions lances dans un constructeur ou dans la mthode Finalize() . . . . . .

517

Le CLR et la gestion des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . .

519

Les exceptions et lenvironnement Visual Studio . . . . . . . . . . . . . . . . . . .

522

Conseils dutilisation des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . .

522

Les mthodes anonymes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Le compilateur C  2 et les mthodes anonymes . . . . . . . . . . . . . . . . . . . .

524

Exemples avancs dutilisation des mthodes anonymes . . . . .


Les itrateurs avec C  1 . . . . . . . . . . . . . . . . . . . . . . . .
Les itrateurs avec C  2 . . . . . . . . . . . . . . . . . . . . . . . .
Interprtation des itrateurs par le compilateur de C  2 . . . . . .

. . . . . . . . . .

536

. . . . . . . . . .

539

. . . . . . . . . .

542

. . . . . . . . . .

Exemples avancs de lutilisation des itrateurs de C 2 . . . . . . . . . . . . . . . .

548

15 Collections

529

552

563

Parcours des lments dune collection avec foreach et in . . . . . . . . . . .

563

Les tableaux

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

565

Les squences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

575

Les dictionnaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

582

Trier les lments dune collection . . . . . . . . . . . . . . . . . . . . . . . . . . .

587

Foncteurs et manipulation des collections . . . . . . . . . . . . . . . . . . . . . . .

591

Correspondance entre System.Collections.Generic et System.Collections . . . . . .

595

16 Bibliothques de classes
Fonctions mathmatiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

597
597

Donnes temporelles (dates, dures...) . . . . . . . . . . . . . . . . . . . . . . . . .

600

Volumes, rpertoires et fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

607

Base des registres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

613

Le dbogage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

617

Les traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

620

Les expressions rgulires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

625

La console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

629

Table des matires

17 Les mcanismes dentre/sortie

xi

633

Introduction aux flots de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . .

633

Lecture/criture des donnes dun fichier . . . . . . . . . . . . . . . . . . . . . . .

636

Support du protocole TCP/IP et des sockets . . . . . . . . . . . . . . . . . . . . . .

641

Obtenir des informations concernant le rseau . . . . . . . . . . . . . . . . . . . .

649

Clients HTTP et FTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

651

Serveur HTTP avec HttpListener et HTTP.SYS . . . . . . . . . . . . . . . . . . . . .

654

Support des protocoles denvoi de mails SMTP et MIME . . . . . . . . . . . . . . .

656

Burisation et compression dun flot de donnes . . . . . . . . . . . . . . . . . .

657

Lecture/criture des donnes sur un port srie . . . . . . . . . . . . . . . . . . . . .

660

Support des protocoles SSL, NTLM et Kerberos de scurisation des donnes changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

660

18 Les applications fentres (Windows Forms)

665

Les applications fentres sous les systmes dexploitation Windows . . . . . . . .

665

Introduction aux formulaires Windows Forms . . . . . . . . . . . . . . . . . . . .

668

Facilits pour dvelopper des formulaires . . . . . . . . . . . . . . . . . . . . . . .

675

Les contrles standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

679

Crer vos propres contrles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

682

Prsentation et dition des donnes . . . . . . . . . . . . . . . . . . . . . . . . . .

688

Internationaliser les fentres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

695

La bibliothque GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

695

19 ADO.NET 2.0

705

Introduction aux bases de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . .

705

Introduction ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

707

Connexion et fournisseurs de donnes . . . . . . . . . . . . . . . . . . . . . . . . .

712

Travailler en mode connect avec des DataReader . . . . . . . . . . . . . . . . . . .

720

Travailler en mode dconnect avec des DataSet . . . . . . . . . . . . . . . . . . .

723

DataSet typs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

731

Pont entre le mode connect et le mode dconnect . . . . . . . . . . . . . . . . .

735

Ponts entre lobjet et le relationnel . . . . . . . . . . . . . . . . . . . . . . . . . . .

736

Fonctionalits spcifiques au fournisseur de donnes de SQL Server . . . . . . . .

738

xii

Table des matires

20 Les transactions

741

Introduction la notion de transaction . . . . . . . . . . . . . . . . . . . . . . . .

741

Le framework System.Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . .

746

Utilisation avances de System.Transactions . . . . . . . . . . . . . . . . . . . . . .

752

Introduction la cration dun RM transactionnel . . . . . . . . . . . . . . . . . .

755

21 XML

759

Introduction XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

759

Introduction XSD, XPath, XSLT et XQuery . . . . . . . . . . . . . . . . . . . . . .

762

Les approches pour parcourir et diter un document XML . . . . . . . . . . . . . .

765

Parcours et dition dun document XML avec un curseur (XmlReader et XmlWriter)

766

Parcours et dition dun document XML avec DOM (XmlDocument) . . . . . . .

769

Parcours et dition dun document XML avec XPath . . . . . . . . . . . . . . . . .

771

Transformer un document XML avec XSLT . . . . . . . . . . . . . . . . . . . . . .

774

Ponts entre le relationnel et XML . . . . . . . . . . . . . . . . . . . . . . . . . . . .

775

Ponts entre lobjet et XML (srialisation XML) . . . . . . . . . . . . . . . . . . . .

779

Visual Studio .NET et XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

783

22 .NET Remoting

785

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

785

Marshaling By Reference (MBR) . . . . . . . . . . . . . . . . . . . . . . . . . . . .

787

Marshalling By Value (MBV) et serialisation binaire . . . . . . . . . . . . . . . . .

790

La classe ObjectHandle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

792

Introduction lactivation des objets . . . . . . . . . . . . . . . . . . . . . . . . . .

793

Service dactivation par le serveur (WKO) . . . . . . . . . . . . . . . . . . . . . . .

795

Activation par le client (CAO) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

799

Le design pattern factory et loutil soapsuds.exe . . . . . . . . . . . . . . . . . . . .

802

Service de dure de vie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

805

Configurer la partie Remoting dune application . . . . . . . . . . . . . . . . . . .

808

Dploiement dune application distribue .NET . . . . . . . . . . . . . . . . . . .

815

Scuriser une conversation .NET Remoting . . . . . . . . . . . . . . . . . . . . . .

816

Proxys et messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

817

Canaux (channels) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

830

Contexte .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

842

Rcapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

857

Table des matires

xiii

23 ASP.NET 2.0

859

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

859

Architecture gnrale dASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . .

861

Stockage du code source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

866

Modles de compilation et de dploiement . . . . . . . . . . . . . . . . . . . . . .

871

Web forms et contrles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

873

Cycle de vie dune page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

883

Configuration dune application ASP.NET . . . . . . . . . . . . . . . . . . . . . . .

888

Pipeline HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

891

Gestion des sessions et des tats . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

897

Le design pattern provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

902

Gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

903

Traces, diagnostics et gestion des vnements . . . . . . . . . . . . . . . . . . . . .

905

Validation des donnes saisies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

908

Contrles utilisateurs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

912

Amliorer les performances avec la mise en cache . . . . . . . . . . . . . . . . . . .

918

Sources de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

929

Prsentation et dition des donnes . . . . . . . . . . . . . . . . . . . . . . . . . .

935

Master pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

947

Internationaliser une application ASP.NET 2.0 . . . . . . . . . . . . . . . . . . . .

953

Aides la navigation dans un site . . . . . . . . . . . . . . . . . . . . . . . . . . . .

954

Scurit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

957

Personnalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

966

Styles, Thmes et Skins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

970

WebParts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

973

24 Introduction au dveloppement de Services Web avec .NET

987

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

987

Dveloppement dun service web simple

. . . . . . . . . . . . . . . . . . . . . . .

991

Tester et dboguer un service web . . . . . . . . . . . . . . . . . . . . . . . . . . .

993

Crer un client .NET dun service web . . . . . . . . . . . . . . . . . . . . . . . . .

994

Appels asynchrones et modle dchange de message . . . . . . . . . . . . . . . . .

998

Utiliser un service web partir dun client .NET Remoting . . . . . . . . . . . . .

998

Encoder les messages au format SOAP . . . . . . . . . . . . . . . . . . . . . . . . .

1000

Dfinir des contrats avec le langage WSDL . . . . . . . . . . . . . . . . . . . . . . .

1003

xiv

Table des matires


Introduction WSE et aux spcifications WS-* . . . . . . . . . . . . . . . . . . . .

1008

Les spcifications WS-* non encore supportes par WSE . . . . . . . . . . . . . . .

1011

Introduction WCF (Windows Communication Framework) . . . . . . . . . . . .

1013

A Les mots-cls du langage C  2.0

1015

B Nouveauts .NET 2.0

1021

C Introduction aux design patterns

1031

D Les outils

1033

Index

1035

Avant-propos
propos de ce livre
La documentation ocielle de Microsoft sur .NET est trs vaste et dcrit en dtail chaque
membre de chaque type du framework .NET. Elle contient aussi de nombreux articles concernant la description ou lutilisation de telle ou telle partie de .NET. En tant que dveloppeur,
je sais combien lutilisation de cette documentation est fondamentale lorsque lon dveloppe
avec les technologies Microsoft. Cependant, de part le volume de cette documentation, il est
assez dicile dacqurir une vision globale des possibilits de .NET. En outre, daprs ma
propre exprience, les nouvelles ides sacquirent mieux partir dun livre. Certes, on pourrait
imprimer les dizaines de milliers de pages des MSDN, mais vous auriez du mal les transporter
pour les lire au calme, dans un jardin ou sur votre canap.
Ce livre a donc t conu dans loptique dtre utilis conjointement avec les MSDN. Il nest pas
question ici dnumrer les dizaines de milliers de membres des milliers de types .NET mais
plutt dexpliquer et dillustrer avec des exemples concrets les multiples facettes du langage C  ,
de la plateforme .NET ainsi que de son framework. Jespre quil rpondra aux problmatiques
que vous rencontrerez et quil vous accompagnera hors des sentiers battus dans votre dcouverte
de la technologie .NET.

Lorganisation de ce livre
Partie I : Larchitecture de la plateforme .NET
La premire partie dcrit larchitecture sous-jacente la plateforme .NET. Cest dans cette partie
que vous trouverez les rponses aux questions du type :

Quels sont les liens entre lexcution des applications .NET et le systme dexploitation sousjacent ?

Quelle est la structure des fichiers produits par la compilation de mon programme ?

Comment sont gres la scurit et la synchronisation des accs aux ressources ?

Comment puis-je tirer parti de tout cela pour amliorer la qualit et les performances de
mes applications ?

Comment puis-je exploiter du code dj dvelopp sous Windows partir de mes applications .NET ?

Table des matires

Partie II : Le langage C  2.0 et la comparaison C  2.0/C++


La deuxime partie dcrit compltement le langage C  2.0. Ce langage est beaucoup plus proche
du langage Java que du langage C++. Je me suis donc eorc de dcrire les similitudes et les dirences entre C  et C++ pour chaque facette du langage C  . Jespre que cette approche rpondra
rapidement aux questions des dveloppeurs C++ qui migrent vers C  .

Partie III : Le framework .NET


La troisime partie dcrit les classes de base du framework .NET. Les fonctionnalits de ces classes
se rpartissent principalement dans les catgories suivantes :

Les collections.

Les classes de base classiques type calculs mathmatiques, dates et dures, rpertoires et
fichiers, traces et dbogage, expressions rgulires, console.

La gestion des entres/sorties au moyen de flots de donnes.

Le dveloppement dapplications graphiques fentres.

La gestion des bases de donnes avec ADO.NET 2.0.

La gestion des transactions.

La cration et lexploitation de documents XML.

Les applications objets distribus avec .NET Remoting.

Le dveloppement dapplication web avec ASP.NET 2.0.

Les services web.

Remarques
Ce plan de prsentation de la technologie .NET permet davoir une vision globale. Nanmoins il
est bien vident quune technologie aussi vaste comporte de multiples facettes qui transcendent
ce dcoupage. Par exemple, nous avons choisi de placer la gestion de la synchronisation des accs
aux ressources dans larchitecture de la plateforme .NET du fait quelle se base sur les notions
sous-jacentes de threads et de processus. Cependant, en tant quensemble de classes utilisables
dans vos applications, la synchronisation fait aussi partie des classes de base du framework .NET.
En outre le langage C  comporte des mots-cls spcialiss pour simplifier lutilisation de ces
classes. Enfin .NET Remoting prsente des techniques qui permettent une approche volue de
la synchronisation.
Cet ouvrage contient donc de nombreuses rfrences internes qui jespre, faciliteront vos recherches sur les dirents sujets exposs.

qui sadresse ce livre et comment lexploiter


Ce livre sadresse vous ds lors que vous tes intress par le dveloppement sous .NET, que
vous soyez tudiant, dveloppeur professionnel ou amateur, enseignant ou formateur, architecte ou chef de projet.
Chaque chapitre a t conu pour tre lu dune manire linaire mais il nen est pas de mme
pour louvrage dans sa globalit. La premire partie, larchitecture de la plateforme .NET, est

Support

considre comme la plus ardue mais aussi comme la plus fondamentale (et mon sens comme
la plus passionnante). Il nest pas possible de dvelopper correctement avec la technologie .NET
sans tenir compte des services de la plateforme sous-jacente.
Le lecteur dbutant pourra commencer par lapprentissage du langage C  et des technologies
de dveloppement objet, tout en dcouvrant petit petit les possibilits de .NET. .NET est un
sujet trs vaste, qui a plus que doubl en volume avec la version 2.0. Aussi, nous nous sommes
eorc de rester prcis et concis.
Le lecteur expriment dans dautres technologies devrait pouvoir bnficier des explications
concernant les nombreuses possibilits rellement innovantes oertes par .NET.
Le lecteur expriment avec .NET 1.x se servira de lAnnexe B qui rfrence toutes les nouveauts apportes par .NET 2.0 prsentes dans le prsent ouvrage.

Support
Le prsent ouvrage est support sur mon site : http://www.smacchia.com
Vous pouvez y tlcharger les exemples de code fournis dans cet ouvrage. Nous croyons que bien
souvent un exemple vaut mieux quun long discours. Le livre contient 648 exemples dont 523
listings C  et 65 pages ASP.NET 2.0.
Vous pouvez galement nous adresser vos remarques sur cet ouvrage en crivant aux ditions
OReilly :
18 rue Sguier, 75006 Paris
Email : info@editions-oreilly.fr

Remerciements
Je souhaite avant tout remercier mon amie Eli Ane pour son soutien qui ma tellement apport
durant la rdaction de cet ouvrage. Jai aussi beaucoup apprci le soutien de Francis, France,
Michel, Christine, Mathieu, Julien, Andre, Patrick, Marie-Laure et Philippe.
Merci Xavier Cazin des Editions OReilly pour son aide et son professionnalisme.
Mes remerciements vont aussi aux relecteurs et amis qui mont apports chacun de prcieux
conseils :
Alain Metge, 18 ans dexprience. Responsable de la cellule architecture logicielle aux autoroutes du Sud de la France
Dr. Bertrand Le Roy, 8 ans dexprience, participe depuis 3 ans la conception et au dveloppement de la technologie ASP.NET Microsoft Corporation.
Bruno Boucard, 18 ans dexprience, architecte/formateur la Socit Gnrale depuis 8 ans.
Microsoft Informed Architect.
Frdric De Lne Mirouze (alias Amthyste), spcialiste en dveloppement web, 20 ans dexprience, collabore notamment avec ELF, Glaxo, Nortel, Usinor. MCAD.NET.
Jean-Baptiste Evain, 3 ans dexprience, spcialiste du Common Language Infrastructure,
contributeur aux projets Mono et AspectDNG.

Table des matires

Laurent Desmons, 10 ans dexprience, architecte et consultant .NET, collabore notamment


avec Pchiney, Arcelor, Sollac. MCSD.NET.
Matthieu Guyonnet-Duluc, 4 ans dexprience, dveloppeur chez France Tlcom dans le domaine commercial.
Dr Michel Futtersack, Matre de Confrences en Informatique, Universit Ren Descartes, enseigne depuis 10 ans la conception et la programmation OO.
Nicolas Frelat, consultant .NET, 4 ans dexprience. Early adopter de la plateforme .NET2.
Olivier Girard, 6 ans dexprience, spcialiste en EAI, architecte la Banque de France.
Patrick Philippot, freelance, 30 ans dexprience (dont 19 IBM), MVP .NET www.mainsoft.
fr.
Sami Jaber, 8 ans dexprience, consultant senior et formateur pour Valtech, collabore notamment avec Airbus, webmaster de www.dotnetguru.org.
Sbastien Ros, 7 ans dexprience, spcialiste en mapping O/R, auteur de loutil DTM, CTO
dEvaluant www.evaluant.com.
Sbastien Vaucouleur, 8 ans dexprience, spcialiste en langages, collabore notamment avec
Bull, Fujitsu, assistant de recherche luniversit ETH (Zurich).
Thibaut Barrre, freelance, spcialiste des plateformes J2EE/.NET/C++, programme depuis
1984 et a rcemment collabor avec Calyon, PPR/Redoute et MCS. Contributeur open-source
sur les projets CruiseControl.Net, NAnt et TestDriven.Net
Thomas Gil, 8 ans dexprience, spcialiste en Programmation Oriente Aspect, chef de projet
de AspectDNG, consultant et formateur indpendant, co-webmaster de www.dotnetguru.org.
Vincent Canestrier, ancien enseignant au Conservatoire National des Arts et Mtiers, ancien
directeur technique de division Cap Gemini Ernst & Young.
Aussi, je souhaiterais remercier Brian Grunkemeyer, Florin Lazar, Krzysztof Cwalina et Michael Marucheck tous ingnieurs Microsoft Corp, pour leurs rponses zles mes interrogations.
Enfin je souhaiterais vous remercier pour avoir choisi mon ouvrage. Jespre sincrement quil
vous aidera dans votre tche.

1
Aborder la plateforme .NET

Quest ce que .NET ?


La technologie de dveloppement logiciel de Microsoft
Sous lappellation .NET on dsigne la plateforme de dveloppement logiciel principale de Microsoft. Le sujet est trs vaste et englobe aussi bien larchitecture interne, le format des composants, les langages de programmation, les classes standard et les outils. Sous lappellation .NET,
on dsigne donc une nouvelle re dans le monde Microsoft, qui supplante petit petit lre
COM/win32/C++/VB/ASP.
Le nom .NET a t choisi du fait que linternet et plus gnralement les rseaux sont de plus
en plus exploits par les logiciels. Les applications sont de plus en plus interconnectes. De ce
fait, la technologie .NET prsente des facilits pour faire communiquer les applications qui font
lobjet des chapitres 22 et 24. Pour faciliter linteroprabilit des applications dans un milieu
htrogne, la plateforme .NET a aussi pour caractristique forte dexploiter XML tous les
niveaux.
Peu peu, tous les produits Microsoft prsentent partiellement ou compltement leurs APIs
avec des types .NET. Par exemple, la version 2005 de SQL Server permet dinjecter du code .NET
excut au sein du processus du SGBD qui ralise des traitements sur les donnes. LAPI de
programmation de Windows Vista, la prochaine version de Windows, est accessible sous forme
de types .NET. La technologie de construction de pages web de .NET, nomme ASP.NET est
maintenant privilgie par le serveur web IIS 7.0. La suite Oce prsente un modle de programmation bas sur .NET qui supplante peu peu le modle VBA.

Un ensemble de spcifications
La plateforme .NET se base sur de nombreuses spcifications, certaines maintenues par dautres
organismes que Microsoft. Ces spcifications dfinissent des nouveaux langages, comme le lan-

Chapitre 1 : Aborder la plateforme .NET

gage C  et le langage IL ou des protocoles dchange de donnes, comme le format SOAP. Plusieurs autres initiatives dimplmentations de ces spcifications sont en cours de ralisation.
terme, .NET devrait donc tre disponible sur plusieurs systmes dexploitation et ne se cantonnera pas quau monde Microsoft. .NET est donc un nouvelle re dans le monde du dveloppement logiciel, comparable lre C, lre C++ et lre Java. Il est intressant de remarquer que ce
phnomne semble se reproduire priodiquement, approximativement tous les 7 ans. chaque
nouvelle re, la productivit des dveloppeurs augmente grce lintroduction de nouvelles
ides et les applications sont plus conviviales et traitent plus de donnes, notamment grce
la puissance croissante du hardware. La consquence est que lindustrie adopte ces nouvelles
technologies pour dvelopper des logiciels de meilleure qualit tout en rduisant les cots.

Prsentation de la technologie .NET


La technologie .NET se compose principalement de trois parties :

Un ensemble extensible de langages de dveloppement dont le langage C  et le langage


VB.NET. Ces langages doivent respecter une spcification nomme CLS (Common Langage
Spcification). Les types de base utiliss par ces langages doivent eux aussi respecter une spcification, nomme CTS (Common Type System).

Un ensemble de classes de base utilisables partir de programmes dvelopps dans ces langages. On les dsigne parfois sous le terme de BCL (Base Class Library). Cest ce que nous
appellerons framework .NET tout au long de cet ouvrage.

Une couche logicielle respectant une spcification nomme CLI (Common Langage Infrastructure). Elle est responsable de lexcution des applications .NET. Cette couche logicielle
ne connat quun langage nomm langage IL (Intermediate Langage). Cette couche logicielle
est notamment responsable durant lexcution dune application, de la compilation du
code IL en langage machine. En consquence les langages supports par .NET doivent chacun disposer dun compilateur permettant de produire du code IL. Limplmentation Microsoft de la spcification CLI est nomme CLR (Common Langage Runtime).

Paralllement ces trois parties, il existe de nombreux outils facilitant le dveloppement dapplications avec .NET. On peut citer Visual Studio qui est un IDE (Integrated Development Environment, ou environnement de dveloppement intgr en franais) permettant de travailler notamment avec les langages C  VB.NET et C++/CLI. La liste de ces outils est disponible dans larticle
.NET Framework Tools des MSDN. La plupart de ces outils sont dcrits dans cet ouvrage et
sont lists dans lAnnexe C.
Le dcoupage du prsent ouvrage se base principalement sur ces trois parties :

Historique
Le pass
Ds 1998, lquipe en charge du dveloppement du produit MTS (Microsoft Transaction Server)
souhaitait dvelopper un nouveau produit pour pallier les problmes de la technologie COM.
Ces problmes concernaient principalement le trop fort couplage entre COM et le systme sousjacent ainsi que la dicult dutilisation de cette technologie, notamment au niveau du dploiement et de la maintenance.

Historique

CLS (Common Language Specification)


C, VB.NET, C++ gr, JScript, etc.

Outils

Bibliothque de classes de base


ADO.NET, Forms, XML, ASP.NET, etc.

Implmentation du CLI
(Common Language Infrastructure)
CLR (Common Language Runtime)

Figure 1 -1 : Vue gnrale de .NET


Paralllement, la communaut Java gagnait du terrain sur la scne du dveloppement logiciel.
De plus en plus dentreprises taient sduites par le concept de machine virtuelle permettant
dexcuter une application sur la plupart des systmes sans eorts supplmentaires. De plus, les
classes Java taient bien plus faciles utiliser que les MFC (Microsoft Fundation Classes) principalement grce labsence de pointeurs et cela augmentait significativement la productivit des
dveloppeurs. Ds juin 2000 Microsoft annona quil tait en train de dvelopper une nouvelle
technologie qui comprenait notamment un nouveau langage, le langage C  . Le 13 fvrier 2002
tait publie la premire version exploitable de .NET. Cet vnement est dcisif dans lhistoire
de lentreprise Microsoft et plus gnralement, dans la scne du dveloppement logiciel.
Parmi les ingnieurs en charge de ce projet, on peut citer Anders Hejlsberg, un des co-fondateurs
de la socit Borland. Cet ingnieur danois, concepteur du langage Turbo Pascal et du langage
Delphi, fut dbauch de Borland par Microsoft en 1996 pour travailler sur les WFC (Windows Fundation Classes), qui sont les classes utilises par la machine virtuelle Java de Microsoft. Rapidement, il est plac dans lquipe qui allait produire ce que lon nomme aujourdhui le CLR et le
langage C  .
En mars 2003, la version 1.1 de .NET est disponible. Elle contient notamment plus de classes
sur le thme des fournisseurs de base de donnes (Oracle et ODBC), de la scurit (cryptographie), de la technologie IP version6 et des technologies XML/XSLT. .NET 1.1 contient des outils
pour dvelopper des applications excutables sous Windows CE (Pocket PC, smart phone...). La
version 1.1 du framework .NET contient aussi le langage J#, destin aider les dveloppeurs Java
migrer ver .NET.

Le prsent
Fin 2005, Microsoft publie la version 2.0 de .NET qui fait lobjet de cet ouvrage. Le nombre de
types de base a plus que doubl couvrant maintenant de nombreux aspects initialement omis
par les versions 1.x. Des amliorations, des volutions et des optimisations sont apparues tant
au niveau de la machine virtuelle en charge dexcuter les applications .NET quau niveau des
langages. Les outils de dveloppement et notamment loutil Visual Studio sont beaucoup plus
complets et conviviaux. Dailleurs, le sentiment gnral est que la qualit et lintgration des
outils est maintenant un lment prpondrant dune plateforme de dveloppement. La liste
des nouveauts couvertes dans le prsent ouvrage fait lobjet de lAnnexe B.
Paralllement, on assiste au dbut de la concrtisation de deux mthodologies dj bien intgrs
dans les autres plateformes de dveloppement : leXtreme Programming (ou XP ne pas confondre
avec Windows XP) et le dveloppement partir de modles.

Chapitre 1 : Aborder la plateforme .NET

LXP consiste rationaliser les mthodologies utilises pour dvelopper un systme dinformation en coordonnant au mieux les activits de tous les acteurs impliqus. Lide est de pouvoir
faire face aux direntes volutions et imprvus qui surviennent inexorablement dans le cahier
des charges. Pour cela, on parle aussi parfois de mthode agile. Lagilit dcoule dun certain
nombre de contraintes. Il faut avant tout tre lcoute du client en lui fournissant souvent
et rgulirement une version testable. Il faut aussi faciliter la communication et le partage des
connaissances entre les membres dune quipe grce des outils polyvalents disponibles en plusieurs versions, chacune adapte une fonction prcise. Le facteur humain est central dans lXP.
Dautres principes sont mis en uvre tels que la conception dune batterie de tests automatiques
excute rgulirement afin de signaler au plus tt les rgressions dues aux volutions. Cette
batterie est en gnral excute aprs une compilation complte de lapplication partir des
derniers sources. Le principe de daily build veut quune telle compilation soit eectue quotidiennement, en gnral pendant la nuit. Toutes ces ides sont maintenant faciles mettre en
uvre grce aux nouvelles extensions Team System proposes par Visual Studio 2005.
Le dveloppement partir de modles consiste gnrer automatiquement le code source
dune application directement partir dun modle. Ce modle est exprim en un langage
de haut niveau, spcialement adapt aux besoins fonctionnels de lapplication et donc trs
expressif. On parle de DSL (Domain Specific Language). Lavantage de cette approche est de
permettre lquipe de travailler sur des sources proches des spcifications, rduisant ainsi
les cycles dvolution et la complexit du code. Visual Studio 2005 propose des extensions
spcialises dans la conception et dans lexploitation des DSLs. Ces extensions proposent aussi
la visualisation de votre code source C  ou VB.NET sous la forme de diagrammes comparables
ceux fournis par UML.

Le futur
Microsoft publiera Windows Vista durant lanne 2006. Cela marquera un tournant dcisif pour la
plateforme .NET puisque pour la premire fois, lenvironnement dexcution .NET sera fourni
de base avec un systme dexploitation. De nombreux nouveaux types .NET seront prsents
par Windows Vista pour permettre daccder aux fonctionnalits de ce systme dexploitation directement partir de votre code .NET. On peut citer notamment le nouveau framework de dveloppement dapplications graphiques WPF (Windows Presentation Framework) et le nouveau framework de dveloppement dapplication distribue WCF (Windows Communication Framework)
prsent brivement en page 1013.
Plus tard, en 2007 voire 2008, Microsoft publiera .NET 3.0 (nom de code Orcas). Cette version
sera surtout axe sur une intgration pousse des technologies introduites par Windows Vista
dans le framework et Visual Studio. Pour linstant, seule lquipe C  commence faire part de ses
travaux de recherches en ce qui concerne la version 3.0 du langage. Ils se focalisent sur un framework dextension du langage et travaillent notamment sur une extension spcialise pour la
rdaction de requtes sur un ensemble de donnes quelconques (objets, relationnelles ou XML).
Des expressions lambda, qui sont un peu dans le mme esprit que les mthodes anonymes de
C  2.0 mais en plus pratiques, viendront sintgrer dans ces requtes. Dautres nouveauts sont
prvues telles que les types anonymes, le typage implicite des variables et des tableaux ainsi
quune syntaxe ecace dinitialisation des objets et des tableaux.
Trois quatre annes plus tard, une version 4.0 de .NET sera publie (nom de code Hawaii) mais
aucune information nest disponible pour linstant.

.NET hors du monde Microsoft / Windows

.NET hors du monde Microsoft / Windows


Lorganisation ECMA et .NET
En Octobre 2000 Microsoft, Intel et Hewlett-Packard ont propos lECMA (European Computer
Manufacturers Association) de standardiser un sous ensemble de .NET. Cette partie comprend
principalement le langage C  , et le CLI. LECMA a accept la demande et a cr une commission
technique pour eectuer cette normalisation.
Microsoft nest donc pas totalement propritaire du langage C  et du CLI et le gant du logiciel a
tolr jusquici le fait que dautres organisations implmentent ces spcifications. Pour plus dinformation et pour obtenir les publications ocielles de ces spcifications, vous pouvez consulter
les URLs suivantes :
http://www.ecma-international.org/
http://www.msdn.microsoft.com/net/ecma/
http://www.ecma-international.org/publications/standards/Ecma-334.htm (spcification
C  2.0)
http://www.ecma-international.org/publications/standards/Ecma-335.htm (spcification
CLI)

Le consortium W3C
Le 9 mai 2000, Microsoft et 10 autres entreprises dont IBM, Hewlett Packard, Compaq et SAP ont
propos au consortium W3C de maintenir le standard SOAP. Le standard SOAP dfinit un format de message bas sur XML. Les services web peuvent communiquer au moyen de messages
au format SOAP. Lide de la standardisation de ce format est de rendre les services web compltement indpendants dune entreprise ou dune plateforme. Le format SOAP est dcrit page
1000. Pour plus dinformations veuillez consulter la page http://www.w3.org/TR/SOAP.
Depuis, un certain nombre de spcifications visant tendre les fonctionnalits des services web
ont t soumis au W3C. Certaines sont en cours dimplmentation et certaines sont encore en
cours de validation (voir page 989 et 1011).

Le projet Mono
Le 9 juillet 2001, lentreprise Ximian, fonde par Miguel de Icaza, a annonc quelle dveloppait
une implmentation open source de .NET. La raison est que ses ingnieurs estiment que .NET
reprsente la meilleure technologie de dveloppement logiciel du moment. Le nom de ce projet
est Mono.
la mi 2003, lentreprise Novell rachte Ximian rcuprant ainsi Mono. Le 30 juin 2004, la version 1.0 du projet est publie. Le projet Mono devrait tre rapidement disponible en version
.NET 2.0 et C  2.0.
Le projet Mono comprend entre autres, un compilateur C  (distribu sous licence GPL General Public Licence), limplmentation dune bonne partie des bibliothques .NET (distribues
sous licence MIT/X11) ainsi quune machine virtuelle qui implmente le CLI (distribue sous
licence LGPL Lesser GPL). Tout ceci est compilable sur Windows, sur Linux et sur plusieurs autres
systmes dexploitation type UNIX tels que Mac OS X. La home page du projet est http://www.
mono-project.com.

10

Chapitre 1 : Aborder la plateforme .NET

Malgr lombre que peut potentiellement faire le projet Mono la version commerciale de
.NET de Microsoft, ce dernier nest pas forcment contre cette initiative. John Montgomery, responsable Microsoft de .NET a dit : ...The fact that Ximian is doing this work is great. Its a validation
of the work weve done, and it validates our standards activities. Also, it has caused a lot of eyeballs in
the open source community to be directed to .Net, which we appreciate... . Le gant du logiciel nest
donc pas mcontent que le monde de lopen source ait une opportunit dutiliser .NET. Cela
reprsente autant de clients potentiels pour les produits dvelopps autour de .NET.

Le projet SSCLI (Rotor) de Microsoft


Le projet Shared Source CLI (aussi nomm SSCLI ou Rotor) de Microsoft consiste distribuer du
code source implmentant le CLI et certaines parties du framework .NET. Le projet SSCLI est
surtout distribu des fins acadmiques pour permettre aux tudiants et aux chercheurs de
sinitier et de travailler sur les internes dune machine virtuelle moderne (GC, JIT compilation
etc). Vous pouvez nanmoins vous en servir pour comprendre le fonctionnement interne de
.NET et pour dboguer vos applications. En revanche, vous navez pas le droit de vous en servir
des fins commerciales.
Concrtement, SSCLI cest plusieurs millions de lignes de code, un compilateur C  , un compilateur JScript et de nombreux outils. Une version 2.0 devrait tre disponible rapidement aprs la
sortie de .NET 2.0. Les parties centrales du framework sont supportes telles que XML ou .NET
Remoting. En revanche, dautres domaines tout aussi importants mais plutt orients fonctionnel tels que ADO.NET, ASP.NET et Windows Form ne sont pas implments par SSCLI.
Contrairement limplmentation Microsoft commerciale de .NET, SSCLI peut fonctionner sur
dautres systmes dexploitation que Windows. lheure actuelle vous pouvez vous servir de
SSCLI sur les systmes dexploitation FreeBSD, Mac OS X et Windows. Tout ceci est possible parce
quen interne SSCLI nappelle pas directement lAPI win32. Le projet utilise une API proche
de win32 nomme PAL (Platform Abstraction Layer). Or, cette API est supporte par ces trois
systmes dexploitation.
La page ocielle du projet est lURL : http://msdn.microsoft.com/net/sscli
Vous pouvez consulter en ligne les fichiers sources lURL : http://dotnet.di.unipi.it/
SharedSourceCli.aspx

Liens sur .NET


Nous vous proposons des liens vers les principaux sites consacrs au dveloppement sous .NET.
Notez quavec le temps, je compte mettre de plus en plus de ressources sur mon site http://
www.smacchia.com propos du dveloppement sous .NET.

Sites franais
Voici des sites qui en plus de contenir des articles parmi les plus intressants disponible sur le
web, sont entirement en franais :
http://www.dotnetguru.org
http://www.microsoft.com/france/msdn
http://forums.microsoft.com/msdn/

Liens sur .NET

11

http://fr.gotdotnet.com
http://www.sharptoolbox.com/
http://www.dotnet-fr.org
http://www.c2i.fr
http://www.csharpfr.com
http://www.dotnet-news.com/
http://www.labo-dotnet.com/
http://www.techheadbrothers.com
http://www.blabladotnet.com
http://www.programmationworld.com
http://www.essisharp.ht.st
Prcisons que le site dotnetguru (DNG) sort du rang grce ses nombreux articles de qualit et
grce aux entres pertinentes des blogs de ses auteurs.

Newsgroup en franais
Sur le serveur msnews.microsoft.com :
microsoft.public.fr.dotnet
microsoft.public.fr.dotnet.adonet
microsoft.public.fr.dotnet.aspnet
microsoft.public.fr.dotnet.csharp

Sites anglais
Voici dautres sites en anglais :
http://www.msdn.microsoft.com
http://www.gotdotnet.com
http://msdn.microsoft.com/msdnmag/
http://www.theserverside.net
http://www.dotnet247.com
http://www.15seconds.com
http://www.codeproject.com/
http://www.eggheadcafe.com/
http://www.devx.com/
http://channel9.msdn.com/
http://dotnet.sys-con.com/
http://dotnet.oreilly.com http://www.ondotnet.com/
http://www.dotmugs.ch/

12

Chapitre 1 : Aborder la plateforme .NET

http://www.asp.net/
http://www.ondotnet.com
http://dotnetjunkies.com/
http://www.codeguru.com
http://www.c-sharpcorner.com http://www.csharp-corner.com
http://www.devhood.com
http://www.developer.com
http://www.4guysfromrolla.com (ASP.NET)
La section download du site http://www.idesign.net

Newsgroup en anglais
Sur le serveur msnews.microsoft.com :
microsoft.public.dotnet.framework
microsoft.public.dotnet.framework.adonet
microsoft.public.dotnet.framework.aspnet
microsoft.public.dotnet.framework.clr
microsoft.public.dotnet.framework.performance
microsoft.public.dotnet.framework.remoting
microsoft.public.dotnet.framework.sdk
microsoft.public.dotnet.framework.webservices
microsoft.public.dotnet.general
microsoft.public.dotnet.languages.csharp

Blogs
Enfin, voici quelques blogs forte valeur ajoute sur le dveloppement logiciel orient sur les
technologies .NET :
BCL Team http://blogs.msdn.com/bclteam/
Bart De Smet (Divers) http://blogs.bartdesmet.net/bart/
Benjamin Mitchell (Web services, eXtreme Programming) http://benjaminm.net/
Brad Abrams (BCL, design) http://blogs.msdn.com/brada/default.aspx
Chris Brumme (CLR) http://blogs.msdn.com/cbrumme/
Chris Sells (Windows Form, divers) http://www.sellsbrothers.com/
Clemens Vaster (SOA, design, divers) http://staff.newtelligence.net/clemensv/
David M. Kean (FxCop, Windows Installer, divers) http://davidkean.net/
Dino Esposito (ASP.NET) http://weblogs.asp.net/despos/
Don Box (WCF, divers) http://www.pluralsight.com/blogs/dbox/default.aspx

Liens sur .NET

13

Dotnetguru blog agrg (Divers) http://www.dotnetguru.org/blogs/index.php


Eric Gunnerson (C  , divers) http://blogs.msdn.com/ericgu/
Frantz Bouma (Divers) http://weblogs.asp.net/fbouma
Fritz Onion (ASP.NET) http://pluralsight.com/blogs/fritz/default.aspx
Florin Lazar (Transactions) http://blogs.msdn.com/florinlazar/
Fredrik Normn (ASP.NET) http://fredrik.nsquared2.com/default.aspx
Jim Johnson (Transactions) http://pluralsight.com/blogs/jimjohn
Junfeng Zhang (GAC, versionning, CLR) http://blogs.msdn.com/junfeng/
Keith Brown (Scurit) http://pluralsight.com/blogs/keith/default.aspx
Krzysztof Cwalina (Design) http://blogs.msdn.com/kcwalina/
Martin Fowler (Architecture, pattern) http://martinfowler.com/bliki/
Matt Pietrek (Win32, Windows) http://blogs.msdn.com/matt_pietrek/
Michele Leroux Bustamente (Divers) http://www.dasblonde.com/
Miguel de Icaza (Mono) http://tirania.org/blog/
Mike Stall (Debug) http://blogs.msdn.com/jmstall/
Mike Taulty (Divers) http://mtaulty.com/blog
Pluarlsight blog agrg (Divers) http://pluralsight.com/blogs/default.aspx
Rico Mariani (Performances, GC) http://blogs.msdn.com/ricom/
Rockford Lhotka (Design, divers) http://www.lhotka.net/WeBlog/
Sahid Malik (ADO.NET, transactions) http://codebetter.com/blogs/sahil.malik/
Sam Gentile (Divers) http://samgentile.com/blog/
Scott Guthrie (ASP.NET) http://weblogs.asp.net/scottgu/
TheServerSide blog agrg (Divers) http://www.theserverside.net/blogs/index.tss
Valery Pryamikov (Scurit) http://www.harper.no/valery/
Wesner Moise (Divers, sujets pointus habituellement peu documents) http://wesnerm.
blogs.com/net_undocumented/

2
Assemblages, modules, langage IL

Les assemblages sont les quivalents .NET des fichiers .exe et .dll de Windows. Ce sont donc les
composants de la plateforme .NET.

Assemblages, modules et fichiers de ressource


Assemblages et modules
Un assemblage est une unit logique qui peut tre dfinie sur plusieurs fichiers appels modules.
Tous les fichiers constituant un assemblage doivent se trouver dans le mme rpertoire.
On a tendance a considrer quun assemblage est une unit physique (i.e un assemblage = un
fichier) car la plupart des assemblages nont quun seul module. Cela est en grande partie
du au fait que lenvironnement Visual Studio na pas la possibilit de gnrer des assemblage
multi modules. Comme nous allons lexposer, pour obtenir des assemblages multi modules, il
faut faire leort de travailler avec des outils en ligne de commande tels que le compilateur C 
csc.exe dcrit page 317, ou loutil al.exe dcrit page 37).
Parmi les modules dun assemblage, il y a un module principal qui joue un rle particulier car :

Tout assemblage en comporte un et un seul.

En consquence, un assemblage mono module se confond avec son module principal.

Dans le cas dun assemblage plusieurs modules, cest toujours ce module qui est charg
en premier par le CLR. Le module principal dun assemblage multi modules rfrence les
autres modules. Ainsi, lutilisateur dun assemblage na besoin de connatre que le module
principal.

Le module principal est un fichier dextension .exe ou .dll selon que son assemblage est un
excutable ou une bibliothque de types. Un module qui nest pas le module principal est un
fichier dextension .netmodule.

16

Chapitre 2 : Assemblages, modules, langage IL

Fichiers de ressource
En plus du code .NET compil , un module peut physiquement contenir dautres types de ressources telles que des images bitmap ou des documents XML. De telles ressources peuvent aussi
tre contenues dans leur fichier dorigine (par exemple dextension .jpg ou .xml) et rfrences
par le module principal. Dans ce cas on dit que ces fichiers rfrencs sont des fichiers de ressources
de lassemblage. Dans ce chapitre, nous verrons que lutilisation de ressources constitue une
technique ecace pour globaliser une application.

Assemblages, modules, types et ressources


La figure suivante utilise la notation UML pour rsumer les relations entre assemblages,
modules, types et ressources. On y voit quun mme module peut tre contenu dans plusieurs assemblages et quun mme type peut tre contenu dans plusieurs modules. Nous vous
conseillons vivement dviter ces deux techniques de rutilisation de code et de prfrer avoir
recours des assemblages bibliothques de types.

1..*

1..*

Fichier
module

1..*

1..*

Assemblage

Type .NET

Ressource
Fichier
ressource

Figure 2 -1 : Diagramme UML : Assemblages, Modules, Ressources

Pourquoi morceler un assemblage en plusieurs modules/fichiers de


ressources ?
On peut se demander quel est lintrt de morceler un assemblage en plusieurs modules. En
eet, cette possibilit est rarement exploite. Nous avons identifi les trois cas de figure suivants
o cette fonctionnalit apporte un rel avantage :

Ladage profr il y a trois dcennies qui disait quun logiciel passe 80% du temps dans 20%
du code est toujours dactualit. Si lon isole la grosse partie du code peu utilise dans des
modules spars, ces modules ne seront peut-tre jamais utiliss tous ensembles. Donc la
plupart du temps on conomisera les ressources ncessaires au chargement total de lassemblage dans le processus. Ces ressources sont la mmoire vive, les accs au disque dur, mais
aussi la bande passante des rseaux si lassemblage est stock sur une machine distante.

Un fichier de ressource ne sera rellement charg que lorsque le programme en aura rellement besoin. Si une application tourne en franais, on fait donc lconomie du chargement
des fichiers de ressources en anglais.

Si un mme assemblage est dvelopp par plusieurs dveloppeurs, il se peut que certains
prfrent le langage VB.NET tandis que dautres prfrent C  . Dans ce cas chaque module
peut tre dvelopp dans un langage dirent.

Anatomie des modules

17

Loutil ILMerge
Sachez qu linverse vous pouvez runir plusieurs assemblages au sein dun mme fichier dextension .exe ou .dll. Pour cela, il vous faut avoir recours loutil ILMerge distribu gratuitement
par Microsoft et tlchargeable partir du web. Les fonctionnalits de cet outil sont aussi exploitables programmatiquement grce une API documente. Loutil sait aussi tenir compte des
assemblages signs.

Anatomie des modules


Notion de fichier Portable Executable (PE)
Un fichier PE (PE pour Portable Executable) est un fichier excutable par Windows. Un fichier
PE est en gnral dextension .exe ou .dll. Les premiers octets dun fichier PE constituent
un en-tte format interprt par Windows au moment o lexcutable est dmarr. Ces octets
contiennent des informations telles que le plus petit numro de version de Windows sur lequel
lexcutable peut tre utilis ou le fait que lexcutable est une application fentre ou une application console.
Le formatage des fichiers PE est optimis pour ne pas dgrader les performances. quelques
octets prs, limage en mmoire dun fichier PE qui est exploite par Windows lors de lexcution est identique au fichier PE. Cest pour cette raison que les fichiers PE sont parfois nomms
fichiers image.
Les modules sont des fichiers PE car la plateforme .NET utilise les services de Windows en ce qui
concerne le dmarrage des applications. Nous expliquons en page comment le CLR est charg
par Windows lors du dmarrage dune application .NET.
Vous entendrez aussi parler de format PE/COFF (pour Common Object File Format). Le format
COFF est utilis par le compilateur C++ lorsquil lie les fichiers objets. Lextension COFF du format PE/COFF est ignore par .NET.

Structure physique dun module


Chaque module comprend une section dentte CLR qui contient la version du CLR pour laquelle lassemblage a t compile. Cette section contient ventuellement une rfrence vers le
point dentre gr si le module en contient un.
Le module principal dun assemblage contient une section appele manifeste qui contient entre
autres les rfrences vers les autres modules et vers les fichiers de ressources. Les donnes du
manifeste sont parfois nommes mtadonnes de lassemblage.
Chaque module contient une section qui dcrit compltement son contenu (les types, les
membres des types, les dpendances entre types, les ressources...). Les informations dauto
description contenues dans cette section sont nommes mtadonnes (metadata en anglais).
Enfin, le module contient le code .NET compil en IL ainsi que les ressources. Le schma suivant
reprsente la structure physique dun module.

Structure du manifeste
Le manifeste contient les informations dauto description de lassemblage. Il y a quatre types
dinformations dauto description et le manifeste contient une table pour chacun de ces types :

18

Chapitre 2 : Assemblages, modules, langage IL


Module principal
Entte
PE

Entte
CLR

Manifeste

Mtadonnes

Code
compil
en IL

Ressources

Autre module
Entte
PE

Entte
CLR

Mtadonnes

Code
compil
en IL

Ressources

Figure 2 -2 : Structure physique dun module

La table AssemblyDef : Cette table a une seule entre qui contient :

Le nom de lassemblage (sans extension, sans chemin).


La version de lassemblage.
La culture de lassemblage.
Des drapeaux dcrivant certaines caractristiques de lassemblage.
Une rfrence vers un algorithme de hachage.
La cl publique de lditeur (qui peut tre nulle ventuellement).
Toutes ces notions sont prsentes dans les pages qui suivent.

La table FileDef : Cette table contient une entre pour chaque module et fichier de ressource
de lassemblage mis part le module principal (donc si un assemblage na quun module,
cette table est vide). Chaque entre inclut le nom du fichier (avec lextension), des drapeaux
dcrivant certaines caractristiques du fichier et une valeur de hachage du fichier.
La table ManifestResourceDef : Cette table contient une entre pour chaque type et chaque
ressource de lassemblage. Chaque entre contient un index vers la table FileDef pour indiquer dans quel fichier est le type ou la ressource. Dans le cas dun type, lentre contient aussi
un oset indiquant o se trouve physiquement le type dans le fichier. Une consquence
est que chaque compilation dun module implique la reconstruction du manifeste, donc la
recompilation du module principal.
La table ExportedTypeDef : Cette table contient une entre pour chaque type visible hors de
lassemblage. Chaque entre contient le nom du type, un index vers la table FileDef et un
oset indiquant o se trouve physiquement le type dans le fichier. Par souci dconomie
de place, les types visibles hors de lassemblage dfinis dans le module principal ne sont pas
rpts dans cette table. En eet, nous allons voir que ces types sont dj dcrits dans la
section mtadonnes.

Structure de la section mtadonnes de type


Les mtadonnes de type sont stockes dans des tables. Il existe trois sortes de tables de mtadonnes de type : les tables de dfinitions, les tables de rfrences et les tables de pointeurs.

Les tables de dfinition


Chaque table de dfinition renferme des informations propos dune catgorie dlment de
module (i.e une table rfrenant les mthodes de toutes les classes, une table rfrenant toutes
les classes etc). Nous ne prsentons pas toutes ces tables mais voici les plus importantes :

Anatomie des modules

19

La table ModuleDef : Cette table contient une seule entre qui dfinit le prsent module.
Cette entre contient notamment le nom du fichier avec son extension mais sans son chemin.

La table TypeDef : Cette table contient une entre pour chaque type dfini dans le module.
Chaque entre contient le nom du type, le type de base, les drapeaux du type (public,
internal, sealed etc), et des index rfrenant les entres des membres du type dans les
tables MethodDef, FieldDef, PropertyDef, EventDef, ... (une entre pour chaque membre).

La table MethodDef : Cette table contient une entre pour chaque mthode dfinie dans
le module. Chaque entre contient le nom de la mthode, les drapeaux de la mthode
(public, abstract, sealed etc), loset permettant de situer physiquement dans le module
le dbut du code IL de la mthode et une rfrence vers la signature de la mthode, qui est
contenue dans un format binaire dans un tas appel #blob dcrit plus loin.

Il y a aussi une table pour les champs (FieldDef), une pour les proprits (PropertyDef), une
pour les vnements (EventDef) etc. La dfinition de ces tables est standard et chaque table a un
numro cod sur un octet. Par exemple toutes les tables MethodDef de tous les modules .NET
ont le numro de table 6.

Les tables de rfrences


Les tables de rfrences contiennent les informations sur les lments rfrencs par le module.
Les lments rfrencs peuvent tre dfinis dans dautres modules du mme assemblage ou
dfinis dans dautres assemblages. Voici quelques tables de rfrences couramment utilises :

La table AssemblyRef : Cette table contient une entre pour chaque assemblage rfrenc
dans le module (i.e chaque assemblage qui contient au moins un lment rfrenc dans
le module). Chaque entre contient les quatre composantes du nom fort savoir : le nom
de lassemblage (sans chemin ni extension), le numro de version, la culture et le jeton de
cl publique (ventuellement la valeur nulle si il ny en a pas).

La table ModuleRef : Cette table contient une entre pour chaque module du mme assemblage rfrenc dans le module (i.e chaque module qui contient au moins un lment rfrenc dans le module). Chaque entre contient le nom du module avec son extension.

La table TypeRef : Cette table contient une entre pour chaque type rfrenc dans le module. Chaque entre contient le nom du type et une rfrence vers l o il est dfini. Si le
type est dfini dans ce module ou dans un autre module du mme assemblage, la rfrence
indique une entre de la table ModuleRef. Si le type est dfini dans un autre assemblage, la
rfrence indique une entre de la table AssemblyRef. Si le type est encapsul dans un autre
type, la rfrence indique une entre de la table TypeRef.

La table MemberRef : Cette table contient une entre pour chaque membre rfrenc dans
le module. Un membre peut tre par exemple une mthode, un champ ou une proprit.
Chaque entre est constitue du nom du membre, de sa signature et dune rfrence vers
la table TypeRef.

La dfinition de ces tables est aussi standard et chaque table a un numro cod sur un octet. Par
exemple toutes les tables MemberRef de tous les modules .NET ont le numro de table 10.

20

Chapitre 2 : Assemblages, modules, langage IL

Les tables de pointeurs


Les tables de pointeurs permettent la compilation de rfrencer des lments du code encore
inconnus (un peu comme les dclaration en avant dans C++). En changeant lordre de dfinition
des lments de votre code, vous pouvez rduire le contenu de ces tables. On peut citer les tables
MethodPtr, ParamPtr ou FieldPtr.

Les tas
En plus de ces tables la section des mtadonnes de type contient quatre tas (heap) nomms
#Strings, #Blob, #US et #GUID.

Le tas #Strings contient des chanes de caractres comme le nom des mthodes. Ainsi les
lments des tables MethodDef ou MemberRef ne contiennent pas de chanes de caractres
mais rfrencent les lments du tas #string.

Le tas #Blob contient des informations binaires, comme la signature des mthodes stocke sous une forme binaire. Ainsi les lments des tables MethodDef ou MemberRef ne
contiennent pas les signatures des mthodes mais rfrencent des lments du tas #blob.

Le tas #US (pour User String) contient les chanes de caractres dfinies directement au sein
du code.

Le tas #GUID contient les GUID dfinis et utiliss dans le programme. Un GUID est une
constante de 16 octets, utilise pour nommer une ressource. La particularit des GUID est
que vous pouvez les gnrer partir doutils tels que guidgen.exe, de faon tre quasicertain davoir fabriqu un GUID unique au monde. Les GUID sont particulirement utiliss dans la technologie COM.

Les mtadonnes de type sont trs importantes dans larchitecture .NET. Elles sont rfrences
par les jetons de mtadonnes du code IL qui font lobjet de la section page 46. Cette section
prsente un exemple qui souligne limportance des tables et des tas de la section mtadonnes
de type. Les mtadonnes de type sont aussi utilises par la notion dattributs .NET (dcrite page
248) et le mcanisme de rflexion (dcrit page 233).

Le tas #
Certains documents se rfrent parfois un tas nomm # . Ce tas spcial contient en fait toutes
les tables de mtadonnes, y compris celles du manifeste sil sagit dun module principal.

Analyse dun assemblage avec ildasm.exe et Reflector


Construction de lassemblage analyser
Nous allons crer un assemblage avec :

Un module principal Foo1.exe.

Un module Foo2.netmodule.

Un fichier de ressource Image.jpg.

Analyse dun assemblage avec ildasm.exe et Reflector

21

Placez dans le mme rpertoire les deux fichiers sources C  suivant (Foo1.cs et Foo2.cs) ainsi
quun fichier au format jpeg nomm Image.jpg.
Foo2.cs

Exemple 2-1 :
namespace Foo {
public class UneClasse {
public override string ToString() {
return "Bonjour de Foo2" ;
}
}
}

Foo1.cs

Exemple 2-2 :
using System ;
using System.Reflection ;
[assembly: AssemblyCompany("Lentreprise")]
namespace Foo {
class Program {
public static void Main(string[] argv) {
Console.WriteLine("Bonjour de Foo1") ;
UneClasse a = new UneClasse() ;
Console.WriteLine( a ) ;
}
}
}

Comme nous souhaitons construire un assemblage avec plus dun module, nous navons pas
dautres choix que dutiliser le compilateur csc.exe en ligne de commande (car lenvironnement Visual Studio ne gre pas les assemblages multi modules). Le compilateur csc.exe est dcrit en dtails page 317.
Crer les fichiers Foo2.netmodule puis Foo1.exe en tapant dans lordre les lignes de commande
suivantes (le compilateur csc.exe se trouve dans le rpertoire <rpertoire-d-installation-deWINDOWS>\Microsoft.NET\Framework\v2.*) :
> csc.exe /target:module Foo2.cs
> csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg

Foo1.cs

Lancez lexcutable Foo1.exe, le programme ache sur la console :


Bonjour de Foo1
Bonjour de Foo2

Analyse dun module avec loutil ildasm.exe


On peut analyser le contenu dun assemblage ou dun module avec loutil ildasm.exe fourni
avec lenvironnement de dveloppement .NET. ildasm signifie dsassembleur de code IL. Cet outil se trouve dans le rpertoire <repertoie-d-installation-de-VisualStudio>\SDK\v2.0\Bin.
Chargez le fichier Foo1.exe avec ildasm.exe :

22

Chapitre 2 : Assemblages, modules, langage IL

Figure 2 -3 : Vue gnrale de ildasm

Vue du manifeste
En double cliquant sur le manifeste le texte suivant apparat dans une nouvelle fentre (certains
commentaires ont t enlevs pour plus de clart) :
.module extern Foo2.netmodule
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly Foo1
{
.custom instance void
[mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) =
( 01 00 0E 4C E2 80 99 65 6E 74 72 65 70 72 69 73 65 00 00 ) //
...L...entreprise..
.custom instance void [mscorlib]System.Runtime.CompilerServices.
CompilationRelaxationsAttribute::.ctor(int32) =
( 01 00 08 00 00 00 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.file Foo2.netmodule
.hash = (80 CC 15 14 E2 AB E0 AF D6 BD 55 B9 1B 02 61 10
.file nometadata Image.JPG
.hash = (0D 84 86 DE 03 E0 05 68 9D 38 F4 B0 B6 19 66 BB
.class extern public Foo.UneClasse
{
.file Foo2.netmodule
.class 0x02000002
}
.mresource public Image.jpg
{
.file Image.JPG at 0x00000000
}
.module Foo1.exe

B4 CF AA 94 )
3D 73 76 06 )

Analyse dun assemblage avec ildasm.exe et Reflector

23

// MVID: {3C680D21-A6C8-4151-A2A6-9B20B8FDDF27}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
// WINDOWS_CUI
.corflags 0x00000001
// ILONLY
// Image base: 0x04110000
On voit clairement que les fichiers Foo2.netmodule et Image.jpg sont rfrencs. On remarque
aussi quun autre assemblage est rfrenc, cest lassemblage mscorlib qui contient entre
autre la classe object. Tous les assemblages .NET rfrencent lassemblage mscorlib car celuici contient les types de base. ce titre, lassemblage mscorlib joue un rle prpondrant et
particulier dans la plateforme .NET. Plus dinformations son sujet sont disponibles en page
94.

Analyse de la classe Foo.Program


En double cliquant sur la classe Foo.Program, on obtient la description de cette classe dans une
nouvelle fentre :
.class private auto ansi beforefieldinit Foo.Program
extends [mscorlib]System.Object
{
} // end of class Foo.Program
On voit clairement les drapeaux associs la classe Program (private...) et que cette classe drive
de la classe System.Object.

Analyse du code de la mthode Main()


En double cliquant par exemple la mthode Main(), on obtient le code en langage IL de cette
mthode dans une nouvelle fentre. Nous prsenterons brivement le langage IL la fin de ce
chapitre :
.method public hidebysig static void Main(string[] argv) cil managed{
.entrypoint
// Code size
31 (0x1f)
.maxstack 1
.locals init (class [.module Foo2.netmodule]Foo.UneClasse V_0)
IL_0000: ldstr
"Bonjour de Foo1"
IL_0005: call
void [mscorlib]System.Console::WriteLine(string)
IL_000a: nop
IL_000b: newobj ...
... instance void [.module Foo2.netmodule]Foo.UneClasse::.ctor()
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call
void [mscorlib]System.Console::WriteLine(object)
IL_0017: nop
IL_0018: call
int32 [mscorlib]System.Console::Read()
IL_001d: pop
IL_001e: ret
} // end of method Program::Main

24

Chapitre 2 : Assemblages, modules, langage IL

Options de ildasm.exe
Loutil ildasm.exe prsente des options trs intressantes comme la visualisation du code IL
binaire (option Show Byte) ou la prsentation du code source correspondant en C  (option Show
Source Lines).
Avec ildasm.exe 2.0 certaines options sont disponibles par dfaut alors quen version 1.x il fallait
utiliser le commutateur /adv en ligne de commande afin de les obtenir. Ces options sont la
possibilit dobtenir des statistiques quant la taille en octets de chaque section dun assemblage
et quant lachage des informations sur les mtadonnes.
Par lintermdiaire de ildasm.exe vous pouvez donc faire du reverse engineering sur un assemblage. Vous pouvez rcuprer des informations comme le code IL des mthodes ou les noms
des lments dun assemblage (classes, mthodes...). En revanche, les commentaires du code
source dun assemblage ne sont pas conservs dans lassemblage. Ils ne peuvent donc pas tre
retrouvs avec ildasm.exe.

Loutil Reflector
Depuis plusieurs annes, lutilitaire Reflector dvelopp par Lutz Roeder a dtrn ildasm.exe
et est devenu loutil incontournable pour analyser des assemblages .NET. Cet outil est tlchargeable gratuitement http://www.aisto.com/roeder/dotnet/. Voici une copie dcran du traitement de notre exemple par Reflector :

Figure 2 -4 : Vue gnrale de Reflector


En plus dun interface conviviale, Reflector prsente un panel de fonctionnalits trs intressantes et absentes de ildasm.exe telles que :

Attributs dassemblage et versionning

25

La dcompilation en C  ou en VB.NET du code en IL dun assemblage ; par exemple la


dcompilation en VB.NET de la mthode Program.Main() est :
Public Shared Sub Main(ByVal argv As String())
Console.WriteLine("Bonjour de Foo1")
Dim classe1 As New UneClasse
Console.WriteLine(classe1)
Console.Read
End Sub

La possibilit de construire localement pour un lment du code le graphe des appelants


et des appels.

De nombreux addins tel que statement graph de Jonathan de Halleux, qui permet de dcompiler une mthode sous la forme dun organigramme, Reflector Di de Sean Hederman qui
permet de mettre en vidence les dirences entre deux versions dun assemblage ou file
disassembler de Denis Bauer qui permet de rcuprer le code source dune application
partir de ses assemblages. Une consquence est que file disassembler permet de migrer une
application, par exemple de VB.NET vers C  .

Attributs dassemblage et versionning


Attributs standard associables aux assemblages
Pour aborder cette section il faut tre familier avec la notion dattribut .NET. Si vous ne savez
pas ce quest un attribut .NET nous vous conseillons de lire la section page 248.
Vous avez la possibilit dutiliser certains attributs du framework pour ajouter des informations
telle que son numro de version lassemblage courant. Ces attributs doivent tre dclars
dans un fichier source de lassemblage aprs les dclaratives using et avant les lments du
programme. Si vous ditez un projet sous lenvironnement de dveloppement Visual Studio, les
attributs relatifs lassemblage se trouvent par dfaut dans le fichier AssemblyInfo.cs.
Pour illustrer ceci, le code de l utilise lattribut AssemblyCompanyAttribute qui est un attribut
dassemblage permettant de prciser le nom de lditeur de logiciel qui a fabriqu lassemblage.
Si vous regardez les proprits du module principal Foo1.exe, linformation Lentreprise
est visible, comme le montre la figure 2-5 ci-dessous :
Voici la liste des attributs standard relatifs aux assemblages les plus utiliss :

Les attributs dinformation :

AssemblyCompany : associe lassemblage le nom de lditeur de logiciel qui la dvelopp.

AssemblyProduct : associe lassemblage le nom du produit (de la solution) auquel il


appartient.

AssemblyTitle : associe un titre lassemblage.

AssemblyDescription : associe une description lassemblage.

AssemblyFileVersion : associe un numro de version du fichier produit par la compilation.

AssemblyInformationalVersion : associe le numro de version du produit.

26

Chapitre 2 : Assemblages, modules, langage IL

Figure 2 -5 : Proprits dun fichier dun assemblage

Les attributs utiliss pour la constitution du nom fort (voir un peu plus loin pour la dfinition dun assemblage nom fort) :

AssemblyVersion : spcifie le numro de version de lassemblage utilis pour constituer


son nom fort.

AssemblyCulture : spcifie la culture de lassemblage.

AssemblyKeyFile : spcifie le fichier dextension .snk (strong name key) gnr par lexcutable sn.exe.

AssemblyFlags : spcifie si lassemblage peut tre excut cte cte ou non. La notion
dutilisation cte cte dun assemblage est expose page 65. Si lexcution cte cte
est autorise, cet attribut spcifie si lutilisation cte cte peut tre faite dans un mme
domaine dapplication, dans un mme processus ou seulement sur la mme machine.

Les attributs relatifs lutilisation de lassemblage au sein dune application COM+. On peut
citer les attributs ApplicationID, Application-Name, ApplicationActivation.

Dans larticle Setting Assembly Attributes des MSDN vous pouvez avoir plus de dtails sur les
attributs standard relatifs aux assemblages.

Numro de version dun assemblage


Un numro de version est compos de quatre numros :

Le numro majeur.

Le numro mineur.

Le numro de compilation.

Attributs dassemblage et versionning

27

Le numro de rvision.

Vous pouvez fixer le numro de version de lassemblage avec lattribut AssemblyVersion. Il est
possible dutiliser dans cet attribut un astrisque qui laisse le choix au compilateur de fixer les
numros de compilation et de rvision, par exemple 2.3.* . Dans ce cas le numro de compilation sera le nombre de jours couls depuis le 1er janvier 2000, et le numro de rvision sera
le nombre de secondes (divis par deux car 24*60*60=86400 et 86400 > 65536) coules dans
la journe. Ce processus de datation du numro de version dun assemblage est trs utile pour
obtenir des numros de version croissants mais toujours dirents.
Il existe en fait trois types de numros de version pour un mme assemblage ce qui entrane
une certaine confusion. Seul le numro de version de lassemblage est utilis par le CLR pour
permettre la possibilit de stockage dun assemblage cte cte. Les deux autres numros de
version, le numro de version du produit (fix par lattribut AssemblyInformationalVersion)
et le numro de version du fichier (fix par lattribut AssemblyFileVersion) sont purement
informatifs.
Lorsque vous estimez que plusieurs assemblages doivent constamment avoir le mme numro
de version, vous pouvez faire en sorte que leurs projets rfrencent le mme fichier qui contient
ce numro. Nanmoins, sachez que si vous avez rencontrez ce besoin, il est peut tre judicieux
de rassembler le code de vos assemblages dans un seul assemblage.
La dfinition des rgles dincrmentation des composantes des versions des assemblages est un
sujet sensible qui doit tre mrement rflchit. Il ny a pas de bonnes solutions dans labsolu
car ces rgles sont fonctions du modle commercial de chaque lentreprise. Elles varient selon
quun assemblage est utilis par un ou plusieurs produits, externes ou internes lentreprise,
selon que lentreprise vend des bibliothques de classes ou des produits excutables etc. Voici
quelques recommandations :

Le numro majeur : incrmenter lorsqu un ensemble significatif de fonctionnalit ont


t ajout.

Le numro mineur : incrmenter lorsquune fonctionnalit a lgrement volu ou lorsquun membre visible de lextrieur (par exemple une mthode protge dune classe publique) a chang ou lorsquun bug majeur a t corrig.

Le numro de compilation : incrmenter chaque recompilation du produit destine


tre utilise lextrieur de lentreprise (en gnral pour correction de bug mineur).

Le numro de rvision : incrmenter chaque compilation.

Notion dassemblages amis


Bien souvent, lorsque vous dveloppez un framework contenant plusieurs assemblages vous rencontrez le problme suivant : vous souhaitez que les types dfinis dans un assemblage du framework, soit accessibles partir du code des autres assemblages du framework sans toutefois tre
accessibles partir du code des assemblages qui utilisent le framework. Aucun des niveaux de visibilit des types du CLR public et internal sont mme de rsoudre ce problme. Dans cette situation, il faut avoir recours lattribut dassemblage System.Runtime.CompilerServices.InternalsVisibleToAttribute. Cet attribut permet de spcifier des assemblages qui ont accs aux types
non publics de lassemblage sur lequel il sapplique. On dit que se sont des assemblages amis de
lassemblage qui contient les types non publics.

28

Chapitre 2 : Assemblages, modules, langage IL

Lutilisation de cet attribut permet de prciser zro, une ou plusieurs composantes du nom fort
dun assemblage amis selon les direntes versions dun assemblage avec lesquelles on souhaite
tre amis :
using System.Runtime.CompilerServices ;
...
[assembly:InternalsVisibleTo("AsmAmi1")]
[assembly:InternalsVisibleTo("AsmAmi2,PublicKeyToken=0123456789abcdef")]
[assembly:InternalsVisibleTo("AsmAmi3,Version=1.2.3.4")]
...

Assemblage nom fort (strong naming)


Introduction
Depuis quelques annes, les systmes dexploitation Windows prsentent une technologie nomme Authenticode permettant dauthentifier un fichier excutable. Cest--dire que lorsque vous
excutez un programme sur votre machine, vous pouvez tre certains que ce programme est
bien celui qui a t dvelopp par lentreprise dans laquelle vous avez confiance. Cette technologie fait lobjet dune section en page 230.
La technologie du nom fort (strong name en anglais) que nous allons dcrire dans la prsente
section peut potentiellement servir authentifier les assemblages .NET dans le cas de grosses
organisations telles que Microsoft ou lECMA. Cependant, elle est surtout destine nommer les
assemblages dune manire unique. Cette possibilit est indite puisque jusqu prsent, on utilisait exclusivement les noms des fichiers pour identifier les units de dploiement. Avec .NET,
lorsquun assemblage volue vers de nouvelles versions, il y a plusieurs fichiers avec le mme
nom mais pas la mme version. Il existe un rpertoire spcial prvu pour stocker plusieurs
versions dun mme assemblage. Ce rpertoire, appel le rpertoire GAC, est prsent page 64.
Concrtement un assemblage acquiert un nom fort lorsquil est sign numriquement. Lorsquun assemblage a un nom fort, ce nom peut tre format en une chane de caractres contenant ces quatre informations :

Le nom du fichier ;

le jeton de cl publique de la signature numrique (tous ces termes sont expliqus cidessous) ;

la version de lassemblage, celle spcifie avec lattribut AssemblyVersion, prsent dans la


section prcdente ;

la culture de lassemblage, que nous dtaillons dans la prochaine section.

Le nom est fort dans le sens o il permet didentifier lassemblage dune manire unique. Cette
unicit est garantie par la seule composante jeton de cl publique .
Un nom fort est aussi sens rendre un assemblage infalsifiable. Richard Grimes dcrit lURL
http://www.grimes.demon.co.uk/workshops/fusionWSCrackThree.htm une mthode permettant de craquer un nom fort dun assemblage (i.e cette mthode permet de falsifier un assemblage sign). Cette mthode se base sur un bug du CLR version 1.1 corrig en version 2.0. Il ny
a plus notre connaissance de telles failles de scurit en .NET 2.0.

Assemblage nom fort (strong naming)

29

Avant de nous pencher sur la cration dun assemblage nom fort, nous vous conseillons de
vous familiariser avec les notions de cls publiques/cls prives et de signature numrique prsentes en page 223. En apposant une signature numrique leurs assemblages, un auteur ou
une entreprise peuvent lauthentifier. Microsoft et lorganisation ECMA ont chacun un couple
de cl publique/cl prive quils utilisent pour authentifier leurs assemblages.

Loutil sn.exe
La premire tape pour un dveloppeur ou une entreprise qui dsire crer des assemblages
noms forts est de crer un couple de cl prive/cl publique. Cette opration utilise des algorithmes mathmatiques complexes. Heureusement le Framework .NET met notre disposition
loutil sn.exe (sn pour strong name) qui eectue cette tche. Cet outil peut fabriquer un fichier
dextension .snk (pour strong name key en anglais) qui contient un couple de cl prive/cl publique en utilisant loption -k. Par exemple :
C:\Test>sn.exe -k Cles.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Key pair written to Cles.snk
C:\Test>
sn.exe permet aussi de visualiser la cl publique et la cl prive contenues dans un fichier dextension .snk avec loption -tp :
C:\Test>sn.exe -tp Cles.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Public key is
070200000024000052534132000400000100010051a7dadde83cf10e8b7c6cd99e4d062b
1aca430e11db76365ab29d6c31fc93a7bea6def9d7b2e8a7c568b0d5ada5e8e131cb98ea
3e9a876236b33b362e433fdd62bb4c5cc5ea23f1dfa76d35b5412d812f66d03e079009ea
76462392663bc08ab5f937524e794948532c679db5eda50210f8a8b2b8b186fcb342859c
48ea76d609d108b1957d3888f75b270cf85029ede8437c36b4ae59342c5fa7aacdb453c7
465cc7027405930627a5b153e5f48cdd0375840bf6feaa3548aa421ab5138fb095efa5d5
81ae61bd9248ac97293ee69b139ef9ae79d907c5cf2c194adf7c2723e269b5eef55157c4
095fccf436d7db1893aed8c63d57e9d5eba5c1dd88f8bda81b6c74b77899071823c85c86
2254865337d2b70d545d17de9b8471527bbd54d4e1bd6cb6b53fed9135c9c7b1b1af2b27
ab0414b423b61334c9c1adb0700145ba1354848b081e09e8a860d24fb9ea6c48ac2657f1
9ff1fab37a177744c377d9d7d09f34498901f4439bc6754b4ac0efcc4d84d4a6c22a05c2
eecaec3f7fabf8b45555d4788eaeda815cf743001477a8c31c24c04b4016f4ef3401617e
22441b95ca78265a0a6133150ca03c2886d4e3893f9d1dc6a3e2d8770a63b8fbd0db52d8
176bbda6e1f4074d9dfda916cf316294f0499eade4aa47d1b780627ab6fb7beb5aa48412
9062d3152e6b6585c865d319018727c1a34866484018f5f1c0ab0bf2b35e63a8a3bbf7a0
b6aeeb110f4b162426a977dc2034adf08ec41cc5d20f2d6beac92a1619aff0e25030e30a
02570eb9ad74eba0f2aba90b18789ae99f8da72

30

Chapitre 2 : Assemblages, modules, langage IL

Public key token is f2bf46103b24f5f0


En fait cet outil ache la cl publique (crite en gras) puis la cl prive. La phrase public key
is : est donc errone. De mme, ce qui est prsent comme tant le jeton de cl publique est
une valeur de hachage du couple cl publique/cl prive. Cependant si Microsoft a fait le choix de
ces termes, cest parce quil suppose que la plupart du temps le mcanisme de signature retarde
(expos un peu plus loin) est utilis. Dans ce cas ces termes prennent toute leur signification.
Lors de la fabrication dune couple cl publique/cl prive par loutil sn.exe, la cl publique fait
128 octets (gris) + 32 octets den-tte (non gris) et la cl prive fait 436 octets (non gris).

Les jetons de cls publiques


La cl publique tant volumineuse et dicile manipuler, larchitecture .NET associe un jeton
de cl publique (public key token en anglais) chaque cl publique. Un jeton de cl publique est
un numro cod sur huit octets. Ce numro est calcul partir de la cl publique en utilisant un
algorithme de hachage. Il identifie dune manire quasi unique la cl publique, tout en tant
beaucoup plus maniable de par sa taille rduite. Les jetons de cls publiques ont t crs pour :

Rduire la taille des noms forts (qui incluent ces jetons la place de la cl publique elle
mme).

Rduire la taille des assemblages qui rfrencent de nombreux assemblages noms forts.

Un assemblage sign avec la cl prive dont le jeton de la cl publique correspondante est


b03f5f7f11d50a3a, est garanti avoir t produit par Microsoft. Un assemblage sign avec la cl
prive dont le jeton de la cl publique correspondante est b77a5c561934e089, est garanti avoir
t produit par lorganisme ECMA. Ces armations ne seraient plus valables, si un jour une de
ces cls prives tait pirate.

Signer un assemblage
Pour attribuer un nom fort un assemblage vous pouvez soit utiliser les options /keycontainer
et /keyfile du compilateur csc.exe soit utiliser le menu Proprit du projet  Signature  Signe
lassemblage de Visual Studio 2005.
Vous pouvez aussi utiliser lattribut dassemblage AssemblyKeyFile dans le code source du module principal. Cet attribut accepte un argument qui est le nom dun fichier dextension .snk.
Par exemple :
[AssemblyKeyFile("Cles.snk")]
Lorsque cet attribut est ajout, le compilateur signe lassemblage mais met un avertissement
vous mettant en garde quune des deux premires techniques cite est prfrable.
Lorsquun assemblage doit tre sign par une de ces trois techniques, le compilateur inclut une
signature numrique et la cl publique dans le corps de lassemblage. Lalgorithme utilis pour
hacher le contenu de lassemblage se nomme SHA-1 (Secure Hash Algorithm). Vous pouvez choisir un autre algorithme de hachage de lassemblage, comme le trs connu MD5 (MD pour Message Digest) au moyen de lattribut dassemblage AssemblyAlgorithmId qui prend en argument
une valeur de lnumration AssemblyHashAlgorithm. La taille dune valeur de hachage cre
par lalgorithme SHA-1 est de 20 octets. Cette taille peut varier selon lalgorithme utilis.

Assemblage nom fort (strong naming)

31

Le compilateur encrypte la valeur de hachage avec la cl prive et obtient ainsi la signature numrique RSA de lassemblage. La cl publique est insre dans la table AssemblyDef du manifeste
tandis que la signature numrique est insre dans une partie spciale de lentte CLR. Voici le
schma dcrivant le processus de signature dun assemblage :
Module principal
de foo1.exe
avant signature

Module principal
de foo1.exe
aprs signature
Signature
numrique

Entte PE
Entte CLR

Entte PE
Entte CLR

Encryption
avec la cl
prive
Manifeste

Signature
numrique
Manifeste

Fichier
cles.snk

Cl publique
Cl prive

Cl publique

Mtadonnes

Mtadonnes

Code compil
en IL

Code compil
en IL

Valeur de
hachage

Ressources

Ressources

Figure 2 -6 : Signer un assemblage

Un exemple
Voici le code dun assemblage Foo1 :
Foo1.cs

Exemple 2-3 :
using System ;
namespace Foo {
class Program {
public static void Main( string[] argv ){
Console.WriteLine( "Bonjour de Foo1" ) ;
}
}
}
Compilons et signons cet assemblage avec la cl Cles.snk :
>csc.exe /keyfile:Cles.snk Foo1.cs

Analysons le manifeste de lassemblage Foo1.exe avec loutil ildasm.exe (pour plus de clart
nous avons enlev quelques commentaires) :
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly Foo1
{
.custom instance void

// .z\V.4..

32

Chapitre 2 : Assemblages, modules, langage IL


[mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) =
( 01 00 08 43 6C 65 73 2E 73 6E 6B 00 00 ) // ...Cles.snk..
[mscorlib]System.Diagnostics.DebuggableAttribute::.ctor
(bool, bool) = ( 01 00 00 01 00 00 )
.publickey = (

00 24 00 00
00 24 00 00
51 A7 DA DD
1A CA 43 0E
BE A6 DE F9
31 CB 98 EA
62 BB 4C 5C
2F 66 D0 3E
B5 F9 37 52
10 F8 A8 B2
.hash algorithm 0x00008004
.ver 0:0:0:0

04
52
E8
11
D7
3E
C5
07
4E
B8

80
53
3C
DB
B2
9A
EA
90
79
B1

00
41
F1
76
E8
87
23
09
49
86

00
31
0E
36
A7
62
F1
EA
48
FC

94
00
8B
5A
C5
36
DF
76
53
B3

00
04
7C
B2
68
B3
A7
46
2C
42

00
00
6C
9D
B0
3B
6D
23
67
85

00
00
D9
6C
D5
36
35
92
9D
9C

06
01
9E
31
AD
2E
B5
66
B5
48

02
00
4D
FC
A5
43
41
3B
ED
EA

00
01
06
93
E8
3F
2D
C0
A5
76

00
00
2B
A7
E1
DD
81
8A
02
D6 )

}
.module Foo1.exe
// MVID: {5DD7C72B-D1C1-49BB-AB33-AF7DA5617BD1}
.imagebase 0x00400000
.file alignment 512
.stackreserve 0x00100000
.subsystem 0x00000003
.corflags 0x00000009
// Image base: 0x03240000
La cl publique est bien la mme que celle que lon a surligne plus haut, lors de lanalyse du
fichier Cles.snk. Loutil ildasm.exe ne nous permet pas de visualiser ni le jeton de cl publique
ni la signature numrique. Loutil sn.exe nous permet de visualiser le jeton de cl publique avec
loption -T :
C:\Code\CodeDotNet\ModuleTest>sn.exe -T Foo1.exe
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Public key token is c64b742bd612d74a
Lassemblage mscorlib est rfrenc et son jeton de cl publique est connu. Cest normal,
puisque ce jeton fait partie intgrante du nom fort de lassemblage. En fait le jeton de cl
publique a t cr pour le rfrencement dautres assemblages. Nous rappelons que la taille
dune cl publique tant de 128 octets, la taille des assemblages rfrenant beaucoup dautres
assemblages aurait t trop grosse sans la technique du jeton cl publique.
Un assemblage nom fort ne peut rfrencer un autre assemblage qui na pas de nom fort. En
revanche, un assemblage sans nom fort, peut rfrencer un assemblage nom fort.
Dans le cas o un assemblage est constitu de plusieurs modules, les modules autres que le
module principal nont pas de cl publique. En revanche durant la compilation du module
principal, le compilateur calcule une valeur de hachage pour chaque module. Ces valeurs sont

Assemblage nom fort (strong naming)

33

intgres dans les entres de la table FileDef du manifeste. Ces valeurs sont donc prises en compte
lors du calcul de la valeur de hachage du module principal. Grce cette astuce les modules dun
assemblage nom fort sont eux aussi nomms dune manire unique et infalsifiables (en .NET
2.0), sans intgrer une signature numrique.

Le mcanisme de signature retarde


Utiliser un systme de signature numrique pour rendre ses assemblages infalsifiables est une
formidable fonctionnalit quore .NET. Cependant la mise en uvre de cette technique oblige
fournir un accs en lecture aux fichiers contenants les couples cl publique/cl prive tous
ceux qui sont susceptibles de compiler un assemblage nom fort dans lentreprise. Dans une
entreprise comme Microsoft, ils sont des milliers. Nul doute quaprs un certain temps, un employ finirait par divulguer la cl prive sur internet. La seule manire de limiter ce risque est
de limiter le nombre demploys qui ont accs la cl prive. .NET fournit le mcanisme de
signature retarde cet eet. Concrtement un assemblage est sign avec la cl prive aprs avoir
t compil et test et seulement avant dtre packag et dploy chez les clients. Dans une
grande entreprise, seul un ou quelques employs sont habilits faire cette manipulation. Le
mcanisme de signature retarde ncessite trois tapes :

Il faut dabord construire un fichier dextension .snk qui ne contient quune cl publique.
Pour cela il faut utiliser loption -p de sn.exe.
C:\Code>sn -p Cles.snk ClePublique.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXXX
Copyright (C) Microsoft Corporation. All rights reserved.
Public key written to ClesPubliques.snk
C:\Code>

Le fichier ClePublique.snk est distribu aux dveloppeurs qui lutilisent durant le dveloppement des assemblages la place du fichier Cles.snk (donc dans lattribut AssemblyKeyFile).
Lattribut dassemblage AssemblyDelaySign initialis avec largument boolen true, doit
tre utilis. Le compilateur comprend quil ne doit pas signer le module principal. Cependant le compilateur prvoit lespace ncessaire la signature, dans le module principal,
insre la cl publique dans le manifeste et calcule la valeur de hachage des modules sans
manifeste.

Lorsque les quipes de dveloppement fournissent des assemblages prts tre packags et
dploys, il sut de signer les assemblages avec loption -R de sn.exe.
C:\Code>sn.exe -R Foo1.exe Cles.snk
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.XXX
Copyright (C) Microsoft Corporation. All rights reserved.
Assembly Foo1.exe successfully re-signed
C:\Code>

34

Chapitre 2 : Assemblages, modules, langage IL

Vous pouvez aussi retarder la signature dun assemblage avec le compilateur csc.exe en utilisant
conjointement les options /delaysign, /keycontainer et /keyfile.
La technique de la signature retarde peut tre aussi utilise pour signer des assemblages modifis aprs la compilation, par exemple avec un framework de programmation oriente aspect.

Internationalisation et assemblages satellites


Notion de culture et de globalisation dune application
Certaines applications doivent pouvoir tre utilises par des utilisateurs de direntes nationalits parlant direntes langues. Tous ce qui peut tre prsent lutilisateur par lapplication
(chanes de caractres, images, animations, sons, dates, nombres virgules etc) doit alors tre
fournis pour chaque culture supporte par lapplication. On appelle ce processus la globalisation
ou parfois linternationalisation voire la localisation de lapplication.
Une culture est une association pays-langue/dialecte telle que fr-FR pour le franais en
France, fr-CA pour le franais au Canada ou en-US pour langlais des Etats Unis. La liste
des cultures standard du framework .NET est prsente dans larticle CultureInfo Class des
MSDN.
Nous avons vu quune culture peut tre prcise dans le nom fort dun assemblage. Nanmoins,
vous tes oblig dutiliser la culture neutre pour tout assemblage contenant du code. Concrtement, la chane de caractres spcifie pour lattribut CultureInfo doit toujours tre vide
lorsque votre assemblage contient du code. Pour indexer les ressources partir dune culture,
il faut crer un assemblage pour chaque culture, qui ne contient que des ressources. Ces assemblages particuliers qui font lobjet de la prsente section sont appels les assemblages satellites.

Fichiers de ressources
Concrtement, pour exploiter des ressources de type chanes de caractres ou des ressources stockes dans un format binaire (images, animations, sons...) dans un assemblage, il faut procder
selon les quatre tapes suivantes :

Editer le fichier de ressources dans un format exploitable par les humains (extension du fichier .txt ou .resx).

Convertir le fichier de ressources dans un format binaire adapt la lecture par un programme (extension du fichier .resources).

Inclure le fichier dextension .resources dans un assemblage (satellite ou non);

Exploiter les ressources dans votre code source au moyen de la classe System.Resources.
ResourceManager. Bien souvent, on se sert dune classe gnre qui encapsule laccs cette
classe.

Nous commencerons par montrer comment faire ces manipulations la main laide doutils en ligne de commande. Ensuite, nous verrons que Visual Studio 2005 permet dautomatiser
grandement ce processus.
Un fichier de ressources donn cible une seule culture. Il existe trois formats de fichiers de ressources, utiliss lors de direntes tapes du dveloppement dune application :

Internationalisation et assemblages satellites

35

Les fichiers de ressources dextension .txt : Ces fichiers associent des identifiants aux
chanes de caractres dune culture. Par exemple voici un tel fichier dont la culture destination est langlais :
Exemple 2-4 :

MyRes.txt

Bonjour = Hello!
AuRevoir = Bye...

Les fichiers de ressources dextension .resx : Ces fichiers sont au format XML. Ils associent
des identifiants aux chanes de caractres dune culture. la dirence des fichiers de ressources dextension .txt, les fichiers dextension .resx peuvent aussi associer des identifiants des ressources stockes dans un format binaire. Dans un fichier dextension .resx,
linformation binaire est convertie au format UNICODE en utilisant lencodage Base64.
Lutilitaire resxgen.exe sert convertir une telle ressource (par exemple une image au format bmp ou jpg) en un fichier dextension .resx. Lutilisation de Visual Studio 2005 simplifie
considrablement la visualisation, ldition et la maintenance des fichiers dextension .resx
grce un diteur ddi.

Les fichiers de ressources dextension .resources : Ces fichiers sont logiquement quivalents aux fichiers dextension .resx. la dirence de ces derniers, ces fichiers sont dans un
format binaire. Ce sont ces fichiers qui sont intgrs dans les assemblages ltape de la
compilation.

Comme nous lavons expliqu, seul un fichier de ressources au format .resources peut tre
intgr dans un assemblage. Loutil resgen.exe utilisable en ligne de commande permet de
construire un fichier de ressources dans un de ces formats, partir dun autre fichier de ressources dans un autre format. resgen.exe connat le format du fichier en entre et du fichier
en sortie grce aux extensions de leurs noms. Voici un exemple dutilisation :
>resgen.exe MyRes.txt MyRes.resources

Ne convertissez pas un fichier de ressources au format resx ou resources qui contient au


moins une ressource qui nest pas une chane de caractres, en un fichier de ressources au
format txt.

Utiliser des ressources dans un assemblage


La classe System.Resources.ResourceManager doit tre utilise pour exploiter les ressources
selon la culture courante. Cependant, on prfre en gnral se servir dune classe gnre par
loutil resgen.exe pour encapsuler les accs cette classe. Le fichier source dune telle classe peut
tre gnr partir dun fichier de ressources au format .txt, .resx ou .ressources avec loption
/str :
>resgen.exe MyRes.resources /str:cs
Loption /str:cs indique que nous souhaitons gnrer notre fichier source en C  . Nous aurions
pu aussi spcifier /str:vb pour lobtenir en VB.NET. Voici un extrait pertinent :

36

Chapitre 2 : Assemblages, modules, langage IL

Exemple :

MyRes.cs

internal class MyRes {


private static System.Resources.ResourceManager resourceMan ;
private static System.Globalization.CultureInfo resourceCulture ;
...
internal static System.Resources.ResourceManager ResourceManager {
get {
if ((resourceMan == null)) {
System.Resources.ResourceManager temp = new
System.Resources.ResourceManager("MyRes",
typeof(MyRes).Assembly) ;
resourceMan = temp ;
}
return resourceMan ;
}
}
internal static global::System.Globalization.CultureInfo Culture {
get { return resourceCulture ; } set{ resourceCulture = value ; } }
...
internal static string Bonjour {
get{return ResourceManager.GetString("Bonjour", resourceCulture);}}
...
internal static string AuRevoir {
get {return ResourceManager.GetString(
"AuRevoir", resourceCulture) ; } }
}
On comprend alors quen plus dencapsuler laccs la classe ResourceManager, la classe MyRes
prsente des proprits qui permettent daccder aux ressources dune manire type. Il ne nous
reste plus qu utiliser ces proprits pour pouvoir exploiter nos ressources. Le programme suivant ache hello :
Exemple 2-5 :

Program.cs

class Program {
static void Main() {
System.Console.WriteLine( MyRes.Bonjour ) ;
}
}
Il est intressant de comparer ce code au code dun programme qui nutilise pas la classe gnre
MyRes :
Exemple 2-6 :

Program.cs

using System.Resources ;
class Program {
static void Main() {
ResourceManager rm = new ResourceManager( "MyRes" ,
typeof(MyRes).Assembly) ;
System.Console.WriteLine( rm.GetString("Bonjour") ) ;

Internationalisation et assemblages satellites

37

}
}
La classe ResourceManager prsente plusieurs versions surcharges de GetString(). De plus, elle
prsente la mthode plus gnrale GetObject() qui peut tre utilise pour charger des chanes
de caractres. Cette mthode peut aussi tre utilise pour charger des ressources stockes dans
un format binaire telles que des images. Par exemple :
...
System.Drawing.Bitmap image = (Bitmap) rm.GetObject("UneImage") ;
string s = (string) rm.GetObject("Bonjour") ;
...
Dans le cas dutilisation de la classe gnre par resgen.exe, une proprit de type System.Drawing.Bitmap reprsente laccs une ressource de type image.
...
System.Drawing.Bitmap image = MyRes.UneImage ;
...
Intressons nous maintenant la compilation de ce programme. Dans le cas o lassemblage
contient du code, il faut prciser les noms des fichiers de ressources dextension .resources
la compilation de lassemblage. Le compilateur csc.exe du langage C  prvoit cet eet les
options /resource et /linkresource. Par exemple :
>csc.exe /resource:MyRes.resources,MyRes.resources Program.cs MyRes.cs
La syntaxe avec une virgule, qui spare le nom physique du fichier de ressources ( gauche de la
virgule) du nom logique ( droite de la virgule) qui sera utilis dans le code de lassemblage pour
lidentifier. Les noms logique et physique sont ici identiques et gaux "MyRes.resources". Notez que loption /resource copie physiquement le contenu du fichier ressource dans le corps du
module compil. Vous pouvez aussi utiliser loption /linkresource pour rfrencer le fichier
de ressources partir du module principal.

Fabriquer un assemblage satellite


Nous avons vu comment encapsuler des ressources dans un assemblage et comment les exploiter. Jusquici, nous navons pas utilis une culture en particulier. Les ressources que nous avons
exploites taient relatives ce que lon nomme la culture invariante. Cest la culture prise par
dfaut lorsque aucune culture nest spcifie. Intressons nous maintenant la notion dassemblage satellite qui permet de diversifier les cultures pour une mme application.
Loutil al.exe (al pour assembly linker), utilisable en ligne de commande, permet de produire
un assemblage bibliothque (i.e un assemblage dont le module principal est dans un fichier
dextension .dll) partir dun ou plusieurs modules. al.exe permet donc de construire des
assemblages tels que les assemblages satellites qui ne contiennent que des ressources. En page 67
nous expliquons que loutil al.exe permet aussi la cration dassemblages de stratgie dditeur.
Un assemblage satellite donn contient des ressources relatives une seule culture. Voici un
exemple dutilisation de al.exe pour construire un assemblage satellite. Notez lutilisation de
loption /c pour prciser la culture (en loccurrence, espagnole) :
>al.exe /out:es-ES\Program.Resources.dll /c:es-ES
/embed:es-ES\MyRes.es-ES.resources

38

Chapitre 2 : Assemblages, modules, langage IL

Nous utilisons en entre de al.exe avec loption /embed un fichier nomm MyRes.es-ES.ressources.
Ce fichier a t fabriqu avec loutil resgen.exe. partir du fichier MyRes.es-ES.txt suivant :
MyRes.es-ES.txt

Exemple 2-7 :
Bonjour = Hola!
AuRevoir = Adios...

Loption /embed fait en sorte que le contenu du fichier MyRes.es-ES.ressources soit physiquement inclus dans le fichier Program.Ressources.dll.

Dployer et utiliser un assemblage satellite


La classe System.Resources.ResourceManager sait grer la notion dassemblage satellite.
Elle est notamment capable de charger un assemblage satellite lexcution. La culture
qui est prise en compte est celle qui est prcise par la valeur de la proprit CultureInfo
Thread.CurrentUICulture{get;set} du thread courant. Pour bnficier de ce service, il est
ncessaire de se plier une certaine discipline :

Le nom du module principal dun assemblage satellite (celui cr avec loutil al.exe) doit
tre
[nom de lassemblage qui contient le code qui manipule les ressources].
Resources.dll.

Un assemblage satellite correspondant une culture xx-XX doit se trouver dans le sous rpertoire xx-XX (par rapport au rpertoire de lassemblage qui contient le code qui manipule
les ressources). On remarque donc que les assemblages satellites relatifs un mme assemblage ont tous le mme nom. Seul les noms des rpertoires qui les stockent permettent de
dterminer pour chacun la culture laquelle il se rfre.

Ces rgles sont illustres par cette organisation des fichiers de notre exemple :
\Program.exe
\es-ES\Program.Resources.dll
...

// fichier fabriqu
e par csc.exe
// fichier fabriqu
e par al.exe

Si le programme suivant sexcute dans le contexte ci-dessus, il ache Hola! :


Exemple 2-8 :

Program.cs

class Program {
static void Main() {
MyRes.Culture = new System.Globalization.CultureInfo("es-ES");
System.Console.WriteLine( MyRes.Bonjour ) ;
}
}
En analysant le code de la classe MyRes gnre par resgen.exe, on saperoit que la recherche de
ressource ne se fait pas selon la culture prcise par la proprit Thread.CurrentUICulture mais
selon la valeur de la proprit MyRes.Culture (qui correspond au champ MyRes.resourceCulture).
On peut rcrire ce programme sans utiliser la classe MyRes comme ceci :

Internationalisation et assemblages satellites


Exemple 2-9 :

39
Program.cs

using System.Resources ;
// Pour la classe ResourceManager.
using System.Threading ;
// Pour la classe Thread.
using System.Globalization ; // Pour la classe CultureInfo.
class Program {
static void Main() {
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-ES");
ResourceManager rm = new ResourceManager( "MyRes" ,
typeof(MyRes).Assembly) ;
System.Console.WriteLine(rm.GetString("Bonjour")) ;
}
}
Les programmes prcdents fonctionnent aussi si lassemblage satellite Program.Resources.dll
de culture es-ES se trouve dans le GAC. Comme tout assemblage situ dans le GAC, un tel assemblage satellite a un nom fort. Il est direnci de ces homologues relatifs dautres cultures
grce la composante culture de ce nom fort.
Lors de la compilation de Program.exe vous navez aucunement besoin de rfrencer un des
assemblages satellites. Il est ainsi possible de rajouter des assemblages satellites aprs la compilation de votre programme. Pour cela, vous devez faire en sorte que votre application rcupre
la culture avec laquelle elle doit sexcuter. Celle-ci peut par exemple tre rcupre partir dun
fichier de configuration ou partir de prfrences utilisateurs.

viter les exceptions dues lchec de la recherche dune ressource


Il est fortement conseill dinclure un fichier de ressource correspondant la culture invariante
dans lassemblage contenant le code. Ainsi, lexcution, si lassemblage satellite correspondant
la culture en cours nest pas trouv ou si celui-ci ne contient pas la traduction de certaines
ressources, la classe ResourceManager fera en sorte de fournir la valeur de la culture invariante.
Si cette valeur ntait pas connue, une exception serait lance.
Pour vous prmunir contre labsence dun assemblages satellites, vous pouvez vrifier son existence avec la mthode ResourceManager.GetResourceSet(). Par exemple :
Exemple 2-10 :

Program.cs

using System ;
using System.Globalization ; // Pour la classe CultureInfo.
class Program {
static void Main() {
CultureInfo spainCulture = new CultureInfo("es-ES") ;
if( MyRes.ResourceManager.GetResourceSet(
spainCulture , true, false )!= null ) {
MyRes.Culture = spainCulture ;
Console.WriteLine(MyRes.Bonjour) ;
}
else
Console.WriteLine("Assemblage satellite es-ES non trouv
e !") ;
}
}

40

Chapitre 2 : Assemblages, modules, langage IL

Visual Studio et les assemblages satellites


Maintenant que nous avons compris le rle des assemblages satellites et des outils resgen.exe et
al.exe nous pouvons montrer comment exploiter Visual Studio 2005 pour simplifier le processus
de globalisation dun assemblage.
Par dfaut, un projet Visual Studio 2005 na pas de fichier de ressources. Vous pouvez en ajouter
avec le menu Project  Add  New Item...  Resources file. Un fichier ressource dextension .resx
est alors ajout au projet. la compilation du projet, ce fichier sera automatiquement compil
en un fichier dextension .resources. Ce fichier .resource sera intgr dans lassemblage compil
ou dans un assemblage satellite selon que Visual Studio peut dterminer la culture laquelle
il se rfre en analysant son nom. Par exemple, un projet nomm MyProject qui contient les
fichiers ressources suivants ...
Res1.resx
Res1.en-US.resx
Res1.fr-FR.resx
Res2.resx
Res2.es-ES.resx
...se compile en ceci :
~/MyProject.exe
contient Res1.resources et Res2.resources
~/en-US/MyProject.Resources.dll contient Res1.en-US.resources
~/fr-FR/MyProject.Resources.dll contient Res1.fr-FR.resources
~/es-ES/MyProject.Resources.dll contient Res2.es-ES.resources
Par dfaut, un fichier source nomm XXX.designer.cs est associ chaque fichier ressources
XXX.resx ajout un projet. Ce fichier source contient une classe nomme [Nom du projet].XXX similaire celle qui est gnre par loption /str de loutil resgen.exe. En gnral, seuls
les fichiers de ressources relatifs la culture invariante (i.e Res1.resx et Res2.resx dans notre
exemple) ont besoin davoir une classe associe. Aussi, sachez que pour dsactiver la gnration
de ce fichier source, il sut de faire : proprit du fichier XXX.resx  Custom Tool  Mettre une
chane de caractres vide la place de la valeur ResXFileCodeGenerator
Visual Studio 2005 contient un diteur de fichier ressources dextension .resx. Vous pouvez trs
facilement ajouter des couples identifiant/chanes de caractres ou identifiant/fichier. Dans ce second cas le fichier peut tre un fichier image, icone, son, texte ou autre.
En page 695 nous dcrivons les facilits de Visual Studio 2005 pour localiser les fentres dune
application Windows Forms 2.0. En page 953 nous dcrivons les facilits de Visual Studio 2005
pour localiser les pages dune application web ASP.NET 2.

Formatage et culture
Le formatage de certains lments prsents aux utilisateurs tels que les dates ou les nombres
peut dpendre de la culture. Le framework .NET se sert de la valeur de la proprit CultureInfo
Thread.CurrentCulture{get;set} pour dterminer quelle culture utiliser lors dune opration
de formatage. Par exemple, le programme suivant...

Introduction au langage IL

41

Exemple 2-11 :
using System.Threading ;
// Pour la classe Thread.
using System.Globalization ; // Pour la classe CultureInfo.
class Program {
static void Main() {
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
System.Console.WriteLine(System.DateTime.Now.ToString()) ;
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
System.Console.WriteLine(System.DateTime.Now.ToString()) ;
}
}
...ache ceci :
6/20/2005 10:54:10 PM
20/06/2005 22:54:10

Introduction au langage IL
Nous avons vu que les assemblages contiennent du code crit en langage IL (Intermediate Language). Le langage IL est en fait un langage objet part entire. Il constitue le plus petit dnominateur commun des langages .NET.
Il faut savoir que certaines documentations, notamment celles de Microsoft, utilisent le terme
de langage MSIL pour nommer limplmentation Microsoft du langage IL. Dautres documentations utilisent le terme CIL (Common Intermediate Langage) pour nommer le langage IL. Le langage IL est entirement spcifi par lorganisation ECMA. Vous pouvez trouver les spcifications
du langage IL sur le site de lECMA.
Les outils ildasm.exe et Reflector prsents prcdemment permettent de visualiser le code
en langage IL contenu dans un module. Nous allons voir et commenter du code IL bien quune
prsentation complte du langage IL dpasserait le cadre de cet ouvrage. Nous pensons quil est
bon que les dveloppeurs .NET aient une ide de ce quest le langage IL. Pour les programmeurs
Java cela ne sera pas sans rappeler le bytecode.
Vous pouvez compiler vos propres sources crites en langage IL, en assemblages, laide du
compilateur ilasm.exe fournis avec le framework .NET ( ne pas confondre avec loutil ildasm.
exe).
Comprenez bien que malgr sa ressemblance avec du langage machine, le langage IL nest support par aucun processeur (pour linstant du moins). Le code IL est compil lexcution, en
un langage machine cible. Ce langage machine est fonction du processeur de la machine. Ce mcanisme de compilation du code IL durant lexcution, est dcrit page 111. Cette ide dutiliser
un langage intermdiaire entre les langages de haut niveau et le langage machine nest pas nouvelle. Cette ide est exploite depuis longtemps sous Java et sous dautres langages/compilateurs.

Prsentation de la pile et des instructions IL correspondantes


Pour comprendre cette section, il est ncessaire davoir assimil la notion dunit dexcution
(i.e la notion de thread) prsente page 136.

42

Chapitre 2 : Assemblages, modules, langage IL

Comme beaucoup dautres langages, IL est un langage avec une pile (stack en anglais). Une pile
est un espace mmoire contigu qui a la particularit davoir seulement un point daccs, appel le sommet de la pile. Tout comme une pile dassiette, vous pouvez ajouter une assiette au
sommet de la pile ou enlever une assiette au sommet de la pile. En informatique les piles ne
contiennent pas des assiettes mais des valeurs types.
Une pile appartient un thread. Pour chaque oprande dune opration excute par le thread
(oprations arithmtiques, appel de fonction...), il faut faire une copie de sa valeur au sommet
de la pile.
En IL, la pile est gre par le CLR. Dans ce cas le CLR joue le rle dun processeur virtuel .
Cest pour cela que dans le monde Java, lquivalent du CLR est nomm machine virtuelle .
Le CLR ne fonctionne pas exactement comme un processeur classique. En eet, les processeurs
travaillent avec une pile et des registres alors que le CLR remplit les mmes fonctions seulement
avec la pile.

Exemple 1 : les variables locales et la pile


Le code dune mthode C  suivant...
...
{
int i1 =5 ;
int i2 =6 ;
int i3 = i1+i2 ;
}
...
...produit le code IL suivant :
.maxstack 2
.locals ([0] int32 i1,
[1] int32 i2,
[2] int32 i3)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.6
IL_0003: stloc.1
IL_0004: ldloc.0
IL_0005: ldloc.1
IL_0006: add
IL_0007: stloc.2
IL_0008: ret
Plusieurs remarques peuvent tre faites :

Le fait qu aucun moment cette mthode ne charge plus de deux valeurs sur la pile, est
sauv dans un attribut appel .maxstack. La taille de la pile est donc borne durant la compilation.
Les variables locales sont types et numrotes.
Chaque instruction prend exactement un octet (IL_XXXX gauche reprsente loset de
linstruction IL correspondante, par rapport au dbut de la mthode).

Introduction au langage IL

43

ldc.i4.5 (load constant 5/charge constante 5) est une instruction IL qui pousse la valeur
constante entire 5 sur la pile, sous la forme dun entier cod sur quatre octets (idem pour
6). Comprenez bien que lentier 5 nest pas un paramtre de cette instruction. En consquence, linstruction ldc.i4.5 ne prend quun octet pour tre stocke. linverse, si lon
avait eu pousser la valeur constante entire 12345678 stocke sur quatre octets sur la pile,
on aurait utilis linstruction IL ldc.i4. Cette instruction IL prend en paramtre un entier
cod sur quatre octets. Cette instruction avec son paramtre aurait alors pris cinq octets
pour tre stock. On saperoit donc, qu linstar de ce qui se fait pour les langages machines, certaines instructions du langage IL ont t spcialement conues pour optimiser
le nombre doctets utiliss pour stocker le code IL. De plus, avec cette pratique, la vitesse
dexcution est aussi optimise, puisquon conomise le temps de lecture du paramtre.
ldloc.N (load local/charge locale) est une instruction IL qui pousse la valeur de la variable
locale numro N au sommet de la pile.
stloc.N (store local/enregistre locale) est une instruction IL qui dpile la valeur au sommet
de la pile et lenregistre dans la variable locale numro N.
add (add/ajoute) est une instruction IL qui dpile les deux valeurs au sommet de la pile
et les ajoute. Le rsultat est alors stock au sommet de la pile. Notez que lautre instruction IL add.ovf teste le dpassement de valeur et lance, le cas chant, une exception
OverflowException. De plus, on ne peut combiner tous les types pour les valeurs dentrs.
Dans le cas o une combinaison de type nest pas prvue, une exception est lance.
ret (return/retourne) est une instruction IL qui provoque le retour au code de la mthode
appelante.

Plus gnralement toutes les instructions IL, dont le nom commence par ld, chargent une valeur au sommet de la pile. chacune de ces instructions IL est associe une instruction IL symtrique, dont le nom commence par st, qui dpile la valeur au sommet et lenregistre.

Exemple 2 : les appels de mthodes et la pile


La pile peut tre vue comme un empilage de fentres de pile (stack frames en anglais). Chaque
appel de mthode correspond la cration dune fentre de pile sur le haut de la pile du thead
courant. Chaque retour de mthode correspond la destruction de la fentre de pile situe sur
le haut de la pile.
Le code C  de la mthode Main() suivante...
Exemple 2-12 :
class Program{
static int f(int i1, int i2){
return i1+i2 ;
}
public static void Main(){
int i1 =5 ;
int i2 =6 ;
int i3 = f(i1,i2) ;
}
}
...produit le code IL suivant pour la mthode Main() :

44

Chapitre 2 : Assemblages, modules, langage IL


.maxstack 2
.locals ([0] int32 i1,
[1] int32 i2,
[2] int32 i3)
IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.6
IL_0003: stloc.1
IL_0004: ldloc.0
IL_0005: ldloc.1
IL_0006: call
int32 Program::f(int32,int32)
IL_000b: stloc.2
IL_000c: ret

On voit bien que, linstar de lexemple prcdent, les deux valeurs de i1 et i2 sont charges
sur la pile, avant lappel de la mthode Program.f(), grce linstruction IL call qui :

dpile les arguments de lappel et les stocke en mmoire ;

cre une nouvelle fentre de pile en haut de la pile du thread ;

eectue lappel la mthode f().

Le code suivant est produit par le compilateur C  pour la mthode f() :


.maxstack 2
.locals init ([0] int32 CS$00000003$00000000)
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: stloc.0
IL_0004: br.s
IL_0006
IL_0006: ldloc.0
IL_0007: ret
Linstruction IL ldarg sert charger la valeur dun argument sur la pile. Rappelez-vous que les
valeurs des arguments ont t sauves en mmoire par linstruction call. Attention, les arguments sont indexs partir de zro dans une mthode statique (comme f()) et partir de un
dans une mthode non statique. En eet lors de lappel dune mthode non statique, largument
index par zro est rserv implicitement pour la rfrence this.
La valeur de retour de la mthode doit tre la seule valeur prsente dans la fentre de pile de
la mthode, juste avant lappel linstruction ret. Linstruction ret indique un retour de la
mthode (donc la destruction de la fentre de pile qui lui est associe) mais garde la valeur de
retour en haut de la pile de faon ce que la mthode appelante puisse la rcuprer.
Notez que le code suivant, qui ne fait pas intervenir de variables locales, aurait t valide aussi :
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: ret

Introduction au langage IL

45

Instructions IL de comparaison et de branchement


Il existe beaucoup dinstructions IL de comparaison des deux valeurs au sommet de la pile. Leurs
noms est, une fois de plus, emprunt aux langages machines existants : ceq (compare val1 equal
val2), cgt (compare val1 greater than val2), clt(compare val1 less than val2).
Nous prcisons que val2 est au sommet de la pile, donc val1 est juste en dessous. De plus val1
et val2 sont dpiles et une valeur entire est place au sommet, 1 si la comparaison est vraie
sinon 0.
Il existe une famille dinstructions IL pour le branchement. La plupart commencent par b
ou br et prennent en paramtre loset localisant linstruction IL, sur laquelle, lunit dexcution doit (ventuellement) se brancher. Linstruction de branchement inconditionnel sappelle br. Toutes les autres instructions sont des branchements conditionnels, cest--dire que
le branchement ne se fait qu une condition. Par exemple linstruction brtrue neectue le
branchement qu la condition que la valeur au sommet de la pile soit non nulle. Linstruction
beq neectue le branchement qu la condition que les deux valeurs au sommet de la pile soient
gales. Les significations des instructions brfalse (Branch if False), blt (Branch if Lower Than),
ble (Branch if Lower or Equal), bgt (Branch if Greater Than), bge (Branch if Greater or Equal),
bne (Branch if Not Equal) ... dcoulent naturellement de tout ceci.

IL et la programmation oriente objet


Analysons le code IL gnr partir dun petit programme C  objet.
Exemple 2-13 :
class Program {
public override string ToString() { return "Program m_i=" + m_i ; }
int m_i = 9 ;
Program(int i) { m_i = i ; }
public static void Main() {
object obj = new Program(12) ;
obj.ToString() ;
}
}
Voici le code IL de la mthode Main() :
.maxstack 2
.locals init ([0] object obj)
IL_0000: ldc.i4.s
12
IL_0002: newobj
instance void Program::.ctor(int32)
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: callvirt
instance string [mscorlib]System.Object::ToString()
IL_000e: ret
On voit quune instruction IL spciale nomme newobj, cre un objet, appelle le constructeur
de la classe et place la rfrence de lobjet sur la pile. On voit que lappel la mthode virtuelle
Program.ToString() (qui redfinie la mthode object.ToString()) se fait avec linstruction IL
callvirt. Avant cet appel, la rfrence de lobjet sur lequel la mthode est appele, est place
sur la pile. Le polymorphisme est donc support par linstruction IL callvirt.

46

Chapitre 2 : Assemblages, modules, langage IL

Notez quen page 498 nous exposons les modifications principales du langage IL pour le support
la gnricit.

Les jetons de mtadonnes


Les tables des mtadonnes de type sont souvent rfrences par le code IL. Lorsquil dsassemble le code IL, ildasm.exe met les noms et les signatures des mthodes directement dans
le code IL dsassembl. En fait, au niveau binaire, le code IL na pas de chanes de caractres
dfinissant le nom des mthodes. Pour rfrencer une mthode, le code IL contient des valeurs
de quatre octets qui pointent vers les tables de la section mtadonnes de type. On appelle ces
valeurs de quatre octets les jetons de mtadonnes (metadata token en anglais).
Le premier octet dun jeton de mtadonnes rfrence la table de mtadonnes. Les trois autres
octets rfrencent un lment dans cette table. Par exemple la table rfrenant les membres
utiliss par un module (la table MethodRef) ayant le numro 10, un jeton de mtadonnes vers
le membre 5 de cette table est : 0x0A000005.
Les jetons de mtadonnes peuvent tre vus en choisissant loption Show token values de ildasm.
exe. Par exemple le code de la mthode Main() du programme suivant...
Exemple 2-14 :
using System ;
class Program{
public static void Main(){
Console.WriteLine( "Hello world!" ) ;
}
}
...contient le code IL suivant : (vu avec la visualisation des jetons de mtadonnes sous ildasm.
exe) :
.method /*06000001*/ public hidebysig static
void Main() cil managed
{
.entrypoint
// Code size
11 (0xb)
.maxstack 1
IL_0000: ldstr
"Hello world!" /* 70000001 */
IL_0005: call
void [mscorlib/* 23000001 */]
System.Console/* 0100000F */::WriteLine(string) /* 0A00000E */
IL_000a: ret
} // end of method Program::Main
Les jetons de mtadonnes vus dans cet exemple sont :

0x06000001 : rfrence lentre dans la table MethodDef (0x06) reprsentant la mthode


Main(0x000001).

0x70000001 : rfrence lentre dans le tas #userstring (0x70) reprsentant la chane de caractres "Hello world!" (0x000001).

0x23000001 : rfrence lentre dans la table AssemblyRef (0x23) reprsentant lassemblage


mscorlib (0x000001).

Introduction au langage IL

47

0x0100000F : rfrence lentre dans la table TypeRef (0x01) reprsentant la classe System.
Console (0x00000F).

0x0A00000E : rfrence lentre dans la table MemberRef (0x0A) reprsentant la mthode


WriteLine (0x00000F). Notez quici, le jeton de mtadonnes de la mthode WriteLine
nest pas dans la table MethodDef car cette mthode est dfinie dans un autre module (et
mme, un autre assemblage).

3
Construction, configuration et
dploiement des applications .NET

Construire vos applications avec MSBuild


La plateforme .NET 2.0 est livre avec un nouvel outil nomm msbuild.exe. Cet outil sert
construire les applications .NET. Il accepte en entre des fichiers XML qui dcrivent lenchanement des tches du processus de construction, un peu dans le mme esprit que des fichiers
makefile. Dailleurs, au dbut du dveloppement de ce projet, Microsoft lavait baptis XMake.
Lexcutable msbuild.exe est situ dans le rpertoire dinstallation de .NET savoir [Rep_d
installation_de_Windows]\Microsoft.NET\Framework\v2.0.XXXX\. Il est prvu que MSBuild
fasse partie du systme dexploitation Windows Vista. Son rayon daction augmentera alors et il
pourra tre utilis pour construire tous types dapplication.
Jusquici, pour construire vos applications .NET, vous deviez :

Soit utiliser la commande Build de Visual Studio.

Soit utiliser lexcutable de Visual Studio devenv.exe en ligne de commande.

Soit avoir recours une tierce technologie telle que loutil open-source Nant, ou mme, utiliser des fichiers batch qui applent le compilateur C  csc.exe.

MSBuild vise unifier toutes ces techniques. Ceux qui connaissent Nant ne seront pas dpayss
car MSBuild reprend beaucoup de concepts de cet outil. Latout majeur de MSBuild sur Nant
est dtre exploit par Visual Studio 2005. MSBuild na aucune dpendance par rapport Visual
Studio 2005 puisque, rappelons le, MSBuild fait partie intgrante de la plateforme .NET 2.0. En
revanche, les fichiers dextension .proj , .csproj, .vbproj etc gnrs par Visual Studio 2005
pour construire les projets sont rdigs au format XML MSBuild. la compilation, Visual Studio
2005 utilise les services de MSBuild. En outre, le format XML MSBuild est pleinement support

50

Chapitre 3 : Construction, configuration et dploiement des applications .NET

et document. Le support de MSBuild est donc une volution consquente de Visual Studio qui
jusquici, utilisait des scripts de compilation non documents.

MSBuild : Cibles, tches, proprits, items et conditions


Fichier .proj, cibles et tches
Llment racine de tous documents XML MSBuild est <Project>. Cet lment contient des lments <Target>. Ces lments <Target> constituent les units de construction nommes cibles.
Un script MSBuild peut contenir plusieurs cibles et msbuild.exe est capable denchaner les
excutions de plusieurs cibles. Lorsque vous lancer msbuild.exe en ligne de commande, il prend
en entre le seul fichier dextension .proj du rpertoire courant. Si plusieurs fichiers .proj sont
prsents, il faut prciser en argument de ligne de commande le fichier .proj que msbuild.exe
doit utiliser. Un seul fichier peut tre prcis.
Une cible MSBuild est un ensemble de tches MSBuild. Chaque lment enfant de llment
<Target> constitue la dfinition dune tche. Les tches dune cible sont excutes en srie dans
leur ordre de dclaration. Une quarantaine de types de tches sont fournis par MSBuild comme
par exemple :
Type de tche

Description

Copy

Copie des fichiers dun rpertoire source vers un rpertoire destination.

MakeDir

Construit un rpertoire.

Csc

Exploite le compilateur C  csc.exe.

Exec

Excute une commande systme.

AL

Exploite loutil al.exe (Assembly Linker)

ResGen

Exploite loutil resgen.exe (Resources Generator).

La liste complte de ces types de tches standard est disponible dans larticle MSBuild Task Reference des MSDN. Un aspect particulirement intressant de MSBuild est quun type de tche
est matrialis par une classe .NET. Il est ainsi possible dtendre MSBuild avec de nouveaux
types de tches en fournissant vos propres classes. Nous reviendrons sur ce point un peu plus
loin.
Reprenons notre exemple dassemblage multi modules de la page 20. Rappelons que pour
construire cet assemblage constitu des trois modules Foo1.exe, Foo2.netmodule et Image.jpg
nous avions excuts les deux lignes de commande suivantes :
> csc.exe /target:module Foo2.cs
> csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg

Foo1.cs

En plus, nous dsirons ici qu lissue de la construction de lassemblage, les trois modules se
trouvent dans un sous rpertoire \bin du rpertoire courant. Voici le script MSBuild Foo.proj
capable de raliser tout ceci :

MSBuild : Cibles, tches, proprits, items et conditions


Exemple 3-1 :

51
Foo.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="FooCompilation">
<MakeDir Directories= "bin"/>
<Copy SourceFiles="Image.jpg" DestinationFiles=".\bin\Image.jpg"/>
<Csc Sources="Foo2.cs"
TargetType="module"
OutputAssembly=".\bin\Foo2.netmodule" />
<Csc Sources="Foo1.cs"
TargetType="exe"
AddModules=".\bin\Foo2.netmodule" LinkResources="Image.jpg"
OutputAssembly=".\bin\Foo1.exe" />
</Target>
</Project>
On voit que la cible nomme FooCompilation est constitue de quatre tches :

Une tche de type MakeDir qui construit le rpertoire \bin.

Une tche de type Copy qui copie le fichier Image.jpg dans le rpertoire \bin ;

Les deux tches de type Csc qui invoquent chacune le compilateur csc.exe.

Pour excuter ce script de compilation, il faut constituer un rpertoire ayant le contenu suivant :
.\Foo.proj
.\Foo1.cs
.\Foo2.cs
.\Image.jpg
Allez dans ce rpertoire avec la fentre de commande SDK Command Prompt (Menu Dmarrer
 Microsoft .NET Framework SDK v2.0  SDK Command Prompt) puis lancer la commande
>msbuild.exe. Chaque cible est obligatoirement nomme. Par dfaut msbuild.exe excute
seulement la premire cible. Vous pouvez spcifier une liste de noms de cibles spars par des
points virgules en ligne de commande avec loption /target (raccourcis /t). Vous pouvez aussi
spcifier une telle liste avec lattribut DefaultTarget de llment <Project>. Si plusieurs cibles
sont spcifies, lordre dexcution nest pas dfini.
Le comportement par dfaut de MSBuild est de stopper la construction ds quune tche met
une erreur. Vous pouvez souhaiter avoir un script de construction tolrant aux erreurs. Aussi,
chaque lment reprsentant une tche peut contenir un attribut ContinueOnError qui est positionn false par dfaut mais qui peut tre positionn true.

Notion de proprit
Pour vous permettre de paramtrer vos scripts, MSBuild prsente la notion de proprit. Une
proprit est un couple cl/valeur dfini dans un lment <PropertyGroup>. Les proprits
MSBuild fonctionnent comme un systme dalias. Chaque occurrence de $(cl
e) dans le script
est remplace par la valeur associe Typiquement, le nom du rpertoire /bin est utilis cinq
reprises dans notre script Foo.proj. Il constitue un bon candidat pour dfinir une proprit :

52

Chapitre 3 : Construction, configuration et dploiement des applications .NET


Foo.proj

Exemple 3-2 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>.\bin</OutputPath>
</PropertyGroup>
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"/>
<Copy SourceFiles="Image.jpg"
DestinationFiles="$(OutputPath)\Image.jpg"/>
<Csc Sources="Foo2.cs"
TargetType="module"
OutputAssembly="$(OutputPath)\Foo2.netmodule" />
<Csc Sources="Foo1.cs"
TargetType="exe"
AddModules="$(OutputPath)\Foo2.netmodule"
LinkResources="Image.jpg"
OutputAssembly="$(OutputPath)\Foo1.exe" />
</Target>
</Project>

Vous pouvez en outre exploiter des proprits dfinies par dfaut par MSBuild telles que :
Type de tche

Description

MSBuildProjectDirectory

Rpertoire qui stocke le script MSBuild courant.

MSBuildProjetFile

Nom du fichier script MSBuild courant.

MSBuildProjectExtension

Extension du fichier script MSBuild courant.

MSBuildProjectFullPath

Chemin complet du fichier script MSBuild courant.

MSBuildProjectName

Nom du fichier script MSBuild courant sans lextension.

MSBuildPath

Rpertoire qui stocke le fichier msbuild.exe.

Lors de ldition des proprits avec Visual Studio 2005, vous vous apercevrez quun certains
nombre de cls vous sont proposes par lintellisense. OutputPath constitue une telle cl. Vous
pouvez utiliser ces cls mais rien ne vous empche de dfinir vos propres cls. Nous aurions
ainsi pu choisir pour cl RepDeSortie la place de OutputPath.

Notion ditem
La base de la construction dun projet par un script est la manipulation de rpertoires, de fichiers
(sources, ressources, excutables etc) et de rfrences (vers des assemblages, vers des services, vers
des classes COM, vers des fichiers ressources, vers des projets etc). On utilise le terme item pour
dsigner ces entits qui constituent les entres et les sorties de la plupart des tches. Dans notre
exemple, le fichier Image.jpg est un item consomm la fois par la tche Copy et par la seconde
tche Csc. Le fichier Foo2.netmodule est un item produit par la premire tche Csc et consomm
par la seconde tche Csc. Rcrivons notre script avec cette notion ditem :

MSBuild : Cibles, tches, proprits, items et conditions


Exemple 3-3 :

53
Foo.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup><OutputPath>.\bin</OutputPath></PropertyGroup>
<ItemGroup>
<Fichier_Image Include="$(OutputPath)\Image.jpg"/>
<NetModule_Foo2 Include="$(OutputPath)\Foo2.netmodule"/>
</ItemGroup>
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"/>
<Copy SourceFiles="Image.jpg"
DestinationFiles="@(Fichier_Image)"/>
<Csc Sources="Foo2.cs"
TargetType="module"
OutputAssembly="@(NetModule_Foo2)" />
<Csc Sources="Foo1.cs"
TargetType="exe"
AddModules="@(NetModule_Foo2)"
LinkResources="@(Fichier_Image)"
OutputAssembly="$(OutputPath)\Foo1.exe" />
</Target>
</Project>
Nous remarquons que lon utilise la syntaxe @(nom de litem) pour se rfrer un item. En
outre un item peut dfinir un ensemble de fichier grce la syntaxe wildcard. Par exemple, litem
suivant fait rfrence tous les fichiers sources C  du rpertoire courant sauf Foo1.cs :
<cs_source Include=".\*.cs" Exclude=".\Foo1.cs" />

Poser des conditions


On peut souhaiter quun mme script MSBuild se dcline sous plusieurs versions. Par exemple,
il serait dommage de devoir crire et maintenir deux scripts pour chaque projet, un pour la
construction en mode Debug et un pour la construction en mode Release. Aussi, MSBuild introduit la notion de condition. Un attribut Condition peut tre appliqu pratiquement nimporte
quel lment dun script MSBuild (proprits, item, cible, tche, groupe de proprits, groupe
ditems etc). Si lexcution la condition dun lment nest pas ralise, le moteur MSBuild
lignorera. Larticle MSBuild Conditions des MSDN dcrit la liste des expressions de condition
qui peuvent tre prcises.
Dans lexemple suivant, nous exploitons les conditions de type test de lgalit de deux chanes
de caractres pour faire en sorte que notre script exemple supporte le mode Debug et Release.
Nous utilisons aussi une condition de type test de lexistence dun fichier ou dune rpertoire pour
nexcuter la tche MakeDir que si le rpertoire crer nexiste pas. Cette condition nest l que
pour des besoins pdagogiques car la tche MakeDir ne sexcute pas si le rpertoire crer existe
dj :
Exemple 3-4 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(Configuration)==Debug">
<Optimize>false</Optimize>
<DebugSymbols>true</DebugSymbols>

Foo.proj

54

Chapitre 3 : Construction, configuration et dploiement des applications .NET


<OutputPath>.\bin\Debug</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration)==Release">
<Optimize>true</Optimize>
<DebugSymbols>false</DebugSymbols>
<OutputPath>.\bin\Release</OutputPath>
</PropertyGroup>
<ItemGroup>
...
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"
Condition="!Exists($(OutputPath))"/>
...

Lorsquelles sont dfinies, les proprits standard Optimize et DebugSymbols sont automatiquement prises en comptes par les tches de type Csc.
Avant de lancer ce script, il faut prciser comme argument en ligne de commande la valeur de
la proprit Configuration. Cela peut se faire avec loption /property (raccourcis /p) :
>msbuild /p:Configuration=Release
Avec un peu dastuce, il est possible dutiliser une condition pour dfinir la valeur par dfaut
de la proprit Condition :
Exemple 3-5 :

Foo.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(Configuration)==">
<Configuration>Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration)==Debug">
...

Concepts avancs de MSBuild


Construction incrmentale et dpendances entre cibles
Dans un environnement rel, lexcution dun script MSBuild peut prendre plusieurs minutes
voire plusieurs heures pour sexcuter. Pour lanecdote, sachez que depuis ses dbuts le systme
dexploitation Windows met environs 12 heures se compiler. Cela signifie que le volume grandissant de code compiler vient compenser la monte en puissance des machines.
Il nest pas souhaitable de relancer compltement une construction pour un changement mineur eectu dans un fichier source sur lequel aucun autre composant ne dpend. Aussi, vous
pouvez utiliser la notion de construction incrmentale. Pour cela vous devez prciser la liste des
items en entre et des items en sortie dune cible au moyen des attributs Inputs et Outputs. Si
MSBuild dtecte quau moins un item en entre est plus vieux quau moins un item en sortie, il
prend la dcision dexcuter la cible.
Cette technique de construction incrmentale vous oblige partitionner lensemble de vos
tches en plusieurs cibles. Nous avons vu que si vous prcisez plusieurs cibles excuter

Concepts avancs de MSBuild

55

MSBuild, par exemple avec lattribut DefaultTargets, vous ne pouvez prsumer daucun
ordre dexcution. Vous pouvez cependant dfinir un systme de dpendance entre cibles avec
lattribut DependsOnTargets. MSBuild nexcute une cible que lorsque lensemble des cibles sur
lesquelles elle dpend a t excut. Naturellement, le moteur de MSBuild dtecte et sanctionne
dune erreur les dpendances circulaires entre cibles :
Exemple 3-6 :

Foo.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="FooCompilation">
...
<Target Name="CreeOutputPath" Condition="!Exists($(OutputPath))">
<MakeDir Directories= "$(OutputPath)"/>
</Target>
<Target Name="FooCompilation" DependsOnTargets="Cr
eeOutputPath"
Inputs="Foo2.cs;Foo1.cs"
Outputs="@(NetModule_Foo2);$(OutputPath)\Foo1.exe">
...
</Target>
</Project>

Transformations MSBuild
Vous avez la possibilit dtablir une correspondance biunivoque entre lensemble des items en
entre et lensemble des items en sortie dune cible. Pour cela, vous devez utiliser les transformations MSBuild dtailles dans larticle MSBuild Transforms des MSDN. Lavantage dutiliser
des transformations est que MSBuild ne dcide dexcuter la cible que si au moins un item en
entre est plus vieux que litem en sortie qui lui correspond. Logiquement, une telle cible est
moins souvent excute do un gain de temps.

Fractionner un script MSBuild sur plusieurs fichiers


Nous avons vu que loutil msbuild.exe ne peut traiter plus dun fichier script chaque excution. Cependant, un fichier script MSBuild peut importer un autre fichier script MSBuild
au moyen de llment <Import>. Dans ce cas, tous les lments enfants de llment racine
<Project> du fichier import sont copis en lieu et place de llment <Import> responsable
de limportation. Notre script exemple peut ainsi tre fractionn sur deux fichiers Foo.proj et
Foo.target.proj comme ceci :
Exemple 3-7 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="FooCompilation">
<PropertyGroup Condition="$(Configuration)=="> ...
<PropertyGroup Condition="$(Configuration)==Debug"> ...
<PropertyGroup Condition="$(Configuration)==Release"> ...
<ItemGroup> ...
<Import Project="Foo.target.proj"/>
</Project>

Foo.proj

56

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Exemple 3-8 :

Foo.target.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >


<Target Name="CreeOutputPath"
...
<Target Name="FooCompilation"
...
</Project>

Intgration de MSBuild avec Visual Studio


Nous avons dj prcis que les fichiers dextension .proj, .csproj, .vbproj etc gnrs par
Visual Studio 2005 pour construire les projets sont rdigs au format XML MSBuild. Si vous analysez un tel fichier, vous vous apercevrez quaucune cible ny est explicitement dfinie. En fait,
les fichiers scripts gnrs par Visual Studio 2005 importent des fichiers dextensions .targets
qui contiennent des cibles gnriques. Par exemple, un fichier dextension .csproj contient
llment suivant :
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
Ce fichier Microsoft.CSharp.targets contient deux cibles gnriques :

Une cible nomme CreateManifestResourceNames qui soccupe de lorganisation des fichiers ressources (transformation des fichiers .resx en .resources etc).

Une cible CoreCompile qui contient une tche Csc qui ralise eectivement la compilation.

Nous vous invitons consulter ces fichiers .targets situs dans le rpertoire $(MSBuildBinPath) qui est le rpertoire dinstallation de .NET 2.0 (i.e [Rep dinstallation de Windows]
\Microsoft.NET\Framework\v2.0.XXXXX).
En plus dimporter un tel fichier .targets, les fichiers gnrs par Visual Studio 2005 contiennent
essentiellement les dfinitions des proprits et items qui servent paramtrer les cibles gnriques.

Crer vos propres tches MSBuild


Un aspect particulirement intressant de MSBuild est que chaque type de tche est matrialis
par une classe .NET. Il est ainsi possible dtendre MSBuild avec de nouveaux types de tches
en fournissant vos propres classes. Une telle classe doit supporter les contraintes suivantes :

Elle doit implmenter linterface ITask dfinie dans lassemblage Microsoft.Build.


Framework.dll ou driver de la classe helper Task dfinie dans lassemblage Microsoft.
Build.Utilities.dll.

Elle doit implmenter la mthode bool Execute() de linterface ITask. Cette mthode
contient le corps de la tche et doit retourner true si elle a t excute avec succs.

Elle peut prsenter des proprits dont les valeurs seront positionnes par MSBuild partir
des valeurs des attributs de la tche, avant lappel la mthode Execute(). Seules les proprit marques avec lattribut Required doivent tre obligatoirement initialises.

Voici en exemple le code dune tche nomme MyTouch qui met jour la date des fichiers spcifis par lattribut Files (nous prcisons quune telle tche nomme Touch est prsente par le
framework MSBuild) :

Concepts avancs de MSBuild


Exemple 3-9 :

57
MyTask.cs

using System ;
using Microsoft.Build.Framework ;
using Microsoft.Build.Utilities ;
namespace MyTask {
public class MyTouch : Task {
public override bool Execute() {
DateTime now = DateTime.Now ;
Log.LogMessage(now.ToString() +
" est la nouvelle date des fichiers suivants:") ;
try {
foreach(string fileName in m_FilesNames) {
Log.LogMessage("
" + fileName) ;
System.IO.File.SetLastWriteTime(fileName, now) ;
}
}
catch (Exception ex) {
Log.LogErrorFromException(ex, true) ;
return false ;
}
return true ;
}
[Required]
public string[] Files {
get { return (m_FilesNames) ; } set { m_FilesNames = value ; }
}
private string[] m_FilesNames ;
}
}
Notez lutilisation de la proprit TaskLoggingHelper Task.Log{get;} qui permet dacher
des informations concernant le droulement de la tche.
Notre tche MyTouch doit tre enregistre auprs de tous les scripts MSBuild qui sont susceptibles dy avoir recours. Cela se fait au moyen dun lment <UsingTask>. Voici un tel script qui
met jour les dates des fichiers sources C  du rpertoire courant (lassemblage MyTask.dll doit
tre situ dans le rpertoire C:\CustomTasks\) :
Exemple 3-10 :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="C:\CustomTasks\MyTask.dll"
TaskName="MyTask.MyTouch"/>
<ItemGroup>
<FichierSrcCs Include="*.cs"/>
</ItemGroup>
<Target Name="ToucheLesFichiersSrcCs" >
<MyTouch Files= "@(FichierSrcCs)"/>
</Target>
</Project>

Foo.proj

58

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Il est intressant de noter que toutes les tches standard sont dclares par des lments <UsingTask> dans le fichier Microsoft.Common.Tasks. Ce fichier est automatiquement et implicitement import par msbuild.exe chaque excution. En analysant ce fichier, on voit que les
classes correspondantes aux tches standard sont dfinies dans lassemblage Microsoft.Build.
Tasks.dll. Vous pouvez ainsi avoir accs au code de ces tches en utilisant un outil tel que
Reflector.

Fichiers de configuration
Un assemblage excutable .NET a la possibilit davoir un fichier de configuration XML qui peut
tre modifi aprs linstallation. Le nom dun tel fichier de configuration doit obligatoirement
commencer par le nom du module contenant le manifeste (extension .exe comprise) auquel
on ajoute lextension .config (par exemple Foo.exe.config). Il doit tre plac imprativement
dans le mme rpertoire que lassemblage.
Visual Studio prsente des facilits pour ldition et la maintenance du fichier de configuration
dune application. Vous pouvez cliquer droit sur le projet qui dfinit un assemblage excutable
 Add  New Item...  Application Configuration File  Gardez le nom App.Config. la compilation, Visual Studio eectuera une copie du contenu de ce fichier dans un fichier nomm
[Nom de lassemblage executable avec extension].config situ dans le mme rpertoire
que lassemblage excutable produit.

Le fichier machine.config
La plupart des lments de configuration dune application .NET peuvent tre dclars dans
un fichier nomm machine.config situ dans le sous rpertoire /CONFIG du rpertoire dinstallation de .NET ([Rep_dinstallation_de_Windows]\Microsoft.NET\Framework\v2.0.XXXXX).
La valeur dun paramtre dfini dans le fichier machine.config nest prise en compte par une application .NET que si ce paramtre nest pas initialis dans son fichier de configuration. En outre,
certains paramtres tels que le modle de processus utilis par ASP.NET nont de sens quau
niveau de la machine. Ils ne peuvent ainsi qutre initialiss dans le fichier machine.config.
Il nest pas conseill de modifier le fichier de configuration de la machine. En eet, vous pouvez
altrer par inadvertance le comportement des applications installes sur votre machine. Cependant, dans le cas dune machine en production (un serveur ou une machine ddie une application) ce fichier peut constituer un moyen ecace pour dfinir une stratgie de configuration
globale la machine.

Les paramtres de configuration standard


Tout fichier de configuration .NET admet un lment racine <configuration> :
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
Voici les principaux sous lments standard de llment <configuration> :

<appSettings> : Contient les lments de configuration propres lapplication.

<configSections> : Permet de dfinir les sections de configuration que nous prsentons un


peu plus loin.

Fichiers de configuration

59

<connectionStrings> : Stocke les chanes de connexion aux bases de donnes (voir page
717).

<location> : Permet de dfinir les lments de configuration ASP.NET pour chaque page
web (voir page 890).

<mscorlib> : (seulement machine.config) Contient un lment <cryptographySettings>


qui contient les paramtres des mcanismes de cryptographie (voir page 219).

<protectedData> : Permet de dfinir des sections dinformation confidentielle dans un fichier de configuration (voir page 718).

<runtime> : Contient une section <gcConcurrent> qui permet de dterminer si le ramasse


miettes sexcute dune manire concurrente (voir page 120). Contient aussi une section
<assemblyBinding> qui positionne les paramtres de lalgorithme de localisation des assemblages (voir page 103).

<system.Data.[Founisseur de donn
ees]> : Permet de paramtrer les fournisseurs de donnes ADO.NET disponibles avec notamment des fichiers XML consomms par le framework
de schma (voir page 718).

<system.Diagnostics> : Contient des informations relatives aux traces (voir page 620).

<system.runtime.remoting> : Permet de configurer les services et canaux .NET Remoting


(voir page 808).

<system.transactions> : Permet de paramtrer les transactions (voir page 751).

<system.web> : Dfinit les paramtres utiliss par ASP.NET. La faon de configurer les applications ASP.NET est un vaste sujet auquel nous consacrons la section 23 Configuration
dune application ASP.NET , page 888.

Dfinir vos propres paramtres de configuration avec llment


<appSettings>
Vous pouvez vous servir de llment <appSettings> du fichier de configuration dune application pour stocker les valeurs des paramtres qui lui sont propres. Par exemple, supposons que
lapplication MyApp ait un paramtre entier nomm MyEntier :
Exemple 3-11 :

MyApp.exe.config

<?xml version="1.0" encoding="utf-8" ?>


<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<appSettings>
<add key="MyEntier" value="1234"/>
</appSettings>
</configuration>
Voici le code de lapplication MyApp. chaque excution, ce code obtient la valeur du paramtre
MyEntier, la multiplie par 10, puis sauvegarde la nouvelle valeur :

60

Chapitre 3 : Construction, configuration et dploiement des applications .NET


MyApp.cs

Exemple 3-12 :

using System.Configuration ;
class Program {
static void Main() {
Configuration appCfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
AppSettingsSection appSettings = appCfg.AppSettings ;
int myEntier ;
if (int.TryParse( appSettings.Settings["MyEntier"].Value,
out myEntier)) {
System.Console.WriteLine(myEntier) ;
myEntier *= 10 ;
appSettings.Settings["MyEntier"].Value = myEntier.ToString() ;
appCfg.Save() ;
}
}
}
Si vous recompilez lapplication MyApp avec Visual Studio et que vous utilisez le fichier App.
Config, la valeur de MyEntier est rinitialise 1234 puisque le contenu du fichier de configuration MyApp.exe.config est cras par le contenu du fichier App.Config.
Il est clair que cette faon de procder prsente deux dsavantages majeurs :

La valeur dun paramtre nest pas type. Il a fallu explicitement parser une chaine de caractre pour obtenir la valeur du paramtre MyEntier dans une variable entire.

Le nom du paramtre nest pas vrifi par le compilateur puisquil est fourni sous forme
dune chane de caractres. Cela nuit la productivit des dveloppeurs qui ne bnficient
pas non plus de lintellisense.

Dfinir vos propres paramtres avec des sections de configuration


Pour viter ces deux problmes, vous pouvez avoir recours la notion de section de configuration.
Une telle section est un ensemble de paramtres de configuration. Chaque paramtre est dfini
soit au niveau de lapplication, soit au niveau de lutilisateur Windows qui excute lapplication.
Ainsi, vous pouvez vous servir simplement de ce mcanisme pour stocker les prfrences de
chaque utilisateur.
Le fichier de configuration suivant dfini une section de configuration nomme MySettings qui
contient un paramtre de configuration utilisateur nomm MyEntierUsr et un paramtre de
configuration de lapplication nomm MyEntierApp :
Exemple 3-13 :

MyApp.exe.config

<?xml version="1.0" encoding="utf-8" ?>


<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,

Fichiers de configuration

61

PublicKeyToken=b77a5c561934e089" >
name="MySettings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" />
</sectionGroup>
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section
name="MySettings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
</sectionGroup>
</configSections>
<userSettings>
<MySettings>
<setting name="MyEntierUsr" serializeAs="String">
<value>1234</value>
</setting>
</MySettings>
</userSettings>
<applicationSettings>
<MySettings>
<setting name="MyEntierApp" serializeAs="String">
<value>4321</value>
</setting>
</MySettings>
</applicationSettings>
</configuration>
<section

Voici le code de lapplication MyApp correspondante. On voit que les deux problmes de
lexemple prcdent sont rsolus grce lintroduction dune classe nomme MySettings drive de la classe System.Configuration.ApplicationSettingsBase. Cette classe prsente une
proprit pour chaque paramtre. Les proprits relatives aux paramtres utilisateurs sont marques avec lattribut UserScopedSettingAttribute tandis que les proprits relatives aux paramtres de lapplication sont marques avec lattribut ApplicationScopedSettingAttribute.
On remarque que la proprit MyEntierApp na pas daccesseur set. En eet, dans le cadre de
cette technique les paramtres de lapplication doivent tre accessible en lecture seule :
Exemple 3-14 :
using System.Configuration ;
class Program {
static void Main() {
MySettings mySettings = new MySettings() ;
int myEntierUsr = mySettings.MyEntierUsr ;
System.Console.WriteLine(myEntierUsr) ;

MyApp.cs

62

Chapitre 3 : Construction, configuration et dploiement des applications .NET


myEntierUsr *= 10 ;
mySettings.MyEntierUsr = myEntierUsr ;
mySettings.Save() ;
System.Console.WriteLine(mySettings.MyEntierApp) ;
}
}
class MySettings : ApplicationSettingsBase {
[UserScopedSetting()]
public int MyEntierUsr {
get { return (int)(this["MyEntierUsr"]) ; }
set { this["MyEntierUsr"] = value ; }
}
[ApplicationScopedSetting()]
public int MyEntierApp {
get { return (int)(this["MyEntierApp"]) ; }
}
}

lexcution, le CLR se sert de la rflexion pour :

Mapper le nom des classes drives de la classe ApplicationSettingsBase avec les noms
des sections de configuration.

Mapper le nom des proprits de ces classes avec les paramtres de configuration.

Crer vos sections de configuration avec Visual Studio 2005


Malgr les avantages utiliser les sections de configuration, vous pouvez tre rebut par la fait de
crer une classe ddie et par le nombre dlments ajouts dans votre fichier de configuration.
Heureusement, Visual Studio 2005 prsente un diteur de configuration qui gre compltement
cette charge supplmentaire.
Pour ajouter une nouvelle section il vous sut de faire : Click droit sur le projet qui va tre
paramtr par la section  Add  New Items...  Settings File  Fournir le nom XXX de la section
 OK. Un nouvel lment XXX.settings est ajout au projet. Si vous slectionn cet lment,
lditeur de paramtres de configuration apparat. Pour chaque paramtre, vous pouvez choisir
son nom, son type, sil est un paramtre utilisateur ou un paramtre de lapplication et une
valeur par dfaut.
Un fichier source XXX.Designer.cs est associ llment XXX.settings. Cest un fichier gnr
qui contient la dfinition de la classe nomme XXX qui drive de ApplicationSettingBase. Cette
dfinition est constamment synchronise avec les modifications eectues dans lditeur. Aussi,
comme dans tous fichier gnr il ne faut pas y insr votre propre code.

Remarques sur les sections de configuration


En page 718 nous expliquons comment se servir de lAPI de protection des donnes par encryption pour sauvegarder des paramtres de configuration sous une forme encrypte.
Vous avez la possibilit de valider les modifications apportes aux paramtres de configuration
avant leur sauvegarde eective. Ceci est expliqu dans larticle How to : Validate Application
Settings des MSDN.

Dploiement des assemblages : XCopy vs. Rpertoire GAC

63

Il est intressant danalyser votre fichier machine.config pour sapercevoir que toutes les
sections standard prsentes un peu plus haut sont dclares par des lments <section>.
chacune de ces sections correspond un type standard tel que ConnectionStringsSection,
AppSettingsSection ou ProtectedConfigurationSection. LExemple 3-12 montre comment
avoir accs une instance de AppSettingsSection afin de modifier le contenu de la section
<appSettings>. Remarquez que certains de ces types tels que SystemDiagnosticsSection ne
doivent tre utiliss que par le CLR. Aussi, ils ne vous sont pas accessibles.
Les valeurs des paramtres utilisateurs ne sont pas stockes dans le fichier de configuration de
lapplication mais dans un fichier nomm [nom de lutilisateur].config. Ce fichier est stock dans le rpertoire spcifi par la proprit statique System.Windows.Forms.Application.
LocalUserAppDataPath.
Dans le cas dune application dploye avec la technologie ClickOnce (prsente dans la suite du
prsent chapitre), les fichiers de configuration (de lapplication et des utilisateurs) sont dploys
dans le rpertoire ClickOnce de lapplication (voir page 83).
Par dfaut, les classes drives de ApplicationSettingBase utilisent en interne la classe
System.Configuration.LocalFileSettingsProvider pour avoir accs en lecture et en criture aux fichiers de configuration. Cette classe drive de la classe System.Configuration.
SettingsProvider. Vous pouvez construire vos propres classes drives de SettingsProvider
afin dimplmenter vos propres mcanisme de persistance des paramtres dune application
(dans une base de donnes, dans la base des registres, par lintermdiaire dun service web
etc). Il sut ensuite dtablir le lien entre vos classes drives de ApplicationSettingBase
et vos classes drives de SettingsProvider en marquant les premires avec des attributs
SettingsProviderAttribute.
Le contrle ToolStrip du framework Windows Form 2.0 prsente des facilits pour stocker directement son tat dans les paramtres de lapplication. En outre, vous pouvez prvoir le mme
genre de facilit pour vos propres types de contrles. Ceci fait lobjet de larticle Application
Settings for Custom Controls des MSDN.

Dploiement des assemblages :


XCopy vs. Rpertoire GAC
.NET propose deux types de stratgies pour dployer les assemblages dune application.

La stratgie XCopy (qualifie aussi de stratgie de dploiement dassemblage titre priv)


consiste simplement copier les fichiers de lapplication dans un rpertoire. Parce quon ne
peut pas faire plus simple, cest la stratgie choisir par dfaut.

Une autre stratgie doit tre utilise pour dployer les assemblages qui sont partags par
plusieurs applications. Cette stratgie prsente des fonctionnalits trs intressantes tel
quune gestion performante du versionning.

Dploiement XCopy
La stratgie de dploiement dassemblages XCopy est la plus simple quil puisse exister. Elle
consiste copier tous les assemblages concernant lapplication dployer, dans un mme rpertoire. En gnral les dveloppeurs crent une arborescence de rpertoires. Ni la base des registres
ni les Active Directory de Windows ne sont sollicits lors dun dploiement de type XCopy.

64

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Pour ne perdre aucun fichier, il est conseill dencapsuler larborescence et ses fichiers dans un
seul fichier (par exemple une archive cab, zip ou rar, comme expliqu plus loin). La dsinstallation de lapplication consiste dtruire larborescence du disque dur. Le changement de version
consiste dtruire larborescence du disque dur et la remplacer par la nouvelle.
Ceux qui on dj eu grer un dploiement sous Windows, que ce soit en tant que dveloppeur,
quadministrateur ou en tant que simple utilisateur, mesurent le gain deort apport par les
dploiements de type XCopy.
Pour accder des fonctionnalits avances, telles que la cration de raccourcis bureaux ou
dicones dans la barre des tches, vous pouvez utiliser le service dinstallation de programmes
Windows nomms MSI, qui fait lobjet dune section un peu plus loin.

Assemblages partags et rpertoire GAC


Le dploiement dassemblages XCopy ne convient pas aux assemblages qui sont partags par
plusieurs applications installes sur la mme machine. En eet, si lon utilisait cette stratgie
pour un assemblage partag, il serait prsent sur le disque dur autant de fois quil y a dapplications qui lutilisent. Hormis le gaspillage despace, cette stratgie a pour principal dfaut dobliger changer plusieurs fichiers en plusieurs endroits lorsque lassemblage partag volue. La
stratgie appliquer pour les assemblages partags consiste utiliser un rpertoire spcialement
conu pour les stocker. Ce rpertoire sappelle le cache global des assemblages. Il est aussi nomm
rpertoire GAC (Global Assembly Cache en anglais). Le rpertoire GAC est en gnral le rpertoire : C:\Windows\Assembly\GAC . Ce rpertoire spcial est connu du CLR et par consquent,
il est connu lexcution de toutes les applications .NET.

Comprenez bien que la grande majorit des assemblages ne sont pas partags entre plusieurs applications. Vous rencontrerez rarement la ncessit de stocker un assemblage dans
le rpertoire GAC.
Un assemblage doit avoir un nom fort pour pouvoir tre stock dans le GAC. La notion de nom
fort est expose page 28. Un assemblage sans nom fort ne peut pas tre stock dans le GAC. En
revanche un assemblage nom fort peut ne pas tre stock dans le GAC (i.e dployer avec la
stratgie de dploiement XCopy).
Chaque fois quun assemblage nom fort est charg dans un processus, la vrification de la
validit de sa signature numrique doit tre faite. Cependant, pour les assemblages partags du
rpertoire GAC, ce nest pas ncessaire. En eet, lorsquun assemblage partag est insr dans
le rpertoire GAC, la validit de sa signature numrique est automatiquement vrifie. Si la
signature numrique nest pas valide, lassemblage ne peut tre insr dans le rpertoire GAC.
Il y a donc un gain de performance lorsquun assemblage est charg partir du rpertoire GAC.
Il est intressant de remarquer que lorsque vous installez la plateforme dexcution .NET
sur une machine, les assemblages contenant les classes du framework .NET (System.dll,
System.Data.dll, System.Security.dll etc) sont installs dans le GAC. Une copie de chacun
de ces assemblages existe dans le rpertoire dinstallation de .NET (i.e [Rep dinstallation
de Windows]\Microsoft.NET\framework\v2.0.XXXXX\). Si vous installez Visual Studio, il aura
recours ces copies lors de lanalyse des rfrences vers les assemblages standard du framework
dun projet. En eet, Visual Studio ne sait pas rfrencer les assemblages stocks dans le GAC.
Seul le CLR sait exploiter les assemblages du GAC grce une couche de code nomme assembly
loader qui fait lobjet de la section page 103.

Dploiement des assemblages : XCopy vs. Rpertoire GAC

65

Le stockage cte cte dans le GAC rsout lenfer des DLLs


Si on le considre comme un rpertoire Windows, le rpertoire GAC a une structure interne
labore dont vous navez heureusement pas vous proccuper. Cette structure est ncessaire
du fait que le GAC peut contenir plusieurs versions dun mme assemblage. Cette particularit
sappelle le stockage cte cte (side by side en anglais) des direntes versions dun mme assemblage. Lorsquun assemblage rfrence un assemblage partag, il utilise son nom fort. Or, le
numro de version fait partie du nom fort. Il ny a donc pas de risque de charger une mauvaise
version.
Le stockage cte cte rsout le problme connu sous le nom denfer des DLLs. (DLL hell en
anglais). Ce problme survient lorsque lon remplace une librairie dynamique Windows (i.e une
DLL pour Dynamic Link Library) par sa nouvelle version. Le moindre problme de compatibilit
entre les versions des DLLs implique que les application qui utilisent cette DLL et qui fonctionnaient correctement jusqualors avec lancienne version rencontrent des problmes dexcution
avec la nouvelle. Ce problme est si important quil a une grande part de responsabilit dans
la rputation dinstabilit des systmes dexploitation Windows. En plus de rduire linstabilit
du systme, le stockage cte cte dissipe lnorme contrainte de la compatibilit ascendante
souvent impose au processus de dveloppement dune nouvelle version.
Le seul problme avec cette technique de stockage cte cte est que le disque dur garde potentiellement toutes les versions de chaque assemblage. Pour les assemblages de tailles raisonnables, cela nest pas vraiment un problme. Pour des assemblages volumineux, il est du devoir
de lditeur de lapplication de faire en sorte de partager un maximum de donnes entre les
direntes versions.

Excution cte cte


Lexcution cte cte dun assemblage consiste autoriser lexcution simultane de plusieurs
versions de cet assemblage. Lattribut .NET AssemblyFlags autorise les versions dun assemblage
tre excutes cte cte dans un mme domaine dapplication ou dans un mme processus
ou seulement sur la mme machine.
Il vaut mieux viter dautoriser lexcution cte cte dun mme assemblage dans un domaine
dapplication ou mme dans un processus. En eet, cette possibilit est surtout utile pour permettre plusieurs applications dutiliser simultanment un assemblage, chacune ayant recours
la version qui lui convient. Il est plutt maladroit den arriver ce que de telles applications
sexcutent au sein dun mme processus ou pire, au sein dun mme domaine dapplication.
En outre, lexcution cte cte dun assemblage peut, dans certains cas, introduire des problmes trs diciles corriger. Ces problmes surviennent lorsquune mme ressource est utilise simultanment par plusieurs versions dun assemblage. Il y a un risque que les direntes
versions de lassemblage ne saccordent pas sur la faon dutiliser la ressource. La consquence
est un comportement imprvisible.

Visualiser et manipuler le GAC


Pour visualiser le GAC partir dun explorateur classique il existe lextension du shell ShFusion.
dll installe automatiquement lors du dploiement de la plateforme .NET sur une machine.
Cette extension permet de visualiser et de manipuler dune manire claire tous les assemblages
partags dans le GAC. Pour utiliser cette extension, il sut de visualiser le rpertoire GAC avec
un explorateur Windows, comme sur la Figure 3-1 :

66

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -1 : Visualisation du rpertoire GAC


Loutil GACUtil.exe utilisable en ligne de commande, permet aussi de visualiser et de manipuler
le rpertoire GAC. GACUtil.exe a de nombreuses options bien dcrites dans les MSDN. Par
exemple loption -l permet de lister tous les assemblages partags et loption -i suivie du nom
du fichier dun assemblage contenant le manifeste permet dinsrer un assemblage nom fort
dans le rpertoire GAC.

Seuls les administrateurs de la machine peuvent modifier le rpertoire GAC. Vous pouvez eectivement visualiser et modifier la structure interne du rpertoire GAC partir
dune fentre de commande. Cependant il est impratif de ne jamais chercher modifier
la structure du rpertoire GAC sans passer par loutil GACUtil.exe ou lextension du shell
ShFusion.dll (bien que cette manipulation soit possible avec un peu dastuce).
Le cache des images natives dcrit page 113, est inclus dans le rpertoire GAC. Ainsi lorsque
lon visualise le rpertoire GAC, que ce soit avec GacUtil.exe ou lextension ShFusion.dll, les
images natives sont aussi visualises.

Assemblage de stratgie dditeur (Publisher policy)


La problmatique
Malgr le problme connu sous le nom denfer des DLLs, le modle de partage des DLLs par
plusieurs applications a un avantage par rapport au modle de partage des assemblages bas
sur la prsence du numro de version dans le nom fort.

Assemblage de stratgie dditeur (Publisher policy)

67

Supposons quun diteur redistribue une nouvelle version de lassemblage partag parce que
des bugs ont ts corrigs. Cette version est compltement compatible avec la prcdente (compatibilit ascendante). Cette nouvelle version va tre installe cte cte avec lancienne version
dans le rpertoire GAC. Cependant les applications ne vont pas utiliser cette nouvelle version
car elles contiennent le numro de lancienne version cod en dur dans leurs manifestes.
Ce problme nexiste pas avec le modle de DLL classique puisque lancienne version de la DLL
est purement et simplement remplace par sa nouvelle version.

La solution
On pourrait rsoudre ce problme en recompilant et en rinstallant toutes les applications
clientes de lassemblage partag. La recompilation permettrait aux manifestes des assemblages
clients de mettre jour le numro de version de lassemblage partag. Clairement cette solution
lourde et fastidieuse est impraticable et il a fallu trouver une autre solution.
La solution propose par Microsoft est de crer un assemblage de stratgie dditeur (publisher policy
en anglais). Cet assemblage, plac dans le rpertoire GAC, nest l que pour crer une redirection
sur le numro de version dun autre assemblage partag, lui aussi stock dans le rpertoire GAC.
Lorsquune application demande la version 3.3.0.0 de lassemblage XXX elle utilisera en fait la
version 3.3.1.0 de lassemblage XXX, car lassemblage de stratgie dditeur associ XXX contient
linformation de redirection de 3.3.0.0 vers 3.3.1.0.
Lassemblage de stratgie dditeur nest quun fichier de configuration. La redirection est eectue par le CLR. En eet, cest le CLR qui est charg de localiser et de charger les assemblages
durant lexcution. Or, le CLR tient compte des assemblages de stratgie dditeur lors de cette
opration.
Un assemblage de stratgie dditeur ne redirige que les appels destins un assemblage. De plus
il peut y avoir plusieurs assemblages de stratgie dditeur pour un mme assemblage. Dans ce
cas, seule la version la plus rcente de lassemblage de stratgie dditeur sera prise en compte
par le CLR.
Le comportement par dfaut du CLR est de tenir compte des assemblages de stratgie dditeur.
Cependant pour une application donne, qui utilise un assemblage partag donn, lutilisateur
de lapplication peut dcider que le CLR ne doit pas tenir compte de lassemblage de stratgie
dditeur. Ceci est bien pratique dans le cas o lutilisateur se rend compte que la nouvelle version cre plus de problmes quelle nen rsout (et nous savons bien que ce genre de situation
arrive !). Nous exposons en page 106 comment raliser cette manipulation.

Comprenez bien que les assemblages de stratgie dditeur sont ncessaires lorsquun diteur a lgrement modifi un assemblage (pour une correction de bug en gnral) et na
pas modifi la compatibilit ascendante. Les assemblages de stratgie dditeur ne doivent
tre utiliss que dans le cadre prcis de cette problmatique.

Cration dun assemblage de stratgie dditeur


Concrtement un assemblage de stratgie dditeur est compos de deux modules. Un module
est un fichier de configuration XML dextension .config qui contient les informations de redirection souhaites par lditeur. Lautre module ne contient quun manifeste et est construit par

68

Chapitre 3 : Construction, configuration et dploiement des applications .NET

loutil al.exe, qui est prsent en page 37. Le fichier de configuration XML doit ressembler
celui-ci :
Exemple 3-15 :

Foo2.config

<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Foo2" culture= "neutral"
publicKeyToken="C64B742BD612D74A" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Remarquez llment <assemblyIdentity> qui reprend les composantes du nom fort de lassemblage sur lequel doit se faire la redirection ( part le numro de version).
Remarquez llment <bindingRedirect> qui redirige le numro de version. Cet lment peut
rediriger un intervalle de numro de version, vers un numro de version, avec la syntaxe suivante : "0.5.0.0-1.2.23.3". Vous pouvez aussi avoir recours plusieurs lments <bindingRedirect>.
La cration du module de lassemblage de stratgie dditeur contenant le manifeste, se fait avec
loutil al.exe utilis avec la syntaxe suivante :
>al.exe /out:policy.1.0.Foo2.dll /version:1.0.0.0 /keyfile:Cles.snk
/linkresource:Foo2.config

Loption /out prcise le nom quaura le module contenant le manifeste. Ce nom est form
par des rgles bien prcises.

policy indiquera au CLR que cest un assemblage de stratgie dditeur ;

1.0 indiquera au CLR que cette stratgie dditeur sapplique aux demandes de lassemblage Foo2.dll avec la version 1.0. Seul les numros majeurs et mineurs sont prciss
ici ;

Foo2 indiquera au CLR que cette stratgie dditeur sapplique aux demandes de lassemblage Foo2.dll.

Loption /version sapplique lassemblage de stratgie dditeur lui-mme et non Foo2.


dll. Le CLR prendra toujours la stratgie dditeur avec la version la plus leve pour un
couple de numros majeur/mineur donn.

Loption /keyfile prcise le fichier contenant la paire de cls publique/priv qui signera
lassemblage de stratgie dditeur. Ces cls doivent tre les mmes que celles qui signent
Foo2.dll pour prouver au CLR que cest bien le mme diteur qui fournit Foo2.dll et sa
stratgie dditeur. De plus, en tant quassemblage partag qui doit tre stock dans le GAC,
lassemblage de stratgie dditeur doit avoir un nom fort et doit donc tre sign.

Loption /linkresource prcise le nom du fichier de configuration au format XML qui


contient les informations de redirection de la stratgie dditeur. Comprenez bien que

Introduction au dploiement dapplications .NET

69

Foo2.config devient un module de lassemblage de stratgie dditeur grce cette option. Concrtement, le fichier Foo2.config nest pas inclus physiquement dans le module
policy.1.0.Foo2.dll mais est inclus logiquement dans lassemblage policy.1.0.Foo2.

Introduction au dploiement dapplications .NET


Lenvironnent de dveloppement Visual Studio 2005 permet la cration de projets spcialement
ddis aux dploiements dapplications. Six types de projets de dploiement sont disponibles
dans le menu File  New  Project...  Other Project Types  Setup and Deployment :

Cab Projet : cre un fichier cab, qui rassemble les fichiers installer et les compresse dans
un fichier .cab (i.e une archive cab). Une section du prsent chapitre est consacre au dploiement par fichier .cab.

Smart Device Cab Projet : cre un fichier cab spcialement adapt au dploiement sur
Windows CE sur des machines type Pocket PC ou Smart Phone.

Merge Module Project : cre un module de dploiement. Un tel module peut tre intgr
dans dautres projets de dploiement. Ainsi un mme module de dploiement peut tre
commun plusieurs projets de dploiement. La notion de modules de dploiement nest
pas la mme que la notion de modules dassemblages.

Setup Project : Cre un projet de dploiement exploitant la technologie MSI. Cette technologie permet deectuer des actions en plus dinstaller des fichiers. Une section est consacre
la technologie MSI un peu plus loin.

Web Setup Project : Permet de dployer un projet dapplication web en installant les fichiers dans des rpertoires virtuels IIS. Le dploiement dapplication web ASP.NET est un
sujet particulier. Plus dinformation ce sujet sont disponibles en page 871.

Setup Wizard : Ceci est une aide pas pas au moyen dun assistant, pour construire un
projet de dploiement dun de ces types.

Depuis sa premire version la plateforme .NET prsente une technologie nomme No Touch
Deployment (NTD) spcialement conue pour le dploiement dapplication partir dinternet.
Cette technologie est toujours supporte par la version 2.0. Elle na pas volue car Microsoft a
prfr miser sur une technologie nomme ClickOnce qui est beaucoup plus adapte aux fortes
contraintes dun dploiement internet (scurit, bande passante, mises jour etc). Ces technologies font chacune lobjet dune section de ce chapitre.
Il ny a pas de type de projets de dploiement spciaux aux technologies NTD et ClickOnce. NTD
se gre partir de fichier de configuration et de dploiement XCopy sur le serveur permettant
le tlchargement de lapplication. En revanche, nous verrons que Visual Studio 2005 prsente
des facilits pour permettre le dploiement dune application avec la technologie ClickOnce.
Pour cela, il sut que lapplication soit reprsente par un projet de type application fentre
Windows Forms ou application console.

MSI vs. .cab vs. XCopy vs. ClickOnce vs. NTD


Vous avez principalement le choix entre ces cinq techniques pour dployer une application
.NET qui nest pas une application web ASP.NET. On peut diviser ce type de dploiement en
deux catgories :

70

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Les dploiements lourds qui impactent profondment le systme dexploitation en installant des assemblages dans le GAC, en enregistrant des classes COM dans la base des registres,
qui sont utilisables par plusieurs utilisateurs de la machine, qui requirent des droits levs type administrateurs pour leur excution etc. Clairement, seule la technologie MSI est
adapte au dploiement de ces applications. De part leur taille et pour des raisons de scurit, ce type de dploiement se fait parfois partir dun CD plutt que par lintermdiaire
dun rseau. Lutilisateur doit payer le prix dun tel dploiement : une gestion de scurit
primaire avec la technologie authenticode (si dploy partir dun rseau), dicult de
mises jour, exposition aux consquences de lenfer des DLLs, dlais dus la livraison dun
CD etc.

Les dploiements lgers dapplication purement .NET qui impactent peu le systme dexploitation. Les quatre techniques cab, XCopy, ClickOnce et NTD peuvent tre envisages pour
ce type de dploiement. ClickOnce est en gnral privilgier de part ces fonctionnalits
avances notamment quant la gestion de la scurit, des mises jour et de la bande passante. Mis part la compatibilit ascendante, il ny a pas de cas o la technologie NTD est
prfrable ClickOnce. Les techniques cab et XCopy prsentent lavantage dtre trs simples
tant pour le dveloppeur que pour lutilisateur en gnral habitu au copier/coller de fichiers. Aussi, ces deux techniques ne sont pas dnues dintrt quand il sagit de raliser des
dploiements trs simples. Dans ce cas, on prfrera srement la technologie cab puisquun
seul fichier est toujours plus facile acheminer de lditeur lutilisateur que plusieurs.

MSI vs. ClickOnce


Voici un tableau qui devrait vous aider dcider lorsque vous hsitez entre le dploiement avec
MSI ou le dploiement avec ClickOnce. Ces deux technologies ne sont pas antagonistes. Il est
courant que le socle/les prrequis dune application se dploient avec MSI tandis la partie fonctionnelle, plus lgre pour le systme dexploitation mais aussi plus volutive, se dploie avec
ClickOnce :
Fonctionnalit

ClickOnce

MSI

Installation de fichiers.

Cration de raccourcis dans le menu des programmes

X
X

Cration de raccourcis bureau et autres.


Installation de classes COM sans enregistrement dans la base des registres (voir page 287).

Installation de classes COM et de composants COM+.

Association dextension de fichiers.

Installation de services Windows.

Installation dassemblages dans le GAC.

Gestion de ODBC.

Modification dans la base des registres.

Dployer une application avec un fichier cab

71

Self rparation

Interaction avec lutilisateur linstallation.

Choix du rpertoire dinstallation des fichiers.

Installation possible pour tous les utilisateurs.

Actions spciales au moment de linstallation ou de la dsinstallation.

Manipulation des droits sur les objets Windows.

Conditions dinstallation et vrification de la version et de ltat du systme dexploitation.

Vrification et tlchargement automatique des mises jour de lapplication.

Gestion de la scurit volue base sur le mcanisme CAS du CLR.

Installation de partie de lapplication la demande.

Possibilit de revenir la version prcdente aprs une mise jour.

Dployer une application avec un fichier cab


Vous avez la possibilit dajouter un projet de dploiement par fichiers cab votre solution,
comme le montre la Figure 3 -2 :

Figure 3 -2 : Un projet cab insr dans une solution


Vous pouvez ajouter des fichiers produits ou contenus par les autres projets de la solution
(Project output) comme illustr sur la Figure 3-3. Ici nous avons ajout les fichiers MonApplication.
exe et MaLibrairie.dll en slectionnant le type Primary Output pour chacun de ces projets.
Aprs compilation dun projet de dploiement cab, un fichier dextension .cab est produit. Avec
un outil de visualisation darchives vous pouvez examiner le contenu du fichier cab, et extraire
les fichiers contenus comme le montre la Figure 3 -4. Notez quun avantage de lutilisation de ce
type darchive, est que les fichiers sont compresss.
Un fichier dextension .osd est contenu dans le fichier cab. Ce fichier au format XML dcrit le
contenu du fichier cab :

72

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -3 : Ajout dans un projet de dploiement cab

Figure 3 -4 : Utiliser Winzip pour lanalyse dun fichier cab


<?XML version="1.0" ENCODING=UTF-8?>
<!DOCTYPE SOFTPKG SYSTEM "http://www.microsoft.com/standards/osd/osd.dtd">
<?XML::namespace href="http://www.microsoft.com/standards/osd/msicd.dtd"
as="MSICD"?>
<SOFTPKG NAME="CabProject" VERSION="1,0,0,0">
<TITLE> CabProject </TITLE>
<MSICD::NATIVECODE>
<CODE NAME="MonApplication">
<IMPLEMENTATION>
<CODEBASE FILENAME="MonApplication.exe">
</CODEBASE>
</IMPLEMENTATION>
</CODE>
</MSICD::NATIVECODE>
<MSICD::NATIVECODE>
<CODE NAME="MaLibrairie">
<IMPLEMENTATION>
<CODEBASE FILENAME="MaLibrairie.dll">
</CODEBASE>

Dployer une application avec la technologie MSI

73

</IMPLEMENTATION>
</CODE>
</MSICD::NATIVECODE>
</SOFTPKG>

Dployer une application avec la technologie MSI


Ajouter les fichiers de votre application
Lorsque vous avez ajout le projet setup votre solution, vous disposez de la vue Systme de
fichiers (File System en anglais). Cette vue contient par dfaut trois rpertoires : Le rpertoire
de lapplication ; Le bureau ; Le menu dmarrer.
Les fichiers que vous allez mettre dans ces rpertoires durant la construction du projet setup
seront automatiquement copis dans les rpertoires correspondants, sur la machine cible, lors
de linstallation. En gnral on fait en sorte de placer dans ces rpertoires les assemblages non
partags, produits des autres projets de la solution. Ceci est illustr sur la Figure 3-5. Remarquez
que cette opration se fait de la mme manire que dans un projet de dploiement utilisant un
fichier cab.

Figure 3 -5 : La vue systme de fichiers

Installer des raccourcis


Il est trs facile dajouter des raccourcis sur le bureau ou dans le menu dmarrer partir de la
vue systme de fichiers. Il sut de cliquer droit sur lexcutable, dajouter un raccourci et de
dplacer le raccourci dans le rpertoire souhait (bureau ou menu dmarrer ).

Ajouter un assemblage partag, dans le rpertoire GAC


Pour ajouter un assemblage partag dans le rpertoire GAC partir dun projet de dploiement
setup project , il faut dabord voir le rpertoire GAC dans la vue systme de fichier . Pour
cela, il sut de cliquer droit dans lespace rserv au rpertoire, et dajouter le rpertoire GAC,
comme sur la Figure 3 -6. Ensuite vous navez plus qu ajouter vos assemblages partags dans ce
rpertoire. De nombreux autres rpertoires spciaux peuvent tre ajouts.

Les proprits du projet


Vous avez en fait les deux types de proprits du projet.

Les pages de proprits, accessibles par maj F4. Elles permettent essentiellement de grer les
configurations du projet setup (Debug /Release...) et dajouter une signature Authenticode au
projet.

74

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -6 : Ajout du rpertoire GAC

La fentre de proprit du projet setup qui vous permet de configurer de nombreux attributs
associs au projet, comme son nom, sa culture ou le nom de lditeur, comme le montre la
Figure 3 -7.

Figure 3 -7 : Proprits dun projet setup

Manipuler la base des registres


Vous avez la possibilit dajouter des cls dans la base des registres relatives votre application
lors de son installation. Il sut pour cela de slectionner la vue Registre et de modifier les cls,

Dployer une application avec la technologie ClickOnce

75

comme sur la Figure 3 -8. Comprenez bien que la plupart des applications .NET ne devraient pas
utiliser la base des registres, aussi faites attention vos modifications.

Figure 3 -8 : Configurer la base des registres

Eectuer des actions personnalises durant linstallation


Il peut tre trs utile de lancer un excutable durant linstallation ou la dsinstallation dune application. Par exemple, vous pouvez crer un excutable qui vrifie certains paramtres de configuration sur votre machine, ncessaires au bon fonctionnement de lapplication. Vous pouvez
aussi souhaitez enregistrer un objet COM durant linstallation et le dsenregistrer durant la
dsinstallation. Dans ce dernier exemple, il est ncessaire dajouter le fichier regsvr32.exe
la liste des fichiers quinstalle lapplication. Ensuite, vous navez plus qu slectionner la vue
actions personnalises et ajouter des rfrences comme illustr par la Figure 3-9 :

Figure 3 -9 : Ajouter des actions personnalises

Modifier linterface utilisateur de linstallation


Vous pouvez modifier simplement le cours de linstallation en modifiant les fentres de
dialogue proposes par Visual Studio dans la vue Interface Utilisateur, comme illustr sur la
Figure 3 -10. Vous pouvez ajouter de nombreux dialogues, comme illustr sur la Figure 3-11.

Dployer une application avec la technologie ClickOnce


Pour tirer partie de la technologie ClickOnce, il est absolument ncessaire davoir assimil la
technologie Code Access Security (CAS) expose dans le chapitre 6 La gestion de la scurit .

76

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -10 : Enchanement des interfaces utilisateurs

Figure 3 -11 : Dialogues possibles pour linstallation

Organisation du rpertoire de dploiement


Commenons par dcrire larborescence de fichiers qui permet le dploiement dune application avec ClickOnce. Elle doit se situer dans un rpertoire de dploiement qui peut tre un rpertoire virtuel dIIS, un rpertoire FTP, un rpertoire partag au sein dun intranet ou un rpertoire dun CD. La technologie ClickOnce requiert deux fichiers particuliers en plus de lensemble
des fichiers de lapplication dployer :

Le manifeste de lapplication : Cest un fichier XML dextension .manifest qui rfrence lensemble des fichiers de lapplication et qui dfinit lensemble des permissions CAS ncessaires pour son excution. Son nom est la concatnation du nom de lassemblage contenant
le point dentre de lapplication (extension .exe comprise) avec lextension .manifest.

Le manifeste du dploiement : Cest un fichier XML dextension .application. qui rfrence


un fichier manifeste de lapplication. Il contient aussi les paramtres du dploiement.

Ces deux fichiers manifestes doivent tre signs numriquement par la technologie authenticode
pour pouvoir tre utiliss. Cela se traduit par une prsence dun lment <signature> dans

Dployer une application avec la technologie ClickOnce

77

chacun deux. La technologie authenticode est dcrit en page 230. Vous pouvez aussi consulter
larticle ClickOnce Deployment and Authenticode des MSDN.
Lorganisation des fichiers dans le rpertoire de dploiement est la suivante :
.\MyApp_1_0_0_0.application
// Manifeste de d
eploiement.
.\MyApp_1_0_0_0\MyApp.exe.manifest
// Manifeste de lapplication.
.\MyApp_1_0_0_0\MyApp.exe.deploy
// Fichiers de lapplication ...
.\MyApp_1_0_0_0\MyAppClassLib.dll.deploy // ... avec lextension .deploy.
.\MyApp_1_0_0_0\en-US\MyApp.resources.dll.deploy
...
On remarque quil existe un sous rpertoire par version de lapplication. Le nom de ce sous rpertoire ne contient pas ncessairement dindication sur le numro de version bien que ceci soit
une bonne pratique. Chacun de ces sous rpertoires contient le manifeste de lapplication relatif
la version ainsi que tous les fichiers dployer pour cette version. Une extension .deploy est
rajoute au nom de chacun de ces fichiers.
Le manifeste de dploiement est stock dans le rpertoire de dploiement. Puisquil rfrence
un manifeste dapplication qui est relatif une version de lapplication, il est lui-mme relatif
une version de lapplication. Aussi, il est judicieux que son nom soit la concatnation du nom
de lassemblage contenant le point dentre de lapplication (extension .exe non comprise) avec
une indication sur le numro de version suivie de lextension .application. Ainsi, plusieurs
manifestes de dploiement relatifs plusieurs versions dune mme application peuvent cohabiter dans le mme rpertoire de dploiement.

Concevoir un dploiement ClickOnce


Il existe plusieurs possibilits pour crer les fichiers manifestes, numres ici de la plus pratique
la plus fastidieuse :

Utiliser le menu Properties  Publish de Visual Studio 2005 sur un projet de type console ou
application Windows Forms.

Utiliser loutil fentr mageui.exe (mage pour Manifest Generation and Editing Tool) disponible dans le rpertoire SDK de linstallation de Visual Studio 2005.

Utiliser loutil en ligne de commande mage.exe disponible dans le mme rpertoire que
mageui.exe.

Construire ces fichiers XML la main en se basant sur la documentation ocielle.

Loutil mage.exe est particulirement utile si vous souhaitez intgrer la cration des fichiers manifestes dans un script MSBuild. Loutil mageui.exe est surtout pdagogique car il permet dobtenir une vue prcise et comprhensible du contenu de chacun des fichiers manifestes. Lutilisation de ces outils est dcrite dans larticle Walkthrough : Deploying a ClickOnce Application
Manually des MSDN. Hormis ces deux raisons, nous vous conseillons davoir recours Visual
Studio 2005 qui prsente linterface suivante :

Application Files... : Permet de choisir lensemble des fichiers qui constituent lapplication.
Lassemblage excutable gnr par le projet courant fait automatiquement partie de cet
ensemble. Les assemblages rfrencs par ce projet, les assemblages satellites gnrs par ce
projet ainsi que tous autres fichiers relatifs ce projet font aussi partie de cet ensemble. Mis

78

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -12 : Visual Studio et ClickOnce

part lassemblage excutable qui est forcment requis, chaque fichier peut tre marqu
comme prrequis, requis ou optionnel. Dans le premier cas, le fichier doit tre disponible
avant linstallation de lapplication. Dans le second cas le fichier doit tre tlcharg lors de
linstallation de lapplication. Dans le troisime cas le fichier doit faire partie dun groupe
de fichiers optionnels. Ces groupes permettent un tlchargement la demande des parties
fonctionnelles dune application. Nous revenons sur ce point un peu plus loin.

Prerequisites... : Permet de choisir les prrequis de linstallation de lapplication. Vous pouvez choisir notamment le framework .NET 2.0, SQL Server 2005 Express Edition mais aussi
nimporte quel application, fichier ou framework installer. Visual Studio 2005 vous permet
de gnrer un fichier setup.exe que la littrature anglo saxonne nomme bootstrapper. ClickOnce proposera au client de tlcharger et dexcuter le bootstrapper avant linstallation
de lapplication si sa machine na pas les prrequis. Vous pouvez spcifier do le bootstrapper doit tlcharger chaque prrequis (site de dploiement, site ociel du composant
etc). Techniquement lutilisateur na pas besoin dtre administrateur de la machine pour
excuter le bootstrapper. En pratique, il a souvent besoin de ltre puisque le bootstrapper doit
gnralement installer des composants qui requirent une installation avec la technologie
MSI. Plus dinformation concernant le bootstrapper sont disponible dans larticle Use the
Visual Studio 2005 Bootstrapper to Kick-Start Your Installation de Sean Draine consultable en ligne dans le MSDN Magazine doctobre 2004.

Updates... : Permet de positionner une politique de mise jour de lapplication. Une section est consacre aux mises jour un peu plus loin.

Dployer une application avec la technologie ClickOnce

79

Options... : Permet de positionner les paramtres de lapplication tels que le nom de lditeur, du produit, la gnration dune page web daide au tlchargement, la culture cible
du dploiement, lutilisation de lextension .deploy etc.
Publish Wizard... et Publish Now : permet de publier lapplication, cest--dire de gnrer les deux fichiers manifestes et de construire larborescence de fichier que nous avons
prsent. Il faut publier votre application dans un rpertoire de dploiement spcifique
pour chaque culture supporte. Vous pouvez choisir si le rpertoire de dploiement est
un rpertoire virtuel IIS, un rpertoire FTP ou un rpertoire Windows classique que vous
pouvez alors dupliquer sur un CD.

Cette interface vous permet aussi de choisir si lapplication est disponible oine. Dans ce cas
un raccourci vers lapplication est install dans le menu des programmes et lutilisateur na pas
besoin dtre connect internet pour la lancer.

Dploiement de lapplication et impratifs de scurit


Pour dployer lapplication sur une machine, il sut dexcuter le fichier manifeste de dploiement. Dans le cas dun dploiement web, cela se fait en gnral par lintermdiaire dune page
qui contient un lien vers ce fichier. Dans les autres cas il sut de double cliquer ce fichier (
partir du rpertoire FTP, du rpertoire partag intranet ou du rpertoire du CD).
moins que les deux fichiers manifestes aient t signs avec un certificat X.509 vrifiable par
la machine cible, une boite de dialogue apparat signalant lutilisateur que lapplication quil
est en train dinstaller a t conue par un diteur inconnue. Il a alors le choix de continuer
linstallation ou non.
Si lditeur de lapplication a pu tre vrifi grce au certificat, lensemble des permissions demandes dans le manifeste de lapplication lui est accord. Il en est de mme si lapplication est
installe partir dun CD. Sinon, un certain ensemble de permissions CAS peut lui tre accord
par les stratgies de scurit CAS. Tout dpend de la zone do linstallation est eectue (internet
untrusted, internet trusted, intranet etc).
Si lensemble des permissions dcrit dans le manifeste de lapplication nest pas compltement
inclus dans lensemble des permissions que les stratgies de scurit CAS sont prtent lui accorder alors une boite de dialogue informe lutilisateur. Sil choisit de continuer linstallation,
lensemble des permissions dcrit dans le manifeste de lapplication sera accord chaque excution de lapplication, sinon linstallation est annule. La littrature anglo saxonne qualifie cette
tape de permission elevation.
Une fois ces deux barrires potentielles passes (au pire nous en sommes trois clicks ),
ClickOnce installe lapplication dans un rpertoire ddi cette application. Nous reviendrons
sur ce rpertoire. Un raccourci vers lapplication est install dans le menu des programmes
si lapplication est disponible oine. Lapplication peut tre dsinstalle partir du menu
Ajout/Suppression de programmes. Le manifeste de dploiement a la possibilit de spcifier que
lapplication doit tre dmarre ds que le linstallation est termine.
Lapplication nest installe que pour lutilisateur courant. Si plusieurs utilisateurs dune machine ont besoin de cette application, elle devra tre installe pour chacun deux dans des rpertoires dirents.
Lapplication des stratgies de scurit de la machine ne se fait qu linstallation et lors des mises
jour de lapplication. Une fois que lapplication est installe elle sexcutera chaque fois dans
le cadre des permissions spcifies dans le manifeste.

80

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Contrairement la technologie MSI, vous ne pouvez pas personnaliser le processus de dploiement dune application ClickOnce. Concrtement, vous ne pouvez pas fournir de dialogues demandant lutilisateur des informations comme le rpertoire o installer lapplication. Cette
contrainte est essentielle pour garantir une scurit optimale.

Installation la demande
Nous avons vu quun fichier dune application peut tre marqu comme optionnel. Dans ce cas,
il fait partie dun groupe de fichiers optionnels. Tous les fichiers dun tel groupe sont tlchargs explicitement par le code de lapplication lorsque qu lexcution elle a besoin dun de ces
fichiers pour la premire fois. Cest linstallation la demande.
Le framework reprsent par lespace de nom standard System.Deployment permet au code
de votre application de tlcharger un groupe de fichier de lapplication non encore install. Ce code doit tre excut lors du dclenchement dun vnement type AppDomain.
AssemblyResolve ou AppDomain.ResourceResolve. On parle parfois dinstallation transactionnelle dun tel groupe de fichiers puisque soit ils sont tous installs soit aucun nest install.
Lexemple suivant expose un assemblage excutable MyApp qui rfrence un assemblage bibliothque MyLib. Supposons que dans un projet ClickOnce de dploiement, MyLib fasse partie dun
groupe de fichiers optionnels nomm MyGroup. Le code de la mthode AssemblyResolveHandler() est dclench lors de la premire excution de MyApp, lorsque MyLib nest pas trouv. Ce
code tlcharge les fichiers du groupe MyGroup puis rcupre et retourne lassemblage MyLib.
Notez la ncessit de la prsence de la classe Foo. Si nous ne nous servions pas dune classe
intermdiaire pour invoquer MyClass, le compilateur JIT compilerait la classe Program avant
davoir excut la mthode Main(). Lvnement AssemblyResolve serait donc dclench avant
mme que la mthode AssemblyResolveHandler() ait pu tre abonne :
Exemple 3-16 :

MyLib.cs

public class MyClass {


public override string ToString() {
return "Bonjour de MyLib." ;
}
}
Exemple 3-17 :

MyApp.cs

using System ;
using System.Reflection ;
using System.Deployment.Application ;
class Program {
public static void Main() {
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveHandler ;
Console.WriteLine("Bonjour de MyApp.") ;
Foo.FooFct() ;
}
static Assembly AssemblyResolveHandler(object sender,
ResolveEventArgs args) {
if ( ApplicationDeployment.IsNetworkDeployed ) {
// La propriete CurrentDeployement retourne null

Dployer une application avec la technologie ClickOnce

81

// lorsque lapplication nest pas d


eploy
ee
// do`u le test de la propri
et
e IsNetworkDeployed.
ApplicationDeployment currentDeployment =
ApplicationDeployment.CurrentDeployment ;
currentDeployment.DownloadFileGroup("MyGroup") ;
Console.WriteLine("Telechargement...") ;
}
return Assembly.Load("MyLib") ;
}
}
class Foo {
public static void FooFct() {
MyClass a = new MyClass() ;
Console.WriteLine(a) ;
}
}

Mises jour dune application installe avec ClickOnce


Les outils Visual Studio 2005, mageui.exe et mage.exe vous permettent de paramtrer la faon
dont une application dploye avec ClickOnce gre ses mises jour. Vous pouvez spcifier quand
lapplication doit vrifier que des mises jour sont disponibles ( chaque excution ou priodiquement). Vous pouvez aussi spcifier quand ces mises jour doivent tre tlcharges :

Aprs le dmarrage de lapplication pour un dmarrage rapide mais une prise en compte
des mises jour qu la prochaine excution.

Ou avant le dmarrage de lapplication pour sassurer que les utilisateurs excutent toujours
la version la plus rcente au prix dun dmarrage parfois ralenti.

Lors de la mise jour dune application la technologie ClickOnce ne tlcharge que les fichiers
qui ont chang. En outre lensemble des permissions accordes lors de linstallation dune application sera automatiquement hrit par les futures mises jour. Enfin, ds lors quune application a t au moins une fois mise jour sur une machine la technologie ClickOnce se met
stocker sur cette machine les donnes ncessaires pour ventuellement revenir la version prcdente. Ainsi, lutilisateur ne prend pas de risques en mettant jour une application puisquil a
la possibilit dans le menu de dsinstallation de programme de revenir la version prcdente.

Facilits pour manipuler lensemble des permissions CAS requises


par une application
Les projets Visual Studio 2005 de type console ou application Windows Forms prsentent un menu
Properties  Security qui facilite grandement la gestion des permissions CAS lors du dveloppement dune application :
Ce menu permet de spcifier si lapplication a besoin de la permission FullTrust pour tre
excute ou non. Dans le second cas, une grille vous aide construire lensemble exact des permissions dont lapplication besoin pour sexcuter. Cest cet ensemble qui sera spcifi dans le
manifeste de lapplication. Lorsquun type de permission est slectionn, vous pouvez cliquer le
bouton Properties pour obtenir une fentre qui permet de configurer finement la permission.

82

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Figure 3 -13 : Visual Studio et les permissions CAS


Visual Studio 2005 vous aide comparer cet ensemble avec les ensembles de permissions accords
par dfaut par les stratgies de scurit. Pour cela, il faut choisir partir de quelle zone lapplication sera dploye. Un avertissement signale quand une permission non accorde par dfaut
la zone slectionne est demande. Un avertissement signifie quau moment de linstallation
partir de cette zone, la technologie ClickOnce demandera lutilisateur de choisir sil souhaite
lever lensemble des permissions, ce qui fait toujours mauvais eet !
Il est fastidieux de dterminer lensemble exact de permissions dont une application a besoin
pour sexcuter correctement. Loutil permcalc.exe fourni avec Visual Studio 2005 permet de
calculer cet ensemble en analysant le code IL de lassemblage excutable ainsi que le code IL
des assemblages rfrencs (rcursivement). Selon la taille de votre application, ce calcul peut
prendre un certain temps, de lordre de la minute. Vous pouvez vous servir de cet outil en ligne
de commande pour produire lensemble des permissions au format XML. Ce contenu peut alors
tre intgr dans le manifeste de lapplication. Le bouton Calculate Permissions du menu Security
permet dinvoquer cet outil et dobtenir son rsultat dans la grille des permissions.
Visual Studio 2005 vous permet de dboguer votre application dans le contexte de scurit dfini
par lensemble de permissions que vous avez construit dans le menu Security. Cette possibilit est
trs pratique puisque cest aussi le contexte de scurit dans lequel sexcutera lapplication chez
les clients. Pour cela, Visual Studio 2005 gnre la compilation un assemblage nomm [Nom de
lapplication].vshost.exe dans le rpertoire de sortie qui contient aussi lassemblage [Nom

Dployer une application avec la technologie ClickOnce

83

de lapplication].exe. Cest cet assemblage excutable VSHost qui est lanc par Visual Studio
2005 lorsque vous demandez de dboguer lapplication. Le code de cet assemblage positionne
lensemble des permissions puis charge et excute lapplication.
Enfin, sachez que lintellisense de Visual Studio 2005 tient compte de lensemble des permissions
dfini dans le menu Security. Concrtement, il grise les noms des classes et mthodes du framework qui requirent des permissions non accordes.

Dtails sur linstallation et lexcution dune application dploye


avec ClickOnce
Si vous ouvrez le gestionnaire des tches sur une machine qui est en train dexcuter une application nomme XXX dploye avec ClickOnce, vous pourrez constater quil ny a pas de processus en
cours nomm XXX.exe. En eet, une application ClickOnce qui na pas la permission FullTrust
sexcute au sein dun processus nomm AppLaunch.exe. Il soccupe de la gestion de lensemble
des permissions CAS accordes lapplication. Le menu ajout pour une application dploye
avec ClickOnce fait rfrence un fichier rfrence dapplication Windows dextension .appref-ms.
Lors de louverture dun tel fichier Windows excute des objets COM dfinis dans le composant
dfshim.dll. Ces objets sont responsables du lancement de AppLaunch.exe.
Lors du lancement dune application dploye avec ClickOnce vous pouvez aussi observer lapparition du processus dfsvc.exe. Cest le moteur dexcution de ClickOnce. Cest ce processus
qui soccupe de la vrification et du tlchargement des mises jour. Ce processus sauto dtruit
aprs 15 minutes dinactivit.
Les fichiers dune application dploye avec ClickOnce sont installs dans un sous rpertoire du
cache des applications dployes avec ClickOnce. Ce cache est propre chaque utilisateur et une
taille borne (de lordre de 100 Mo). Cest le rpertoire cach \Documents and Settings\[Nom
de lutilisateur]\Local Settings\Apps\. Le nom du rpertoire dune de ces sous rpertoires est compos de valeurs de hachages calcules sur lapplication lors de linstallation.
Une association entre ce rpertoire et le fichier rfrence dapplication est prsente dans la
base des registres. Vous pouvez retrouver ce rpertoire soit par lintermdiaire de la proprit
AppDomain.CurrentDomain.BaseDirectory qui reprsente le rpertoire dhbergement de
lassemblage de lapplication, soit en lanant une recherche des fichiers de votre application
sous le rpertoire Apps. Lors de la mise jour dune application, un nouveau rpertoire frre du
prcdent est cr et ClickOnce fait en sorte quil ny ait que deux rpertoires consacrs cette
application : celui de la version prcdente et celui de la version courante.
Lors du dploiement dune application, ClickOnce fait la dirence entre les fichiers propres
lapplication (essentiellement larborescence de rfrencement des assemblages obtenue
partir de lassemblage excutable de lapplication, assemblages satellites compris) et les fichiers
de donnes de lapplication (les fichiers de configuration, les fichiers de base de donnes
.mdb, les fichiers XML etc). Les fichiers de donnes sont dploys dans un sous rpertoire
du cache des donnes des applications dployes avec ClickOnce ( savoir \Documents and Settings\[Nom de lutilisateur]\Local Settings\Apps\Data). Ce rpertoire de donnes de
lapplication est programmatiquement accessible par la proprit string ApplicationDeployment.DataDirectory{get;} et par la proprit stringSystem.Windows.Forms.Application.
LocalUserAppDataPath\{get;\}. Il faut que lapplication ait les permissions CAS daccs ce
rpertoire pour pouvoir exploiter les donnes. Pour cette raison, vous pouvez prfrer utiliser
le mcanisme de stockage isol dcrit en page 205 qui ne requiert que la permission de faire du
stockage isol. En eet, cette permission est accorde par dfaut aux zones internet et intranet.

84

Chapitre 3 : Construction, configuration et dploiement des applications .NET

Lors de la mise jour dune application, les anciens fichiers de donnes sont copis dans le nouveau rpertoire de donnes. Si ClickOnce tlcharge une nouvelle version dun fichier de donnes, celle-ci crase lancienne version. Cependant, pour viter la perte de donnes lancienne
version est alors copie dans un sous rpertoire inc.

Dployer une application avec la technologie


No Touch Deployment
Lorsque le framework .NET est install sur une machine, vous pouvez dmarrer un assemblage
tlchargeable sur le web directement partir dinternet explorer. Cette possibilit est nomme
No Touch Deployment (NTD).
Supposons que lassemblage Foo.exe qui a le code suivant, soit tlchargeable partir de
lURL : \texttt{www.smacchia.com/Foo.exe}. Nous utilisons le fait que la proprit Assembly.
CodeBase contienne lURL partir de laquelle a t tlcharge un assemblage :
Foo.cs

Exemple 3-18 :
using System.Reflection ;
using System.Windows.Forms ;
class Program {
static void Main() {
Assembly asm = Assembly.GetExecutingAssembly() ;
MessageBox.Show("Mon CodeBase est:" + asm.CodeBase) ;
}
}
Louverture dinternet explorer sur cette URL est illustre par la figure suivante :

Figure 3 -14 : Excution dun assemblage partir du web


Vous remarquez quinternet explorer ne vous a pas demand votre permission pour tlcharger
et dmarrer lassemblage. En eet, le fait que lexcutable soit un assemblage .NET a t dtect.
Ainsi, internet explorer dlgue la gestion de la scurit au CLR. Le CLR est hberg dans le
processus de internet explorer par un hte du moteur dexcution particulier introduit en page
95.

Et si .NET nest pas install sur la machine cible ?

85

Lexcution de lassemblage Foo.exe ne requiert aucune permission sensible. Le CLR nentrave


pas son excution car lapplication des stratgies de scurit permet par dfaut lexcution dassemblages tlchargs partir du web, tant quil ne demande pas de permissions particulires.
Cependant, le CLR naurait pas permis lassemblage Foo.exe deectuer une action sensible,
comme accder un fichier du disque dur ou modifier une cl de la base des registres, moins
bien sur, que la stratgie de scurit accorde une certaine confiance au site \url{www.smacchia.
com}. Un assemblage excut partir du web peut ne pas avoir de nom fort.

Le cache de tlchargement
Lorsquun assemblage est tlcharg pour la premire fois partir du web, il est automatiquement stock dans le cache de tlchargement (download cache en anglais). Le cache de tlchargement est un rpertoire entirement gr par le CLR. Le cache de tlchargement est indpendant du cache dinternet explorer ainsi que des dirents caches de la technologie ClickOnce. Les
avantages du cache de tlchargement sont les suivant :

Il vite de devoir tlcharger un assemblage accessible partir du web chaque fois quil
doit tre excut, do la notion de cache.

Il permet disoler physiquement les assemblages tlchargs du web des autres assemblages.

Le cache de tlchargement prsente les deux dirences suivantes avec le rpertoire GAC :

Le rpertoire GAC est global une machine. Il ne peut y avoir quun rpertoire GAC par
machine. En revanche, le CLR fait en sorte quil existe un cache de tlchargement par utilisateur dune machine. Ainsi, il peut y avoir plusieurs caches de tlchargement sur une
machine.

Le CLR accorde une plus grande confiance (i.e accorde plus de permissions) aux assemblages
contenus dans le rpertoire GAC quaux assemblages contenus dans le cache de tlchargement.

Enfin, sachez que loption /ldl de loutil gacutil.exe vous permet de visualiser le contenu du
cache de tlchargement :
C:>gacutil.exe /ldl
Microsoft (R) .NET Global Assembly Cache Utility. Version 2.0.XXXXX
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
The cache of downloaded files contains the following entries:
Foo.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null,
Custom=null
Number of items = 1
Plus dinformation sur la technologie No-Touch Deployment sont disponibles dans larticle NoTouch Deployment in the .NET Framework des MSDN.

Et si .NET nest pas install sur la machine cible ?


Lors de la compilation dun projet setup MSI le compilateur ache cet avertissement :

86

Chapitre 3 : Construction, configuration et dploiement des applications .NET


WARNING: This setup does not contain the .NET Framework which must be
installed on the target machine by running dotnetfx.exe before this
setup will install. You can find dotnetfx.exe on the Visual Studio .NET
Windows Components Update media. Dotnetfx.exe can be redistributed
with your setup.

Cela signifie que lors de linstallation de lapplication .NET, le framework .NET ne sera pas install sur la machine cible. Toutes les applications .NET ont besoin que le framework .NET soit
install sur la machine pour pouvoir tre excutes.
Lavertissement nous apprend que pour installer le framework .NET sur la machine cible, il faut
excuter le fichier dotnetfx.exe. Ce fichier peut tre redistribu librement.

dotnetfx.exe se trouve dans le rpertoire \wcu\dotNetFramework\dotnetfx.exe si vous


avez obtenu .NET partir dun DVD.

dotnetfx.exe se trouve dans le rpertoire \dotNetFramework\dotnetfx.exe si vous avez


obtenu .NET partir dun CD.

dotnetfx.exe peut facilement tre tlcharg partir du site de Microsoft.

Ce fichier fait une taille denviron 23 Mo. Il existe un tel fichier par version de .NET. Bien
entendu, il faut que le framework .NET 2.0 soit install sur une machine avant linstallation
dune application .NET, quelle que soit la technologie de dploiement utilise. Dans le cas dun
dploiement avec la technologie ClickOnce nous avons vu que vous pouvez prciser en prrequis
la prsence du framework .NET. Sinon, il faut soit fournir dotnetfx.exe par exemple sur le CD
distribu soit prvoir un lien pour permettre au client de le tlcharger.

4
Le CLR
(le moteur dexcution
des applications .NET)

Le CLR (Common Langage Runtime en anglais et moteur dexcution en franais) est llment central de larchitecture de la plateforme .NET. Le CLR est une couche logicielle qui gre lexcution le code des applications .NET. Le mot grer recouvre en fait une multitude dactions
ncessaires au bon droulement de lapplication. Listons en quelques-unes :

hbergement de plusieurs applications dans un mme processus Windows ;

compilation du code IL en code machine ;

gestion des exceptions ;

destruction des objets devenus inutiles ;

chargement des assemblages ;

rsolution des types.

Le CLR est conceptuellement proche de la couche logicielle communment nomm machine


virtuelle en Java.

Les domaines dapplication


Notion de domaine dapplication
Un domaine dapplication (application domain en anglais, en gnral nomm AppDomain) peut
tre vu comme un processus lger. Un processus Windows peut contenir plusieurs domaines

88

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

dapplications. La notion de domaine dapplication est particulirement utilise pour quun


mme serveur physique hberge un grand nombre dapplications. Par exemple, la technologie
ASP.NET utilise les domaines dapplication pour hberger plusieurs applications web dans
un mme processus Windows. Les stress test de Microsoft crent jusqu 1000 applications web
simples dans un mme processus. Le gain de performance apport par la notion de domaine
dapplication est double :

La cration dun domaine dapplication consomme beaucoup moins de ressources que la


cration dun processus Windows.

Les domaines dapplication hbergs dans un mme processus Windows partagent les ressources du processus telles que le CLR, les types de base de .NET, lespace dadressage ou les
threads.

Lorsquun assemblage excutable est dmarr, le CLR cre automatiquement un domaine dapplication par dfaut pour lexcuter. Chaque domaine dapplication a un nom et le nom du domaine dapplication par dfaut est le nom du module principal de lassemblage lanc (extension
.exe comprise).
Si un mme assemblage est charg par plusieurs domaines dapplication dans un mme processus, il peut y avoir deux comportements :

soit le CLR va charger plusieurs fois lassemblage, une fois pour chaque domaine dapplication du processus.

soit le CLR va charger une seule fois lassemblage hors de tous domaines dapplication du
processus. Lassemblage pourra nanmoins tre utilis par chaque domaine dapplication
du processus. On dit que lassemblage est domain neutral.

On verra un peu plus loin dans ce chapitre que le choix de ce comportement est configurable.
Le comportement par dfaut est de charger plusieurs fois lassemblage.

Domaines dapplication et threads


Ne confondez pas les termes unit dexcution (les threads) et unit disolation dexcution (les domaines dapplication).
Il ny a pas de notion dappartenance entre les threads dun processus et les domaines dapplication de ce mme processus. Rappelons que ce nest pas le cas pour les processus, puisquun thread
appartient un seul processus, et chaque processus a un ou plusieurs threads. Concrtement
un thread nest pas confin un domaine dapplication et un instant donn plusieurs threads
peuvent sexcuter dans le contexte dun mme domaine dapplication.
Soit deux domaines dapplication DA et DB hbergs dans un mme processus. Supposons quune
mthode dun objet A, dont lassemblage contenant sa classe se trouve dans DA, appelle une
mthode dun objet B, dont lassemblage contenant sa classe se trouve dans DB. Dans ce cas cest
le mme thread qui excute la mthode appelante et la mthode appele. Ce thread traverse la
frontire entre les domaines dapplications DA et DB.
Les concepts de thread et de domaine dapplication sont donc orthogonaux.

Les domaines dapplication

89

Dchargement dun domaine dapplication


Une fois quun assemblage est charg dans un domaine dapplication, vous navez pas la possibilit de le dcharger de ce domaine. En revanche, vous pouvez dcharger un domaine dapplication tout entier. Cette opration est lourde de consquences. Les threads en cours dexcution
dans le domaine dapplication dcharger doivent tre avorts par le CLR. Des problmes se
posent si certains dentre eux sont en train dexcuter du code non gr. Les objets grs contenus dans le domaine dapplication doivent tre collects.
Nous vous dconseillons davoir une architecture qui compte sur le fait de dcharger rgulirement des domaines dapplication. Nous verrons nanmoins que ce type darchitecture constitue parfois un mal ncessaire pour implmenter des serveurs qui ncessitent un grand taux de
disponibilit (type 99,999% du temps comme SQL Server 2005).

Lisolation des domaines dapplication


Lisolation entre domaines application est le fruit des caractristiques suivantes :

Un domaine dapplication peut tre dcharg indpendamment des autres domaines applications.
Un domaine dapplication na pas daccs direct aux assemblages et aux objets des autres
domaines application.
Un domaine dapplication peut avoir sa propre stratgie de gestion dexception. Du moment quil ne laisse pas sortir une exception hors de ses frontires, les problmes dun
domaine dapplication nont pas dincidence sur les autres domaines dapplication du mme
processus.
Chaque domaine dapplication peut dfinir sa propre stratgie de scurit pour paramtrer
le mcanisme CAS quutilise le CLR pour accorder des permissions au code dun assemblage.
Chaque domaine dapplication peut dfinir ses propres rgles pour paramtrer le mcanisme quutilise le CLR pour localiser ses assemblages avant de les charger.

La classe System.AppDomain
Une instance de la classe System.AppDomain est une rfrence vers un domaine dapplication du
processus courant. La proprit statique CurrentDomain{get;} de cette classe vous permet de
rcuprer une rfrence vers le domaine dapplication courant. Lexemple suivant illustre lutilisation de cette classe pour numrer les assemblages contenus dans le domaine dapplication
courant :
Exemple 4-1 :
using System ;
using System.Reflection ; // Pour la classe Assembly
class Program {
static void Main() {
AppDomain curAppDomain = AppDomain.CurrentDomain;
foreach ( Assembly assembly in curAppDomain.GetAssemblies() )
Console.WriteLine( assembly.FullName ) ;
}
}

90

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Lancer une nouvelle application dans le processus courant


La classe AppDomain contient la mthode statique CreateDomain() qui permet de crer un nouveau domaine dapplication dans le processus courant. Cette mthode est surcharge en plusieurs formes. Pour utiliser cette mthode, vous devez prciser :

(obligatoire) Un nom pour le nouveau domaine dapplication.

(optionnel) Les rgles de scurit qui paramtrent le mcanisme CAS sur ce domaine dapplication (par un objet de type System.Security.Policy.Evidence).

(optionnel) Des informations qui paramtrent le mcanisme de localisation des assemblages de ce domaine dapplication utilis par le CLR (par un objet de type System.
AppDomainSetup).

Les deux proprits importantes dun objet de type System.AppDomainSetup sont :

ApplicationBase : Cette proprit dfinit le rpertoire de base du domaine dapplication.


Ce rpertoire est notamment utilis par le mcanisme de localisation dassemblages charger dans ce domaine dapplication.

ConfigurationFile : Cette proprit rfrence un ventuel fichier de configuration du domaine dapplication. Ce fichier doit tre au format XML et contient des informations sur
les rgles de versionning et/ou la localisation des assemblages.

Maintenant que vous savez crer un domaine dapplication, nous pouvons prsenter comment
charger et excuter un assemblage excutable dans un domaine en appelant la mthode System.AppDomain.ExecuteAssembly(). Lassemblage doit tre de type excutable et son excution
commence son point dentre. Cest le thread qui appelle ExecuteAssembly() qui excute le
code de lassemblage charg. Cette constatation illustre le fait quun thread peut indiremment
traverser les frontires entre domaines dapplication.
Voici un exemple de code en C  . Le premier code est lassemblage qui va tre charg dans un
domaine dapplication par lassemblage produit par la compilation du second code :
Exemple 4-2 :

AssemblyACharger.exe

using System ;
using System.Threading ;
public class Program {
public static void Main() {
Console.WriteLine(
"Thread:{0} Vous avez le bonjour du domaine : {1}",
Thread.CurrentThread.Name,
AppDomain.CurrentDomain.FriendlyName) ;
}
}
Exemple 4-3 :
using System ;
using System.Threading ;
public class Program {
public static void Main() {

AssemblyChargeur.exe

Les domaines dapplication

91

// Nomme le thread courant.


Thread.CurrentThread.Name = "MonThread" ;
// Cree un objet de type AppDomainSetup.
AppDomainSetup info = new AppDomainSetup() ;
info.ApplicationBase = "file:///"+ Environment.CurrentDirectory ;
// Cree un domaine dapplication sans param`
etres de s
ecurit
e.
AppDomain newDomaine = AppDomain.CreateDomain(
"NouveauDomaine", null, info) ;
Console.WriteLine(
"Thread:{0} Appel `a ExecuteAssembly() `
a partir du appdomain {1}",
Thread.CurrentThread.Name,
AppDomain.CurrentDomain.FriendlyName) ;
// Charge lassemblage AssemblyACharger.exe dans
// newDomain puis lexecute.
newDomaine.ExecuteAssembly("AssemblyACharger.exe") ;
// Decharge le nouveau domaine
// ainsi que lassemblage quil contient.
AppDomain.Unload(newDomaine) ;
}
}
Cet exemple ache :
Thread:MonThread Appel `a ExecuteAssembly() `
a partir du appdomain
AssemblyChargeur.exe
Thread:MonThread Vous avez le bonjour du domaine : NouveauDomaine
Notez la ncessit de lajout "file:///" pour prciser que lassemblage se trouve en local. Si ce
ntait pas le cas on aurait pu utiliser "http:///". Dans ce cas, lassemblage aurait pu tre charg
partir du web.
Cet exemple illustre aussi que le nom du domaine dapplication par dfaut est le nom du module principal de lassemblage lanc (ici AssemblyChargeur.exe).

Excuter du code dans le contexte dun domaine dapplication


Grce la mthode dinstance AppDomain.DoCallBack() vous avez la possibilit dexcuter du
code dun assemblage du domaine dapplication courant dans le contexte dun autre domaine
dapplication. Pour cela, ce code doit tre contenu dans une mthode que vous rfrencez
laide dun dlgu de type System.CrossAppDomainDelegate. Tout ceci est illustr par lexemple
suivant :
Exemple 4-4 :
using System ;
using System.Threading ;
public class Program {
public static void Main() {
Thread.CurrentThread.Name = "MonThread" ;
AppDomain newDomaine = AppDomain.CreateDomain(
"NouveauDomaine") ;

92

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


CrossAppDomainDelegate deleg = new CrossAppDomainDelegate(Fct);
newDomaine.DoCallBack(deleg);
AppDomain.Unload(newDomaine) ;
}
public static void Fct() {
Console.WriteLine(
"Thread:{0} Fct() execut
ee dans {1}",
Thread.CurrentThread.Name,
AppDomain.CurrentDomain.FriendlyName) ;
}
}

Cet exemple ache :


Thread:MonThread Fct() executee dans

NouveauDomaine

Soyez conscient que cette possibilit dinjecter du code dans un domaine dapplication peut
provoquer la leve dune exception de scurit si vous navez pas les droits susants.

Les vnements dun domaine dapplication


La classe AppDomain prsente les vnements suivants :
vnement

Description

AssemblyLoad

Dclench lorsquun assemblage vient dtre charg.

AssemblyResolve

Dclench lorsquun assemblage charger na pu tre trouv.

DomainUnload

Dclench lorsque le domaine dapplication est sur le point dtre


dcharg.

ProcessExit

Dclench lorsque le processus se termine (dclench avant


DomainUnload).

ReflectionOnlyPreBindAssemblyResolve

Dclench juste avant le chargement dun assemblage destin


tre utilis par le mcanisme de rflexion.

ResourceResolve

Dclench lorsquune ressource na pu tre trouve.

TypeResolve

Dclench lorsquun type na pu tre trouv.

UnhandledException

Dclench lorsquune exception nest pas rattrape dans le code du


domaine dapplication.

Certains de ces vnements peuvent tre utiliss pour remdier au problme qui la dclench.
Lexemple suivant illustre comment exploiter lvnement AssemblyResolve pour faire en sorte
de charger un assemblage partir dun emplacement qui na pas t pris en compte par le mcanisme de localisation des assemblages du CLR :

Les domaines dapplication

93

Exemple 4-5 :
using System ;
using System.Reflection ; // pour Assembly
public class Program {
public static void Main() {
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve ;
Assembly.Load("AssemblyACharger.dll");
}
public static Assembly AssemblyResolve(object sender,
ResolveEventArgs e) {
Console.WriteLine("Assemblage {0} non trouv
e : ", e.Name) ;
return Assembly.LoadFrom(@"C:\AppDir\CetAssemblyACharger.dll");
}
}
Si la seconde tentative de chargement marche, aucune exception nest lance. Le nom de lassemblage charg la seconde tentative nest pas ncessairement le mme que celui que lon a
essay de charger la premire tentative.
En page 519 nous prsentons un programme exploitant lvnement UnhandledException.
En page 80 nous expliquons que les classes de lespace de nom System.Deployment peuvent avoir
recours au vnements AssemblyResolve et ResourceResolve afin de tlcharger dynamiquement un groupe de fichier lorsquune application dploye avec la technologie ClickOnce en a
besoin pour la premire fois.

Echanger des informations entre domaines


Vous avez la possibilit de stocker des donnes dans un domaine dapplication grce aux mthodes SetData() et GetData() de la classe AppDomain. Comme le montre lexemple suivant,
chacune de ces donnes est indexe par une chane de caractres :
Exemple 4-6 :
using System ;
using System.Threading ;
public class Program {
public static void Main() {
AppDomain newDomaine = AppDomain.CreateDomain(
"NouveauDomaine") ;
CrossAppDomainDelegate deleg = new CrossAppDomainDelegate(Fct) ;
newDomaine.DoCallBack(deleg) ;
// Recup`ere dans lappDomain par d
efaut lentier index
e par la
// string "UnEntier" dans lappDomain NouveauDomaine.
int unEntier = (int) newDomaine.GetData("UnEntier");
AppDomain.Unload(newDomaine) ;
}
public static void Fct() {
// Cette methode sexecute dans lappDomain NouveauDomaine.
// Indexe la valeur 691 par la string "UnEntier".
AppDomain.CurrentDomain.SetData("UnEntier", 691);

94

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


}
}

Cet exemple prsente le cas simple car nous stockons un entier qui est un type valeur connu
de tous les domaines dapplication. La technologie .NET Remoting qui fait lobjet du chapitre 22
permet de partager des objets entre domaines dapplication dune manire plus volue mais
plus complexe.

Chargement du CLR dans un processus grce lhte du


moteur dexcution
Les DLLs mscorsvr.dll mscorwks.dll
Chaque version du CLR est publie avec deux DLLs :

La DLL mscorsvr.dll contient une version du CLR spcialement optimise pour les machines ayant plusieurs processeurs ( svr est employ pour serveur ).

La DLL mscorwks.dll contient une version du CLR spcialement optimise pour les machines ayant un seul processeur ( wks est employ pour workstation ou station de
travail ).

Ces deux DLLs ne sont pas des assemblages. En consquence, elles ne contiennent pas de code
IL, et elles ne peuvent tre analyses avec loutil ildasm.exe. Chaque processus excutant une
ou plusieurs applications .NET contient une de ces deux DLLs. On dit que le processus hberge
le CLR. Nous allons expliquer ici comment le chargement dans le processus dune de ces DLLs
se droule.

Lassemblage mscorlib.dll
Une autre DLL joue un rle prpondrant dans lexcution des applications .NET. Cest la DLL
mscorlib.dll qui est en fait lunique module de lassemblage du mme nom. Cet assemblage
contient les implmentations des types de base du framework .NET (comme la classe System.
String, la classe System.Object ou la classe System.Int32). Cet assemblage est rfrenc par
tous les assemblages .NET. Cette rfrence est cre automatiquement par tous les compilateurs qui produisent du code IL. Il est intressant danalyser lassemblage mscorlib avec loutil
ildasm.exe. Nous prcisons que lassemblage mscorlib rside lexcution hors de tous domaines dapplication. En outre, il ne peut tre charg/dcharg quune fois durant la vie dun
processus.

Notion dhte du moteur dexcution


Le fait que .NET ne soit encore intgr aucun systme dexploitation entrane que le chargement du CLR la cration dun processus, doit tre pris en charge par le processus lui-mme.
La tche de charger le CLR dans le processus incombe une entit appele lhte du moteur
dexcution (runtime host en anglais). Lhte du moteur dexcution tant l pour charger le CLR,
une partie de son code est forcment non gr, puisque cest le CLR qui gre le code. Cette
partie soccupe de charger le CLR, de le configurer, et de passer le thread courant en mode gr.

Chargement du CLR dans un processus grce lhte du moteur dexcution

95

Une fois le CLR charg dans le processus, lhte du moteur dexcution a dautres responsabilits telles que la dcision prendre lorsquune exception nest pas rattrape. La figure suivante
illustre les direntes couches de cette architecture. On voit que le CLR et lhte schangent
des informations grce une API :
CLR

Host API
Hte du moteur dexcution

Win32 API
Systme dexploitation Windows

Figure 4 -1 : Dcoupage en couches de lhbergement du CLR


Il existe plusieurs htes du moteur dexcution et vous avez mme la possibilit de crer vos
propres htes du moteur dexcution. Le choix de tel ou tel hte du moteur dexcution pour
une application a un impact sur les performances de lapplication et ltendue des fonctionnalits utilisables par lapplication. Les htes du moteur dexcution dj existants fournis par
Microsoft sont :

Lhte du moteur dexcution des applications Console et Winform : Lassemblage excutable est charg dans le domaine dapplication par dfaut. Lorsquun assemblage est charg
implicitement, il est charg dans le mme domaine dapplication que lassemblage qui le
sollicite. En gnral ce type dapplication na pas utiliser dautres domaines dapplication
que le domaine dapplication par dfaut.

Lhte du moteur dexcution ASP.NET : Cre un domaine dapplication pour chaque


application web. Une application web est identifie par son rpertoire virtuel racine
ASP.NET. Si une requte web sadresse une application dj charge dans un domaine,
la requte est automatiquement route vers ce domaine par lhte.

Lhte du moteur dexcution Microsoft Internet Explorer : Par dfaut, il cre un domaine dapplication par site web visit. Ainsi les assemblages de dirents sites peuvent
sexcuter avec dirents niveaux de scurit. Le CLR nest charg que lorsque Internet Explorer a besoin dexcuter un assemblage pour la premire fois. Plus dinformations au sujet
de cet hte sont disponibles en page 84.

Lhte du moteur dexcution de SQL Server 2005 : Les requtes vers la base peuvent tre
crites en langage IL. Le CLR nest charg que la premire fois quune telle requte doit
tre eectue. Un domaine dapplication est cr pour chaque couple utilisateur/base de
donnes. Dans le chapitre courant, nous aurons loccasion de revenir sur les possibilits et
proprits de cet hte trs particulier introduit avec .NET 2.0.

Plusieurs versions du CLR sur la mme machine


mscorlib tant un assemblage nom fort, plusieurs versions peuvent cohabiter cte
cte sur la mme machine. De plus, toutes les versions du CLR sont dans le rpertoire
%windir%\Microsoft.NET\Framework . Tous les fichiers concernant une version du framework

96

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

.NET sont dans un sous rpertoire portant le numro de version. Comme il existe un rpertoire
par version du framework .NET installe sur la machine, il peut y avoir aussi plusieurs versions
des DLLs mscorsvr.dll et mscorwks.dll sur la mme machine. Cependant une seule version
du CLR peut tre charge et hberge par chaque processus.
Le fait davoir potentiellement plusieurs versions du CLR entrane lexistence dune petite
couche logicielle qui prend en paramtre la version dsire du CLR et la charge. Ce code est
appel cale (shim en anglais) et est stock dans la DLL mscoree.dll (MSCOREE veut dire Microsoft
Component Object Runtime Execution Engine).
Il ne peut y avoir quune seule DLL cale par machine. La cale est directement appele par
lhte du moteur dexcution par la fonction CorBindToRuntimeEx(). La DLL mscoree.dll
contient des interfaces et des classes COM. La fonction CorBindToRuntimeEx() cre un objet
COM, instance de la classe COM CorRuntimeHost. Cest cet objet qui va sinterfacer avec le
CLR. Pour manipuler cet objet la fonction CorBindToRuntimeEx() retourne linterface COM
ICLRRuntimeHost.
Lappel CorBindToRuntimeEx() pour crer lobjet COM sinterfaant avec le CLR, transgresse
des rgles fondamentales de COM : il ne faut pas appeler la fonction CoCreateInstance() pour
crer un objet COM. De plus, les appels aux mthodes AddRef() et Release() sur linterface
ICLRRuntimeHost nont pas deet.

Chargement du CLR avec CorBindToRuntimeEx()


Voici le prototype de la fonction CorBindToRuntimeEx() qui charge la DLL cale, qui charge
son tour la DLL contenant le CLR :
HRESULT CorBindToRuntimeEx(
LPWSTR
pwszVersion,
LPWSTR
pwszBuildFlavor,
DWORD
flags,
REFCLSID
rclsid,
REFIID
riid,
LPVOID *
ppv) ;
Ce prototype est spcifi dans le fichier mscoree.h et le code de cette fonction est dans la DLL
cale mscoree.dll.

PwszVersion : Le numro de version du CLR sous la forme dune chane de caractres commenant par v (exemple "v2.0.50727"). Si cette chane nest pas prcise (i.e si on passe
un pointeur nul), la version la plus rcente disponible du CLR sera alors choisie.

PwszBuildFlavor : Ce paramtre indique si lon souhaite charger le CLR pour workstation (mscorwks.dll) en prcisant la chane de caractres "wks" ou le CLR pour serveur
(mscorsvr.dll) en prcisant la chane de caractres "svr". Si vous navez quun seul processeur, le CLR pour workstation sera charg quelle que soit la valeur de ce paramtre. Microsoft
prvoit dautres types de CLR, donc dautres valeurs pour ce paramtre.

flags : Ce paramtre est constitu par un ensemble de drapeaux.


En utilisant le drapeau STARTUP_CONCURRENT_GC on indique que lon souhaite que le
ramasse-miettes soit excut en mode concurrent, dit aussi mode simultan. Ce mode
permet un thread de raliser une bonne partie du travail du ramasse-miettes sans avoir
interrompre les autres threads de lapplication. Dans le cas dune excution non simultane

Chargement du CLR dans un processus grce lhte du moteur dexcution

97

du ramasse-miettes, le CLR utilise rgulirement les threads de lapplication pour eectuer


le travail du ramasse-miettes. Les performances globales du mode non concurrent sont
meilleures. En revanche le mode concurrent est souhaitable dans le cas dinterfaces utilisateurs, car il permet une plus grande ractivit de linterface.
On peut aussi positionner dautres drapeaux pour indiquer que lon souhaite que les assemblages soient chargs dune manire neutre par rapport aux domaines (loaded domainneutrally en anglais) ou non. Cela signifie que toutes les parties en lecture des assemblages
(le code, les structures...) ne seront prsentes physiquement quune seule fois dans le processus, mme si plusieurs domaines dapplication chargent le mme assemblage (dans le mme
esprit que le mapping des DLLs entre processus sous les systmes dexploitation Windows).
Un assemblage charg dune manire neutre par rapport aux domaines nappartient donc
pas un domaine spcifique. Cela prsente le principal inconvnient quil ne peut pas tre
dcharg. Il faut donc dtruire puis relancer tout un processus lors de la mise jour dun
tel assemblage. En outre il faut que le mme ensemble de permissions lui soit accord par
la stratgie de scurit de chaque domaine dapplication sinon il est charg plusieurs fois.
Chaque constructeur de classe dun assemblage charg dune manire neutre est invoqu
pour chaque domaine dapplication et il existe une copie de chaque champ statique de
chaque classe pour chaque domaine dapplication. En interne cela est possible grce une
table dindirection gre par le CLR. Ceci implique une petite baisse des performances. Cependant, charger un assemblage dune manire neutre par rapport aux domaines, permet
de ne le charger quune seule fois. En consquence, cette pratique est plus conomique en
terme de consommation de la mmoire, do un eet positif sur les performances. En outre,
chacune des mthodes dun tel assemblage nest compile par le compilateur JIT quune
seule fois.
Vous avez trois drapeaux possibles :

STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN : Aucun assemblage nest charg dune


manire neutre par rapport aux domaines. Cest ce comportement qui est pris par dfaut.
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN : Tous les assemblages sont chargs
dune manire neutre par rapport aux domaines. Aucun hte du moteur dexcution
nutilise cette option ce jour.
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST : Seuls les assemblages partags
contenus dans le GAC sont chargs dune manire neutre par rapport aux domaines.

Lassemblage mscorlib a un traitement spcial puisquil est charg dune manire neutre
une seule fois, quelle que soit la valeur de ce paramtre.
rclsid : Le classe ID (CLSID) de la classe COM (coclass) qui implmente linterface que
vous cherchez. Seules les valeurs CLSID_CorRuntimeHost, CLSID_CLRRuntimeHost ou null
sont acceptes. La deuxime valeur est apparue avec la version 2.0 de .NET car de nouvelles
fonctionnalits dues lhbergement du CLR dans le processus de SQL Server 2005 ont ncessit une nouvelle interface et une nouvelle classe COM.
pwszBuildFlavor : Linterface ID (IID) de linterface COM dont vous avez besoin. Seules les
valeurs IID_CorRuntimeHost, IID_CLRRuntimeHost ou null sont acceptes
ppv : Un pointeur vers linterface COM retourne, de type ICorRuntimeHost ou IClrRuntimeHost selon ce qui a t demand.

La cale ne fait que charger le CLR dans le processus. Le cycle de vie du CLR est contrl par la partie non gre du code de lhte du moteur dexcution grce linterface COM ICLRRuntimeHost

98

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

renvoye par la fonction CorBindToRuntimeEx(). Cette interface prsente entre autres deux mthodes Start() et Stop() dont les noms illustrent leurs actions.

Exemple de code C++ non gr dun hte du moteur dexcution


propritaire
Dans la trs grande majorit des projets, vous naurez pas crer votre hte du moteur dexcution. Cependant, il est rassurant de savoir que ceci est possible et instructif den avoir dj vu
un.
Pour tre bien compris, ce code ncessite une bonne connaissance de la technologie COM. Noublions pas que les systmes dexploitation type Windows 2000/XP utilisent encore COM comme
mcanisme de communication/modlisation. Le CLR prsente la possibilit dtre configur
et manipul par du code non gr, par lintermdiaire dinterfaces COM. Parmi ces interfaces
COM, linterface ICCLRRuntimeHost joue un rle prpondrant. Nous avons vu un peu plus
haut quune telle interface peut tre obtenue en appelant la fonction CorBindToRuntimeEx().
Voici donc le code C++ du plus basique des htes du moteur dexcution :
Exemple 4-7 :
// Pour compiler ce code, il faut vous lier avec
// la biblioth`eque statique mscoree.lib.
#include <mscoree.h>
// Limport doit se faire sur une seule ligne.
#import <mscorlib.tlb> raw_interfaces_only ...
... high_property_prefixes("_get","_put","_putref") ...
... rename("ReportEvent","ReportEventG
er
e") rename_namespace("CRL")
// Utilise lespace de nom ComRuntimeLibrary.
using namespace CRL ;
ICLRRuntimeHost * pClrHost = NULL ;
void main (void){
// Obtient une interface COM ICorRuntimeHost sur le CLR.
HRESULT hr = CorBindToRuntimeEx(
NULL, // On demande la derni`
ere version du CLR.
NULL, // On demande la version workstation du CLR.
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(LPVOID *) &pClrHost) ;
if (FAILED(hr)){
printf("Echec de lobtention du ptr ICLRRuntimeHost !") ;
return ;
}
printf("Pointeur ICCLRRuntimeHost obtenu.\n") ;
printf("Lance le CLR.\n") ;
pClrHost->Start();

Chargement du CLR dans un processus grce lhte du moteur dexcution

99

// Ici, on peut utiliser notre pointeur COM sur le CLR.


pClrHost->Stop();
printf("CLR stoppe.\n") ;
pClrHost->Release();
printf("Ciao !\n") ;
}
Supposons que nous ayons un assemblage MyManagedLib.dll stock dans le rpertoire C:\Test
compil partir de ce code :
Exemple 4-8 :

MyManagedLib.cs

namespace MyProgramNamespace {
public class MyClass {
public static int MyMethod(string s) {
System.Console.WriteLine(s) ;
return 0 ;
}
}
}
Vous pouvez facilement invoquer la mthode Main() partir de notre hte comme ceci :
Exemple 4-9 :
...
pClrHost->Start() ;
DWORD retVal=0 ;
hr = pClrHost->ExecuteInDefaultAppDomain(
L"C:\\test\\MyManagedLib.dll", // Chemin + Asm.
L"MyProgramNamespace.MyClass", // Nom entier du type.
L"MyMethod",
// Nom de la m
ethode elle doit avoir
//
la signature int XXX(string).
L"Hello from host!",
// Chane de caract`
eres en argument.
&retVal) ;
// Valeur OUT de retour.
pClrHost->Stop() ;
...

Modifier la configuration du CLR partir dun hte du moteur


dexcution propritaire
Il faut savoir que lorsque le CLR est vu comme un objet COM, linterface ICLRRuntimeHost nest
pas la seule interface prsente par cet objet COM. Vous pouvez en fait manipuler le CLR
travers les interfaces COM suivantes :

ICLRRuntimeHost permet de crer et de dcharger des domaines dapplications, de grer le


cycle de vie du CLR et de crer des preuves pour le mcanisme de scurit CAS.

100

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

ICorConfiguration permet de spcifier au CLR certaines interfaces callback pour pouvoir


tre averti de certains vnements. Ces interfaces callback sont : IGCThreadControl IGCHostControl IDebuggerThreadControl. Les vnements prsents par ces interfaces sont
beaucoup moins fins que ceux prsents par lAPI de profiling du CLR, prsente un peu
plus bas.

ICorThreadPool permet de manipuler le pool de threads .NET du processus et de modifier


certains paramtres de configuration.

IGCHost permet dobtenir des informations sur le fonctionnement du ramasse-miettes et de


modifier certains paramtres de configuration.

IValidator permet de valider lentte PE/COFF des assemblages (utilis notamment par
loutil peverify.exe).

IMetaDataConverter permet de convertir les mtadonnes COM (i.e tlb/tlh) en mta donnes .NET (utilis notamment par loutil tlbexp.exe).

Pour savoir quelles sont les mthodes prsentes par ces interfaces, il sut dobserver les fichiers
mscoree.h, ivalidator.h et gchost.h. Pour obtenir une de ces interfaces partir de votre interface IClrRuntimeHost, il sut dutiliser la fameuse mthode QueryInterface() comme ceci :
...
ICorThreadpool * pThreadPool
= NULL ;
hr = pClrHost->QueryInterface( IID_ICorThreadpool,
(void**)&pThreadPool);
...

Spcificits de lhte du moteur dexcution de SQL Server 2005


Comme nous lavons mentionn, lhte du moteur dexcution de SQL Server 2005 est trs particulier du fait des contraintes de fiabilit, de scurit et de performance hors normes imposes
par un tel SGBD.
La contrainte prpondrante de ce type de serveur est la fiabilit. Les trois mcanismes rgion
dexcution contrainte (CER), finaliseur critique et rgion critique (CR) ont t ajouts au CLR pour
renforcer la fiabilit dune application .NET. Ils font lobjet de la section 4 Facilits fournies
par le CLR pour rendre votre code plus fiable , page 125 du prsent chapitre.
La seconde contrainte est la scurit. Pour viter que du code malveillant utilisateur soit charg
par inadvertance, tous les assemblages utilisateurs sont chargs par lhte du moteur dexcution partir de la base de donnes. Cela implique une phase de prchargement des assemblages
dans la base par ladministrateur DB (le DBA). Durant cette phase, le DBA peut prciser que
lassemblage appartient une des catgories SAFE, EXTERNAL_ACCESS et UNSAFE selon le niveau
de confiance quil accorde son code. Lensemble des permissions qui sera accord lassemblage lexcution par le mcanisme CAS est fonction de cette catgorie et donc, de ce niveau
de confiance. Enfin, certaines fonctionnalits du framework .NET considres comme sensibles,
telles que certaines classes de lespace de noms System.Threading, ne sont pas exploitables
partir dun assemblage charg dans SQL Server 2005.
La troisime contrainte est la performance. On la satisfait en exploitant les ressources dune
manire optimale. Dans ce contexte, les ressources les plus prcieuses sont les threads et les pages
mmoires charges en mmoires vive. Lide est de minimiser le nombre de context switching

Chargement du CLR dans un processus grce lhte du moteur dexcution

101

entre threads et de minimiser le nombre de pages stockes en mmoire virtuelle sur le disque
dur pour profiter au mieux de la mmoire vive disponible.

Les context switching sont normalement grs par le mcanisme de multitche premptif du rpartiteur de Windows, dcrit en page 138. Lhte du moteur dexcution de SQL Server 2005 implmente sont propre mcanisme de multitche plutt bas sur un modle de multitche coopratif.
Dans ce modle, ce sont les threads eux mmes qui dcident du moment o le processeur peut
passer un autre thread. Un avantage est que les choix de ces moments sont plus fins, car lis
la smantique des traitements. Il en rsulte une gestion globale plus ecace des threads. Un
autre avantage est que ce modle est adapt lutilisation du mcanisme de fibre de Windows
(fiber en anglais).

Une fibre est un thread logique qualifi aussi de thread lger. Un mme thread physique Windows peut enchaner lexcution de direntes fibres. Lavantage est que le passage dune fibre
une autre est une opration beaucoup moins coteuse que le context switching. En contrepartie,
lorsque le mode fibre est utilis par lhte du moteur dexcution de SQL Server 2005, on perd
la garantie dune relation biunivoque entre les threads physiques Windows et les threads grs
.NET. Un mme thread gr nest plus forcment excut par le mme thread physique durant
son toute son existence. Il faut donc absolument saranchir de tout type danit entre ces
deux entits. Parmi les types danits possibles entre un thread gr et son thread physique
sous-jacent on peut citer les Thread Local Storage, la culture courante et les objets de synchronisation Windows qui drivent de la classe WaitHandle style mutex, smaphore ou vnement.
Sachez que vous avez la possibilit de communiquer lhte du moteur dexcution le commencement et la fin dune rgion de code qui exploite ce type danit avec les mthode BeginThreadAffinity() et EndThreadAffinity() de la classe Thread. Ce dernier saura alors dsactiver temporairement le mode fibre, auquel on attribut un facteur doptimisation de 20% des
performances. (Note : Dans la version actuelle de SQL Server 2005, le mode fibre a t retir car les
ingnieurs de Microsoft ntaient pas certains de la fiabilit de ce mode. Ce mode sera rintroduit dans
les versions ultrieures de ce produit).

Le stockage des pages mmoires est normalement gr par le mcanisme de mmoire virtuelle
de Windows dcrit en page 134. Lhte du moteur dexcution de SQL Server 2005 sintercale
entre les demandes mmoire du CLR et ce mcanisme afin de profiter au mieux de la mmoire
vive disponible. Cela permet aussi dobtenir un comportement prvisible lorsquune demande
dallocation mmoire du CLR choue. Comme nous le verrons plus tard, cette particularit est
essentielle pour assurer la fiabilit de serveurs tels que SQL Server 2005.

Toutes ces nouvelles possibilits sont accessibles grce une API qui permet au CLR et son
hte de dialoguer. Une trentaine de nouvelles interfaces ont t prvues. Elles sont listes un
peu plus bas. Il incombe lhte de fournir un objet qui implmente linterface IHostControl
au moyen de la mthode ICLRRuntimeHost.SetHostControl(IHostControl*). Cette interface
prsente la mthode GetHostManager(IID,[out]obj). Le CLR appelle cette mthode pour obtenir un objet de lhte auquel il va dlguer une responsabilit telles que la gestion des threads
ou le chargement des assemblages. Plus dinformation ce sujet sont disponibles en analysant
les interfaces suivantes dans le fichier mscoree.h.

102

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Responsabilit
Chargement
blages

des

Interfaces
par lhte.
assem-

implmentes

Interfaces implmentes par


le CLR.

IHostAssemblyManager
IHostAssemblyStore

ICLRAssemblyReferenceList
ICLRAssemblyIdentityManager

Scurit

IHostSecurityManager
IHostSecurityContext

ICLRHostProtectionManager

Gestion des checs

IHostPolicyManager

ICLRPolicyManager

Gestion de la mmoire

IHostMemoryManager
IHostMalloc

ICLRMemoryNotificationCallback

Ramasse-miettes

IHostGCManager

ICLRGCManager

Threading

IHostTaskManager
Task

Pool de threads

IHostThreadPoolManager

Synchronisation

IHostSyncManager IHostCriticalSection
IHostManualEvent IHostAutoEvent
IHostSemaphore

ICLRSyncManager

I/O Completion

IHostIoCompletionManager

ICLRIoCompletionManager

ICLRTaskManager ICLRTask

ICLRDebugManager

Dbogage
vnements du CLR

IHost-

IActionOnCLREvent

ICLROnEventManager

Profiler vos applications


Cette section pour but de vous sensibiliser une fonctionnalit particulirement utile du
CLR : la possibilit de profiler trs finement son excution. En dautres termes, vous pouvez
demander au CLR dexcuter une de vos mthodes non gres lorsquun vnement particulier
survient tel que le commencement de la compilation JIT dune mthode ou la fin du chargement dun assemblage. Il est assez logique que ces callbacks soient des mthodes non gres. En
eet, ces mthodes sont censes nous permettre dobserver ltat du CLR donc il vaut mieux que
ce ne soit pas ce dernier qui prenne en charge leurs excutions.
Pour exploiter le profiling du CLR il faut que vous construisiez une classe COM implmentant
linterface ICorProfilerCallback. Cette interface est dfinie dans le fichier corprof.h.qui se
trouve dans le rpertoire SDK\v2.0\Include de linstallation de Visual Studio. Elle contient environs 70 mthodes. Une fois linterface ICorProfilerCallback implmente, il faut que le CLR
sache que cest cette implmentation quil doit utiliser pour raliser les callbacks. Pour communiquer le CLSID de cette implmentation au CLR, vous navez pas besoin de crer votre hte du

Localisation et chargement des assemblages lexcution

103

moteur dexcution. Il sut de positionner correctement les deux variables denvironnement


Cor_Enable_Profiling et Cor_Profiler. Cor_Enable_Profiling doit tre positionne une
valeur non nulle pour spcifier que le CLR doit raliser les callbacks. Cor_Profiler doit tre
positionne avec le CLSID ou le ProgID de limplmentation de ICorProfilerCallback.
Nous nallons pas dtailler cette possibilit car larticle The .NET Profiling API and the DNProfiling Tool de Matt Pietrek du numro de Dcembre 2001 de MSDN Magazine (disponible
gratuitement en ligne) le fait dj. Nous vous conseillons vivement de tlcharger le code de cet
article est de faire quelques essais sur vos propres applications .NET. Vous prendrez ainsi toute
la mesure de la quantit de tches ralises par le CLR ! Le code fourni avec cet article a aussi
lavantage de montrer comment utiliser un masque pour navoir accs qu certaines catgories
de callback. La manipulation ncessaire pour utiliser ce code est trs facile :

Enregistrez la classe COM sur votre machine ;

ouvrez une fentre de commande ;

positionnez les variables denvironnement en excutant le fichier batch fourni ;

lancez votre application partir de cette fentre de commande.

Localisation et chargement des assemblages


lexcution
Que lon applique la stratgie de dploiement des assemblages titre priv, ou la stratgie de
dploiement des assemblages partags, cest le CLR qui localise et charge les assemblages lexcution. Plus exactement, ces tches incombent au sous systme chargeur dassemblages (assembly
loader en anglais) du CLR plus communment nomm fusion. Lide gnrale est que le processus de localisation des assemblages par le CLR est configurable et intelligent.

Configurable dans le sens o un administrateur peut facilement dplacer des assemblages


tout en permettant quils soient encore localisables. Configurable aussi dans le sens o lon
peut rediriger lutilisation de certaines versions vers dautres versions (de deux faons direntes, avec les assemblages de stratgie dditeur, ou avec un fichier de configuration prsent plus loin).

Intelligent dans le sens o lorsque le CLR ne trouve pas un assemblage dans un rpertoire,
il applique un algorithme qui lui permet, par exemple, daller chercher dans les sous rpertoires qui ont le mme nom que lassemblage. Intelligent aussi dans le sens o si une
application marchait mais ne marche plus cause dun assemblage qui nest plus localisable,
on puisse revenir trs simplement en arrire.

Ces deux contraintes sont satisfaites grce un lalgorithme de localisation de fusion.


Nous avons vu page 15 quun assemblage pouvait tre constitu de plusieurs fichiers nomms
modules. Ici, nous parlons du processus de localisation dun assemblage, cest--dire du processus de localisation du module principal dun assemblage, (celui avec le manifeste). Rappelons
que tous les modules dun mme assemblage doivent se trouver imprativement dans le mme
rpertoire.

104

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Quand le processus de localisation est-il dmarr ?


Le processus de localisation du CLR est souvent sollicit lorsque le code dun assemblage en
cours dexcution besoin de charger un autre assemblage. Si la localisation choue, elle se solde
par lenvoi de lexception System.IO.FileNotFoundException. Le processus de localisation est
utilis lors de :

Lutilisation dune des surcharges de la mthode AppDomain.Load() qui charge un assemblage dans le domaine dapplication sur lequel est appele la mthode.

Le chargement implicite par le CLR dun assemblage. Ceci est dcrit dans la prochaine section lorsque nous expliquons comment le CLR rsout les types.

Cet algorithme nest pas utilis lors de :

Lutilisation de la mthode AppDomain.ExecuteAssembly() que nous avons vu au dbut de


ce chapitre lors de la prsentation des domaines dapplication.

Lutilisation de la mthode statique Assembly.LoadFrom() qui accepte le chemin (absolu ou


relatif partir du rpertoire dexcution de lapplication) et le nom de lassemblage.

Remarquez que ces deux mthodes ne fonctionnent pas selon la philosophie .NET puisquelles
prennent en argument un chemin vers un fichier et non le nom dun assemblage. Il est prfrable de ne jamais utiliser LoadFrom() qui peut toujours tre remplace par Load(). En revanche
lutilisation trs simple de la mthode AppDomain.ExecuteAssembly() peut savrer tre un raccourci ecace.

Lalgorithme de localisation
Recherche dans le rpertoire GAC
Lalgorithme va dabord aller chercher lassemblage dans le rpertoire GAC, condition que le
nom de lassemblage fourni soit un nom fort. Lors de la recherche dans le rpertoire GAC, lutilisateur de lapplication peut choisir ou non (par lintermdiaire du fichier de configuration de
lapplication) dutiliser les assemblages de stratgies dditeurs relatifs lassemblage localiser.

Utilisation de llment CodeBase


Si le nom fort nest pas correctement fourni ou sil est fourni mais que lassemblage nest pas
trouv dans le rpertoire GAC alors peut tre que lassemblage doit tre charg partir dune
URL (Unique Resource Locator). Cette possibilit est la base du mcanisme de dploiement No
Touch Deployment dcrit en page 84.
En eet, le fichier de configuration de lapplication peut avoir un lment <codebase> relatif
lassemblage localiser. Dans ce cas, cet lment dfinit une URL et le CLR tentera de charger
lassemblage partir de cette URL. Si lassemblage nest pas trouv cette URL, la localisation
chouera. Si lassemblage localiser est dfini avec un nom fort dans le fichier de configuration,
alors lURL peut tre une adresse internet, une adresse intranet ou un rpertoire de la machine
courante. Si lassemblage localiser nest pas dfini avec un nom fort, lURL ne peut tre quun
sous rpertoire du rpertoire du rpertoire de lapplication.
Voici un extrait dun fichier de configuration dune application avec llment codebase> :

Localisation et chargement des assemblages lexcution

105

...
<dependentAssembly>
<assemblyIdentity name="Foo3" publicKeyToken="C64B742BD612D74A"
culture= "fr-FR"/>
<codebase version="3.0.0.0"
href = "http://www.smacchia.com/Foo3.dll>"/>
</dependentAssembly>
...
Vous remarquez que lURL contient le nom du module de lassemblage contenant le manifeste (en loccurrence Foo3.dll). Si lassemblage a dautres modules, comprenez bien que tous
ces modules doivent tre aussi tlchargeables cette adresse (en loccurrence http://www.
smacchia.com/).
Lorsquun assemblage est tlcharg partir du web grce llment <codebase>, il est stock
dans le cache de tlchargement. Aussi, avant de tenter de charger un assemblage partir dune
URL, fusion va consulter ce cache pour vrifier sil na pas dj t tlcharg.

Le mcanisme de probing
Si le nom fort nest pas correctement fourni ou sil est fourni mais que lassemblage nest pas
trouv dans le rpertoire GAC et si le fichier de configuration ne contient pas dlment <codebase> relatif lassemblage localiser, alors lalgorithme tente de trouver lassemblage en
sondant certains rpertoires. Cest le mcanisme de probing (qui peut se traduire par sondage en
franais) qui est expos par lexemple suivant :

Supposons que lon veuille localiser lassemblage Foo (notez quon ne fournit pas lextension du fichier).
Supposons que les sous rpertoires indiqus par llment <probing> du fichier de configuration de lapplication pour lassemblage Foo soit "Path1" et "Path2\Bin".
Supposons que le rpertoire de base de lapplication soit "C:\AppDir\".
Supposons enfin quaucune information de culture nait t fournie avec le nom dassemblage, ou quil soit de culture neutre (i.e ce nest pas un assemblage satellite).

La recherche de lassemblage se fait dans cet ordre, dans les rpertoires suivants :
C:\AppDir\Foo.dll
C:\AppDir\Foo\Foo.dll
C:\AppDir\Path1\Foo.dll
C:\AppDir\Path1\Foo\Foo.dll
C:\AppDir\Path2\Bin\Foo.dll
C:\AppDir\Path2\Bin\Foo\Foo.dll
C:\AppDir\Foo.exe
C:\AppDir\Foo\Foo.exe
C:\AppDir\Path1\Foo.exe
C:\AppDir\Path1\Foo\Foo.exe
C:\AppDir\Path2\Bin\Foo.exe
C:\AppDir\Path2\Bin\Foo\Foo.exe
Si lassemblage est un assemblage satellite (i.e il na pas une culture neutre, par exemple il a la
culture "fr-FR") la recherche se fait dans cet ordre, dans les rpertoires suivants :

106

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


C:\AppDir\fr-FR\Foo.dll
C:\AppDir\fr-FR\Foo\Foo.dll
C:\AppDir\Path1\fr-FR\Foo.dll
C:\AppDir\Path1\fr-FR\Foo\Foo.dll
C:\AppDir\Path2\Bin\fr-FR\Foo.dll
C:\AppDir\Path2\Bin\fr-FR\Foo\Foo.dll
C:\AppDir\fr-FR\Foo.exe
C:\AppDir\fr-FR\Foo\Foo.exe
C:\AppDir\Path1\fr-FR\Foo.exe
C:\AppDir\Path1\fr-FR\Foo\Foo.exe
C:\AppDir\Path2\Bin\fr-FR\Foo.exe
C:\AppDir\Path2\Bin\fr-FR\Foo\Foo.exe

Lvnement AppDomain.AssemblyResolve
Enfin, si lassemblage na pas t trouv aprs toutes ces tapes, le CLR dclenche lvnement
AssemblyResolve de la classe AppDomain. Les mthodes abonnes cet vnement peuvent retourne un objet de type Assembly. Cela vous permet de fournir votre propre mcanisme de
localisation dassemblage. Cette possibilit est notamment exploite par la technologie de dploiement ClickOnce pour tlcharger des groupes de fichiers la demande comme illustr en
page 80.

Schma rcapitulatif de lalgorithme de localisation


Vous pouvez vous servir de loutil fuslogvw.exe pour analyser les logs produits par fusion. Cet
outil est trs pratique pour dterminer les causes dun chec du chargement dun assemblage.

Llment <assemblyBinding> du fichier de configuration


Le fichier de configuration dun assemblage ASM peut contenir des paramtres utiliss par fusion lorsque le code de ASM dclenche la localisation et le chargement dun autre assemblage.
Le fichier dinstallation peut contenir un lment <probing> qui spcifie un ou plusieurs sous
rpertoires que le mcanisme de probing doit sonder. Il peut aussi contenir un lment <dependentAssembly> pour chaque assemblage localiser potentiellement. Chacun de ces lments
<dependentAssembly> peut contenir les informations suivantes sur la faon dont cet assemblage
doit tre localis :

Llment <publisherPolicy> dtermine si les ventuelles stratgies dditeur relatives


lassemblage charger doivent tre prises en compte lors de sa localisation.

Llment <codebase>, dcrit dans la section prcdente, dfinit une URL partir de laquelle lassemblage localiser doit tre charg.

Llment <bindingRedirect> permet de rediriger le numro de version comme le ferait


une stratgie dditeur. La stratgie dditeur sapplique toutes les applications clientes
dun assemblage partag, alors quici seule lapplication paramtre par ce fichier tient
compte de cet lment.

Voici quoi peut ressembler un fichier de configuration (notez la ressemblance avec le fichier
de configuration dune stratgie dditeur) :

Localisation et chargement des assemblages lexcution

107

Assembly.Load() est appele par lapplication foo pour localiser lassemblage ASM

Le
nom fourni
est-il un
nom fort ?

foo est-elle
configure pour tenir
compte des stratgies
dditeur pour
ASM ?
N

Y a-t-il
un assemblage de
stratgie dditeur dans
le GAC pour
ASM ?

ASM
avec ce nom
fort, est-il dans
le GAC ?

La version de lassemblage
chercher est modifie
selon la stratgie dditeur

O
ASM est localis dans le GAC

foo.exe.config
a-t-il un lment codeBase
avec une URL
O
pour ASM ?
N
ASM est-il
localis dans un
sous-rpertoire du rpertoire de
lOO (en tenant compte des sous-rpertoires spcifis parllment
N
probing) ?

ASM est-il
localis dans le cadre
de tlchargement ?

ASM est localis lURL

ASM
est-il localis
lURL spcifie ?

ASM se localise dans le


cache de tlchargement
Lvnement
AssemblyResolve
retourne-t-il un
assemblage ?
chec de AssemblyLoad()

O
ASM est localis dans le sous-rpertoire

ASM a t charg par une mthode

Figure 4 -2 : Lalgorithme du CLR de localisation dun assemblage


Exemple 4-10 :
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Path1;Path2/bin" />
<dependentAssembly>
<assemblyIdentity name="Foo1" culture= "neutral"
publicKeyToken="C64B742BD612D74A" />
<publisherPolicy apply="no" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Foo2" culture= "neutral"
publicKeyToken="C64B742BD612D74A" />
<codebase version="2.0.0.0"
href = "file:///C:/Code/Foo2.dll>"/>
</dependentAssembly>

108

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


<dependentAssembly>
<assemblyIdentity name="Foo3" culture= "fr-FR"
publicKeyToken="C64B742BD612D74A" />
<codebase version="3.0.0.0"
href = "http://www.smacchia.com/Foo3.dll>"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

Si vous ne souhaitez pas manipuler les documents XML, sachez que ces informations peuvent
tre configures partir de loutil .NET Framework Configuration accessible par : Menu Dmarrer  Panneau de configuration  Outils dadministration  Microsoft .NET Framework 2.0 Configuration  menu configured assembly.

Le mcanisme de Shadow Copy


Lorsquun assemblage est charg dans un processus, le ou les fichiers correspondants sont automatiquement verrouills par Windows. Vous ne pouvez ni les mettre jour ni les dtruire.
Vous pouvez seulement les renommer. Dans le cas dun serveur type ASP.NET, o un mme
processus hberge potentiellement plusieurs applications, ce comportement pourrait tre particulirement gnant puisquil nous obligerait redmarrer tous le processus chaque mise
jour dune seule application.
Heureusement, le chargeur dassemblages du CLR prsente le mcanisme dit de shadow copy.
Il consiste copier les fichiers dun assemblage dans un rpertoire cache ddi lapplication,
avant de le charger eectivement. Ainsi, les fichiers originaux de lassemblage peuvent tre mis
jour mme lorsque celui-ci est en cours dexcution. ASP.NET exploite cette possibilit et vrifie
priodiquement si un assemblage charg avec cette technique a t mis jour. Le cas chant,
lapplication est redmarre sans perte de requtes.
La proprit stringShadowCopyDirectories{get;set;} de la classe AppDomainSetup vous permet de prciser les rpertoires qui contiennent les assemblages qui doivent tre shadow copis .

Rsolution des types lexcution


Notion de chargement implicite et explicite dun assemblage
Nous avons souvent besoin dutiliser dans le code dun assemblage des types dclars dans
dautres assemblages. Il existe deux techniques pour exploiter les types dfinis dans un autre
assemblage :

Soit vous comptez sur votre compilateur pour crer un lien prcoce avec le type et vous
comptez sur le CLR pour charger lassemblage qui le contient au bon moment. Cest cette
technique nomme chargement implicite que nous dtaillons ici.

Soit vous chargez explicitement lassemblage lexcution et vous crer un lien tardif avec le
type que vous souhaitez exploiter.

Rsolution des types lexcution

109

Dans les deux cas la responsabilit du CLR incombe une partie du CLR nomme chargeur de
classe (class loader). Le chargement implicite dun assemblage A est dclench lors de la premire
compilation JIT dune mthode qui utilise un type de A.
La notion de chargement implicite se rapproche conceptuellement du mcanisme de chargement des DLLs. De mme, la notion de chargement explicite se rapproche conceptuellement du
mcanisme dutilisation dun objet COM par un langage de script (notamment le mcanisme
Automation et sa fameuse interface IDispatch).

Rfrencer un assemblage la compilation


Lorsque lassemblage A sexcute et lorsque le compilateur JIT compile une mthode de A qui
a besoin de types dfinis dans un assemblage B, B est implicitement charg par le CLR sil a t
rfrenc durant la compilation de A. Pour que A rfrence B, il faut avoir eectu une de ces
deux manipulations durant la compilation :

Soit vous compilez A en utilisant le compilateur csc.exe en ligne de commande ou dans


un script de construction. Il faut alors utiliser les options de compilation /reference /r et
/lib.

Soit vous utilisez lenvironnement Visual Studio et il faut utiliser le menu Reference  AddReference. Rappelons que lenvironnement Visual Studio .NET utilise dune manire implicite
le compilateur csc.exe pour produire des assemblages partir de code source C  . Cette
manipulation ne fait donc que forcer Visual Studio utiliser les options de compilation
/reference /r et /lib du compilateur csc.exe.

Dans les deux cas il faut prciser lassemblage charger implicitement par son nom (fort ou
non). Les types de lassemblage B ainsi que leurs membres, qui sont rfrencs dans lassemblage A sont rfrencs dans les tables TypeRef et MemberRef de lassemblage A. Lassemblage B est
rfrenc dans la table AssemblyRef de lassemblage A. Un assemblage peut rfrencer plusieurs
autres assemblages mais il faut absolument viter les rfrencements cycliques (A rfrence B qui
rfrence C qui rfrence A). Visual Studio sait dtecter et interdit les rfrencements cycliques.
Vous pouvez aussi utiliser loutil NDepend (dcrit en page 1034) pour dtecter les rfrencements cycliques dassemblages.

Un exemple
Voici un exemple illustrant linfrastructure mise en uvre pour que le CLR puisse charger
implicitement un assemblage. Le premier code dfinit lassemblage rfrenc par lassemblage
dfini par le deuxime code.
Exemple 4-11 :

Code de lassemblage r
ef
erenc
e : AssemblageBibliotheque.cs

using System ;
namespace MesTypes{
public class UneClasse{
public static int Somme(int a,int b){return a+b;}
}
}

110

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Exemple 4-12 :

Code de lassemblage r
ef
eren
cant : AssemblageExecutable.cs

using System ;
using MesTypes;
class Program{
static void Main(){
int i = UneClasse.Somme(3,4) ;
}
}
Notez lutilisation de lespace de noms MesTypes dans le code de lassemblage rfrenant. On
aurait pu aussi mettre les classes UneClasse et Program dans un mme espace de noms ou dans
lespace de noms anonyme. Dans ce cas, on aurait illustr le fait quun espace de noms peut
stendre sur plusieurs assemblages.
Il est intressant danalyser le manifeste de lassemblage rfrenant avec lutilitaire ildasm.
exe). On y voit clairement le fait que lassemblage AssemblageBibliotheque est rfrenc.
Cette rfrence est matrialise par une entre de la table AssemblyRef.

...
.assembly extern AssemblageBibliotheque{
.ver 0:0:0:0
}
...
Il est aussi intressant danalyser le code IL de la mthode Main(). On y voit clairement que
la mthode Somme() se trouve dans un autre assemblage appel AssemblageBibliotheque
(physiquement cette information est contenue dans les tables de mtadonnes MemberRef et
TypeRef) :

.method private hidebysig static void Main() cil managed


{
.entrypoint
// Code size
9 (0x9)
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldc.i4.3
IL_0001: ldc.i4.4
IL_0002: call
int32
[AssemblageBibliotheque]MesTypes.UneClasse::Somme(int32,int32)
IL_0007: stloc.0
IL_0008: ret
} // end of method Program::Main

La compilation Juste temps (JIT Just In Time)

111

Schma rcapitulatif
Un type inconnu dans ce domaine dapplication est rencontr
par le compilateur JIT dans le code IL dune mthode

Le type est
dfini dans
le module
courant de
lassemblage

Le type est dfini dans un autre assemblage


non encore charg
Comment
Lassemblage
ce type est-il
peut-il tre
rfrenc dans la table
localis ?
TypeRef ?
Le type est dfini dans un module de
lassemblage non encore charg
Le module
est-il dans le mme
rpertoire que le module
principal ?

Lexception System.IO.FileNotFoundException
est lance

O
Charge le module

Cration dune instance


de System.Type
dcrivant ce type

Charge lassemblage

Figure 4 -3 : Rsolution dun type lexcution

La compilation Juste temps (JIT Just In Time)


La portabilit au niveau binaire
Nous rappelons que les applications .NET sont, quel que soit leur langage source, compiles en
code IL. Le code IL est stock dans une section prvue cet eet, dans les assemblages et les
modules. Lapplication reste code en IL, jusquau moment o elle est excute.
Comme son nom lindique, le langage IL (Intermediate Langage) est un langage objet intermdiaire entre les langages .NET de haut niveau (C  , VB.NET...) et le langage machine. Lide est
que la compilation du code IL vers le langage machine natif, se fasse durant lexcution de lapplication, quand on connat les types de processeurs de la machine. Cela permet aux applications
.NET distribues sous forme dassemblages contenant du code IL, dtre excutables sur toutes
machines et tous systmes dexploitation supportant la plateforme .NET. Le point important
souligner est que pour distribuer une application .NET sur dirents systmes dexploitation,
il ny a pas besoin de compiler plus dune fois le code source de haut niveau (i.e le code source
C  , VB.NET...). On dit que les applications .NET sont portables au niveau binaire.

Comprendre le mcanisme de compilation Just In Time


La compilation du code IL en langage machine seectue durant lexcution de lapplication.
On pourrait imaginer quun des deux scnarios suivants soit appliqu :

Lapplication est entirement compile en langage machine ds son lancement.

112

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


Les instructions IL sont interprtes les unes aprs les autres. On parle de langage interprt.

Aucun de ces scnarios nest utilis pour la compilation du code IL en langage machine. Une
solution intermdiaire et plus performante a t mise en place. Cette solution consiste compiler le corps dune mthode en langage IL en langage machine, juste avant le premier appel
de la mthode. Cest pour cela que le mcanisme sappelle Juste temps (JIT Just In Time). La
compilation se fait juste temps pour que lexcution de la mthode en langage machine puisse
se faire.

Intercepter le premier appel grce au stub


Pour raliser la compilation JIT le CLR utilise une astuce bien connue dans les mcanismes
darchitecture distribue type RPC, DCOM .NET Remoting ou Corba. chaque fois quune classe
est charge par le CLR, un stub (ou souche en franais) est associ chacune des mthodes de la
classe. En architecture distribue un stub est une petite couche logicielle ct client qui intercepte les appels aux mthodes pour les convertir en appels distants (en DCOM le terme proxy
dsigne cette notion de stub). Dans le cas du JIT le stub dune mthode est matrialis par du
code qui intercepte le premier appel celle-ci.
Ce code contient un branchement vers une fonction du compilateur JIT. Cette fonction vrifie le
code IL puis le compile en langage natif. Le code natif rsultant est stock dans lespace mmoire
du processus, puis excut. Bien entendu le stub est alors remplac par un saut vers ladresse du
code natif de la mthode. Ainsi chaque mthode nest compile quune fois.

Vrification du code IL
Avant de compiler une mthode en langage natif, le compilateur JIT eectue une srie de vrification quant la validit du corps de la mthode. Pour dcider quune mthode est valide ou
pas, le compilateur JIT vrifie lenchanement des instructions IL, value lvolution du contenu
de la pile et dtecte les accs mmoires interdits. Une exception est envoye si cette vrification
choue.
Votre code C  crit en mode vrifiable est automatiquement traduit en code IL vrifiable. Cependant, en page 501 nous montrons que sous certaines conditions le compilateur C  peut produire des instructions IL spciales qui ont la particularit dtre non vrifiables par le compilateur JIT.

Optimisations ralises par le compilateur JIT


Le compilateur JIT ralise des optimisations. En voici quelques exemples pour fixer les ides :

Pour viter le cot du passage des arguments entrants et sortants, le compilateur JIT a la
possibilit dinsrer le corps dune mthode appele dans le corps de la mthode appelante.
Cette optimisation est nomme inlining. Pour que le cot de cette optimisation ne soit pas
suprieur au gain de performance, la mthode appele doit satisfaire un certains nombre
de contraintes simples vrifier. Son corps compil en IL doit avoir une taille infrieure
32 octets, elle ne doit pas rattraper dexceptions, elle ne doit pas contenir de boucles, elle ne
doit pas tre virtuelle etc.
Le compilateur JIT peut positionner null une variable locale de type rfrence aprs sa
dernire utilisation et avant la fin de la mthode. Ainsi, lobjet rfrenc aura une rfrence
de moins vers lui. Cela augmente les chances quil soit collect plus tt par le ramassemiettes. Cette optimisation peut tre localement dsactive en utilisant la mthode System.GC.KeepAlive().

La compilation Juste temps (JIT Just In Time)

113

Le compilateur JIT a la possibilit de stocker les variables locales les plus frquemment utilises directement dans les registres du processeur plutt que sur la pile. Cela constitue bien
une optimisation car laccs aux registres du processeur est significativement plus rapide.
Cette optimisation est nomme Enregistration.

Notion de pitching
La compilation des mthodes par le JIT consomme de la mmoire puisque le code natif des
mthodes est stock. Si le CLR dtecte que la mmoire devient une ressource critique pour
lexcution de lapplication, il a la possibilit de rcuprer de la mmoire en librant le code
natif de certaines mthodes. Naturellement un stub est regnr pour chacune de ces mthodes.
Cette fonctionnalit est appele pitching.
La mise en uvre du pitching est beaucoup plus complique quil ny parat. Pour tre ecace
le code des mthodes libres doit tre contigu pour viter la fragmentation de la mmoire.
Dautres problmes importants apparaissent dans les piles des threads puisquelles contiennent
des adresses mmoire rfrenant le code natif de certaines mthodes. Heureusement toutes ces
considrations sont totalement masques au dveloppeur.
Le code natif dune mthode produit par le compilateur JIT ne survit pas au dchargement du
domaine dapplication qui le contient.

Acronymes JIT et JITA


Pour ceux qui viennent du monde COM/DCOM/COM+ lacronyme JIT peut leur rappeler
lacronyme JITA (Just In Time Activation, dcrit page 296). Les deux mcanismes sont dirents, ils
ont des buts dirents et sont utiliss dans des technologies direntes. Cependant lide sousjacente est la mme : on ne mobilise des ressources pour une entit (compilation dune mthode
IL pour JIT, activation dun objet COM pour JITA) que lorsque lentit est eectivement utilise
(juste avant que lentit soit utilise). On qualifie souvent les mcanismes qui utilisent cette ide
avec ladjectif paresseux (lazy en anglais).

Compilation avant excution : loutil ngen.exe


Microsoft fournit loutil ngen.exe capable de compiler les assemblages avant leur excution. Cet
outil fonctionne donc comme un compilateur classique et remplace le mcanisme JIT. Le vrai
nom de ngen.exe est Native Image Generator . Loutil ngen.exe est utiliser si vous constatez
que le mcanisme JIT introduit une baisse de performance notable (par exemple en utilisant les
compteurs de performance de la compilation JIT dcrits dans la section suivante). Cependant
le compilateur normal ralise un grand nombre doptimisations inaccessibles ngen.exe. Lutilisation du compilateur normal est en gnral prfrable.
La compilation par ngen.exe se fait en gnral linstallation de lapplication. Vous navez pas
manipuler les nouveaux fichiers contenant le code en langage machine, appels images natives.
Ceux-ci sont automatiquement stocks dans un rpertoire spcial de votre machine appel le
cache des images natives (Native Image Cache en anglais). Ce rpertoire est accessible partir de
ngen.exe avec les options /show et /delete. Ce rpertoire est aussi visible lorsquun utilisateur
visualise le cache global des assemblages car le cache des images natives est inclus dans ce rpertoire. Cette visualisation est dcrite page 65).

114

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Option de ngen.exe

Description

/show[nom assemblage
|nom repertoire]

Permet de visualiser lensemble des images natives contenues


dans le Native Image Cache .Si vous faite suivre cette option
du nom dun assemblage, vous ne verrez que les images natives
ayant le mme nom.Si vous faite suivre cette option du nom
dun rpertoire, vous ne verrez que les images natives des assemblages contenues dans ce rpertoire

/delete[nom assemblage
|nom repertoire]

Supprime lensemble des images natives contenues dans le


Native Image Cache .Si vous faite suivre cette option du nom
dun assemblage, vous ne supprimerez que les images natives
ayant le mme non.Si vous faite suivre cette option du nom
dun rpertoire, vous ne supprimerez que les images natives des
assemblages contenues dans ce rpertoire

/debug

Produit une image native utilisable par un dbogueur.

De nombreuses autres options existent. Elles sont dcrites dans les MSDN larticle Native
Image Generator (Ngen.exe). Notamment, des nouvelles possibilits ont t ajoutes pour
supporter les assemblages exploitant la rflexion et pour automatiser la mise jour de la version
compile dun assemblage lorsquune de ses dpendances volue. Plus dinformation ce sujet
sont disponibles en ligne dans larticle NGen Revs Up Your Performance with Powerful New
Features de Reid Wilkes du numro dAvril 2005 de MSDN Magazine.

Compteurs de performance de la compilation JIT


Vous avez accs aux six compteurs suivants de performance du JIT dans la catgorie ".NET CLR
Jit".
Nom du compteur, fournir sous
forme dune chane de caractres.

Description

"# of IL Methods JITted"

Compte le nombre de mthodes compiles. Ce


compteur ninclut pas les mthodes dj compiles
par ngen.exe.

"# of IL Bytes JITted"

Compte le nombre doctets de code IL compil. Les


mthodes pitches ne sont pas soustraites de ce
total.

"Total # of IL Bytes Jitted"

Compte le nombre doctet de code IL compil. Les


mthodes pitches sont contenues dans ce total.

"% Time in Jit"

Pourcentage du temps pass dans le compilateur JIT.


Ce compteur est mis jour chaque fin de compilation JIT.

La compilation Juste temps (JIT Just In Time)

115

"IL Bytes Jitted / sec"

Nombre moyen doctets de code intermdiaire compil par seconde.

"Standard Jit Failures"

Nombre de fois o une mthode considre comme


invalide na pu tre compile par le JIT.

Vous avez le choix de visualiser ces compteurs soit pour toutes les applications excutes
en mode gr jusquici depuis le boot de la machine, soit pour le processus courant. Le
choix de cette visualisation se fait avec largument de la mthode PerformanceCounterCategory.GetCounters(). Dans le premier cas il faut fournir la chane de caractres "_Global_"
en argument cette mthode. Dans le second cas il faut fournir la chane de caractres gale au
nom du processus en argument cette mthode.
Ces compteurs sont principalement utiliss pour valuer le cot de la compilation JIT. Si ce cot
vous parat trop lev, il faut prvoir lutilisation de loutil ngen.exe lors du dploiement de
lapplication. Voici un exemple dutilisation de ces compteurs (notez que le nom de lassemblage
est MonAssemblage.exe) :
Exemple 4-13 :

MonAssemblage.cs

using System.Diagnostics ;
class Program {
static void DisplayJITCounters() {
PerformanceCounterCategory perfCategory
= new PerformanceCounterCategory(".NET CLR Jit") ;
PerformanceCounter[] perfCounters ;
perfCounters = perfCategory.GetCounters("MonAssemblage") ;
foreach(PerformanceCounter perfCounter in perfCounters)
System.Console.WriteLine("{0}:{1}",
perfCounter.CounterName,
perfCounter.NextValue()) ;
}
static void f() {
System.Console.WriteLine("--> Appel `
a f().") ;
}
static void Main() {
DisplayJITCounters() ;
f() ;
DisplayJITCounters() ;
}
}
Ce programme ache :
# of Methods Jitted:2
# of IL Bytes Jitted:108
Total # of IL Bytes Jitted:108
IL Bytes Jitted / sec:0
Standard Jit Failures:0
% Time in Jit:0

116

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


Not Displayed:0
--> Appel `a f().
# of Methods Jitted:3
# of IL Bytes Jitted:120
Total # of IL Bytes Jitted:120
IL Bytes Jitted / sec:0
Standard Jit Failures:0
% Time in Jit:0,02865955
Not Displayed:0

Prcisons que les compteurs de performances sont aussi visualisables avec loutil perfmon.exe
(accessible avec Menu dmarrer  Excuter...  perfmon.exe).

Gestion du tas par le ramasse-miettes


Introduction au ramasse-miettes
Dans les langages .NET, la destruction des objets grs nengage aucunement la responsabilit
du programmeur. Le problme de la destruction dun objet de type valeur est trs simple
rsoudre. Lobjet tant physiquement allou sur la pile du thread courant, lorsque la pile se
vide cet endroit, lobjet est dtruit. Le problme de la destruction dun objet de type rfrence
est beaucoup plus ardu. Pour simplifier la tche du dveloppeur, la plateforme .NET fournit
un ramasse-miettes (garbage collector ou GC en anglais) qui prend en charge automatiquement la
rcupration de la mmoire des objets dsallous. On parle de tas gr.
Lorsquil ny a plus de rfrences vers un objet, ce dernier devient inaccessible par le programme.
Le programme nen a donc plus besoin. Le ramasse-miettes fonctionne sur ce principe : il
marque les objets accessibles. Les objets non marqus sont donc inaccessibles par le programme,
et doivent tre dtruits. Un problme est le fait que le ramasse-miettes dsalloue un objet inaccessible seulement quand il le dcide (en gnral lorsque le programme a besoin de mmoire).
Le dveloppeur a une marge de manuvre restreinte sur la conduite du ramasse-miettes. Par
rapport dautres langages, comme le langage C++, cela introduit un certain non dterminisme
dans lexcution des programmes. Une consquence est que les dveloppeurs se sentent frustrs
de ne pas avoir un contrle total. En revanche, avec ce systme, les problmes rcurrents de
fuite de mmoire sont beaucoup moins nombreux.

Les fuites de mmoire subsistent malgr la prsence dun ramasse-miettes. En eet, un dveloppeur peut, par inadvertance, concevoir un programme qui garde des rfrences vers
des objets dont il na plus besoin (par exemple si une collection gonfle indfiniment).
Cependant, la cause la plus courante des fuites de mmoire dune application .NET est la
non dsallocation de ressources non gres.
Pour fixer les ides, le ramasse-miettes est une couche logicielle comprise dans le CLR, prsente
en un seul exemplaire dans chaque processus.

Problmatique des algorithmes possibles pour un ramasse-miettes


Le but nest pas de faire un expos des dirents algorithmes de ramasse-miettes existants, mais
de sensibiliser le lecteur aux problmes qui se posent aux concepteurs du ramasse-miettes. Ainsi

Gestion du tas par le ramasse-miettes

117

il comprendra mieux pourquoi lalgorithme dcrit dans la section suivante a t choisi par Microsoft pour son implmentation du ramasse-miettes.
On pourrait penser quil sut de librer un objet lorsque celui-ci nest plus rfrenc. Cette
approche simpliste peut facilement tre mise en dfaut. Imaginez deux objets A et B ; A maintient une rfrence vers B et B maintient une rfrence vers A ; ces deux objets nadmettent pas
dautres rfrences que leurs rfrences mutuelles. Clairement le programme na plus besoin de
ces objets et le ramasse-miettes doit les dtruire, alors quil existe encore une rfrence valide
sur chacun deux. Le ramasse-miettes utilise donc des algorithmes du type arbres de rfrences
et dtection de boucles dans un graphe. Larbre de rfrences admet pour racines certains objets
considrs comme immuables.
Un autre problme que celui de la destruction des objets incombe au ramasse-miettes. Cest la
fragmentation du tas. Aprs un certain temps, force davoir dsallou et allou des zones mmoires de tailles trs variables, le tas est fragment. Il est parsem despaces mmoire non utiliss
par le programme. Ce fait induit un norme gaspillage de mmoire. Il incombe au ramassemiettes de dfragmenter le tas, afin de limiter les consquences de ce problme. L encore plusieurs algorithmes et approches existent.
Enfin, le bon sens et lapproche empirique nous enseigne la rgle suivante : plus un objet est
ancien plus son esprance de vie est longue, plus il est rcent plus son esprance de vie est courte.
Si cette rgle est prise en compte par un algorithme, cest--dire si lon essaye plus souvent de
dsallouer les objets rcents que les objets anciens, alors le ramasse-miettes sous-jacent gagnera
ncessairement en performance.
Lalgorithme du ramasse-miettes .NET prend en compte ces problmatiques et cette rgle temporelle.

Algorithme du ramasse-miettes .NET


Une collecte des objets par le ramasse-miettes est dclenche automatiquement par le CLR lorsquune pnurie potentielle de mmoire est pressentie. Lalgorithme de cette prise de dcision
nest pas document par Microsoft.
Le terme de gnration dfinit le laps de temps entre deux collectes. Chaque objet appartient
donc la gnration qui la vue natre. De plus, chaque instant lquation suivante est vrifie :
{Nombre de gnration quil y a eu dans ce processus} = 1+{Nombre de collectes quil y a eu dans
ce processus}.
Les gnrations sont numrotes. Le numro dune gnration est augment chaque collecte
jusqu ce quil atteigne un numro de gnration maximal (gal 2 dans limplmentation
actuelle du CLR). Par convention la gnration 0 est la gnration la plus jeune. Lorsquun objet
doit tre allou sur le tas, il fait partie de la gnration 0.
Une consquence de ce systme de gnrations est que les objets dune mme gnration sont
contigus en mmoire comme lindique la Figure 4-4 :

tape 1 : dfinir les racines de larbre des rfrences


vers les objets actifs
Les racines de larbre de rfrences vers les objets actifs (i.e les objets ne pas dsallouer) sont
principalement, les objets rfrencs par les champs statiques des classes, les objets rfrencs

118

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


A

Gnration 2

Gnration 1

Gnration 0

(A B C D E et F sont des objets)

Figure 4 -4 : Gnrations dobjets


dans les piles des threads du processus et les objets dont ladresse physique (ou un oset bas sur
celle-ci) est contenue dans un registre de lunit centrale.

tape 2 : fabriquer larbre et marquer les objets qui


sont encore rfrencs
Le ramasse-miettes construit larbre partir des racines, en ajoutant les objets rfrencs par
les objets dj dans larbre, et en itrant cette opration. Chaque objet rfrenc par larbre est
marqu comme actif. Lorsque lalgorithme rencontre un objet dj marqu comme actif, il ne
le prend pas en compte afin dviter les problmes de cycle de rfrencement dobjets. Cette
tape se termine lorsque le ramasse-miettes ne peut plus collecter un objet rfrenc, qui ne soit
dj marqu comme actif. Le ramasse-miettes se sert des mtadonnes de type pour trouver les
rfrences contenues dans un objet partir de la classe de lobjet.
Sur la Figure 4-5, on voit que les objets A et B sont des objets qui constituent les racines de larbre.
A et B rfrencent C. B rfrence E et C rfrence F. Lobjet D nest donc pas marqu comme actif.
Objets racines
F
A
B

C
E

Figure 4 -5 : Larbre des rfrences du ramasse-miettes

tape 3 : dsallouer les objets inactifs


Le ramasse-miettes parcourt linairement le tas et dsalloue les objets non marqus comme
actifs. Certains objets ont besoin que leur mthode Finalize() soit appele pour pouvoir tre
physiquement dtruit. Cette mthode nomme finaliseur, est dfinie par la classe Object. Elle
doit tre appele avant la destruction dun objet dont la classe redfinit cette mthode. Les invocations des finaliseurs sont prises en charge par un thread ddi de faon ne pas surcharger le
thread qui eectue la collecte des objets. Une consquence est que les emplacements mmoires
des objets qui ont un finaliseur survivent une collecte.
Le tas nest pas ncessairement entirement parcouru, soit parce que le ramasse-miettes a collect susamment de mmoire, soit parce que nous avons faire une collecte partielle. Lalgorithme de parcours de larbre des rfrences est profondment impact par cette possibilit de
collecte partielle car il se peut que des objets anciens (de la gnration 2) rfrencent des objets
jeunes (de la gnrations 0 ou 1). Bien que la frquence de dclenchement des collectes dpend
compltement de votre application vous pouvez vous baser sur ces ordres de grandeur : une

Gestion du tas par le ramasse-miettes

119

collecte partielle de la gnration 0 par seconde, une collecte partielle de la gnration 1 pour
10 collectes partielles de la gnration 0, une collecte complte pour 10 collectes partielles de
la gnration 1.
La Figure 4 -6 montre que lobjet D nest pas marqu. Il est donc inactif et va tre dtruit.
A

Gnration 2

Gnration 1

Gnration 0

Figure 4 -6 : Destruction des objets inactifs

tape 4 : dfragmentation du tas


Le ramasse-miettes dfragmente le tas, cest--dire que les objets actifs sont dplacs vers le bas du
tas pour combler les espaces mmoires vides qui contenaient les objets dsallous ltape
prcdente. Ladresse du haut du tas est recalcule et chaque gnration est incrmente. Les
objets anciens sont vers le bas du tas et les objets rcents vers le haut. De plus les objets actifs
crs au mme moment sont aussi physiquement proches. Seuls les objets jeunes sont souvent
examins. Ainsi, ce sont toujours les mmes pages mmoire qui sont souvent sollicites. Ce fait
est trs important pour obtenir de bonnes performances.
Sur la Figure 4-7 (par rapport la Figure 4 -6) le ramasse-miettes a incrment les numros de
gnration. On suppose que la classe de lobjet D navait pas de finaliseur. Ainsi, la mmoire de
lobjet D a t dsallou et le tas a t dfragment (E et F ont t bougs pour combler lespace
mmoire anciennement allou D). Notez que A et B nont pas vu leur numro de gnration
augmenter puisquil tait dj dans la gnration 2.
A

B
Gnration 2

Gnration 1

Gnration 0

Figure 4 -7 : Dfragmentation du tas


Certains objets sont inamovibles (on dit aussi pingls ) cest--dire quils ne peuvent tre
bougs physiquement par le ramasse-miettes. Cette particularit sappelle to pin an object en
anglais (pin veut dire punaise/pingle en franais). Un objet est inamovible lorsquil est point
par du code non protg. Plus de dtails ce sujet sont disponibles en page 505.

tape 5 : recalculer les adresses contenues dans les rfrences


Les adresses mmoire de certains objets actifs ont pu tre modifies par ltape prcdente. Il
faut donc parcourir larbre des objets actifs et actualiser les rfrences avec les nouvelles adresses.

Bonnes pratiques
Les bonnes pratiques suivantes dcoulent naturellement des proprits de lalgorithme que
nous venons dexposer :

Librez vos objets ds que possible pour viter la promotion dobjets dans les gnrations.

120

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Identifiez quels objets sont susceptibles davoir une vie longue, analysez les causes et essayez
de rduire leurs dures de vie. Pour cela, nous vous conseillons davoir recours loutil
CLR Profiler fournit gratuitement par Microsoft ou au profiler fourni avec Visual Studio Team
System .

Dans la mesure du possible, vitez de rfrencer un objet avec une vie courte partir dun
objet avec une vie longue.

viter dimplmenter un finaliseur dans vos classes pour que vos objets ne survivent pas
une collecte.

Assign vos rfrences null ds que possible, surtout avant un long appel.

Tas spcial pour les gros objets


Tous les objets dont la taille est infrieure une certaine taille sont traits dans le tas gr dcrit
dans les tapes prcdentes. Cette taille est non documente par Microsoft. Un ordre de grandeur est de 20 100 Ko. Les objets dont la taille est suprieure ce seuil sont stocks dans un
autre tas spcial, pour des raisons de performances. En eet, dans ce tas, les objets ne sont pas
physiquement bougs par le ramasse-miettes. Une page mmoire Windows a une taille de 4 ou
8Ko en fonction du processeur sous-jacent. Chacun de ces objets est stock sur un nombre entier
de page mmoire mme sil na pas une taille exactement multiple de 4 ou 8Ko. Ceci induit un
peu de gaspillage mais ce dfaut naecte pas significativement le gain de performance. Cette
dirence de stockage est compltement transparente pour le dveloppeur.

Ramasse-miettes et applications multithreads


Lexcution dune collecte du ramasse-miettes peut se faire avec un des threads applicatifs si elle
est dclanche manuellement ou avec un thread du CLR lorsque celui-ci dcide quune collecte
doit avoir lieu. Avant de commencer une collecte, le CLR doit stopper lexcution des autres
threads de lapplication pour viter quils modifient le tas. Pour cela, direntes techniques
existent. On peut citer linsertion de point protgs (safe point en anglais) par le compilateur JIT,
qui permettent aux threads applicatifs de vrifier si une collecte est en attente de commencement. Notez que la gnration 0 du tas est en gnral dcoupe en portions (appeles arnes),
une par thread, pour viter les problmes de synchronisation lis aux accs concurrents au tas.
Rappelons enfin que les finaliseurs sont excuts par un thread du CLR ddi cette tche.

Les rfrences faibles (weak reference)


La problmatique
Lors de lexcution dune application, chaque instant, chaque objet est soit actif, cest--dire
que lapplication a encore une rfrence vers lui, soit inactif. Lorsquun objet passe du stade actif
au stade inactif, cest--dire lorsque lapplication vient de dtruire la dernire rfrence vers cet
objet, il ny a plus aucun espoir daccder lobjet.
En fait il existe un troisime stade intermdiaire entre actif et inactif. Lorsque lobjet est dans
ce stade, lapplication peut laccder, mais le ramasse-miettes peut le dsallouer. Il y a, priori,
contradiction, car si lobjet est accessible il est rfrenc et sil est rfrenc il ne peut tre dsallou. En fait, il faut introduire la nouvelle notion de rfrence faible (weak reference en anglais)
pour pouvoir comprendre ce stade intermdiaire. Lorsquun objet est rfrenc par une rfrence faible, il est la fois accessible par lapplication et dsallouable par le ramasse-miettes.

Gestion du tas par le ramasse-miettes

121

Pourquoi utiliser des rfrences faibles ?


Le dveloppeur peut utiliser une rfrence faible sur un objet si ce dernier satisfait toutes les
conditions suivantes :

Lobjet peut tre potentiellement utilis plus tard mais ce nest pas certain. Si nous sommes
certains de lutiliser plus tard il faut utiliser une rfrence forte.

Lobjet peut tre intgralement reconstruit lidentique (par exemple partir dune base
de donnes). Si on a potentiellement besoin de lobjet plus tard mais quon ne peut le reconstruire lidentique il ne faut pas que le ramasse-miettes dtruise lobjet.

Lobjet est relativement volumineux en mmoire (plusieurs Ko). Si lobjet est lger on peut
le garder en mmoire. Cependant si les deux conditions prcdentes sappliquent un
grand nombre dobjets lgers il est judicieux dutiliser une rfrence faible pour chacun de
ces objets.

Tout ceci est bien thorique. Dans la pratique on dit quon utilise un cache dobjets. En eet, ces
conditions sont toutes remplies par les objets contenus dans un cache (on parle du concept de
cache en gnral et pas dune implmentation particulire).
Lutilisation de caches peut tre considre comme une rgulation naturelle et automatique
du compromis entre lespace mmoire, la puissance de calcul et la bande passante du rseau.
Lorsque le cache est trop rempli, une partie des objets cachs sont dtruits. Cependant, dans
lhypothse, vraisemblable, o lon doit accder un de ces objets plus tard, il faudra utiliser de
la puissance de calcul (pour fabriquer lobjet) ou/et de la bande passante rseaux (pour obtenir
les donnes contenues dans lobjet, partir dune base de donnes par exemple).
En rsum, si vous avez implmenter un cache nous vous conseillons dutiliser les rfrences
faibles.

Comment utiliser des rfrences faibles ?


Lutilisation des rfrences faibles est particulirement aise grce la classe System.WeakReference. Un exemple valant mieux quun long discours, examinez le code C  suivant :
Exemple 4-14 :
class Program {
public static void Main() {
// obj est une reference forte vers lobjet cr
e
e ci-dessous.
object obj = new object() ;
// wobj est une reference faible vers notre objet.
System.WeakReference wobj = new System.WeakReference(obj);
obj = null ; // Destruction de la r
ef
erence forte obj.
// ...
// Ici lobjet est potentiellement d
etruit par le GC.
// ...
// Creation dune reference forte `
a partir de la r
ef
erence faible.
obj = wobj.Target;
if (obj == null) {

122

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


// Le GC a detruit lobjet, il nest plus accessible !
}
else {
// Lobjet existe toujours , nous pouvons lutiliser et il
// ne peut plus etre d
etruit par le GC.
}
}
}

La classe WeakReference prsente la mthode bool IsAlive() qui retourne true si lobjet rfrenc faiblement na pas encore t dtruit. En outre, il est classique et recommand de mettre
la rfrence forte null ds quune rfrence faible est cre pour sassurer que la rfrence forte
est dtruite.

Rfrences faibles courtes et rfrences faibles longues


Il y a deux constructeurs pour la classe WeakReference :
WeakReference(object target) ;
WeakReference(object target, bool trackResurrection) ;
Dans le premier constructeur le paramtre trackResurrection est positionn implicitement
false. Si ce paramtre est positionn true lapplication peut encore accder lobjet entre
linstant o la mthode Finalize() a t appele et linstant o lemplacement mmoire de
lobjet est rellement modifi (en gnral par la recopie dun autre objet dans cet emplacement
mmoire lors de la dfragmentation du tas). Dans ce cas on dit que cest une rfrence faible
longue (long weak reference en anglais). Si le paramtre trackResurrection est positionn false
lapplication ne peut plus accder lobjet ds que la mthode Finalize() a t appele. Dans
ce cas on dit que cest une rfrence faible courte (short weak reference en anglais).
Malgr le gain potentiel (mais minime) des rfrences faibles longues il vaut mieux ne jamais
les utiliser, car elles sont diciles maintenir. En eet, une volution du corps de la mthode
Finalize() qui ne tiendrait pas compte du fait quil y a des rfrences faibles longues sur des
instances de cette classe pourrait entraner la rsurrection dobjet dans un tat incorrect.

Agir sur le comportement du ramasse-miettes


avec la classe System.GC
On peut se servir des mthodes statiques de la classe System.GC pour agir sur le comportement du ramasse-miettes ou seulement analyser ce comportement. Lide est bien videmment
damliorer les performances de nos applications. Cependant, Microsoft ayant investi beaucoup
de ressources optimiser le ramasse-miettes .NET, nous vous conseillons de nutiliser les services
de la classe System.GC que lorsque vous tes certain du gain de performance produit par vos
modifications. Voici la proprit et les mthodes statiques de cette classe :

static int MaxGeneration{ get ; }


Cette proprit renvoie le nombre maximal de gnration du tas gr -1. Par dfaut,
dans limplmentation Microsoft courante de .NET, cette proprit vaut 2 et est garantie
constante durant le cycle de vie dune application.

Gestion du tas par le ramasse-miettes

123

static void WaitForPendingFinalizers()


Cette mthode suspend le thread qui lappelle, jusqu ce que lensemble des finaliseurs ait
t excut par le thread ddi cette tche.

static void Collect()


static void Collect( int generation )
Cette mthode dclenche une collecte du ramasse-miettes. Vous avez la possibilit de dclancher une collecte partielle, auquel cas le ramasse-miettes ne soccupera que des objets
dont le numro de gnration est entre generation et 0. generation ne peut tre suprieur
MaxGeneration. Lors de lappel de la surcharge sans paramtre, le ramasse-miettes collecte
toutes les gnrations.
Cette mthode est en gnral invoque mauvais escient, parce que le dveloppeur espre
quelle va rsoudre les problmes de mmoire inhrents au design de son application (trop
dobjets allous, trop de rfrences entre objets, fuite de mmoire etc).
Il est cependant intressant de dclancher une collecte du ramasse-miettes juste avant lappel de traitements critiques, qui seraient gns par une surcharge de la mmoire ou une
baisse de performance subite, due au dclenchement ramasse-miettes. Dans ce cas, il est
conseill dappeler les mthodes dans cet ordre afin dtre certains que lon commence notre
traitement avec le maximum de mmoire possible :
// Lance une premi`ere collecte.
GC.Collect()
// Attend que tous les finaliseurs aient
et
e ex
ecut
es.
GC.WaitForPendingFinalizers()
// Lib`ere la memoire de chaque objet dont le finaliseur
// vient detre execute.
GC.Collect()

static int CollectionCount( int generation )


Retourne le nombre de collectes dj eectues pour la gnration indique. Cette mthode peut tre utile pour dtecter si une collecte eu lieu durant un intervalle de temps.

static int GetGeneration( object obj )


static int GetGeneration( WeakReference wo )
Retourne le numro de gnration de lobjet rfrenc soit par une rfrence forte (premier
cas) soit par une rfrence faible (deuxime cas).

static void AddMemoryPressure( long pressure )


static void RemoveMemoryPressure ( long pressure )
La problmatique sous-jacente lutilisation de ces mthodes est une consquence du fait
que le ramasse-miettes ne tient pas compte de la mmoire non gre dans ses algorithmes.
Imaginez que 32 instances de la classe Bitmap qui ont chacune une taille de 32 octets maintiennent chacune une rfrence vers un bitmap non gr de 6Mo. Sans lutilisation de ces
mthodes, le ramasse-miettes se comporterait comme si seulement 32x32 octets taient allous, savoir, il ne jugerait pas ncessaire de dclancher des collectes. Une bonne pratique
est dutiliser ces mthodes dans les constructeurs et les finaliseurs de vos classes dont les
instances maintiennent des grosses zones de mmoire non gres. Nous prcisons que la
classe HandleCollector dcrite en page 277 permet de fournir le mme genre de service.

static long GetTotalMemory( bool forceFullCollection )

124

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)


Retourne une estimation de la taille courante en octets du tas gr. Vous pouvez aner
lestimation en mettant forceFullCollection true. Dans ce cas la mthode devient bloquante, si une collecte est en cours dexcution. La mthode retourne lorsque la collecte est
termine ou avant si lattente dpasse un certain intervalle de temps. La valeur retourne
sera plus prcise si la mthode retourne lorsque le ramasse-miettes a fini sa tche.

static void KeepAlive( object obj )


Garantit que obj ne peut tre dtruit par le ramasse-miettes durant lexcution de la mthode appelant KeepAlive(). KeepAlive() doit tre appele la fin du corps de la mthode.
Vous pouvez penser que cette mthode ne sert rien puisquelle ncessite une rfrence
forte vers lobjet qui garantit par son existence ce comportement de non destruction. En
fait, le compilateur JIT optimise le code natif produit en positionnant une variable locale
de type rfrence nulle aprs sa dernire utilisation et avant la fin du corps de la mthode.
La mthode KeepAlive() ne fait que dsactiver cette optimisation.

static void SuppressFinalize(object obj)


La mthode Finalize() ne sera pas appele par le ramasse-miettes sur lobjet pass en paramtre. Rappelez-vous que lappel Finalize() sur tous les objets avant la fin du processus,
est un comportement garanti par le ramasse-miettes.
La mthode Finalize() devrait logiquement, contenir du code pour dsallouer des ressources possdes par lobjet. Cependant on ne matrise pas le moment de lappel Finalize(). Ainsi on cre souvent une mthode spcialement ddie cette dsallocation de
ressources que lon appelle quand on le souhaite. En gnral on utilise la mthode IDisposable.Dispose() cet eet. Cest dans cette mthode spciale que lon doit appeler SuppressFinalize() puisque aprs son invocation il ny aura plus lieu dappeler Finalize().
Plus de dtails ce sujet sont disponibles en page 423.

static void ReRegisterForFinalize(object obj)


La mthode Finalize() de lobjet pass en paramtre sera appele par le ramasse-miettes.
On utilise en gnral cette mthode dans deux cas :

1er cas : Lorsque la mthode SuppressFinalize() a dj t appele et que lon change


davis (cette pratique est nanmoins viter car elle signale une faiblesse de conception).

2e cas : Si on appelle ReRegisterForFinalize() dans le code de Finalize() lobjet survivra aux collectes. Cela peut servir analyser le comportement du ramasse-miettes.
Cependant si on rpte indfiniment lappel Finalize() le programme ne sarrtera jamais, donc il faut prvoir une condition avant lappel de ReRegisterForFinalize() dans le code de Finalize(). De plus si le code dune telle mthode Finalize()
rfrence dautres objets il faut tre trs prudent car ils peuvent tre dtruits par le
ramasse-miettes sans que lon sen aperoive. En eet, cet objet indestructible nest
pas constamment considr comme actif, donc ses rfrences vers dautres objets ne
sont pas forcment prises en compte lors de la construction de larbre des rfrences
vers les objets actifs.

Facilits fournies par le CLR pour rendre votre code plus fiable

125

Facilits fournies par le CLR


pour rendre votre code plus fiable
Les exceptions asynchrones du CLR et la fiabilit du code gr
Nous esprons que le contenu du prsent chapitre vous a convaincu quexcuter du code dune
manire gre est un progrs dterminant. Il est temps maintenant de sintresser la face obscure des environnements grs. Dans un tel environnement, des traitements coteux peuvent
tre dclenchs implicitement par le CLR pratiquement nimporte quel moment de lexcution. Cette particularit implique quil est impossible de prvoir quand une pnurie de ressource
peut survenir. Voici quelques exemples classiques de tels traitements coteux dclenchs inopinment par le CLR (cette liste est loin dtre exhaustive) :

Chargement dun assemblage.

Excution dun constructeur de classe.

Collecte dobjets par le ramasse miettes.

Compilation JIT dune classe.

Parcours de la pile CAS.

Boxing implicite.

Cration des champs statiques dune classe dont lassemblage est partag par les domaines
dapplication du processus.

Une pnurie de ressource se traduit en gnral par la leve dune exception par le CLR de type
OutOfMemoryException, StackOverflowException ou ThreadAbortException sur le thread qui
excute la demande de ressource responsable de la pnurie. On parle dexception asynchrone.
Cette notion soppose la notion dexception applicative. Lorsquune exception applicative est
leve, il incombe au code courant de la rattraper et de la traiter. Typiquement lorsque vous
souhaitez accder un fichier il faut prvoir quune exception de type FileNotFoundException
peut tre leve. En revanche, lorsquune exception asynchrone est leve par le CLR, le code en
cours dexcution ne peut en tre tenu pour responsable. En consquence, il est dconseill de
cder la psychose en mettant des blocs try/catch/finally partout dans votre code pour limiter
les eets de bord des exceptions asynchrones. Lentit responsable du traitement des exceptions
asynchrones est lhte du moteur dexcution que nous avons dj eu loccasion de prsenter
dans ce chapitre.
Dans le cas dune application console ou fentre classique, la leve dune exception asynchrone
est un vnement rare qui traduit en gnral un problme dalgorithme (fuite de mmoire,
appels rcursifs abusifs ou infinis etc). En consquence lhte du moteur dexcution de ces
applications fait en sorte de terminer le processus tout entier lorsquune exception asynchrone
est non rattrape par le code applicatif.
Il en est de mme dans le cas des applications ASP.NET. En eet, on constate que les dirents
mcanismes de dtection dun comportement anormal recyclent le processus en gnral avant
mme que des exceptions asynchrones soient lances. Ces mcanismes sont exposs en page 890.
Ainsi, jusqu lintgration du CLR dans le produit SQL Server 2005, les exceptions asynchrones
ntaient pas si problmatiques. Pour ne pas rgresser, la version 2005 de SQL Server doit fournir
un taux de fiabilit de cinq neufs. En dautres termes le service de persistance des donnes doit

126

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

tre disponible 99.999% du temps soit, au pire, 5 minutes et 15 secondes dindisponibilit par
an. En outre, pour tre ecace, le processus de SQL Server 2005 doit charger un maximum de
donnes en mmoire et limiter les chargements de pages mmoire partir du disque dur. Il est
donc amen flirter rgulirement avec la limite de 2 ou 3GB du processus si la mmoire vive
disponible le permet (ce qui est maintenant le cas sur la plupart des serveurs). Enfin, les mcanismes de time out sur les excutions des requtes fonctionnent base de leve de lexception
ThreadAbortException. En rsum, lorsque vous avez faire ce type de serveur qui pousse
le systme dans ses retranchements non seulement les exceptions asynchrones deviennent des
vnements banals mais en plus il faut viter absolument quelles ne provoquent le crash du
processus.
Face de tels impratifs les concepteurs du CLR ont du imaginer de nouvelles techniques. Elles
constituent le sujet de la prsente section.

Garder bien lesprit que ces techniques doivent tre utilises avec une grande sagesse,
seulement lorsque vous dveloppez un serveur de grande envergure qui ncessite son
propre moteur dexcution et qui est susceptible de faire face des exceptions asynchrones.

Les rgions dexcution contraintes (CER)


Pour viter de faire tomber le processus tout entier, il faut pouvoir dcharger un domaine dapplication lorsquune exception asynchrone y survient. On parle de recyclage de domaines dapplication. Toute la problmatique dun tel recyclage est de le raliser proprement, sans fuite de mmoire et sans corrompre ltat gnral du processus. Il faut donc un mcanisme permettant de se
protger ponctuellement des exceptions asynchrones. Sans un tel mcanisme, il est impossible
de garantir que les ressources non gres dtenues au moment o une exception asynchrone
survient soient dsalloues correctement.
Le framework .NET 2.0 vous permet dindiquer au CLR les portions de code o la leve inopine
dune exception asynchrone serait catastrophique. Si une exception asynchrone doit survenir,
lide est de forcer le CLR la lever soit avant lexcution de la portion de code soit aprs mais
pas pendant. On nomme de telles portions de code des rgions dexcution contraintes (Constrained
Execution Regions en anglais ou plus simplement CER).
Pour viter de lever une exception asynchrone durant lexcution dune CER, le CLR doit effectuer un certains nombre de prparation juste avant de commencer lexcuter. Lide est de
tenter de dclencher la pnurie de ressource si celle-ci doit avoir lieu avant mme que la CER
soit excute. Typiquement, le CLR compile en code natif toutes les mthodes susceptibles dtre
excutes. Pour connatre ces mthodes, il parcourt statiquement le graphe dappel ayant pour
racine la CER. En outre, le CLR sait retenir une exception de type ThreadAbortException qui
survient pendant lexcution dune CER jusqu la fin de lexcution.
Le dveloppeur doit veiller ne pas allouer de mmoire au sein dune CER. Cette contrainte
est particulirement forte puisque de multiples conditions implicites peuvent dclencher une
allocation mmoire. On peut citer les instructions de boxing, les accs un tableau multidimensionnel ou les manipulations des objets de synchronisation.

Dfinir vos propres CER


Une CER se dfinit par lappel la mthode statique void PrepareConstrainedRegion() de la
classe System.Runtime.CompilerServices.RuntimeHelpersjuste avant la dclaration dun bloc

Facilits fournies par le CLR pour rendre votre code plus fiable

127

try/catch/finally. Tout le code atteignable partir des blocs catch et finally reprsente alors
une CER.
La mthode statique ProbeForSufficientStack() de cette classe est ventuellement appele
lors de lappel PrepareConstrainedRegion(). Cela dpend du fait que limplmentation des
CER par votre hte du moteur dexcution doit grer les cas de dpassement de la taille maximale de la pile dappels du thread courant (stack overflow). Sur un processeur x86, cette mthode
tentera de rserver 48Ko.
Malgr cette quantit de mmoire rserve, il se peut quune situation de stack overflow survienne. Aussi vous pouvez vous faire suivre votre appel PrepareConstrainedRegion() par
un appel la mthode statique ExecuteCodeWithGuaranteedCleanup() afin dindiquer une mthode contenant du code de nettoyage invoquer le cas chant. Une telle mthode doit tre
marque avec lattribut PrePrepareMethodAttribute pour prciser loutil ngen.exe sa fonction particulire.
La classe RuntimeHelpers prsente des mthodes permettant aux dveloppeurs dassister le CLR
dans la prparation du terrain avant lexcution de la CER. Vous pouvez par exemple les appeler
dans des constructeurs de classes.

static void PrepareMethod(RuntimeMethodHandle m)


Le CLR parcours statiquement le graphe des mthodes appeles dans une CER afin de les
compiler en code natif. Un tel parcours statique ne peut dtecter quelle version dune mthode virtuelle est appele. Aussi, il incombe au dveloppeur de forcer la compilation de
la version appele avant lexcution de la CER.

static void PrepareDelegate(Delegate d)


Les dlgus qui vont tre invoqus durant lexcution dune CER doivent tre prpars au
pralable en ayant recours cette mthode.

static void RunClassConstructor(type t)


Vous pouvez forcer lexcution dun constructeur de classe avec cette mthode. Naturellement, lexcution na lieu que si le constructeur de classe concern na pas dj t invoqu.

Les portails de mmoire (memory gates)


Dans le mme esprit que la mthode statique ProbeForSufficientStack() vous pouvez avoir
recours la classe System.Runtime.MemoryFailPoint qui sutilise comme suit :
// On est sur le point deffectuer une op
eration qui a besoin
// dau plus 15 Mb de memoire.
using( new MemoryFailPoint(15) ) {
// Effectue loperation ...
}
Contrairement la mthode ProbeForSufficientStack() la quantit de mmoire indique
nest pas rserve. Le constructeur de cette clase value seulement si lors de son excution cette
quantit de mmoire pourrait tre demande au systme dexploitation par le CLR sans quune
exception de type OutOfMemoryException soit leve. Notez quentre le moment o seectue
cette demande et le droulement eectif de lopration, les conditions peuvent avoir volues,
notamment parce quun autre thread a demand beaucoup de mmoire. Malgr cette faiblesse
lutilisation de cette technique est gnralement ecace.

128

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

Sil savre que la quantit de mmoire indique est indisponible, le constructeur de la classe
MemoryFailPoint lve une exception de type System.InsufficientMemoryException. Pour
cette raison, on parle parfois de portail de mmoire (memory gates en anglais) pour dsigner cette
fonctionnalit.

Les contrats de fiabilit


Le framework .NET 2.0 prsente lattribut System.Runtime.ConstrainedExecution.ReliabilityContractAttribute qui ne sapplique quaux mthodes. Cet attribut permet de documenter
le niveau de gravit maximal auquel on peut sattendre si une exception asynchrone survient
lors de lexcution dune mthode marque. Ces niveaux de gravit sont dfinis par les valeurs
de lnumration System.Runtime.ConstrainedExecution.Consistency :
Consistency

Description

MayCorruptProcess

La mthode marque peut au pire corrompre ltat du processus tout entier et ainsi, provoquer un crash.

MayCorruptAppDomain

La mthode marque peut au pire corrompre ltat du domaine dapplication courant et ainsi, provoquer son dchargement.

MayCorruptInstance

La mthode marque peut au pire corrompre ltat de linstance sur laquelle elle est appele et ainsi, provoquer sa destruction.

WillNotCorruptState

La mthode marque ne peut corrompre aucun tat.

Une deuxime valeur de type System.Runtime.ConstrainedExecution.Consistency sapplique


chaque attribut ReliabilityContractAttribute. Elle permet de documenter si la mthode
peut mettre en dfaut les garanties dune ventuelle CER qui lappelle. Bien entendu, si la mthode peut corrompre un des tats processus ou domaine dapplication, elle peut mettre en
dfaut les garanties et ainsi il faut prvoir la valeur Cer.None.
Les contrats de fiabilit constituent un moyen de documenter votre code. Sachez cependant
quils sont aussi exploits par le CLR lorsque celui-ci parcours le graphe dappels statique lors de
la prparation de lexcution dune CER. Si une mthode sans contrat de fiabilit susant est
rencontre, ce parcours sarrte (puisque de toutes faons le code est non fiable) mais la CER sera
quand mme excute. Ce comportement dangereux a t dcid par les ingnieurs de Microsoft
car trop peu de mthodes du framework sont pour linstant annotes avec un contrat de fiabilit
susant. Notez que seuls les trois contrats de fiabilit suivants sont considrs comme susant
pour une CER :
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

CLI et CLS

129

Les finaliseurs critiques


Le CLR considre que le code dun finaliseur dune classe qui drive de la classe System.
Runtime.ConstrainedExecution.CriticalFinalizerObject est une CER. On parle alors de
finaliseur critique. En plus dtre des CER, les finaliseurs critiques seront, au sein dune mme
collecte, tous excuts aprs lexcution de tous les finaliseurs normaux. Cela garantit que les
ressources les plus critiques, celles sur lesquelles les objets grs dpendent, seront libres en
dernier.
Le mcanisme de finaliseur critique est notamment exploit par les classes du framework
responsables du cycle de vie dun handle win32 ( savoir System.Runtime.InteropServices.
CriticalHandle et System.Runtime.InteropServices.SafeHandle dcrites en page 278).
Les autres classes du framework qui drivent de la classe CriticalFinalizerObject sont
System.Security.SecureString (voir page 227) et System.Threading.ReaderWriterLock (voir
page 158).

Les rgions critiques


Un second mcanisme de renforcement de la fiabilit est prvu en plus des CERs. Lide est de
fournir une information au CLR pour quil sache quand une ressource partage entre plusieurs
threads est mise jour. Une portion de code responsable de la mise jour dune telle ressource
est nomme rgion critique (Critical Region en anglais ou CR). Pour dfinir le commencement et
la fin dune rgion critique il sut dappeler les mthode BeginCriticalRegion() et EndCriticalRegion() de la classe Thread.
Si une exception asynchrone survient dans une rgion critique ltat de la ressource partage
entre plusieurs threads est potentiellement corrompu. Le thread courant va tre dtruit mais
cela ne sut pas garantir que lapplication va pouvoir continuer normalement son excution. En eet, dautres threads peuvent avoir accs aux donnes corrompues et ainsi, avoir un
comportement imprvisible. La seule solution envisageable est de dcharger le domaine dapplication courant et cest eectivement ce quil se passe. Ce comportement de propager un problme local un thread au domaine dapplication tout entier est nomm politique de lescalade
(escalation policy en anglais). Un second eet inhrent la notion de rgion critique est de forcer
le CLR dcharger le domaine dapplication courant si une demande dallocation mmoire
choue.
Si vous avez recours aux classes de verrous du framework (la classe Monitor et mot cl lock, la
classe ReaderWriterLock etc) pour synchroniser les accs vos ressources partages, vous naurez
pas besoin de dfinir explicitement les rgions critiques de votre code. En eet, les mthodes
dacquisition et de libration du verrou de ses classes applent implicitement les mthodes BeginCriticalRegion() et EndCriticalRegion().

En consquence, les rgions critiques sont principalement utiliser si vous dveloppez


votre propre mcanisme de synchronisation, ce qui, vous en conviendrez, nest pas une
tche courante.

CLI et CLS
Sous ces deux acronymes se cache la magie qui permet .NET de supporter plusieurs langages.
Le CLI (Common Langage Infrastructure) est une spcification dcrivant les contraintes respec-

130

Chapitre 4 : Le CLR (le moteur dexcution des applications .NET)

tes par le CLR et les assemblages. Une couche logicielle qui supportent les contraintes du
CLI est mme de grer lexcution des applications .NET. Cette spcification est produite par
lECMA. Elle est disponible sur le site de lECMA lURL : http://www.ecma-international.
org/publications/standards/ECMA-335.HTM

Introduction aux contraintes imposes aux langages .NET


Pour que les assemblages, compils partir dun langage, puissent tre grs par le CLR (ou
par une couche logicielle supportant le CLI) et utiliser toutes les classes et outils du framework
.NET, il faut que le langage et son compilateur respectent un ensemble de contraintes appeles
CLS (Common Langage Specification). Parmi ces contraintes on peut inclure le support des types
du CTS mais ce nest pas la seule contrainte.
Les contraintes imposes aux langages et leurs compilateurs par le CLS sont nombreuses. En
voici quelques-unes parmi les plus couramment cites :

Le langage doit prvoir une syntaxe pour rsoudre le cas dune classe qui implmente deux
interfaces qui ont un conflit de dfinitions de mthodes. Il y a conflit de dfinitions de mthodes lorsque deux mthodes, une dans chaque interface implmente par la classe, ont
le mme nom et la mme signature. Le CLS impose que la classe doit implmenter deux
mthodes distinctes.

Seulement certains types primitifs sont compatibles avec le CLS. Par exemple le type ushort
de C  nest pas compatible avec le CLS.

Les types des paramtres des mthodes publiques doivent tre CLS compliant. Cette notion
de CLS compliant est prsente quelques lignes plus loin.

Un objet lanc dans une exception doit tre une instance de la classe System.Exception, ou
dune classe drive de celle-ci.

La liste exhaustive de ces contraintes est disponible dans les MSDN larticle Common Language Specification .
La compatibilit dun langage avec le CLS nest pas forcment totale et on observe deux niveaux
de compatibilit :

Un langage supporte la compatibilit consommateur sil peut instancier et utiliser les


membres publics des classes publiques contenues dans des assemblages compatibles avec
le CLS.

Un langage supporte la compatibilit extenseur sil peut produire des classes drivant de
classes publiques contenues dans des assemblages compatibles avec le CLS. Cette compatibilit induit la compatibilit consommateur.

Les langages C  , VB.NET et C++/CLI supportent ces deux niveaux de compatibilit.

Le point de vue du dveloppeur


Un assemblage est compatible avec le CLS (on dit CLS compliant en anglais) si les lments suivants sont compatibles avec le CLS :

La dfinition des types publics.

CLI et CLS

La dfinition des membres publics et des membres protgs des types publics.

Les paramtres des mthodes publiques et des mthodes protges.

131

Ce qui veut dire que le code des classes et mthodes prives na pas tre compatible avec la
CLS.
Le dveloppeur a tout intrt dvelopper des librairies compatibles avec le CLS. Il lui sera
ainsi plus ais de rutiliser ces classes dans le futur. Heureusement, le dveloppeur na pas
tre spcialiste des contraintes imposes par le CLS pour vrifier si ses classes sont compatibles
avec le CLS. Vous pouvez, grce lattribut System.CLSCompliantAttribute, faire vrifier par
le compilateur si les lments de vos applications (assemblages, classes, mthodes...) sont compatibles avec le CLS. Par exemple :
Exemple 4-15 :
using System ;
[assembly : CLSCompliantAttribute(true)]
namespace CLSCompliantTest {
public class Program {
public static void Fct(ushort i ) { ushort j = i; }
static void Main(){}
}
}
Le compilateur gnre un avertissement car le type ushort, qui nest pas compatible avec le
CLS, est utilis comme type dun paramtre dune mthode publique dune classe publique. En
revanche, lutilisation du type ushort lintrieur du corps de la mthode Fct() ne provoque
pas derreur ni davertissement de compilation.
Avec lattribut CLSCompliantAttribute, on peut aussi indiquer au compilateur de ne pas tester
la compatibilit avec le CLS pour la mthode Fct() tout en gardant la vrification de la compatibilit avec le CLS dans le reste du programme comme ceci :
Exemple 4-16 :
using System ;
[assembly : CLSCompliantAttribute(true)]
namespace CLSCompliantTest {
public class Program {
[CLSCompliantAttribute(false)]
public static void Fct(ushort i ) { ushort j = i ; }
static void Main(){}
}
}
Comprenez bien que la mthode Fct() ne peut pas tre appele partir du code dun langage
ne connaissant pas le type ushort.

5
Processus, threads
et gestion de la synchronisation

Nous exposons ici les notions fondamentales que sont les processus et les threads, dans larchitecture des systmes dexploitation de type Windows NT/2000/XP. Il faut avoir lesprit
que le CLR (ou moteur dexcution) dcrit dans le chapitre prcdent est une couche logicielle charge dans un processus par un hte du moteur dexcution lorsquun assemblage
.NET est lanc.

Introduction
Un processus (process en anglais) est concrtement une zone mmoire contenant des ressources.
Les processus permettent au systme dexploitation de rpartir son travail en plusieurs units
fonctionnelles.
Un processus possde une ou plusieurs units dexcution appele(s) threads. Un processus possde aussi un espace dadressage virtuel priv accessible en lecture et criture, seulement par ses
propres threads.
Dans le cas des programmes .NET, un processus contient aussi dans son espace mmoire la
couche logicielle appele CLR ou moteur dexcution. La description du CLR fait lobjet du
chapitre prcdent. Cette couche logicielle est charge ds la cration du processus par lhte
du moteur dexcution (ceci est dcrit page 94).
Un thread ne peut appartenir qu un processus et ne peut utiliser que les ressources de ce processus. Quand un processus est cr par le systme dexploitation, ce dernier lui alloue automatiquement un thread appel thread principal (main thread ou primary thread en anglais). Cest ce
thread qui excute lhte du moteur dexcution, le chargeur du CLR.

134

Chapitre 5 : Processus, threads et gestion de la synchronisation

Une application est constitue dun ou plusieurs processus cooprants. Par exemple lenvironnement de dveloppement Visual Studio est une application, qui peut utiliser un processus pour
diter les fichiers sources et un processus pour la compilation.
Sous les systmes dexploitation Windows NT/2000/XP, on peut visualiser un instant donn
toutes les applications et tous les processus en lanant le gestionnaire des tches (task manager
en anglais). Il est courant davoir une trentaine de processus en mme temps, mme si vous
avez ouvert un petit nombre dapplications. En fait le systme excute un grand nombre de
processus, un pour la gestion de la session courante, un pour la barre des tches, et bien dautres
encore.

Les processus
Introduction
Dans un systme dexploitation Windows 32 bits, tournant sur un processeur 32 bits, un processus peut tre vu comme un espace linaire mmoire de 4Go (232 octets), de ladresse
0x00000000 0xFFFFFFFF. Cet espace de mmoire est dit priv, car inaccessible par les autres
processus. Cet espace se partage en 2Go pour le systme et 2Go pour lutilisateur. Windows et
certains processeurs soccupent de faire lopration de translation entre cet espace dadressage
virtuel et lespace dadressage rel.
Si N processus tournent sur une machine il nest (heureusement) pas ncessaire davoir Nx4Go
de RAM.

Windows alloue seulement la mmoire ncessaire chaque processus, 4Go tant la limite
suprieure dans un environnement 32 bits.

Un mcanisme de mmoire virtuelle du systme sauve sur le disque dur et charge en RAM
des morceaux de processus appels pages mmoire. Chaque page a une taille de 4Ko. L
encore tout ceci est transparent pour le dveloppeur et lutilisateur.

La classe System.Diagnostics.Process
Une instance de la classe System.Diagnostics.Process rfrence un processus. Les processus
qui peuvent tre rfrencs sont :

Le processus courant dans lequel linstance est utilise.

Un processus sur la mme machine autre que le processus courant.

Un processus sur une machine distante.

Les mthodes et champs de cette classe permettent de crer, dtruire, manipuler ou obtenir des
informations sur ces processus. Nous exposons ici quelques techniques courantes dutilisation
de cette classe.

Crer et dtruire un processus fils


Le petit programme suivant cre un nouveau processus, appel processus fils. Dans ce cas le
processus initial est appel processus parent. Ce processus fils excute le bloc note. Le thread du

Les processus

135

processus parent attend une seconde avant de tuer le processus fils. Le programme a donc pour
eet douvrir et de fermer le bloc note :
Exemple 5-1 :
using System.Diagnostics ;
using System.Threading ;
class Program {
static void Main() {
// Cree un processus fils qui lance notepad.exe
// avec le fichier texte hello.txt.
Process process = Process.Start("notepad.exe", "hello.txt") ;
// Endors le thread 1 seconde.
Thread.Sleep(1000) ;
// Tue le processus fils.
process.Kill();
}
}
La mthode statique Start() peut utiliser les associations qui existent sur un systme dexploitation, entre un programme et une extension de fichier. Concrtement ce programme a le mme
comportement si lon crit :
Process process = Process.Start("hello.txt") ;
Par dfaut un processus fils hrite du contexte de scurit de son processus parent. Cependant
une version surcharge de la mthode Process.Start() permet de lancer le processus fils dans
le contexte de scurit de nimporte quel utilisateur pourvu que vous ayez pu fournir le couple
login/mot de passe dans une instance de la classe System.Diagnostics.ProcessStartInfo.

Empcher de lancer deux fois le mme programme sur la mme


machine
Cette fonctionnalit est requise par de nombreuses applications. En eet, il est courant quil
ny ait pas de sens lancer simultanment plusieurs fois une mme application sur la mme
machine. Par exemple il ny a pas de sens lancer plusieurs fois WindowMessenger sur la mme
machine.
Jusquici, pour satisfaire cette contrainte sous Windows, les dveloppeurs utilisaient le plus souvent la technique dite du mutex nomm dcrite page 154. Lutilisation de cette technique pour
satisfaire cette contrainte soure des dfauts suivants :

Il y a le risque faible, mais potentiel, que le nom du mutex soit utilis par une autre application, auquel cas cette technique ne marche absolument plus et peut provoquer des bugs
diciles dtecter.

Cette technique ne peut rsoudre le cas gnral o lon nautorise que N instances de lapplication.

Grce aux mthodes statiques GetCurrentProcess() (qui retourne le processus courant) et


GetProcesses() (qui retourne tous les processus lancs sur la machine) de la classe System.

136

Chapitre 5 : Processus, threads et gestion de la synchronisation

Diagnostics.Process, ce problme trouve une solution lgante et trs facile implmenter


expose par le programme suivant :
Exemple 5-2 :
using System.Diagnostics ;
class Program {
static void Main() {
if ( TestSiDejaLance() ) {
System.Console.WriteLine("Ce programme est d
ej`
a lanc
e.") ;
}
else{
// ici le code de lapplication
}
}
static bool TestSiDejaLance() {
Process processCurrent = Process.GetCurrentProcess() ;
Process[] processes = Process.GetProcesses() ;
foreach ( Process process in processes )
if ( processCurrent.Id != process.Id )
if ( processCurrent.ProcessName == process.ProcessName )
return true ;
return false ;
}
}
La mthode GetProcesses() peut aussi retourner tous les processus sur une machine distante
en communiquant comme argument le nom de la machine distante.

Terminer le processus courant


Vous pouvez dcider de terminer le processus courant en appelant une des mthodes statiques
Exit(int exitCode) ou FailFast(string message) de la classe System.Environment.
La mthode Exit() reprsente lalternative de choix. Le processus se terminera proprement et
retournera au systme dexploitation le code de sorti spcifi. La terminaison est propre dans le
sens o tous les finaliseurs des objets courants sont correctement appels et les ventuelles excutions de blocs finally en attente seront eectus par les dirents threads. En contrepartie,
la terminaison du processus prendra un certain temps.
Comme son nom lindique, lalternative FailFast() termine rapidement le processus. Les prcautions cites pour la mthode Exit() ne sont pas prises. Une erreur fatale contenant le message prcis est logue par le systme dexploitation. Cette mthode est utiliser si lors de la
dtection dun problme non rcuprable vous jugez que la continuation du programme peut
potentiellement engendrer une corruption de donne.

Les threads
Introduction
Un thread comprend :

Les threads

137

Un compteur dinstructions, qui pointe vers linstruction en cours dexcution.

Une pile.

Un ensemble de valeurs pour les registres, dfinissant une partie de ltat du processeur
excutant le thread.

Une zone prive de donnes.

Tous ces lments sont rassembls sous le nom de contexte dexcution du thread. Lespace dadressage et par consquent toutes les ressources qui y sont stockes, sont communs tous les threads
dun mme processus.
Nous ne parlerons pas de lexcution des threads en mode noyau et en mode utilisateur. Ces deux
modes, utiliss par Windows depuis bien avant .NET, existent toujours puisquils se situent dans
une couche en dessous du CLR. Nanmoins ces modes ne sont absolument pas visibles du framework .NET.
Lutilisation en parallle de plusieurs threads constitue souvent une rponse naturelle limplmentation des algorithmes. En eet, les algorithmes utiliss dans les logiciels sont souvent
constitus de tches dont les excutions peuvent se faire en parallle. Attention, utiliser une
grande quantit de threads gnre beaucoup de context switching, et finalement nuit aux performances.
En outre, nous constatons depuis quelques annes que la loi de Moore qui prdisait un doublement de la rapidit dexcution des processeurs nest plus vrifie. Leur frquence semble
stagner autour de 3/4GHz. Cela est du des limites physiques qui prendront quelques temps
tre surmontes. Aussi, pour continuer la course aux performances, les grands fabricants de
processeurs tels que AMD et Intel sorientent vers des solutions types multi processeurs dans un
seul chip. En consquence, on peut sattendre voir prolifrer ce type darchitecture dans les
prochaines annes. La seule solution pour amliorer les performances des applications sera alors
davoir un recours massif au multithreading, do limportance des notions prsentes dans le
prsent chapitre.

Notion de thread gr
Il faut bien comprendre que les threads qui excutent les applications .NET sont bien ceux de
Windows. Cependant on dit quun thread est gr quand le CLR connat ce dernier. Concrtement, un thread est gr sil est cr par du code gr. Si le thread est cr par du code non gr,
alors il nest pas gr. Cependant un tel thread devient gr ds quil excute du code gr.
Un thread gr se distingue dun thread non gr par le fait que le CLR cre une instance de la
classe System.Threading.Thread pour le reprsenter et le manipuler. En interne, le CLR garde
une liste des threads grs nomme ThreadStore.
Le CLR fait en sorte que chaque thread gr soit excut au sein dun domaine dapplication,
un instant donn. Cependant un thread nest absolument pas cantonn un domaine dapplication, et il peut en changer au cours du temps. La notion de domaine dapplication est prsente
page 87.
Dans le domaine de la scurit, lutilisateur principal dun thread gr est indpendant de lutilisateur principal du thread non gr sous-jacent.

138

Chapitre 5 : Processus, threads et gestion de la synchronisation

Le multitche premptif
On peut se poser la question suivante : mon ordinateur a un processeur (voire deux) et pourtant
le gestionnaire des tches indique quune centaine de threads sexcutent simultanment sur ma
machine ! Comment cela est-il possible ?
Cela est possible grce au multitche premptif qui gre lordonnancement des threads. Une partie
du noyau de Windows, appele rpartiteur (scheduler en anglais), segmente le temps en portions
appeles quantum (appeles aussi time slices). Ces intervalles de temps sont de lordre de quelques
millisecondes et ne sont pas de dure constante. Pour chaque processeur, chaque quantum est
allou un seul thread. La succession trs rapide des threads donne lillusion lutilisateur que
les threads sexcutent simultanment. On appelle context switching lintervalle entre deux
quantums conscutifs. Un avantage de cette mthode est que les threads en attente dune ressource nont pas dintervalle de temps allou jusqu la disponibilit de la ressource.
Ladjectif premptif utilis pour qualifier une telle gestion du multitche vient du fait que les
threads sont interrompus dune manire autoritaire par le systme. Pour les curieux sachez que
durant le context switching, le systme dexploitation place une instruction de saut vers le prochain context switching dans le code qui va tre excut par le prochain thread. Cette instruction
est de type interruption software. Si le thread doit sarrter avant de rencontrer cette instruction
(par exemple parce quil est en attente dune ressource) cette instruction est automatiquement
enleve et le context switching a lieu prmaturment.
Linconvnient majeur du multitche premptif est la ncessit de protger les ressources dun
accs anarchique avec des mcanismes de synchronisation. Il existe thoriquement un autre modle de la gestion du multitche, dit multitche coopratif, o la responsabilit de dcider quand
donner la main incombe au threads eux-mmes, mais ce modle est dangereux car les risques de
ne jamais rendre la main sont trop grands. Comme nous lexpliquons en page 100, ce mcanisme
est cependant utilis en interne pour optimiser les performances de certains serveurs tels que
SQL Server2005. En revanche, les systmes dexploitation Windows nimplmentent plus que le
multitche premptif.

Les niveaux de priorit dexcution


Certaines tches sont plus prioritaires que dautres. Concrtement elles mritent que le systme
dexploitation leur alloue plus de temps processeur. Par exemple, certains pilotes de priphriques pris en charge par le processeur principal, ne doivent pas tre interrompus. Une autre
catgorie de tches prioritaires sont les interfaces graphiques utilisateurs. En eet, les utilisateurs
naiment pas attendre que linterface se rafrachisse.
Ceux qui viennent du monde win32, savent bien que le systme dexploitation Windows, sousjacent au CLR, assigne un numro de priorit chaque thread entre 0 et 31. Cependant, il nest
pas dans la philosophie de la gestion des threads sous .NET de travailler directement avec cette
valeur, parce que :

Elle est peu explicite.


Cette valeur est susceptible de changer au cours du temps.

Niveau de priorit dun processus


Vous pouvez assigner une priorit vos processus avec la proprit PriorityClass de la classe
System.Diagnostics.Process. Cette proprit est de type lnumration System.Diagnostics.
ProcessPriorityClass qui a les valeurs suivantes :

Les threads

139

Valeur de ProcessPriorityClass

Niveau de priorit correspondant

Low

BelowNormal

Normal

AboveNormal

10

High

13

RealTime

24

Le processus propritaire de la fentre en premier plan voit sa priorit incrmente dune unit
si la proprit PriorityBoostEnabled de la classe System.Diagnostics.Process est positionne
true. Cette proprit est par dfaut positionne true. Cette proprit nest accessible sur une
instance de la classe Process, que si celle-ci rfrence un processus sur la mme machine.
Vous avez la possibilit de changer la priorit dun processus en utilisant le gestionnaire des tches
avec la manipulation : Click droit sur le processus choisi  Dfinir la priorit  Choisir parmi les
six valeurs proposes savoir Temps rel, Haute ; Suprieure la normale ; Normale ; Infrieure la
normale ; Basse.
Les systmes dexploitation Windows ont un processus dinactivit (idle en anglais) qui a la priorit
0. Cette priorit nest accessible aucun autre processus. Par dfinition lactivit des processeurs,
note en pourcentage, est :
100% moins le pourcentage de temps pass dans le thread du processus dinactivit.

Niveau de priorit dun thread


Chaque thread peut dfinir sa propre priorit par rapport celle de son processus, avec la proprit Priority de la classe System.Threading.Thread. Cette proprit est de type lnumration System.Threading.ThreadPriority qui prsente les valeurs suivantes :
Valeur
ThreadPriority

de

Eet sur la priorit du thread

Lowest

-2 units par rapport la priorit du processus

BelowNormal

-1 unit par rapport la priorit du processus

Normal

mme priorit que la priorit du processus

AboveNormal

+1 unit par rapport la priorit du processus

Highest

+2 units par rapport la priorit du processus

140

Chapitre 5 : Processus, threads et gestion de la synchronisation

Dans la plupart de vos applications, vous naurez pas modifier la priorit de vos processus et
threads, qui par dfaut, est assigne Normal.

La classe System.Threading.Thread
Le CLR associe automatiquement une instance de la classe System.Threading.Thread chaque
thread gr. Vous pouvez utiliser cet objet pour manipuler le thread partir dun autre thread
ou partir du thread lui-mme. Vous pouvez obtenir cet objet associ au thread courant avec
la proprit statique CurrentThread de la classe System.Threading.Thread :
using System.Threading ;
...
Thread threadCurrent = Thread.CurrentThread ;
Une fonctionnalit de la classe Thread, bien pratique pour dboguer une application multithread (multitches), est la possibilit de pouvoir nommer ses threads avec une chane de caractres :
threadCurrent.Name = "thread Foo" ;

Crer et joindre un thread


Pour crer un nouveau thread dans le processus courant, il sut de crer une nouvelle
instance de la classe Thread. Les dirents constructeurs de cette classe prennent en argument un dlgu de type System.Threading.ThreadStart ou de type System.Threading.
ParametrizedThreadStart qui rfrence la mthode qui va tre excute par le thread cr.
Lutilisation dun dlgu de type ParametrizedThreadStart permet de passer un objet la
mthode qui va tre excute par un nouveau thread. Des constructeurs de la classe thread
acceptent aussi un paramtre entier permettant de fixer la taille maximale en octet de la pile
du thread cr. Cette taille doit tre au moins gale 128Ko (i.e 131072 octets). Aprs quune
instance de type Thread est cre, il faut appeler la mthode Thread.Start() pour eectivement
dmarrer le thread :
Exemple 5-3 :
using System.Threading ;
class Program {
static void f1() { System.Console.WriteLine("f1") ; }
void f2() { System.Console.WriteLine("f2") ; }
static void f3(object obj) { System.Console.WriteLine(
"f3 obj = {0}",obj) ; }
static void Main() {
// Specification explicite de la d
el
egation ThreadStart.
Thread t1 = new Thread(new ThreadStart(f1));
Program program = new Program();
// Utilisation de la possibilit
e C#2 dinf
erer
// le type dun delegue.
Thread t2 = new Thread( program.f2 );

Les threads

141

// Inference dun delegue de type ParametrizedThreadStart


// puisque f3() a un unique param
etre de type object.
Thread t3 = new Thread( f3 );
t1.Start() ; t2.Start() ; t3.Start("hello") ;
t1.Join() ; t2.Join() ; t3.Join() ;
}
}
Ce programme ache :
f1
f2
f3 obj = hello
Dans cet exemple, nous utilisons la mthode Join(), qui suspend lexcution du thread courant jusqu ce que le thread sur lequel sapplique cette mthode ait termin. Cette mthode
existe aussi en une version surcharge qui prend en paramtre un entier qui dfinie le nombre
maximal de millisecondes attendre la fin du thread (i.e un time out). Cette version de Join()
retourne un boolen positionn true si le thread sest eectivement termin.

Suspendre lactivit dun thread


Vous avez la possibilit de suspendre lactivit dun thread pour une dure dtermine en utilisant la mthode Sleep() de la classe Thread. Vous pouvez spcifier la dure au moyen dun
entier qui dsigne un nombre de millisecondes ou avec une instance de la structure System.
TimeSpan. Bien quune telle instance puisse spcifier une dure avec la prcision du dixime de
milliseconde (100 nano secondes) la granularit temporelle de la mthode Sleep() nest qu la
milliseconde.
// Le thread courant est suspendu pour une seconde.
Thread.Sleep(1000) ;
On peut aussi suspendre lactivit dun thread en appelant la mthode Suspend() de la classe
Thread, partir dun autre thread ou partir du thread suspendre. Dans les deux cas le
thread se bloque jusqu ce quun autre thread appelle la mthode Resume() de la classe Thread.
Contrairement la mthode Sleep(), un appel Suspend() ne suspend pas immdiatement
le thread, mais le CLR suspendra ce thread au prochain point protg rencontr. La notion de
point protg est prsente en page 120.

Terminer un thread
Un thread gr peut se terminer selon plusieurs scnarios :

Il sort de la mthode sur laquelle il avait commenc sa course (la mthode Main() pour
le thread principal, la mthode rfrence par le dlgu ThreadStart pour les autres
threads).

Il sauto interrompt (il se suicide).

Il est interrompu par un autre thread.

142

Chapitre 5 : Processus, threads et gestion de la synchronisation

Le premier cas tant trivial, nous ne nous intressons quaux deux autres cas. Dans ces deux cas,
la mthode Abort() peut tre utilise (par le thread courant ou par un thread extrieur). Elle
provoque lenvoi dune exception de type ThreadAbortException. Cette exception a la particularit dtre relance automatiquement lorsquelle est rattrape par un gestionnaire dexception
car le thread est dans un tat spcial nomm AbortRequested. Seul lappel la mthode statique
ResetAbort() (si on dispose de la permission ncessaire) dans le gestionnaire dexception empche cette propagation.
Exemple 5-4 :

Suicide du thread principal

using System ;
using System.Threading ;
namespace ThreadTest{
class Program {
static void Main() {
Thread t = Thread.CurrentThread ;
try{
t.Abort() ;
}
catch( ThreadAbortException ) {
Thread.ResetAbort() ;
}
}
}
}
Lorsquun thread A appelle la mthode Abort() sur un autre thread B, il est conseill que A
attende que B soit eectivement termin en appelant la mthode Join() sur B.
Il existe aussi la mthode Interrupt() qui permet de terminer un thread lorsquil est dans un
tat dattente (i.e bloqu sur une des mthodes Wait(), Sleep() ou Join()). Cette mthode a
un comportement dirent selon que le thread terminer est dans un tat dattente ou non.

Si le thread terminer est dans un tat dattente lorsque Interrupt() est appele par un
autre thread, lexception ThreadInterruptedException est lance.

Si le thread terminer nest pas dans un tat dattente lorsque Interrupt() est appele, la
mme exception sera lance ds que ce thread rentrera dans un tat dattente. Le comportement est le mme si le thread terminer appelle Interrupt() sur lui-mme.

Notion de threads foreground et background


La classe Thread prsente la proprit boolenne IsBackground. Un thread foreground est un
thread qui empche la terminaison du processus tant quil nest pas termin. loppos un
thread background est un thread qui est termin automatiquement par le CLR (par lappel
la mthode Abort()) lorsquil ny a plus de thread foreground dans le processus concern.
IsBackground est positionne false par dfaut, ce qui fait que les threads sont foreground par
dfaut.
On pourrait traduire thread foreground par thread de premier plan, et thread background par thread
de fond.

Introduction la synchronisation des accs aux ressources

143

Diagramme dtats dun thread


La classe Thread a le champ ThreadState de type lnumration System.Threading.ThreadState.
Les valeurs de cette numration sont :
Aborted
Running
Suspended
WaitSleepJoin

AbortRequested
Stopped
SuspendRequested

Background
StopRequested
Unstarted

La description de chacun de ces tats se trouve dans larticle ThreadState Enumeration des
MSDN. Cette numration est un indicateur binaire, cest--dire que ses instances peuvent
prendre plusieurs valeurs la fois. Par exemple un thread peut tre la fois dans ltat Running
AbortRequested et Background. La notion dindicateur binaire est prsente page 369.
Daprs ce quon a vu dans la section prcdente, on peut dfinir le diagramme dtat simplifi
suivant :
Unstarted
Start()

Wait()
Sleep()
Join()

WaitSleepJoin
Interrupt()

Suspended()
Running

Suspended
Resume()

Abort()
Stopped

Figure 5 -1 : Diagramme dtat dun thread, simplifi

Introduction la synchronisation
des accs aux ressources
En informatique, le mot synchronisation ne peut tre utilis que dans le cas des applications
multithreads (mono ou multi-processus). En eet, la particularit de ces applications est davoir
plusieurs units dexcution, do possibilit de conflits daccs aux ressources. Les objets de
synchronisation sont des objets partageables entre threads excuts sur la mme machine. Le
propre dun objet de synchronisation est de pouvoir bloquer un des threads utilisateur jusqu
la ralisation dune condition par un autre thread.
Comme nous allons le voir, il existe de nombreuses classes et mcanismes de synchronisation.
Chacun rpond un ou plusieurs besoins spcifiques et il est ncessaire davoir assimil tout ce
chapitre avant de concevoir une application professionnelle multithreads utilisant la synchronisation. Nous nous sommes eorc de souligner les dirences, surtout les plus subtiles, qui
existent entre les dirents mcanismes. Quand vous aurez compris les dirences, vous serez
capable dutiliser ces mcanismes.
Synchroniser correctement un programme est une des tches du dveloppement logiciel les plus
subtiles. Le sujet remplit de nombreux ouvrages. Avant de vous plonger dans des spcifications

144

Chapitre 5 : Processus, threads et gestion de la synchronisation

compliques, soyez certains que lutilisation de la synchronisation est incontournable. Souvent


lutilisation de quelques rgles simples sut viter davoir grer la synchronisation. Parmi
ces rgles, citons la rgle danit entre threads et ressources que nous dcrivons un peu plus
loin dans ce chapitre.
Soyez conscient que la dicult de synchroniser les accs aux ressources dun programme vient
du dilemme entre une granularit des verrous trop fine et une granularit trop grossire. Si
vous synchronisez trop grossirement les accs vos ressources, vous simplifierez votre code
mais vous vous exposez des problmes de contention type goulot dtranglement. Si vous les
synchronisez trop finement, votre complexifierez votre code et terme vous ne pourrez plus le
maintenir. Vous tes alors expos aux problmes de deadlock et de race condition dcris ci aprs.
Avant daborder les mcanismes de synchronisation, il est ncessaire davoir une ide prcise
des notions de race conditions (situations de comptition en franais) et de deadlocks (interblocages
en franais).

Race conditions
Il sagit dune situation o des actions eectues par des units dexcution direntes senchanent dans un ordre illogique, entranant des tats non prvus.
Par exemple un thread T modifie une ressource R, rend les droits daccs dcriture R, reprend
les droits daccs en lecture sur R et utilise R comme si son tat tait celui dans lequel il lavait
laiss. Pendant lintervalle de temps entre la libration des droits daccs en criture et lacquisition des droits daccs en lecture, il se peut quun autre thread ait modifi ltat de R.
Un autre exemple classique de situation de comptition est le modle producteur consommateur. Le producteur utilise souvent le mme espace physique pour stocker les informations produites. En gnral on noublie pas de protger cet espace physique des accs concurrents entre
producteurs et consommateurs. On oublie plus souvent que le producteur doit sassurer quun
consommateur a eectivement lu une ancienne information avant de produire une nouvelle
information. Si lon ne prend pas cette prcaution, on sexpose au risque de produire des informations qui ne seront jamais consommes.
Les consquences de situations de comptition mal gres peuvent tre des failles dans un systme de scurit. Une autre application peut forcer un enchanement dactions non prvues
par les dveloppeurs. Typiquement il faut absolument protger laccs en criture un boolen
qui confirme ou infirme une authentification. Sinon il se peut que son tat soit modifi entre
linstant ou ce boolen est positionn par le mcanisme dauthentification et linstant ou ce
boolen est lu pour protger des accs des ressources. De clbres cas de failles de scurit
dues une mauvaise gestion des situations de comptition ont exist. Une de celles-ci concernait
notamment le noyau Unix.

Deadlocks
Il sagit dune situation de blocage cause de deux ou plusieurs units dexcution qui sattendent mutuellement. Par exemple :
Un thread T1 acquiert les droits daccs sur la ressource R1.
Un thread T2 acquiert les droits daccs sur la ressource R2.
T1 demande les droits daccs sur R2 et attend, car cest T2 qui les possde.

Synchronisation avec les champs volatiles et la classe Interlocked

145

T2 demande les droits daccs sur R1 et attend, car cest T1 qui les possde.
T1 et T2 attendront donc indfiniment, la situation est bloque ! Il existe trois grandes approches
pour viter ce problme qui est plus subtil que la plupart des bugs que lon rencontre.

Nautoriser aucun thread avoir des droits daccs sur plusieurs ressources simultanment.

Dfinir une relation dordre dans lacquisition des droits daccs aux ressources. Cest--dire
quun thread ne peut acqurir les droits daccs sur R2 sil na pas dj acquis les droits daccs
sur R1. Naturellement la libration des droits daccs se fait dans lordre inverse de lacquisition.

Systmatiquement dfinir un temps maximum dattente (timeout) pour toutes les demandes daccs aux ressources et traiter les cas dchec. Pratiquement tous les mcanismes
de synchronisation .NET orent cette possibilit.

Les deux premires techniques sont plus ecaces mais aussi plus dicile implmenter. En effet, elles ncessitent chacune une contrainte trs forte et dicile maintenir durant lvolution
de lapplication. En revanche les situations dchecs sont inexistantes.
Les gros projets utilisent systmatiquement la troisime technique. En eet, si le projet est gros,
le nombre de ressources est en gnral trs grand. Dans ces projets, les conflits daccs simultans une ressource sont donc des situations marginales. La consquence est que les situations
dchec sont, elles aussi, marginales. On dit dune telle approche quelle est optimiste. Dans le
mme esprit, on dcrit en page 727 le modle de gestion optimiste des accs concurrents une
base de donnes.

Synchronisation avec les champs volatiles


et la classe Interlocked
Les champs volatiles
Un champ dun type peut tre accd par plusieurs threads. Supposons que ces accs, en lecture
ou en criture, ne soient pas synchroniss. Dans ce cas, les nombreux mcanismes internes du
CLR pour grer le code font quil ny a pas de garantie que chaque accs en lecture au champ
charge la valeur la plus rcente. Un champ dclar volatile vous donne cette garantie. En langage
C  , un champ est dclar volatile si le mot-cl volatile est crit devant sa dclaration.
Tous les champs ne peuvent pas tre volatiles. Il y a une restriction sur le type du champ. Pour
quun champ puisse tre volatile, il faut que son type soit dans cette liste :

Un type rfrence.

Un pointeur (dans une zone de code non protge).

sbyte, byte, short, ushort, int, uint, char, float, bool (double, long et ulong,
la condition de travailler avec une machine 64 bits).

Une numration dont le type sous-jacent est parmi : byte, sbyte, short, ushort, int,
uint (double, long et ulong condition de travailler avec une machine 64 bits).

Comme vous laurez remarquez, seuls les types dont la valeur ou la rfrence fait au plus le
nombre doctets dun entier natif (quatre ou huit selon le processeur sous-jacent) peuvent tre

146

Chapitre 5 : Processus, threads et gestion de la synchronisation

volatiles. Cela implique que les oprations concurrentes sur une valeur de plus de ce nombre
doctets (une grosse structure par exemple) doivent tre protges en utilisant les mcanismes
de synchronisation prsents ci-aprs.

La classe System.Threading.Interlocked
Lexprience a montr que les ressources protger dans un contexte multithreads sont souvent des variables entires. Les oprations les plus courantes ralises par les threads sur ces
variables entires partages sont lincrmentation et la dcrmentation dune unit et laddition de deux variables. Le framework .NET prvoit donc un mcanisme spcial avec la classe
System.Threading.Interlocked pour ces oprations trs spcifiques, mais aussi trs courantes.
Cette classe les mthodes statiques Increment(), Decrement() et Add() qui respectivement incrmente, dcrmente et additionne des entiers de type int ou long passs par rfrence. On dit
que lutilisation de la classe Interlocked rend ces oprations atomiques (cest--dire indivisibles,
comme ce que lon pensait il y a quelques dcennies pour les atomes de la matire).
Le programme suivant prsente laccs concurrent de deux threads la variable entire
compteur. Un thread lincrmente cinq fois tandis que lautre la dcrmente cinq fois.
Exemple 5-5 :
using System.Threading ;
class Program {
static long compteur = 1 ;
static void Main() {
Thread t1 = new Thread(f1) ;
Thread t2 = new Thread(f2) ;
t1.Start() ; t2.Start() ; t1.Join() ; t2.Join() ;
}
static void f1() {
for (int i = 0 ; i < 5 ; i++){
Interlocked.Increment(ref compteur);
System.Console.WriteLine("compteur++ {0}", compteur) ;
Thread.Sleep(10) ;
}
}
static void f2() {
for (int i = 0 ; i < 5 ; i++){
Interlocked.Decrement(ref compteur);
System.Console.WriteLine("compteur-- {0}", compteur) ;
Thread.Sleep(10) ;
}
}
}
Ce programme ache ceci (dune manire non dterministe, cest--dire que lachage pourrait
varier dune excution une autre) :
compteur++ 2
compteur-- 1
compteur++ 2

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock


compteur-compteur++
compteur-compteur++
compteur-compteur-compteur++

147

1
2
1
2
1
0
1

Si on nendormait pas les threads 10 millimes de seconde chaque modification, les threads
auraient le temps de raliser leurs tches en un quantum et il ny aurait pas lentrelacement des
excutions, donc pas daccs concurrent.

Autre possibilit dutilisation de la classe Interlocked


La classe Interlocked permet de rendre atomique une autre opration usuelle qui est la recopie
de ltat dun objet source vers un objet destination au moyen de la mthode statique surcharge
Exchange(). Elle permet aussi de rendre atomique lopration de comparaison des tats de deux
objets, et dans le cas dgalit, la recopie de cet tat vers un troisime objet au moyen de la
mthode statique surcharge CompareExchange().

Synchronisation avec la classe


System.Threading.Monitor et le mot-cl lock
Le fait de rendre des oprations simples atomiques (des oprations comme lincrmentation, la
dcrmentation ou la recopie dun tat), est indniablement important mais est loin de couvrir
tous les cas o la synchronisation est ncessaire. La classe System.Threading.Monitor permet
de rendre nimporte quelle portion de code excutable par un seul thread la fois. On appelle
une telle portion de code une section critique.

Les mthodes Enter() et Exit()


La classe Monitor prsente les mthodes statiques Enter(object) et Exit(object). Ces mthodes prennent un objet en paramtre. Cet objet constitue un moyen simple didentifier de
manire unique la ressource protger dun accs concurrent. Lorsquun thread appelle la mthode Enter(), il attend davoir le droit exclusif de possder lobjet rfrenc (il nattend que
si un thread a dj ce droit). Une fois ce droit acquis et consomm, le thread libre ce droit en
appelant Exit() sur ce mme objet.

Un thread peut appeler Enter() plusieurs fois sur le mme objet la condition quil appelle
Exit() autant de fois sur le mme objet pour se librer des droits exclusifs.
Un thread peut possder des droits exclusifs sur plusieurs objets la fois, mais cela peut
mener une situation de deadlock.
Il ne faut jamais appeler les mthodes Enter() et Exit() sur un objet de type valeur,
comme un entier !
Il faut toujours appeler la mthode Exit() dans un bloc finally afin dtre certain de
librer les droits daccs exclusifs quoi quil arrive.

148

Chapitre 5 : Processus, threads et gestion de la synchronisation

Si dans lExemple 5-5, un thread doit lever la variable compteur au carr tandis que lautre
thread doit la multiplier par deux, il faudrait remplacer lutilisation de la classe Interlocked
par lutilisation de la classe Monitor. Le code de f1() et f2() serait alors :
Exemple 5-6 :
using System.Threading ;
class Program {
static long compteur = 1 ;
static void Main() {
Thread t1 = new Thread(f1) ;
Thread t2 = new Thread(f2) ;
t1.Start() ; t2.Start() ; t1.Join() ; t2.Join() ;
}
static void f1() {
for (int i = 0 ; i < 5 ; i++){
try{
Monitor.Enter( typeof(Program) );
compteur *= compteur ;
}
finally{ Monitor.Exit( typeof(Program) ) ; }
System.Console.WriteLine("compteur^2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
static void f2() {
for (int i = 0 ; i < 5 ; i++){
try{
Monitor.Enter( typeof(Program) );
compteur *= 2 ;
}
finally{ Monitor.Exit( typeof(Program) ) ; }
System.Console.WriteLine("compteur*2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
}
Il est tentant dcrire compteur la place de typeof(Program) mais compteur est un membre
statique de type valeur. Remarquez que les oprations lvation au carr et multiplication
par deux ntant pas commutatives, la valeur finale de compteur est ici non dtermine.

Le mot cl lock de C 
Le langage C  prsente le mot-cl lock qui remplace lgamment lutilisation des mthode Enter() et Exit(). La mthode f1() pourrait donc scrire :
Exemple 5-7 :
using System.Threading ;
class Program {

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock

149

static long compteur = 1 ;


static void Main() {
Thread t1 = new Thread(f1) ;
Thread t2 = new Thread(f2) ;
t1.Start() ; t2.Start() ; t1.Join() ; t2.Join() ;
}
static void f1() {
for (int i = 0 ; i < 5 ; i++){
lock( typeof(Program) ) { compteur *= compteur ; }
System.Console.WriteLine("compteur^2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
static void f2() {
for (int i = 0 ; i < 5 ; i++){
lock( typeof(Program) ) { compteur *= 2 ; }
System.Console.WriteLine("compteur*2 {0}", compteur) ;
Thread.Sleep(10) ;
}
}
}
linstar des blocs for et if, les blocs dfinis par le mot-cl lock ne sont pas tenus davoir des
accolades sils ne contiennent quune instruction. On aurait donc pu crire :
...
lock( typeof(Program) )
compteur *= compteur;
...
Lusage du mot-cl lock provoque bien la cration par le compilateur C  dun bloc try/finally
qui permet danticiper les leves dexceptions. Vous pouvez le vrifier avec un des outils Reflector
ou ildasm.exe.

Le pattern SyncRoot
linstar des exemples prcdents, on utilise en gnral la classe Monitor avec une instance de
la clase Type du type courant lintrieur dune mthode statique. De mme, on se synchronise
souvent sur le mot cl this lintrieur dune mthode non statique. Dans les deux cas, on
se synchronise sur un objet visible hors de la classe. Cela peut poser des problmes si dautres
parties du code se synchronisent sur ces objets. Pour viter ces problmes potentiels, nous vous
conseillons dutiliser un membre priv SyncRoot de type object, statique ou non selon vos besoins :
Exemple 5-8 :
class Foo {
private static object staticSyncRoot = new object();
private object instanceSyncRoot = new object();
public static void StaticFct() {
lock (staticSyncRoot) { /*...*/ }

150

Chapitre 5 : Processus, threads et gestion de la synchronisation


}
public void InstanceFct() {
lock (instanceSyncRoot) { /*...*/ }
}
}

Linterface System.Collections.ICollection prsente la proprit object SyncRoot{get;}.


La plupart des classes de collections (gnriques ou non) implmentent cette interface. Aussi,
vous pouvez vous servir de cette proprit pour synchroniser les accs aux lments dune collection. Ici, le pattern SyncRoot nest pas vraiment appliqu puisque lobjet sur lequel on synchronise les accs nest pas priv :
Exemple 5-9 :
using System.Collections.Generic ;
using System.Collections ;
public class Program {
public static void Main() {
List<int> list = new List<int>() ;
// ...
lock (((ICollection)list).SyncRoot) {
foreach (int i in list) {
// faire un traitement...
}
}
}
}

Notion de classe thread-safe


Une classe thread-safe est une classe dont chaque instance ne peut tre accde par plusieurs
threads la fois. Pour fabriquer une classe thread-safe, il sut dappliquer le pattern SyncRoot que
lon vient de voir toutes ses mthodes dinstances. Un moyen ecace pour ne pas encombrer le
code dune classe que lon souhaite tre thread-safe est fournir une classe drive wrapper threadsafe comme ceci :
Exemple 5-10 :
class Foo {
private class FooSynchronized : Foo {
private object syncRoot = new object() ;
private Foo m_Foo ;
public FooSynchronized(Foo foo) { m_Foo = foo ; }
public override bool IsSynchronized { get { return true ; } }
public override void Fct1(){ lock(syncRoot) { m_Foo.Fct1() ; } }
public override void Fct2(){ lock(syncRoot) { m_Foo.Fct1() ; } }
}
public virtual bool IsSynchronized { get { return false ; } }
public static Foo Synchronized(Foo foo){
if( ! foo.IsSynchronized )

Synchronisation avec la classe System.Threading.Monitor et le mot-cl lock

151

return new FooSynchronized( foo );


return foo ;
}
public virtual void Fct1() { /*...*/ }
public virtual void Fct2() { /*...*/ }
}
Un autre moyen est davoir recours lattribut System.Runtime.Remoting.Contexts.Synchronization prsent un peu plus loin dans ce chapitre.

La mthode Monitor.TryEnter()
public static bool TryEnter(object [,int] )
Cette mthode est similaire Enter() mais elle nest pas bloquante. Si les droits daccs exclusifs
sont dj possds par un autre thread, cette mthode retourne immdiatement et sa valeur de
retour est false. On peut aussi rendre un appel TryEnter() bloquant pour une dure limite
spcifie en millisecondes. Puisque lissue de cette mthode est incertaine, et que dans le cas
o lon acquerrait les droits daccs exclusifs il faudrait les librer dans un bloc finally, il est
conseill de sortir immdiatement de la mthode courante dans le cas o lappel TryEnter()
chouerait :
Exemple 5-11 :
using System.Threading ;
class Program {
static void Main() {
// `A commenter pour tester le cas o`u TryEnter() retourne true.
Monitor.Enter(typeof(Program)) ;
Thread t1 = new Thread(f1) ;
t1.Start() ; t1.Join() ;
}
static void f1() {
bool bOwner = false ;
try {
if( ! Monitor.TryEnter( typeof(Program) ) )
return;
bOwner = true;
// ...
}
finally {
// Ne surtout pas appeler Monitor.Exit() si on a pas lacc`
es.
// Noubliez pas que lon passe n
ecessairement par le bloc
// finally, y compris si lappel `
a TryEnter() retourne false.
if( bOwner )
Monitor.Exit( typeof(Program) );
}
}
}

152

Chapitre 5 : Processus, threads et gestion de la synchronisation

Les mthodes Wait(), Pulse() et PulseAll() de la classe Monitor


public static bool Wait(object [,int])
public static void Pulse(object)
public static void PulseAll(object)
Les trois mthodes Wait(), Pulse() et PulseAll() doivent tre utilises ensembles et ne
peuvent tre correctement comprises sans un petit scnario. Lide est quun thread ayant les
droits daccs exclusifs un objet dcide dattendre (en appelant Wait()) que ltat de lobjet
change. Pour cela, ce thread doit accepter de perdre momentanment les droits daccs exclusifs
lobjet afin de permettre un autre thread de changer ltat de lobjet. Ce dernier doit signaler
le changement avec la mthode Pulse(). Voici un petit scnario expliquant ceci dans les dtails :

Le thread T1 possdant laccs exclusif lobjet OBJ, appelle la mthode Wait(OBJ) afin de
senregistrer dans une liste dattente passive de OBJ.

Par cet appel T1 perd laccs exclusif OBJ. Ainsi un autre thread T2 prend laccs exclusif
OBJ en appelant la mthode Enter(OBJ).

T2 modifie ventuellement ltat de OBJ puis appelle Pulse(OBJ) pour signaler cette modification. Cet appel provoque le passage du premier thread de la liste dattente passive de OBJ
(en loccurrence T1) en haut de la liste dattente active de OBJ. Le premier thread de la liste
active de OBJ a la garantie quil sera le prochain avoir les droits daccs exclusifs OBJ ds
quils seront librs. Il pourra ainsi sortir de son attente dans la mthode Wait(OBJ).

Dans notre scnario T2 libre les droits daccs exclusifs sur OBJ en appelant Exit(OBJ) et
T1 les rcupre et sort de la mthode Wait(OBJ).

La mthode PulseAll() fait en sorte que les threads de la liste dattente passive, passent
tous dans la liste dattente active. Limportant est que les threads soient dbloqus dans le
mme ordre quils ont appels Wait().

Si Wait(OBJ) est appele par un thread ayant appel plusieurs fois Enter(OBJ), ce thread devra
appeler Exit(OBJ) le mme nombre de fois pour librer les droits daccs OBJ. Mme dans ce
cas, un seul appel Pulse(OBJ) par un autre thread sut dbloquer le premier thread.
Le programme suivant illustre cette fonctionnalit au moyen de deux threads ping et pong qui
se obtiennent tour de rle les droits daccs un objet balle :
Exemple 5-12 :
using System.Threading ;
public class Program {
static object balle = new object();
public static void Main() {
Thread threadPing = new Thread( ThreadPingProc ) ;
Thread threadPong = new Thread( ThreadPongProc ) ;
threadPing.Start() ; threadPong.Start() ;
threadPing.Join() ; threadPong.Join() ;
}
static void ThreadPongProc() {
System.Console.WriteLine("ThreadPong: Hello!") ;
lock (balle)
for (int i = 0 ; i < 5 ; i++){

Synchronisation avec des mutex, des vnements et des smaphores

153

System.Console.WriteLine("ThreadPong: Pong ") ;


Monitor.Pulse(balle);
Monitor.Wait(balle);
}
System.Console.WriteLine("ThreadPong: Bye!") ;
}
static void ThreadPingProc() {
System.Console.WriteLine("ThreadPing: Hello!") ;
lock (balle)
for(int i=0 ; i< 5 ; i++){
System.Console.WriteLine("ThreadPing: Ping ") ;
Monitor.Pulse(balle);
Monitor.Wait(balle);
}
System.Console.WriteLine("ThreadPing: Bye!") ;
}
}
Ce programme ache dune manire non dterministe :
ThreadPing:
ThreadPing:
ThreadPong:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:
ThreadPong:
ThreadPing:

Hello!
Ping
Hello!
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Bye!

Le thread pong ne se termine pas et reste bloqu sur la mthode Wait(). Ceci rsulte du fait que
le thread pong a obtenu le droit daccs exclusif sur lobjet balle en deuxime.

Synchronisation avec des mutex, des vnements et des


smaphores
La classe de base abstraite System.Threading.WaitHandle admet trois classes drives dont lutilisation est bien connue de ceux qui ont dj utilis la synchronisation sous win32 :

La classe Mutex (le mot mutex est la concatnation de mutuelle exclusion. En franais on
parle parfois de mutant)).

La classe AutoResetEvent qui dfinit un vnement repositionnement automatique. La


classe ManualResetEvent qui dfinit un vnement repositionnement manuelle. Ces deux
classes drivent de la classe EventWaitHandle qui reprsente un vnement au sens large.

154

Chapitre 5 : Processus, threads et gestion de la synchronisation


La classe Semaphore.

La classe WaitHandle et ses classes drives, ont la particularit dimplmenter la mthode non
statique WaitOne() et les mthodes statiques WaitAll(), WaitAny() et SignalAndWait(). Elles
permettent respectivement dattendre quun objet soit signal, que tous les objets dans un tableau soient signals, quau moins un objet dans un tableau soit signal, de signaler un objet et
dattendre sur un autre. Contrairement la classe Monitor et Interlocked, ces classes doivent
tre instancies pour tre utilises. Il faut donc raisonner ici en terme dobjets de synchronisation et non dobjets synchroniss. Ceci implique que les objets passs en paramtre des mthodes statiques WaitAll(), WaitAny() et SignalAndWait() sont soit des mutex, soit des vnements soit des smaphores.

Partage dobjets de synchronisation


Il est important de noter une autre grosse distinction entre le modle de synchronisation propos par les classes drives de WaitHandle et celui de la classe Monitor. Lutilisation de la classe
Monitor se cantonne un seul processus. Lutilisation des classes drives de WaitHandle fait appel des objets win32 non grs qui peuvent tre connus de plusieurs processus de la mme machine. Pour cela, certains constructeurs des classes drives de la classe WaitHandle prsentent
un argument de type string qui permet de nommer lobjet de synchronisation. Ces classes prsentent aussi la mthode OpenExisting(string) qui permet dobtenir une rfrence vers un
objet de synchronisation existant.
Vous pouvez en fait allez plus loin puisque la classe WaitHandle drive de la classe MarshalByRefObject. Ainsi un objet de synchronisation peut tre partag entre plusieurs processus de
machines direntes qui communiquent avec la technologie .NET Remoting.
En page 211 nous expliquons comment exploiter les types MutexSecurity, EventWaitHandleSecurity et SemaphoreSecurity de lespace de noms System.Security.AccessControl pour
manipuler les droits daccs Windows accords aux objets de synchronisation.

Les Mutex
En terme de fonctionnalits les mutex sont proches de lutilisation de la classe Monitor ces
dirences prs :

On peut utiliser un mme mutex dans plusieurs processus dune mme machine ou sexcutant sur des machines direntes.

Lutilisation de Monitor ne permet pas de se mettre en attente sur plusieurs objets.

Les mutex nont pas la fonctionnalit des mthodes Wait(), Pulse() et PulseAll() de la
classe Monitor.

Sachez que lorsque lutilisation dun mutex se cantonne un processus, il vaut mieux synchroniser vos accs avec la classe Monitor qui est plus performante.
Le programme suivant montre lutilisation dun mutex nomm pour protger laccs une ressource partage par plusieurs processus sur la mme machine. La ressource partage est un fichier dans lequel chaque instance du programme crit 10 lignes.

Synchronisation avec des mutex, des vnements et des smaphores

155

Exemple 5-13 :
using System.Threading ;
using System.IO ;
class Program {
static void Main() {
// Le mutex est nomme MutexTest.
Mutex mutexFile = new Mutex(false, "MutexTest");
for (int i = 0 ; i < 10 ; i++){
mutexFile.WaitOne();
// Ouvre le fichier, ecrit Hello i, ferme le fichier.
FileInfo fi = new FileInfo("tmp.txt") ;
StreamWriter sw = fi.AppendText() ;
sw.WriteLine("Hello {0}", i) ;
sw.Flush() ;
sw.Close() ;
// Attend 1 seconde pour rendre
evidente laction du mutex.
System.Console.WriteLine("Hello {0}", i) ;
Thread.Sleep(1000) ;
mutexFile.ReleaseMutex();
}
mutexFile.Close();
}
}
Remarquez lutilisation de la mthode WaitOne() qui bloque le thread courant jusqu lobtention du mutex et lutilisation de la mthode ReleaseMutex() qui libre le mutex.
Dans ce programme, new Mutex ne signifie pas forcment la cration du mutex mais la cration
dune rfrence vers le mutex nomm "MutexTest". Le mutex est eectivement cr par le systme dexploitation seulement sil nexiste pas dj. De mme la mthode Close() ne dtruit
pas forcment le mutex si ce dernier est encore rfrenc par dautres processus.
Pour ceux qui avaient lhabitude dutiliser la technique du mutex nomm en win32 pour viter
de lancer deux instances du mme programme sur la mme machine, sachez quil existe une
faon plus lgante de procder sous .NET, dcrite dans la section page 135.

Les vnements
Contrairement aux mcanismes de synchronisation vus jusquici, les vnements ne dfinissent
pas explicitement de notion dappartenance dune ressource un thread un instant donn.
Les vnements servent passer une notification dun thread un autre, cette notification
tant un vnement sest pass . Lvnement concern est associ une instance dune
des deux classes dvnement, System.Threading.AutoResetEvent et System.Threading.
ManualResetEvent. Ces deux classes drivent de la classe System.Threading.EventWaitHandle.
Une instance de EventWaitHandle peut tre initialise avec une des deux valeurs AutoReset ou
ManualReset de lnumration System.Threading.EventResetMode ce qui fait que vous pouvez
viter de vous servir de ses deux classes filles.
Concrtement, un thread attend quun vnement soit signal en appelant la mthode bloquante WaitOne() sur lobjet vnement associ. Puis un autre thread signale lvnement en

156

Chapitre 5 : Processus, threads et gestion de la synchronisation

appelant la mthode Set() sur lobjet vnement associ et permet ainsi au premier thread de
reprendre son excution.
La dirence entre les vnement repositionnement automatique et les vnement repositionnement manuel est que lon a besoin dappeler la mthode Reset() pour repositionner lvnement
en position non active, sur un vnement de type repositionnement manuel aprs lavoir
signal.

La dirence entre le repositionnement manuel et automatique est plus importante que


lon pourrait croire. Si plusieurs threads attendent sur un mme vnement repositionnement automatique, il faut signaler lvnement une fois pour chaque thread. Dans le
cas dun vnement repositionnement manuel il sut de signaler une fois lvnement
pour dbloquer tous les threads.
Le programme suivant cre deux threads, t0 et t1, qui incrmentent chacun leur propre compteur des vitesses direntes. t0 signale lvnement events[0] lorsquil est arriv 2 et t1
signale lvnement events[1] lorsquil est arriv 3. Le thread principal attend que les deux
vnements soient signals pour acher un message.
Exemple 5-14 :
using System ;
using System.Threading ;
class Program {
static EventWaitHandle[] events ;
static void Main() {
events = new EventWaitHandle[2] ;
// Position initiale de l
ev
enement : false.
events[0] = new EventWaitHandle(false,EventResetMode.AutoReset);
events[1] = new EventWaitHandle(false,EventResetMode.AutoReset);
Thread t0 = new Thread(ThreadProc0) ;
Thread t1 = new Thread(ThreadProc1) ;
t0.Start() ; t1.Start() ;
AutoResetEvent.WaitAll(events);
Console.WriteLine("MainThread: Thread0 est arriv
e `
a 2"
+
" et Thread1 est arriv
e `
a 3." ) ;
t0.Join();t1.Join() ;
}
static void ThreadProc0() {
for (int i = 0 ; i < 5 ; i++){
Console.WriteLine("Thread0: {0}", i) ;
if (i == 2) events[0].Set();
Thread.Sleep(100) ;
}
}
static void ThreadProc1() {
for (int i = 0 ; i < 5 ; i++){
Console.WriteLine("Thread1: {0}", i) ;
if (i == 3) events[1].Set();

Synchronisation avec des mutex, des vnements et des smaphores

157

Thread.Sleep(60) ;
}
}
}
Ce programme ache :
Thread0: 0
Thread1: 0
Thread1: 1
Thread0: 1
Thread1: 2
Thread1: 3
Thread0: 2
MainThread: Thread0 est arrive `
a 2 et Thread1 est arriv
e `
a 3
Thread1: 4
Thread0: 3
Thread0: 4

Les smaphores
Une instance de la classe System.Threading.Semaphore permet de contraindre le nombre daccs simultans une ressource. Vous pouvez vous limaginez comme la barrire dentre dun
parking automobile qui contient un nombre fix de places. Elle ne souvre plus lorsque le parking est complet. De mme, une tentative dentre dans un smaphore au moyen de la mthode
WaitOne() devient bloquante lorsque le nombre dentres courantes a atteint le nombre dentre maximal. Ce nombre dentre maximal est fix par le deuxime argument des constructeurs
de la classe Semaphore. Le premier argument dfinit le nombre dentres libres initiales. Si le
premier argument a une valeur infrieure celle du deuxime, le thread qui appelle le constructeur dtient automatiquement un nombre dentres gal la dirence des deux valeurs. Cette
dernire remarque montre quun mme thread peut dtenir simultanment plusieurs entres
dun mme smaphore.
Lexemple suivant illustre tout ceci en lanant 3 threads qui tentent rgulirement dentrer dans
un smaphore dont le nombre dentres simultanes maximal est fix cinq. Le thread principal
dtient durant toute la dure de lexcution trois de ces entres, obligeant les 3 threads fils se
partager 2 entres.
Exemple 5-15 :
using System ;
using System.Threading ;
class Program {
static Semaphore semaphore;
static void Main() {
// Nombre dentrees libres initiales
: 2.
// Nombre maximal dentrees simultan
ees : 5.
// Nombre dentrees detenues par le thread principal : 3 (5-2).
semaphore = new Semaphore(2, 5);
for (int i = 0 ; i < 3 ; ++i){
Thread t = new Thread(WorkerProc) ;

158

Chapitre 5 : Processus, threads et gestion de la synchronisation


t.Name = "Thread" + i ;
t.Start() ;
Thread.Sleep(30) ;
}
}
static void WorkerProc() {
for (int j = 0 ; j < 3 ; j++){
semaphore.WaitOne();
Console.WriteLine(Thread.CurrentThread.Name + ": Begin") ;
Thread.Sleep(200) ; // Simule un travail de 200 ms.
Console.WriteLine(Thread.CurrentThread.Name + ": End") ;
semaphore.Release();
}
}
}

Voici lachage de ce programme. On voit bien quil ny a jamais plus de deux threads fils qui
travaillent simultanment :
Thread0:
Thread1:
Thread0:
Thread2:
Thread1:
Thread1:
Thread2:
Thread2:
Thread1:
Thread1:
Thread2:
Thread2:
Thread1:
Thread0:
Thread2:
Thread0:
Thread0:
Thread0:

Begin
Begin
End
Begin
End
Begin
End
Begin
End
Begin
End
Begin
End
Begin
End
End
Begin
End

Synchronisation avec la classe


System.Threading.ReaderWriterLock
La classe System.Threading.ReaderWriterLock implmente le mcanisme de synchronisation
accs lecture multiple/accs criture unique . Contrairement au modle de synchronisation
accs exclusif oert par la classe Monitor ou Mutex, ce mcanisme tient compte du fait quun
thread demande les droits daccs en lecture ou en criture. Un accs en lecture peut tre obtenu
lorsquil ny a pas daccs en criture courant. Un accs en criture peut tre obtenu lorsquil
ny a aucun accs courant, ni en lecture ni en criture. De plus lorsquun accs en criture a

Synchronisation avec la classe System.Threading.ReaderWriterLock

159

t demand mais pas encore obtenu, les nouvelles demandes daccs en lecture sont mises en
attente.

Lorsque ce modle de synchronisation peut tre appliqu, il faut toujours le privilgier par
rapport au modle propos par les classes Monitor ou Mutex. En eet, le modle accs exclusif ne permet en aucun cas des accs simultans et est donc moins performant. De plus,
empiriquement, on se rend compte que la plupart des applications accdent beaucoup plus
souvent aux donnes en lecture quen criture.
linstar des mutex et des vnements et au contraire de la classe Monitor, la classe ReaderWriterLock doit tre instancie pour tre utilise. Il faut donc ici aussi raisonner en terme dobjets
de synchronisation et non dobjets synchroniss.
Voici un exemple de code qui montre lutilisation de cette classe, mais qui nen exploite pas
toutes les possibilits. En eet les mthodes DowngradeFromWriterLock() et UpgradeToWriterLock() permettent de demander un changement de droit daccs sans avoir librer son droit
daccs courant.
Exemple 5-16 :
using System ;
using System.Threading ;
class Program {
static int laRessource = 0 ;
static ReaderWriterLock rwl = new ReaderWriterLock();
static void Main() {
Thread tr0 = new Thread(ThreadReader) ;
Thread tr1 = new Thread(ThreadReader) ;
Thread tw = new Thread(ThreadWriter) ;
tr0.Start() ; tr1.Start() ; tw.Start() ;
tr0.Join() ; tr1.Join() ; tw.Join() ;
}
static void ThreadReader() {
for (int i = 0 ; i < 3 ; i++)
try{
// AcquireReaderLock() lance une
// ApplicationException si timeout.
rwl.AcquireReaderLock(1000);
Console.WriteLine(
"Debut Lecture laRessource = {0}", laRessource) ;
Thread.Sleep(10) ;
Console.WriteLine(
"Fin
Lecture laRessource = {0}", laRessource) ;
rwl.ReleaseReaderLock() ;
}
catch ( ApplicationException ) { /* `
a traiter */ }
}
static void ThreadWriter() {
for (int i = 0 ; i < 3 ; i++)

160

Chapitre 5 : Processus, threads et gestion de la synchronisation


try{
// AcquireWriterLock() lance une
// ApplicationException si timeout.
rwl.AcquireWriterLock(1000);
Console.WriteLine(
"Debut Ecriture laRessource = {0}", laRessource) ;
Thread.Sleep(100) ;
laRessource++ ;
Console.WriteLine(
"Fin
Ecriture laRessource = {0}", laRessource) ;
rwl.ReleaseWriterLock() ;
}
catch ( ApplicationException ) { /* `
a traiter */ }
}
}

Ce programme ache :
Debut
Debut
Fin
Fin
Debut
Fin
Debut
Debut
Fin
Fin
Debut
Fin
Debut
Debut
Fin
Fin
Debut
Fin

lecture
lecture
lecture
lecture
ecriture
ecriture
lecture
lecture
lecture
lecture
ecriture
ecriture
lecture
lecture
lecture
lecture
ecriture
ecriture

laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource
laRessource

=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=

0
0
0
0
0
1
1
1
1
1
1
2
2
2
2
2
2
3

Synchronisation avec lattribut


System.Runtime.Remoting.Contexts.SynchronizationAttribute
Lorsque lattribut System.Runtime.Remoting.Contexts.Synchronization est appliqu une
classe, une instance de cette classe ne peut pas tre accde par plus dun thread la fois. On
dit alors que la classe est thread-safe.

Pour que ce comportement sapplique correctement il faut que la classe sur laquelle sapplique lattribut soit context-bound, cest--dire quelle doit driver de la classe System.
ContextBoundObject. La signification du terme context-bound est explique page 842.

Synchronisation avec lattribut System...SynchronizationAttribute

161

Voici un exemple illustrant comment appliquer ce comportement :


Exemple 5-17 :
using System.Runtime.Remoting.Contexts ;
using System.Threading ;
[Synchronization(SynchronizationAttribute.REQUIRED)]
public class Foo : System.ContextBoundObject {
public void AfficheThreadId() {
System.Console.WriteLine("D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId ) ;
Thread.Sleep(1000) ;
System.Console.WriteLine("Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId ) ;
}
}
public class Program {
static Foo m_Objet = new Foo() ;
static void Main() {
Thread t0 = new Thread( ThreadProc ) ;
Thread t1 = new Thread( ThreadProc ) ;
t0.Start() ; t1.Start() ;
t0.Join() ; t1.Join() ;
}
static void ThreadProc() {
for (int i = 0 ; i < 2 ; i++)
m_Objet.AfficheThreadId() ;
}
}
Cet exemple ache :
Debut
Fin :
Debut
Fin :
Debut
Fin :
Debut
Fin :

: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId
: ManagedThreadId
ManagedThreadId

=
=
=
=
=
=
=
=

27
27
28
28
27
27
28
28

La notion de domaine de synchronisation


Pour une bonne comprhension de ce qui suit, il faut avoir une connaissance des notions suivantes :

domaine dapplication (dcrit page 87),

contexte .NET et classe context-bound/context-agile (dcrit page 842),

intercepteur de messages (dcrit page 822).

162

Chapitre 5 : Processus, threads et gestion de la synchronisation

Un domaine de synchronisation est une entit entirement prise en charge par le CLR. Un tel
domaine contient un ou plusieurs contextes .NET et donc, contient les objets de ces contextes.
Un contexte .NET ne peut appartenir qu au plus un seul domaine de synchronisation. En
outre, la notion de domaine de synchronisation est plus fine que la notion de domaine dapplication. La figure suivante illustre les relations dinclusions entre processus, domaines dapplication, domaines de synchronisation, contextes .NET et objets .NET :
Processus

Domaine dapplication
Contexte

Domaine de
synchronisation
Contexte

Objet

Objet

Figure 5 -2 : Processus, domaine dapplication, domaine de synchronisation, contexte.NET et


objet .NET
Puisque nous parlons de la synchronisation, et donc des applications multi-threaded, il est utile
de rappeler que les domaines dapplications et les threads dun processus sont deux notions orthogonales. En eet, un thread peut traverser librement la frontire entre deux domaines dapplication, par exemple en appelant un objet A situ dans le domaine dapplication DA, partir
dun objet B situ dans le domaine dapplication DB. De plus, le code dun domaine dapplication
peut tre excut simultanment par zro, un ou plusieurs threads.
En revanche, tout lintrt dun domaine de synchronisation rside dans le fait quil ne peut tre
partag simultanment par plusieurs threads. Autrement dit, les mthodes des objets contenus
dans un domaine de synchronisation ne peuvent tre simultanment excutes par plusieurs
threads. Ceci implique qu un instant donn, au plus un thread se trouve dans domaine de
synchronisation. On parle alors de droits daccs exclusifs au domaine de synchronisation. Encore
une fois, la gestion de ces droits daccs exclusifs est entirement assure par le CLR.

Lattribut System.Runtime.Remoting.Contexts.Synchronization et
les domaines de synchronisation
Vous avez peut-tre dj devin que lattribut System.Runtime.Remoting.Contexts.Synchronization sert en fait indiquer au CLR quand crer un domaine de synchronisation et comment en dlimiter sa frontire. Ces rglages se font en utilisant dans la dclaration dun attribut System.Runtime.Remoting.Contexts.Synchronization une des quatre valeurs suivantes
NOTSUPPORTED, SUPPORTED, REQUIRED ou REQUIRES_NEW. Notez que la valeur REQUIRED est choisie
par dfaut.
Lappartenance un domaine dapplication se communique de proche en proche lorsquun objet en cre un autre. On parle ainsi dobjet crateur, mais prenez en compte quun objet peut tre
aussi cr au sein dune mthode statique. Or, il se peut que lexcution de la mthode statique
soit le fruit dun appel dun objet situ dans un domaine de synchronisation. Il faut alors savoir

Synchronisation avec lattribut System...SynchronizationAttribute

163

que dans ce cas, la mthode statique propage lappartenance au domaine dapplication et joue
ainsi le rle dun objet crateur. Voici lexplication des quatre comportements possibles :
NOT_SUPPORTED

Ce paramtre assure quune instance de la classe sur laquelle


sapplique lattribut Synchronization nappartiendra jamais
un domaine de synchronisation (que son objet crateur appartienne un domaine de synchronisation ou non).

SUPPORTED

Ce paramtre indique quune instance de la classe sur laquelle


sapplique lattribut Synchronization na pas besoin dappartenir un domaine de synchronisation. Nanmoins, une telle instance appartiendra au domaine de synchronisation de son objet
crateur si ce dernier en a un. Ce comportement est peu utile, car
le dveloppeur doit quand mme prvoir un autre mcanisme
de synchronisation au sein des mthodes de la classe. Cependant,
il peut ventuellement permettre de propager lappartenance
un domaine de synchronisation partir dun objet qui na pas
besoin dtre synchroniser (ce type dobjet est rare, puisquil ne
doit pas avoir dtat).

REQUIRED

Ce paramtre assure quune instance de la classe sur laquelle sapplique lattribut Synchronization sera de toutes faons dans
un domaine de synchronisation. Si lobjet crateur appartient
dj un domaine de synchronisation, alors on se satisfera de
celui l. Sinon, un nouveau domaine de synchronisation est cr
pour accueillir ce nouvel objet.

REQUIRES_NEW

Ce paramtre assure quune instance de la classe sur laquelle sapplique lattribut Synchronization sera dans un nouveau domaine de synchronisation (que son objet crateur appartienne
un domaine de synchronisation ou non).

Ces comportements peuvent se rsumer ainsi :


La
valeur
applique:

Est-ce que lobjet


crateur
est dans un
domaine
de
synchronisation ?

NOT_SUPPORTED Non

Lobjet cr rsidera...

... lextrieur de tout domaine de synchronisation.

Oui
SUPPORTED

Non

... lextrieur de tout domaine de synchronisation.

Oui

...dans le domaine de synchronisation de lobjet crateur.

164

Chapitre 5 : Processus, threads et gestion de la synchronisation

REQUIRED

REQUIRES_NEW

Non

...dans un nouveau domaine de synchronisation.

Oui

...dans le domaine de synchronisation de lobjet crateur.

Non

...dans un nouveau domaine de synchronisation.

Oui

La notion de rentrance dans les domaines de synchronisation


Dans un domaine de synchronisation D, lorsque le thread T1 qui a les droits daccs exclusifs
eectue un appel sur un objet situ hors de D, deux comportements peuvent alors tre appliqus
par le CLR :

Soit le CLR autorise un autre thread T2 acqurir les droits daccs exclusifs de D. Dans ce cas,
il se peut que T1 doivent attendre au retour de son appel lextrieur de D que T2 libre les
droits daccs exclusifs de D. On dit quil y a rentrance puisque T1 demande rentrer dans
D.

Soit les droits daccs exclusifs de D restent allous T1. Dans ce cas, un autre thread T2
souhaitant invoquer une mthode dun objet de D devra attendre que T1 libre les droits
daccs exclusifs de D.

Un appel une mthode statique nest pas considr comme un appel lextrieur dun domaine de synchronisation.
Temps

Course du thread T1

Domaine de synchronisation D
Objet 1

Objet 2

Objet 3

Mthode
Mthode
On dit quil y a rentrance dans le domaine
dapplication D si un
autre thread T1 peut
ventuellement acqurir
les droits daccs exclusifs de D

Mthode

Sil y a rentrance de T1 dans D alors


elle seffectue ce moment prcis

Figure 5 -3 : La rentrance dans un domaine de synchronisation


Certains constructeurs de la classe System.Runtime.Remoting.Contexts.Synchronization acceptent un boolen en paramtre. Ce boolen dfinit sil y a rentrance lors dun appel hors du
domaine de synchronisation courant. Lorsque ce boolen est positionn true, il y a rentrance.

Synchronisation avec lattribut System...SynchronizationAttribute

165

Notez quil sut que lattribut de synchronisation de la classe du premier objet rencontr par
un thread dans un domaine de synchronisation ait positionn la rentrance true pour quil y
ait eectivement rentrance.
Lexemple suivant est lillustration par le code de la figure ci-dessus :
Exemple 5-18 :
using System ;
using System.Runtime.Remoting.Contexts ;
using System.Threading ;
[Synchronization(SynchronizationAttribute.REQUIRES_NEW, true)]
public class Foo1 : ContextBoundObject {
public void AfficheThreadId() {
Console.WriteLine("Foo1 D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
Thread.Sleep(1000) ;
Foo2 obj2 = new Foo2() ;
obj2.AfficheThreadId() ;
Console.WriteLine("Foo1 Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
[Synchronization(SynchronizationAttribute.REQUIRED)]
public class Foo2 : ContextBoundObject {
public void AfficheThreadId() {
Console.WriteLine("Foo2 D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
Thread.Sleep(1000) ;
Foo3 obj3 = new Foo3() ;
obj3.AfficheThreadId() ;
Console.WriteLine("Foo2 Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
// On est certains que les instances de cette classe ne
// seront dans aucun domaine de synchronisation.
[Synchronization(SynchronizationAttribute.NOT_SUPPORTED)]
public class Foo3 : ContextBoundObject {
public void AfficheThreadId() {
Console.WriteLine("Foo3 D
ebut : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
Thread.Sleep(1000) ;
Console.WriteLine("Foo3 Fin : ManagedThreadId = " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
public class Program {
static Foo1 m_Objet = new Foo1() ;
static void Main() {

166

Chapitre 5 : Processus, threads et gestion de la synchronisation


Thread t1 = new Thread( ThreadProc ) ;
Thread t2 = new Thread( ThreadProc ) ;
t1.Start() ; t2.Start() ;
t1.Join() ; t2.Join() ;
}
static void ThreadProc() {
m_Objet.AfficheThreadId() ;
}
}

Ce programme ache ceci :


Foo1
Foo2
Foo1
Foo2
Foo3
Foo3
Foo3
Foo2
Foo1
Foo3
Foo2
Foo1

Debut
Debut
Debut
Debut
Debut
Debut
Fin :
Fin :
Fin :
Fin :
Fin :
Fin :

:
:
:
:
:
:

ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId
ManagedThreadId

=
=
=
=
=
=
=
=
=
=
=
=

3
3
4
4
4
3
4
4
4
3
3
3

Il est clair que si lon avait dsactiv la rentrance dans Foo1, lachage de ce programme aurait
t le suivant :
Foo1
Foo2
Foo3
Foo3
Foo2
Foo1
Foo1
Foo2
Foo3
Foo3
Foo2
Foo1

Debut
Debut
Debut
Fin :
Fin :
Fin :
Debut
Debut
Debut
Fin :
Fin :
Fin :

: ManagedThreadId = 3
: ManagedThreadId = 3
: ManagedThreadId = 3
ManagedThreadId = 3
ManagedThreadId = 3
ManagedThreadId = 3
: ManagedThreadId = 4
: ManagedThreadId = 4
: ManagedThreadId = 4
ManagedThreadId = 4
ManagedThreadId = 4
ManagedThreadId = 4

La rentrance est utilise pour optimiser la gestion des ressources car elle permet de rduire
globalement les dures daccs exclusifs des threads sur les domaines de synchronisation. Cependant par dfaut, la rentrance est dsactive. En eet, lorsquil y a rentrance, notre comprhension de la notion de domaine de synchronisation est fortement perturbe. Bien pire encore,
activer tort et travers la rentrance peut rapidement amener des situations de deadlock. Pour
ces raisons, il est fortement dconseill dactiver la rentrance.

Un autre attribut nomm Synchronization


Le framework .NET prsente un autre attribut ayant ce nom mais faisant partie dune autre
espace de noms. Cet attribut System.EntrepriseServices.Synchronization a la mme fina-

Le pool de threads du CLR

167

lit, mais il utilise le service dentreprise COM+ de synchronisation. Lutilisation de lattribut


System.Runtime.Remoting.Contexts.Synchronization est prfrable pour deux raisons :

Son utilisation est plus performante.

Ce mcanisme supporte les appels asynchrones, contrairement la version COM+.

La notion de service dentreprise COM+ est prsente page 295.

Le pool de threads du CLR


Introduction
Le concept de pool de threads nest pas nouveau. Cependant le framework .NET vous permet dutiliser un pool de threads beaucoup plus simplement que nimporte quelle autre technologie,
grce la classe System.Threading.ThreadPool.
Dans une application multithreads, la plupart des threads passent leur temps attendre des vnements. Concrtement vos threads sont globalement sous exploits. De plus, le fait de devoir
tenir compte de la gestion des threads lors du design de votre application est une dicult dont
on se passerait volontiers.
Lutilisation dun pool de threads rsout de faon lgante et performante ces problmes. Vous
postez des tches au pool qui se charge de les distribuer ses threads. Le pool est entirement
responsable de :

la cration et de la destruction de ses threads ;

la distribution des tches ;

lutilisation optimale de ses threads.

Le dveloppeur est donc dcharg de ces responsabilits. Malgr tous ces avantages, il est souhaitable de ne pas utiliser de pool de threads lorsque :

Vos tches doivent tre gres avec un systme de priorit.

Vos tches sont longues sexcuter (plusieurs secondes).

Vos tches doivent tre traites dans des STA (Single Apartment Thread). En eet les threads
dun pool sont de type MTA (Multiple Apartement Thread). Cette notion de thread apartment,
inhrente la technologie COM, est explique page 285.

Utilisation dun pool de threads


En .NET il ny a quun pool de threads par processus. Ainsi toutes les mthodes de la classe
ThreadPool sont statiques puisquelles sappliquent lunique pool. Le framework .NET utilise
ce pool pour les appels asynchrones, dcrits un peu plus loin dans ce chapitre, les mcanismes
dentr/sortie asynchrones ou les timers dcrits dans la prochaine section.
Le nombre maximal de threads du pool est de 25 threads par processeur pour traiter les oprations asynchrones (completion port thread ou thread I/O en anglais) et de 25 threads ouvrier (worker
thread en anglais) par processeur. Ces deux limites par dfaut sont modifiables tout moment en
appelant la mthode statique ThreadPool.SetMaxThreads(). Si le nombre maximal de threads

168

Chapitre 5 : Processus, threads et gestion de la synchronisation

est atteint dans le pool, ce dernier ne cre plus de nouveaux threads et les tches dans la file du
pool ne seront traites que lorsquun thread du pool se librera. En revanche, le thread responsable de la cration de la tche, na pas attendre quelle soit traite. Vous pouvez utiliser le pool
de threads de deux faons direntes :

En postant vos propres tches et leurs mthodes de traitement avec la mthode statique
ThreadPool.QueueUserWorkItem(). Une fois quune tche a t poste au pool, elle ne peut
plus tre annule.

En crant un timer qui poste priodiquement une tche prdfinie et sa mthode de traitement au pool. Pour cela, il faut utiliser la mthode statique ThreadPool.RegisterWaitForSingleObject().

Chacune de ces deux mthodes existe dans une version non protge (UnsafeQueueUserWorkItem() et UnsafeRegisterWaitForSingleObject()) . Ces versions non protges permettent
aux threads ouvriers du pool de ne pas tre dans le mme contexte de scurit que le thread qui
a dpos la tche. Lutilisation de ces mthodes amliore donc les performances puisque la pile
du thread qui a dpos la tche nest pas vrifie lors de la gestion des contextes de scurit. Les
mthodes de traitement sont rfrences par des dlgus.
Voici un exemple qui montre lutilisation de tches utilisateurs (paramtres par un numro et
traites par la mthode ThreadTache()) et de tches postes priodiquement (sans paramtre
et traites par la mthode ThreadTacheWait()). Les tches utilisateurs sont volontairement
longues pour forcer le pool crer de nouveaux threads.
Exemple 5-19 :
using System.Threading ;
public class Program {
public static void Main() {
// Positionnement initial de l
ev
enement : false.
ThreadPool.RegisterWaitForSingleObject(
new AutoResetEvent(false),
// Methode de traitement de la t
ache p
eriodique.
new WaitOrTimerCallback( ThreadTacheWait ),
null,
// La tache periodique na pas de param`
etres.
2000,
// La periode est de 2 secondes.
false) ; // La tache p
eriodique est ind
efiniment d
eclench
ee.
// Poste 3 taches utilisateur de param`
etres 0,1,2.
for (int count = 0 ; count < 3 ; ++count)
ThreadPool.QueueUserWorkItem(
new WaitCallback(ThreadTache), count) ;
// Attente de 12 secondes avant de finir le processus
// car les threads du pool sont des threads background.
Thread.Sleep(12000) ;
}
static void ThreadTache(object obj) {
System.Console.WriteLine("Thread#{0} T
ache#{1} D
ebut",
Thread.CurrentThread.ManagedThreadId , obj.ToString()) ;

Timers

169
Thread.Sleep(5000) ;
System.Console.WriteLine("Thread#{0} T
ache#{1} Fin",
Thread.CurrentThread.ManagedThreadId , obj.ToString()) ;
}
static void ThreadTacheWait(object obj, bool signaled) {
System.Console.WriteLine("Thread#{0} T
acheWait",
Thread.CurrentThread.ManagedThreadId ) ;
}

}
Ce programme ache ceci (dune manire non dterministe):
Thread#4
Thread#5
Thread#6
Thread#7
Thread#7
Thread#4
Thread#5
Thread#6
Thread#7
Thread#7

Tache#0 Debut
Tache#1 Debut
Tache#2 Debut
TacheWait
TacheWait
Tache#0 Fin
Tache#1 Fin
Tache#2 Fin
TacheWait
TacheWait

Timers
La plupart des applications contiennent des tches qui doivent tre excutes priodiquement.
Par exemple vous pouvez imaginez de vrifier priodiquement la disponibilit dun serveur. La
premire solution qui vient lesprit pour implmenter ce paradigme est de crer un thread ddi une tche qui appelle la mthode Thread.Sleep() entre deux excutions. Cette implmentation prsente linconvnient de consommer inutilement un thread entre deux excutions.
Nous allons prsenter plusieurs implmentations plus performantes fournies par le framework
.NET.
Plus prcisment, le framework prsente les trois classes System.Timers.Timer, System.Threading.Timer et System.Windows.Forms.Timer. La classe de lespace de noms Forms doit tre utilise pour excuter des tches priodiques graphiques, telles que le rafrachissement de donnes
sur lcran ou lachage conscutif des images dune animation. Le choix entre les classes des
espaces de noms Timers et Threading est plus dlicat et dpend de vos besoins.

La classe System.Timers.Timer
Limplmentation System.Timers.Timer utilise les threads du pool de threads pour excuter
une tche. Aussi, vous devez synchroniser les accs aux ressources consommes par une telle
tche.
Cette classe prsente la proprit double Interval{get;set;} qui permet daccder la priode
exprime en millisecondes. Les mthodes Start() et Stop() vous permettent dactiver ou de
dsactiver le timer. Vous pouvez aussi changer lactivation du timer en utilisant la proprit
bool Enabled{get;set;}.

170

Chapitre 5 : Processus, threads et gestion de la synchronisation

Un dlgu de type ElapsedEventHandler rfrence la mthode qui reprsente la tche excuter. Cette rfrence est reprsente par lvnement ElapsedEventHandler Elapsed. La signature
propose par la dlgation ElapsedEventHandler est celle-ci :
void ElapsedEventHandler(object sender, ElapsedEventArgs e)
Le premier argument rfrence le timer qui a dclench la tche. Ainsi, plusieurs timers peuvent
dclencher une mme mthode et vous pouvez les direncier au moyen de cet argument.
De plus, puisquun dlgu peut rfrencer plusieurs mthodes, un mme timer peut appeler
conscutivement plusieurs mthodes chaque dclenchement. Le second argument contient la
date de dclenchement du timer que vous pouvez rcuprer avec la proprit DateTime ElapsedEvenArgs.SignalTime{get;}. Le programme suivant illustre lutilisation de la classe System.
Timers.Timer :
Exemple 5-20 :
using System.Timers ;
class Program {
static Timer Timer1 = new Timer() ;
static Timer Timer2 = new Timer() ;
static void Main() {
Timer1.Interval = 1000 ; // P
eriode = 1 seconde.
Timer1.Elapsed +=
new ElapsedEventHandler(PeriodicTaskHandler) ;
Timer2.Interval = 2000 ; // P
eriode = 2 secondes.
Timer2.Elapsed +=
new ElapsedEventHandler(PeriodicTaskHandler) ;
Timer2.Elapsed +=
new ElapsedEventHandler(PeriodicTaskHandler) ;
Timer1.Start() ; Timer2.Start() ;
System.Threading.Thread.Sleep(5000) ; // Dors 5 secondes.
Timer1.Stop() ; Timer2.Stop() ;
}
static void PeriodicTaskHandler(object sender,
ElapsedEventArgs e) {
string str = (sender == Timer1) ? "Timer1 " : "Timer2 " ;
str += e.SignalTime.ToLongTimeString() ;
System.Console.WriteLine(str) ;
}
}
Ce programme ache ceci :
Timer1
Timer1
Timer2
Timer2
Timer1
Timer1
Timer2
Timer2
Timer1

19:42:49
19:42:50
19:42:50
19:42:50
19:42:51
19:42:52
19:42:52
19:42:52
19:42:53

Appel asynchrone dune mthode

171

Enfin, sachez que la proprit ISynchronizeInvoke Timer.SynchronizingObject vous permet


de prciser le thread qui doit excuter la tche. Linterface ISynchronizeInvoke est prsente
un peu plus loin dans ce chapitre.

La classe System.Threading.Timer
La classe System.Threading.Timer est assez similaire la classe System.Timers.Timer. Cette
classe utilise aussi les threads du pool de threads pour excuter une tche mais la dirence
de la classe System.Timers.Timer, elle ne vous permet de prciser exactement un thread.
Une autre dirence est que cette classe vous permet de fournir une chance de dmarrage (due
time en anglais). Lchance de dmarrage dfinie linstant o le timer va dmarrer en prcisant une dure. Vous pouvez modifier lchance de dmarrage nimporte quel moment en
appelant la mthode Change(). En prcisant une chance de dmarrage nulle vous pouvez dmarrer le timer immdiatement. En prcisant la constante System.Threading.Timer.Infinite
vous pouvez stopper le timer. Vous pouvez aussi stopper le timer en appelant la mthode Dispose() mais alors vous ne pourrez plus le redmarrer.

La classe System.Windows.Forms.Timer class


La philosophie dutilisation de la classe System.Windows.Forms.Timer est plus proche de celle
de la classe System.Timers.Timer que celle de la classe System.Threading.Timer. La particularit de cette classe est quelle dmarre ses tches toujours avec le thread ddi la fentre concerne. Ceci vient du fait que cette implmentation utilise en interne le message Windows WM_TIMER.
Vous ne devez pas utiliser cette classe pour dmarrer des tches dont lexcution prend plus
quune fraction de seconde sous peine de geler votre interface graphique. Un exemple dutilisation de ce type de timer peut tre trouv dans le chapitre consacr Windows Forms en page
702.

Appel asynchrone dune mthode


Pour aborder cette section, il est ncessaire davoir compris la notion de dlgu, explique
en page 379.
On dit dun appel de mthode quil est synchrone lorsque le thread du cot qui ralise lappel,
attend que la mthode soit excute avant de continuer. Ce comportement consomme des ressources puisque le thread est bloqu pendant ce temps. Lors dun appel sur un objet distant,
cette dure est potentiellement immense, puisque le cot dun appel rseau reprsente des milliers, voire des millions, de cycles processeurs. Cependant cette attente nest obligatoire que dans
le cas o les informations retournes par lappel de la mthode sont immdiatement consommes aprs lappel. Dans la programmation en gnral et dans les architectures distribues en
particulier, il arrive souvent quun appel de mthode eectue une action et ne retourne que
linformation dcrivant si laction sest bien passe ou non. Dans ce cas, le programme na pas
forcment besoin de savoir immdiatement si laction sest bien passe. On peut dcider dessayer de recommencer cette action plus tard si on apprend quelle a chou.
Pour grer ce type de situation on peut utiliser un appel asynchrone. Lide est que le thread
qui ralise un appel de mthode sur un objet, retourne immdiatement, sans attendre la fin

172

Chapitre 5 : Processus, threads et gestion de la synchronisation

de lappel. Lappel est automatiquement pris en charge par un thread du pool de threads du
processus. Le programme peut ultrieurement rcuprer les informations retournes par lappel asynchrone. La technique dappel asynchrone est entirement gre par le CLR.
Le mcanisme que nous dcrivons peut tre utilis dans votre propre architecture. Il est aussi
utilis par les classes du framework .NET, notamment pour grer les flots de donnes dune
manire asynchrone ou pour grer des appels asynchrones sur des objets distants, cest--dire
situs dans un autre domaine dapplication.

Dlgation asynchrone
Lors dun appel asynchrone vous navez pas crer ni vous occupez du thread qui excute le
corps de la mthode. Ce thread est gr par le pool de threads dcrit un peu plus haut.
Avant dutiliser eectivement un dlgu asynchrone, il est judicieux de remarquer que toutes les
dlgations prsentent automatiquement les deux mthodes BeginInvoke() et EndInvoke(). La
signature de ces deux mthodes est calque sur la signature de la dlgation qui les prsente. Par
exemple la dlgation suivante...
delegate int Deleg(int a,int b) ;
...expose les deux mthodes suivantes :
IAsyncResult BeginInvoke(int a,int b,AsyncCallback callback,object o) ;
int EndInvoke(IAsyncResult result) ;
Ces deux mthodes sont produites par le compilateur de C  . Cependant, le mcanisme dintellisense de Visual Studio est capable de les infrer partir dune dlgation de faon vous les
prsenter.
Pour appeler une mthode dune manire asynchrone, il faut dabord la rfrencer avec un
dlgu ayant la mme signature. Il sut ensuite dappeler la mthode BeginInvoke() sur ce
dlgu. Comme vous lavez remarqu, le compilateur a fait en sorte que les premiers arguments
de BeginInvoke() soient les arguments de la mthode appeler. Les deux derniers arguments
de cette mthode de type AsyncCallback et object sont expliqus un peu plus loin.
La valeur de retour de lappel asynchrone dune mthode, peut tre rcupre en appelant la
mthode EndInvoke(). L aussi, le compilateur a fait en sorte que le type de la valeur de retour
de EndInvoke() soit le mme que le type de la valeur de retour de la dlgation (ce type est int
dans notre exemple). Lappel EndInvoke() est bloquant. Cest--dire que lappel ne retourne
que lorsque lexcution asynchrone est eectivement termine.
Le programme suivant illustre lappel asynchrone dune mthode WriteSomme(). Notez que
pour bien direncier le thread excutant la mthode Main() et le thread excutant la mthode
WriteSomme(), nous achons la valeur de hachage du thread courant (qui est dirente pour
chaque thread).
Exemple 5-21 :
using System.Threading ;
class Program {
public delegate int Deleg(int a, int b) ;
static int WriteSomme(int a, int b) {
int somme = a + b ;

Appel asynchrone dune mthode

173

System.Console.WriteLine("Thread#{0} : WriteSomme() somme = {1}",


Thread.CurrentThread.ManagedThreadId , somme) ;
return somme ;
}
static void Main() {
Deleg proc = WriteSomme ;
System.IAsyncResult async= proc.BeginInvoke(10, 10, null, null);
// Possibilite de faire quelquechose ici...
int somme = proc.EndInvoke(async);
System.Console.WriteLine("Thread#{0} : Main()
somme = {1}",
Thread.CurrentThread.ManagedThreadId , somme) ;
}
}
Ce programme ache :
Thread 15 : WriteSomme() Somme = 20
Thread 18 : Main()
Somme = 20
Un appel asynchrone est matrialis par un objet dont la classe implmente linterface
System.IAsyncResult. Dans cet exemple la classe sous-jacente est System.Runtime.Remoting.
Messaging.AsyncResult. Lobjet AsyncResult est retourn par la mthode BeginInvoke(). Il
est pass en argument de la mthode EndInvoke() pour identifier lappel asynchrone.
Si une exception est lance lors dun appel asynchrone, elle est automatiquement intercepte
et stocke par le CLR. Le CLR relancera lexception lors de lappel EndInvoke().

Procdure de finalisation
Vous avez la possibilit de spcifier une mthode qui sera automatiquement appele lorsque
lappel asynchrone sera termin. Cette mthode est appele procdure de finalisation. Une procdure de finalisation est appele par le mme thread que celui qui a excut lappel asynchrone.
Pour utiliser une procdure de finalisation, il vous sut de spcifier la mthode dans une dlgation de type System.AsyncCallback comme avant-dernier paramtre de la mthode BeginInvoke(). Cette mthode doit tre conforme cette dlgation, cest--dire quelle doit retourner
le type void et prendre pour seul argument une interface IAsyncResult. Comme le montre
lexemple suivant, cette mthode doit appeler EndInvoke().
Un problme se pose, car les threads du pool utiliss pour traiter les appels asynchrones sont
des threads background. linstar de lexemple ci-dessous, il faut que vous implmentiez un
mcanisme de gestion dvnements pour vous assurer que lapplication ne se termine pas sans
avoir termin lexcution asynchrone. Linterface IAsyncResult prsente un objet de synchronisation dune classe drive de WaitHandle, mais cet objet est signal ds que le traitement
asynchrone est fini et avant que la procdure de finalisation soit appele. Cet objet ne peut donc
pas permettre dattendre la fin de lexcution de la procdure de finalisation.
Exemple 5-22 :
using
using
using
class

System ;
System.Threading ;
System.Runtime.Remoting.Messaging ;
Program {

174

Chapitre 5 : Processus, threads et gestion de la synchronisation


public delegate int Deleg(int a, int b) ;
// Position initiale de lev
enement : false.
static AutoResetEvent ev = new AutoResetEvent(false);
static int WriteSomme(int a, int b) {
Console.WriteLine(
"{0} : Somme = {1}",Thread.CurrentThread.ManagedThreadId ,a+b) ;
return a + b ;
}
static void SommeFinie(IAsyncResult async) {
// Attend une seconde pour simuler un traitement long.
Thread.Sleep(1000) ;
Deleg proc =
((AsyncResult)async).AsyncDelegate as Deleg ;
int somme = proc.EndInvoke(async) ;
Console.WriteLine("{0} : Proc
edure de finalisation somme = {1}",
Thread.CurrentThread.ManagedThreadId , somme) ;
ev.Set();
}
static void Main() {
Deleg proc = WriteSomme ;
IAsyncResult async = proc.BeginInvoke(10, 10,
new AsyncCallback(SommeFinie), null) ;
Console.WriteLine(
"{0} : BeginInvoke() appel
ee ! Attend l
ex
ecution de SommeFinie()",
Thread.CurrentThread.ManagedThreadId ) ;
ev.WaitOne();
Console.WriteLine(
"{0} : Bye... ", Thread.CurrentThread.ManagedThreadId ) ;
}
}

Cet exemple ache :


12
14
14
12

:
:
:
:

BeginInvoke() appelee ! Attend l


ex
ecution de SommeFinie()
Somme = 20
Procedure de finalisation Somme = 20
Bye...

Si vous enlevez le mcanisme dvnement, cet exemple ache ceci :


12 : BeginInvoke() appelee ! Attend l
ex
ecution de SommeFinie()
12 : Bye...
Lapplication nattend pas la fin de lexcution du traitement asynchrone et de sa procdure de
finalisation.

Passage dun tat la procdure de finalisation


Si vous ne le positionnez pas null, le dernier paramtre de la mthode BeginInvoke() reprsente une rfrence vers un objet utilisable la fois dans le thread qui dclenche lappel

Appel asynchrone dune mthode

175

asynchrone et dans la procdure de finalisation. Une autre rfrence vers cet objet est la proprit AsyncState de linterface IAsyncResult. Vous pouvez vous en servir pour reprsenter un
tat positionn dans la procdure de finalisation. Par exemple, lvnement de lexemple de la
section prcdente peut tre vu comme un tat. Voici lexemple prcdent rcrit pour utiliser
cette fonctionnalit :
Exemple 5-23 :
using System ;
using System.Threading ;
using System.Runtime.Remoting.Messaging ;
class Program {
public delegate int Deleg(int a, int b) ;
static int WriteSomme(int a, int b) {
Console.WriteLine(
"{0} : Somme = {1}", Thread.CurrentThread.ManagedThreadId , a+b) ;
return a + b ;
}
static void SommeFinie(IAsyncResult async) {
// Attend une seconde pour simuler un traitement long.
Thread.Sleep(1000) ;
Deleg proc =
((AsyncResult)async).AsyncDelegate as Deleg ;
int somme = proc.EndInvoke(async) ;
Console.WriteLine("{0} : Proc
edure de finalisation somme = {1}",
Thread.CurrentThread.ManagedThreadId , somme) ;
((AutoResetEvent)async.AsyncState).Set();
}
static void Main() {
Deleg proc = WriteSomme ;
AutoResetEvent ev = new AutoResetEvent(false) ;
IAsyncResult async = proc.BeginInvoke(10, 10,
new AsyncCallback(SommeFinie), ev) ;
Console.WriteLine(
"{0} : BeginInvoke() appelee ! Attend l
ex
ecution de SommeFinie()",
Thread.CurrentThread.ManagedThreadId ) ;
ev.WaitOne();
Console.WriteLine(
"{0} : Bye... ", Thread.CurrentThread.ManagedThreadId ) ;
}
}

Appels sans retour (One-Way)


Vous avez la possibilit dappliquer lattribut System.Runtime.Remoting.Messaging.OneWay
nimporte quelle mthode, statique ou non. Cet attribut indique au CLR que cette mthode
ne retourne aucune information. Mme si une mthode qui retourne une valeur de retour ou
des arguments de retour (i.e dfinis avec le mot-cl out) est marque avec cet attribut, elle ne
retourne rien.

176

Chapitre 5 : Processus, threads et gestion de la synchronisation

Une mthode marque avec lattribut OneWay peut tre appele dune manire synchrone ou
asynchrone. Si une exception est lance et non rattrape durant lexcution dune mthode marque avec lattribut OneWay, elle est propage si lappel est synchrone. Dans le cas dun appel
asynchrone sans retour lexception nest pas propage. Dans la plupart des cas, les mthodes
marques sans retour sont appeles de manire asynchrone.
Les appels asynchrones sans retour eectuent en gnral des tches annexes dont la russite ou
lchec nont pas dincidence sur le bon droulement de lapplication. La plupart du temps, on
les utilise pour communiquer des informations sur le droulement de lapplication.

Anit entre threads et ressources


Vous pouvez grandement simplifier la synchronisation des accs vos ressources en utilisant la
notion danit entre threads et ressources. Lide est daccder une ressource toujours avec
le mme thread. Ainsi, vous supprimez le besoin vous protger des accs concurrents puisque
la ressource nest jamais partage. Le framework .NET prsente plusieurs mcanismes pour implmenter ce concept danit.

Lattribut System.ThreadStatic
Par dfaut, un champ statique est partag par tous les threads dun processus. Ce comportement
oblige le dveloppeur synchroniser les accs un tel champ. En appliquant lattribut System.
ThreadStaticAttribute sur un champ statique, vous pouvez contraindre le CLR crer une
instance de ce champ pour chaque thread du processus. Ainsi, lutilisation de ce mcanisme est
bien un moyen dimplmenter la notion danit entre threads et ressources.
Il vaut mieux viter dinitialiser directement lors de sa dclaration un champ statique qui est
marqu avec cet attribut. En eet, dans ce cas seul le thread qui charge la classe eectuera linitialisation sur sa propre version du champ. Ce comportement est illustr par le programme
suivant :
Exemple 5-24 :
using System.Threading ;
class Program {
[System.ThreadStatic]
static string str = "Valeur initiale ";
static void DisplayStr() {
System.Console.WriteLine("Thread#{0} Str={1}",
Thread.CurrentThread.ManagedThreadId , str) ;
}
static void ThreadProc() {
DisplayStr() ;
str = "Valeur ThreadProc" ;
DisplayStr() ;
}
static void Main() {
DisplayStr() ;
Thread thread = new Thread(ThreadProc) ;
thread.Start() ;

Anit entre threads et ressources

177

thread.Join() ;
DisplayStr() ;
}
}
Ce programme ache ceci :
Thread#1
Thread#2
Thread#2
Thread#1

Str=Valeur initiale
Str=
Str=Valeur ThreadProc
Str=Valeur initiale

Thread local storage


La notion danit entre threads et ressources peut tre implmente laide du concept de
thread local storage (souvent nomm TLS). Ce concept nest pas nouveau et existe au niveau de
win32. Dailleurs le framework .NET se base sur cette implmentation.
Le concept de TLS utilise la notion de slot de donnes. Un slot de donnes est une instance de la
classe System.LocalDataStoreSlot. Un slot de donnes peut tre vu comme un tableau dobjets. La taille de ce tableau est en permanence gale au nombre de threads dans le processus courant. Ainsi, chaque thread a son propre objet dans le slot de donnes. Cet objet est invisible des
autres threads. Pour chaque slot de donnes, le CLR soccupe dtablir la correspondance entre
les threads et leurs objets. La classe Thread fournit les deux mthodes suivantes pour accder en
lecture et en criture un objet stock dans un slot de donnes :
static public object GetData(LocalDataStoreSlot slot) ;
static public void SetData(LocalDataStoreSlot slot, object obj) ;

Les slots de donnes nomms


Vous avez la possibilit de nommer un slot de donnes pour lidentifier. La classe Thread fournit
les mthodes suivantes pour crer, obtenir ou dtruire un slot de donnes nomm :
static public LocalDataStoreSlot AllocateNamedDataSlot(string slotName) ;
static public LocalDataStoreSlot GetNamedDataSlot(string slotName) ;
static public void FreeNamedDataSlot(string slotName) ;
Le ramasse-miettes ne dtruit pas les slot de donnes nomms. Cette responsabilit incombe
donc au dveloppeur.
Le programme suivant utilise un slot de donnes nomm pour fournir un compteur chaque
thread du processus. Ce compte est incrment chaque appel de la mthode fServer(). Ce programme bnficie des TLS dans la mesure o la mthode fServer() ne prend pas de rfrence
vers un compteur en argument. Un autre avantage est que le dveloppeur na pas maintenir
lui-mme un compteur pour chaque thread.
Exemple 5-25 :
using System ;
using System.Threading ;
class Program {
static readonly int NTHREAD = 3 ;

// 3 threads `
a cr
eer.

178

Chapitre 5 : Processus, threads et gestion de la synchronisation


// 2 appels a` fServer() pour chaque thread cr
e
e.
static readonly int MAXCALL = 2 ;
static readonly int PERIOD = 1000 ; // 1 seconde entre chaque appel.
static bool fServer() {
LocalDataStoreSlot dSlot = Thread.GetNamedDataSlot("Compteur");
int compteur = (int)Thread.GetData(dSlot);
compteur++ ;
Thread.SetData(dSlot, compteur);
return !(compteur == MAXCALL) ;
}
static void ThreadProc() {
LocalDataStoreSlot dSlot = Thread.GetNamedDataSlot("Compteur");
Thread.SetData(dSlot, (int) 0);
do{
Thread.Sleep(PERIOD) ;
Console.WriteLine(
"Thread#{0} Jai appel
e fServer(), Compteur = {1}",
Thread.CurrentThread.ManagedThreadId ,
(int)Thread.GetData(dSlot)) ;
} while (fServer()) ;
Console.WriteLine("Thread#{0} bye",
Thread.CurrentThread.ManagedThreadId ) ;
}
static void Main() {
Console.WriteLine(
"Thread#{0} Je suis le thread principal, hello world",
Thread.CurrentThread.ManagedThreadId ) ;
Thread.AllocateNamedDataSlot("Compteur");
Thread thread ;
for (int i = 0 ; i < NTHREAD ; i++) {
thread = new Thread(ThreadProc) ;
thread.Start() ;
}
// Nous nutilisons pas un m
ecanisme pour attendre la
// terminaison des threads fils, aussi faut-il attendre
// assez longtemps pour les laisser finir leur travail.
Thread.Sleep( PERIOD * (MAXCALL + 1) ) ;
Thread.FreeNamedDataSlot("Compteur");
Console.WriteLine("Thread#{0} Je suis le thread principal,bye.",
Thread.CurrentThread.ManagedThreadId ) ;
}
}

Ce programme ache ceci :


Thread#1
Thread#3
Thread#4
Thread#5
Thread#3

Je suis le thread principal, hello world


Jai appele fServer(), Compteur = 0
Jai appele fServer(), Compteur = 0
Jai appele fServer(), Compteur = 0
Jai appele fServer(), Compteur = 1

Anit entre threads et ressources


Thread#3
Thread#4
Thread#4
Thread#5
Thread#5
Thread#1

179

bye
Jai appele fServer(), Compteur = 1
bye
Jai appele fServer(), Compteur = 1
bye
Je suis le thread principal, bye.

Les slots de donnes anonymes


Vous pouvez appeler la mthode statique AllocateDataSlot() de la classe Thread pour
crer un slot de donnes anonyme. Vous ntes pas responsable de la destruction dun slot
de donnes anonyme. En revanche, vous devez faire en sorte quune instance de la classe
LocalDataStoreSlot soit visible de tous les threads. Rcrivons le programme prcdent avec
la notion de slot de donnes anonyme :
Exemple 5-26 :
using System ;
using System.Threading ;
class Program {
static readonly int NTHREAD = 3 ;
// 3 threads `
a cr
eer.
// 2 appels `a fServer() pour chaque thread cr
e
e.
static readonly int MAXCALL = 2 ;
static readonly int PERIOD = 1000 ; // 1 seconde entre chaque appel.
static LocalDataStoreSlot dSlot;
static bool fServer() {
int Counter = (int)Thread.GetData(dSlot);
Counter++ ;
Thread.SetData(dSlot, Counter);
return !(Counter == MAXCALL) ;
}
static void ThreadProc() {
Thread.SetData(dSlot, (int) 0);
do{
Thread.Sleep(PERIOD) ;
Console.WriteLine(
"Thread#{0} Jai appel
e fServer(), Compteur = {1}",
Thread.CurrentThread.ManagedThreadId ,
(int)Thread.GetData(dSlot)) ;
} while (fServer()) ;
Console.WriteLine("Thread#{0} bye",
Thread.CurrentThread.ManagedThreadId ) ;
}
static void Main() {
Console.WriteLine(
"Thread#{0} Je suis le thread principal, hello world",
Thread.CurrentThread.ManagedThreadId ) ;
dSlot = Thread.AllocateDataSlot();
for (int i = 0 ; i < NTHREAD ; i++){
Thread thread = new Thread(ThreadProc) ;

180

Chapitre 5 : Processus, threads et gestion de la synchronisation


thread.Start() ;
}
Thread.Sleep(PERIOD * (MAXCALL + 1)) ;
Console.WriteLine("Thread#{0} Je suis le thread principal, bye",
Thread.CurrentThread.ManagedThreadId ) ;
}
}

Linterface System.ComponentModel.ISynchronizeInvoke
Linterface System.ComponentModel.ISynchronizeInvoke est dfinie comme ceci :
public
public
public
public
public
}

object System.ComponentModel.ISynchronizeInvoke{
object
Invoke(Delegate method,object[] args) ;
IAsyncResult BeginInvoke(Delegate method,object[] args) ;
object
EndInvoke(IAsyncResult result) ;
bool
InvokeRequired{get;}

Une implmentation de cette interface peut faire en sorte que certaines mthodes soient toujours excutes par le mme thread, dune manire synchrone ou asynchrone :

Dans le scnario synchrone, un thread T1 appelle une mthode M() sur un objet OBJ. En
fait, T1 appelle la mthode ISynchronizeInvoke.Invoke() en spcifiant un dlgu qui
rfrence OBJ.M() et un tableau contenant les arguments. Un autre thread T2 excute la
mthode OBJ.M(). T1 attend la fin de lexcution puis rcupre les informations de retour
de lappel.

Le scnario asynchrone dire du scnario synchrone par le fait que T1 appelle la mthode
ISynchronizeInvoke.BeginInvoke(). T1 ne reste pas bloqu pendant que T2 excute la
mthode OBJ.M(). Lorsque T1 a besoin des informations de retour de lappel il appelle la
mthode ISynchronizeInvoke.EndInvoke() qui les lui fournira si T2 termin lexcution
de OBJ.M().

Linterface ISynchronizeInvoke est notamment utilise par le framework pour forcer la technologie Windows Form excuter les mthodes dun formulaire avec un mme thread. Cette
contrainte vient du fait que la technologie Windows Form est construite autour de la notion de
messages Windows. Le mme genre de problmatique est aussi adresse par la classe System.
ComponentModel.BackgroundWorker dcrite en page .
Vous pouvez dvelopper vos propres implmentations de linterface ISynchronizeInvoke en
vous inspirant de lexemple Implementing ISynchronizeInvoke fourni par Juval Lowy lURL
http://docs.msdnaa.net/ark_new3.0/cd3/content/Tech_System%20Programming.htm.

Contexte dexcution
Le framework .NET 2.0 prsente des nouvelles classes qui permettent de capturer et de propager
le contexte dexcution du thread courant un autre thread :

System.Security.SecurityContext

Contexte dexcution

181

Une instance de cette classe contient lidentit de lutilisateur Windows sous-jacent sous la
forme dune instance de la classe System.Security.Principal.WindowsIdentity ainsi que
ltat de la pile du thread sous la forme dune instance de la classe System.Threading.
CompressedStack. Ltat de la pile est notamment exploit par le mcanisme CAS lors du
parcours de la pile.

System.Threading.SynchronizationContext
Permet de saranchir des contraintes de compatibilit entre dirents modles de synchronisation.

System.Threading.HostExecutionContext
Permet un hte du moteur dexcution dtre pris en compte dans le contexte dexcution
du thread courant.

System.Runtime.Remoting.Messaging.LogicalCallContext
.NET Remoting permet de propager des informations au travers de contexte .NET Remoting au moyen dinstances de cette classe. Plus dinformations ce sujet sont disponibles en
page 856.

System.Threading.ExecutionContext
Une instance de cette classe contient la runion des contextes cits.

Reprenons lexemple en page 209 qui modifie le contexte de scurit en impersonifiant lutilisateur invite sur le thread courant :
Exemple 5-27 :
...
static void Main() {
System.IntPtr pJeton ;
if (LogonUser(
"invite" ,
// login
string.Empty, // domaine Windows
"invitepwd" , // mot de passe
2,
// LOGON32_LOGON_INTERACTIVE
0,
// LOGON32_PROVIDER_DEFAUT
out pJeton)) {
WindowsIdentity.Impersonate(pJeton) ;
DisplayContext("Main");
ThreadPool.QueueUserWorkItem(Callback,null) ;
CloseHandle(pJeton) ;
}
}
static void Callback(object o) {
DisplayContext("Callback");
}
static void DisplayContext(string s) {
System.Console.WriteLine(s+" Thread#{0} Current user is {1}",
Thread.CurrentThread.ManagedThreadId,
WindowsIdentity.GetCurrent().Name) ;
}
...

182

Chapitre 5 : Processus, threads et gestion de la synchronisation

Cet exemple ache ceci :


Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\invit
e
En .NET 1.1 cet exemple acherait ceci :
Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\pat
En eet, en .NET 2.0 le pool de thread propage par dfaut le contexte du thread postant une
tche au thread excutant la tche. Ce nest pas le cas en .NET 1.1. Lutilisation de la mthode
ExecutionContext.SuppressFlow() permet de retrouver le comportement de .NET 1.1 en
.NET 2.0 :
Exemple 5-28 :
...
DisplayContext("Main") ;
ExecutionContext.SuppressFlow();
ThreadPool.QueueUserWorkItem( Callback, null ) ;
...
Cet exemple ache ceci :
Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\pat
Lexemple suivant montre comment propager soit mme le contexte dexcution. Tout dabord
il faut le capturer avec la mthode ExecutionContext.Capture(). Ensuite, nous en crons une
copie que lon passe au thread du pool solicit. Ce dernier propage le contexte quon lui fournit en appelant la mthode ExceutionContext.Run(). Cette mthode prend en paramtre un
contexte dexcution et un dlgu. Elle invoque sur le thread courant la mthode rfrence
par le dlgu, en ayant au pralable positionn le contexte du thread courant :
Exemple 5-29 :
...
static void Main() {
...
WindowsIdentity.Impersonate(pJeton) ;
DisplayContext("Main") ;
ExecutionContext ctx = ExecutionContext.Capture();
ExecutionContext.SuppressFlow();
ThreadPool.QueueUserWorkItem(
SetContextAndThenCallback, ctx.CreateCopy() ) ;
CloseHandle(pJeton) ;
}
}
static void SetContextAndThenCallback(object o) {
ExecutionContext ctx = o as ExecutionContext;
ExecutionContext.Run(ctx, Callback, null);
}

Contexte dexcution
static void Callback(object o) {
DisplayContext("Callback") ;
}
...
Sans surprise, cet exemple ache :
Main Thread#1 Current user is PSMACCHIA\invit
e
Callback Thread#3 Current user is PSMACCHIA\invit
e

183

6
La gestion de la scurit

Ce chapitre prsente les direntes facettes de la scurit sous .NET :

Nous commencerons par exposer la technologie Code Access Security (CAS). La technologie CAS permet de mesurer le degr de confiance que lon peut avoir en un assemblage en
vrifiant sa provenance et en sassurant sa non falsification.

Nous verrons ensuite comment mesurer le degr de confiance que lon peut avoir en un utilisateur. La notion dutilisateur est implmente plusieurs niveaux (Windows, ASP.NET,
COM+ etc).

Enfin nous exposerons les dirents mcanismes de cryptographie que le framework met
notre disposition.

Dautres informations relatives la scurit sont disponibles dans cet ouvrage. Notamment
en page 660 nous prsentons direntes techniques pour tablir une communication scurise entre deux machines et en page 957 nous prsentons la scurisation dune application web
ASP.NET.

Introduction Code Access Security (CAS)


Notion de code mobile
Le modle de dploiement des logiciels a considrablement volu avec la puissance accrue des
rseaux. Nous tlchargeons de plus en plus nos logiciels partir dInternet et nous utilisons
de moins en moins de supports physiques tel que le CD pour le dploiement. On utilise la
mtaphore de code mobile pour dsigner le code de ce type dapplication que lon distribue par
lintermdiaire de rseaux.
Les avantages de lutilisation dun rseau pour distribuer un logiciel sont nombreux : disponibilit immdiate, mise jour en temps rel etc. Cependant, le code mobile pose de gros problmes

186

Chapitre 6 : La gestion de la scurit

de scurit. En eet, un individu mal intentionn peut exploiter les faiblesses des dirents rseaux et les failles des systmes dexploitations pour substituer son propre code du code mobile
ou pour faire parvenir du code mobile sur votre machine. En outre, la simplicit dobtention
du code mobile nous pousse tlcharger des logiciels que lon naurait pas pris la peine de
commander. On est donc moins regardant quant lditeur qui publie le logiciel. Il est donc
ncessaire de limiter lensemble des permissions accordes du code mobile (peut il dtruire
des fichiers sur mon disque dur ? peut il avoir accs au rseau ? etc).
La technologie COM adresse ce problme dune manire grossire. Avant dexcuter un composant COM qui vient dtre tlcharg, lutilisateur est averti par une fentre popup qui lui laisse
le choix dexcuter ou non le composant. Un certain niveau de garantie quant la provenance
du composant peut tre fourni grce un mcanisme de certificat mais le problme majeur
subsiste : une fois que lutilisateur a choisi dexcuter le composant, le code de celui-ci a les
mmes droits que lutilisateur.
Le fait davoir une machine virtuelle telle que le CLR permet la plateforme .NET dadresser
cette problmatique avec un mcanisme beaucoup plus fin que celui de COM. En eet, le CLR
est mme dintercepter et peut empcher une action malicieuse telle que la destruction dun
fichier avant que celle-ci ne se produise. Ce mcanisme est nomm CAS (Code Access Security). Il
fait lobjet de la prsente section.
Le dploiement de code mobile dvelopp avec .NET 2.0 se fait de prfrence avec la technologie
ClickOnce qui fait lobjet dune section page 76. Il est absolument ncessaire de bien comprendre
CAS pour tirer partie de ClickOnce.

Schma gnral de CAS


.NET dfinit une vingtaine de permissions (paramtrables) qui peuvent tre accordes ou non
lexcution du code dun assemblage. Chacune de ces permissions dfinit les rgles qui rgissent
laccs une ressource critique, comme la base des registres ou les fichiers et les rpertoires.
Accorder de la confiance un assemblage signifie quon lui accorde certaines permissions dont
il a besoin pour sexcuter correctement.
Le mcanisme CAS est utilis par le CLR lors de deux situations :

Lors du chargement dun assemblage le CLR lui attribue des permissions.

Lorsque du code demande deectuer une opration critique, le CLR doit vrifier au pralable que les assemblages contenant ce code en ont tous la permission.

Attribution des permissions lors du chargement dun assemblage


linstar des relations humaines, en .NET la confiance se mrite. Le CLR accorde de la confiance
un assemblage seulement sil peut en extraire un certain nombre preuves. Ces preuves sont
relatives la provenance et la non corruption des donnes contenues dans lassemblage.
Ltape daccord de permissions lassemblage en fonction des preuves quil a fourni, est entirement configurable. Le paramtrage de cette tape est stock dans des entits nommes stratgies
de scurit. Les informations contenues dans une stratgie de scurit ressemblent : Si les
informations contenues dans un assemblage prouvent quil a t produit par lentreprise XXX
alors on peut lui accorder cet ensemble de permissions . Nous exposerons comment configurer
les stratgies de scurit. On peut noter qu ce stade lapplication na pas encore reu la permission de sexcuter. Dailleurs il se peut quau vu du jeu de preuves cette permission ne lui soit

CAS : Preuves et permissions

187

finalement pas accorde. On peut aussi signaler que le mcanisme de rsolution des permissions
naccorde aucune permission par dfaut.
Lorsquun ensemble de permissions a t accord lassemblage, le code de lassemblage peut
modifier cet ensemble et le comportement de la gestion de la scurit durant lexcution. Naturellement ces modifications ne peuvent jamais accorder plus de permissions quil nen a
t accord lassemblage.
Assemblage charger
Obtention des preuves que
peut fournir lassemblage
charger
Preuves
Application des
stratgies de scurit
(configurable)
Permissions accordes au
code de lassemblage

Figure 6 -1 : Attribution des permissions un assemblage

Vrification des permissions lexcution


Avant deectuer une opration critique telle que laccs un fichier, le code du framework .NET
demande au CLR de vrifier si le code appelant en a la permission. Le code appelant nest pas
seulement reprsent par la mthode qui demande au framework .NET deectuer lopration
critique. Le CLR considre que le code appelant est lensemble des mthodes qui constitue
la pile du thread courant. Le CLR vrifie donc que tous les assemblages contenant toutes ces
mthodes ont chacun la permission requise. Ce comportement est appel parcours de la pile
dappels (stack walk en anglais). Le parcours de la pile empche la manipulation frauduleuse
dassemblages ayant un haut degr de confiance (comme ceux dvelopps par Microsoft) par des
assemblages dans lesquels on ne fait pas confiance.
Dans la figure suivante, on voit que la mthode File.OpenRead() demande au CLR de vrifier
que tous les appelants ont la permission FileIOPermissionAccess.Read sur un fichier avant de
procder son ouverture. Dans cet exemple, il faudrait que cette permission soit P3. Sinon une
exception de type SecurityException serait automatiquement lance par le CLR.
Nous aurons loccasion dexpliquer quune permission peut tre matrialise par un objet .NET
et que lon peut appeler la mthode Demand() sur un tel objet afin de sassurer quau stade de
lappel, la permission est accorde tous les appelants.

CAS : Preuves et permissions


Quest ce quune preuve ?
Une preuve est une information extraite partir dun assemblage. Le mot preuve est utilis dans
le sens o si lon extrait telle ou telle information partir de lassemblage, alors cela prouve de

188

Chapitre 6 : La gestion de la scurit


Permissions accordes par le CLR
lors du chargement des assemblages

void Main(...) {
Fct1(...);
}
void Fct1(...) {
Fct2(...);
} void Fct2(...) {
FileStream fs-File.openRead(...);
}
System.IO.File.OpenRead(string path) {
CodeAccessPermission perm=new
FileIOPermission(
FileIOPermissionAccess.Read,
path);
perm.Demand();
...
}

P3

P3

Assemblage
foo1.exe

Assemblage
foo2.dll

P1

P4

Assemblage
mscorlib.dll

P2
P1
P3

Lappel Demand()
provoque le parcours de la pile

Figure 6 -2 : Le parcours de la pile


manire irrfutable un fait. Ces faits concernent la provenance et la non falsification de lassemblage entre le moment o il a t cr par un compilateur chez lditeur de logiciel et le moment
o il est excut chez le client.

Les types de preuves standard du Framework .NET


Voici la liste des huit types de preuves que lon peut extraire dun assemblage, et donc, la liste
des faits qui peuvent tre prouvs partir dun assemblage. Chacune de ces preuves peut tre
matrialise par une instance dune classe du framework .NET que nous prcisons. Ces classes
font partie de lespace de noms System.Security.Policy.

On peut prouver quun assemblage est stock dans un certain rpertoire de la machine.
Ce type de preuve peut tre reprsent par une instance de la classe System.Security.Policy.
ApplicationDirectory.
On peut prouver quun assemblage est stock dans le GAC. Ce type de preuve peut tre
reprsent par une instance de la classe System.Security.Policy.Gac.
On peut prouver quun assemblage a t obtenu/tlcharg partir dun certain site (par
exemple www.smacchia.com). Ce type de preuve peut tre reprsent par une instance de la
classe System.Security.Policy.Site.
On peut prouver quun assemblage a t obtenu partir dune certaine URL (par exemple
www.smacchia.com/asm/UnAssemblage.dll). Ce type de preuve peut tre reprsent par une
instance de la classe System.Security.Policy.Url.
On peut prouver quun assemblage a t obtenu partir dune certaine zone. .NET prsente
cinq zones :

Internet.

CAS : Preuves et permissions

189

Un site internet que vous avez ajout dans votre zone Sites sensibles (untrusted site) d
Internet Explorer.
Un site internet que vous avez ajout dans votre zone Sites de confiance (trusted site)
dInternet Explorer.
Intranet local.
Le systme de stockage local (My Computer).

Chacune de ces zones correspond une valeur de lnumration System.Security.SecurityZone. Ce type de preuves peut tre reprsent par une instance de la classe System.Security.
Policy.Zone.
Si lassemblage a t sign par son diteur avec la technologie Authenticode, on peut tablir
une preuve partir de ce certificat. Cette technologie est dcrite en page 230. Ce type de
preuves peut tre reprsent par une instance de la classe System.Security.Policy.Publisher.
Si lassemblage a un nom fort, on peut tablir une preuve partir de ce nom fort. Ce type de
preuves peut tre reprsent par une instance de la classe System.Security.Policy.StrongName.
La composante culture du nom fort nest pas prise en compte dans cette preuve.
Voici un programme permettant de construire et dacher les noms forts des assemblages contenus dans le domaine dapplication courant. Notez lutilisation de la classe
System.Security.Permissions.StrongNamePublicKeyBlob pour rcuprer la cl publique.
Notez quen informatique blob signifie Binary Large Objet. On rappelle quune cl publique
contient 128 octets en plus des 32 octets den-tte.
Exemple 6-1 :
using System ;
using System.Security.Permissions ;
using System.Security.Policy ;
using System.Reflection ;
[assembly: AssemblyKeyFile("Cles.snk")]
class Program {
static void DisplayStrongName(Assembly assembly) {
AssemblyName name = assembly.GetName() ;
byte[] publicKey = name.GetPublicKey() ;
StrongNamePublicKeyBlob blob =
new StrongNamePublicKeyBlob(publicKey);
StrongName sn = new StrongName(blob, name.Name, name.Version);
Console.WriteLine(sn.Name) ;
Console.WriteLine(sn.Version) ;
Console.WriteLine(sn.PublicKey) ;
}
static void Main() {
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() ;
foreach (Assembly assembly in assemblies)
DisplayStrongName(assembly) ;
}
}

On peut tablir une preuve partir de la valeur de hachage dun assemblage. La valeur
de hachage dun assemblage permet didentifier le rsultat dune compilation dun assemblage, un peu comme un numro de version sauf que la valeur de hachage ne contient

190

Chapitre 6 : La gestion de la scurit


pas dinformation dordonnancement temporel comme le fait une version (par exemple
la version 2.3 vient toujours aprs la version 2.1). En outre, mme un changement mineur
dans le code dun assemblage sut modifier compltement la valeur de hachage, contrairement la version. Ce type de preuves peut tre reprsent par une instance de la classe
System.Security.Policy.Hash.

Vous pouvez ajouter cette liste vos propres preuves. Celles-ci doivent tre imprativement
ajoutes lassemblage avant quil soit sign. Lide est de vous permettre de configurer totalement le mcanisme de scurit. Ce sujet dpasse le cadre de cet ouvrage.
Une instance de la classe System.Security.Policy.Evidence reprsente une collection de preuves.
En fait chaque instance de cette classe contient deux collections de preuves :

Une collection pour stocker les preuves prsentes par le framework .NET (un des huit types
dcrits ci-dessus).

Une collection pour stocker les preuves propritaires.

En pratique les dveloppeurs ont peu dintrt manipuler les preuves. Les instances de la
classe Evidence sont manipules en interne par le framework .NET. Notamment, nous rappelons
quune telle collection de preuves est attribue chaque assemblage lors de son chargement.
Voici un programme qui ache les types des preuves fournies par les assemblages du domaine
dapplication courant. Pour ne pas compliquer inutilement cet exemple, nous nachons pas
directement les preuves sur la console. Nous prfrons acher le type des preuves :
Exemple 6-2 :

PreuveTest.cs

using System ;
using System.Reflection ;
[assembly: AssemblyKeyFile("Cles.snk")]
class Program {
static void DisplayEvidence(Assembly assembly) {
Console.WriteLine(assembly.FullName) ;
foreach (object obj in assembly.Evidence)
Console.WriteLine("
" + obj.GetType()) ;
}
static void Main() {
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() ;
foreach (Assembly assembly in assemblies)
DisplayEvidence(assembly) ;
}
}
Ce programme ache ceci :
mscorlib, Version=2.0.XXXXX.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
System.Security.Policy.Zone
System.Security.Policy.Url
System.Security.Policy.StrongName
System.Security.Policy.Hash
PreuveTest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=e0a058df80c8a007

CAS : Preuves et permissions

191

System.Security.Policy.Zone
System.Security.Policy.Url
System.Security.Policy.StrongName
System.Security.Policy.Hash

Qui fournit les preuves ?


Les preuves (evidences en anglais) dun assemblage sont fournies au CLR juste avant quun assemblage soit charg dans un domaine dapplication :

Soit par lhte dun domaine dapplication juste avant le chargement du premier assemblage
du domaine dapplication. Dans ce cas lassemblage qui contient lhte du domaine dapplication doit avoir obtenu la mta-permission ControlEvidence. En gnral vous navez
pas vous soucier de cela. En eet, la plupart du temps vous utilisez un hte de domaine
dapplication dvelopp par Microsoft, en qui les stratgies de scurit font entirement
confiance. Pour linstant Microsoft en fournit quatre. Ils sont dcrits en page 95.

Soit par le chargeur de classes juste avant le chargement dun assemblage qui contient un
type demand par un assemblage dj charg dans le domaine dapplication. Puisque le
chargeur de classe fait partie intgrante du CLR, le CLR lui fait entirement confiance et
lui accorde la mta-permission ControlEvidence.

Dans tous les cas le mcanisme dobtention de preuves a imprativement besoin de la mtapermission ControlEvidence (voir SecurityPermission dans la liste des permission ci-dessous).

Les permissions
Comme son nom lindique, une permission permet du code dexcuter un ensemble dactions.
Certains ouvrages qualifient les permissions de privilges, dautorisations ou de droits. Ces termes
sont cependant trs connots par le vocabulaire Windows aussi nous utiliserons le terme de permission dans le prsent ouvrage.
On verra dans la section suivante quel est lalgorithme qui permet dobtenir lensemble des
permissions pour un assemblage en fonction des preuves apportes par lassemblage. En .NET,
il existe quatre catgories de permissions : les permissions standard, les permission didentit,
les mta-permissions et les permissions propritaires.

Les permissions standard


Une trentaine de permissions standard permettent de dfinir lensemble des ressources systmes
exploites par un assemblage. Chacune de ces permissions est matrialise par une classe qui drive de la classe System.Security.CodeAccessPermission. Cette classe contient des mthodes qui
permettent partir du code de sassurer quon a une permission, de demander une permission
de refuser une permission etc. Bien videmment ces mthodes ne permettent pas dobtenir une
permission que les stratgies de scurit ne nous accordent pas. Nous dtaillerons lutilisation
de ces classes la fin de cette section. Voici la liste des classes de permissions standard :
System.Security.Permissions.EnvironmentPermission
System.Security.Permissions.FileDialogPermission
System.Security.Permissions.FileIOPermission
System.Security.Permissions.IsolatedStoragePermission

192

Chapitre 6 : La gestion de la scurit


System.Security.Permissions.ReflectionPermission
System.Security.Permissions.RegistryPermission
System.Security.Permissions.UIPermission
System.Security.Permissions.DataProtectionPermission
System.Security.Permissions.KeyContainerPermission
System.Security.Permissions.StorePermission
System.Security.Permissions.SecurityPermission
System.Configuration.UserSettingsPermission
System.Security.Permissions.ResourcePermissionBase
System.Diagnostics.EventLogPermission
System.Diagnostics.PerformanceCounterPermission
System.DirectoryServices.DirectoryServicesPermission
System.ServiceProcess.ServiceControllerPermission
System.Net.DnsPermission
System.Net.SocketPermission
System.Net.WebPermission
System.Net.NetworkInformation.NetworkInformationPermission
System.Net.Mail.SmtpPermission
System.Web.AspNetHostingPermission
System.Messaging.MessageQueuePermission
System.Drawing.Printing.PrintingPermission
System.Data.Common.DBDataPermission
System.Data.OleDb.OleDbPermission
System.Data.SqlClient.SqlClientPermission
System.Data.Odbc.OdbcPermission
System.Data.OracleClient.OraclePermission
System.Data.SqlClient.SqlNotificationPermission
System.Transactions.DistributedTransactionPermission

Les permissions didentit


Les permissions didentit (identity permission en anglais) : Pour pratiquement chaque preuve apporte par un assemblage, le CLR accorde lassemblage une permission didentit. Les classes
prvues pour les permissions didentit sont :
System.Security.Permissions.PublisherIdentityPermission
System.Security.Permissions.SiteIdentityPermission
System.Security.Permissions.StrongNameIdentityPermission
System.Security.Permissions.UrlIdentityPermission
System.Security.Permissions.GacIdentityPermission
System.Security.Permissions.ZoneIdentityPermission
Ces classes drivent aussi de la classe CodeAccessPermission ce qui permet de les traiter comme
toutes les autres permissions partir du code. Ce qui direncie les permissions didentit des
permissions prsentes ci-dessus, est le fait que laccord ou non dune permission didentit ne
dpend pas dune stratgie de scurit mais seulement des preuves fournies par lassemblage. En
outre, une permission didentit ne vous permet pas de raliser une action que vous nauriez pu
raliser sans elle. Une telle permission nest utilise que dans le cadre de vrifications.

CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit

193

Les mta-permissions
Les meta-permissions ou permissions de scurit : Ce sont des permissions alloues au
gestionnaire de scurit lui-mme. La liste des meta-permissions est disponible larticle
SecurityPermissionFlag Enumeration des MSDN. On peut citer la mta-permission dexcuter du code non gr (valeur UnmanagedCode), la mta-permission de fournir des preuves
partir dun assemblage (prsente un peu plus haut, valeur ControlEvidence), la mtapermission dexcuter du code non protg (valeur SkipVerification) etc. La classe System.Security.Permissions.SecurityPermission qui drive de la classe CodeAccessPermission
permet de manipuler les mta-permissions partir du code, sans toutefois pouvoir sautoattribuer de telles permissions.

Comprenez que la mta-permission UnmanagedCode est une espce de super permission


puisquelle permet de saranchir de toutes les autres permissions en donnant laccs
lAPI win32. De mme la mta-permission SkipVerification peut tre utilise de faon
contourner les vrifications du CLR. En consquence, du code mobile ne devrait jamais
avoir une des permissions UnmanagedCode ou SkipVerification.

Les permissions propritaires


Vous pouvez dfinir vos propres permissions pour laccs vos ressources. Larticle Implementing a Custom Permission des MSDN dcrit lutilisation de cette possibilit en dtail.

CAS : Accorder des permissions en fonction des preuves


avec les stratgies de scurit
Nous allons clarifier dans la prsente section ce quest une stratgie de scurit, de quoi une
stratgie de scurit se compose, selon quel algorithme une stratgie de scurit est applique
et enfin, quelles sont les configurations par dfaut des stratgies de scurit.

Les niveaux de stratgie de scurit


Appliquer une stratgie de scurit (security policy en anglais) un assemblage permet dobtenir
un ensemble de permissions accordes en fonction des preuves que le mcanisme CAS a pu
obtenir partir de lassemblage.
.NET prsente quatre stratgies de scurit. Lensemble des permissions accordes un assemblage est lintersection des ensembles des permissions accordes par chacune de ces stratgies
cet assemblage. Le choix de lintersection au profit de lunion a t fait car le modle de scurit
est bas sur laccord de permissions, et non le retrait de permissions.
Stratgie de scurit

Configure par...

Sapplique...

Entreprise

un administrateur.

au code gr contenu dans les assemblages


situs sur les machines dune entreprise.

194

Chapitre 6 : La gestion de la scurit

Machine

un administrateur.

au code gr contenu dans les assemblages


stocks sur la machine.

Utilisateur

un administrateur ou
lutilisateur concern.

au code gr contenu dans les processus qui


sexcutent avec les droits de lutilisateur
concern.

lhte du domaine
dapplication.

au code gr contenu dans le domaine dapplication.

Domaine
tion

dapplica-

Il existe une hirarchie dans les stratgies de scurit. Concrtement elles sont appliques les
unes aprs les autres, dans lordre dans lequel elles sont numres ci-dessus (de entreprise
domaine dapplication ). Pour cette raison on parle de niveaux de stratgie de scurit. Lapplication dune stratgie de scurit peut imposer que les stratgies de scurit des niveaux suivant ne sappliquent pas. Par exemple lapplication de la stratgie de scurit machine peut
empcher lapplication des stratgies de scurit utilisateur et domaine dapplication . En
gnral, on constate que la plupart des rgles de scurits se trouvent dans la stratgie de scurit
machine (dailleurs, par dfaut les stratgies des autres niveaux accordent toutes les permissions).

Quelle est la composition dune stratgie de scurit ?


Une stratgie de scurit se compose comme ceci :

Des groupes de code (code groups en anglais) stocks dans une arborescence,

Une liste densembles de permissions (permission sets en anglais),

Une liste dassemblages auxquels la stratgie de scurit donne son entire confiance (policy
assemblies ou fully trusted assemblies en anglais).

partir de ces lments et des preuves prsentes par un assemblage, on peut calculer un
ensemble de permissions. Cest lapplication de la stratgie de scurit. Avant dexposer lalgorithme utilis pour cela, il faut expliquer ce que sont les groupes de code.
Un groupe de code associe une preuve un ensemble de permissions de la liste densemble
de permissions de la stratgie de scurit. Les groupes de code sont stocks dans une arborescence dans une stratgie de scurit, cest--dire quun groupe de code parent peut avoir zro,
un ou plusieurs groupes de code enfants. Il ny a pas dobligation de relation entre la preuve
dun groupe enfant et la preuve de son groupe parent, il en va de mme pour les permissions
accordes. Cependant afin de faciliter ladministration de la scurit, il est recommand (dans
la mesure du possible) de positionner les relations (parent enfant) avec des liens logiques et de
dfinir les permissions accordes de faon hirarchique.
Pour comprendre pourquoi les groupes de code sont stocks dans une arborescence, il faut se
pencher sur lalgorithme utilis lors de lapplication dune stratgie de scurit.
Les documentations ocielles utilisent ce vocabulaire : si lune des preuves dun assemblage est
identique la preuve dun groupe de code, on dit que lassemblage est membre de ce groupe de
code. Cela justifie lappellation groupe de code. La preuve dun groupe de code permet de dfinir
un groupe dassemblages (de code) : ce groupe est constitu par les assemblages sui vrifient la
preuve.

CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit

195

Sachez que dans le cas o vous fabriqueriez vos propres preuves, il faudrait fabriquer vos propres
groupes de code pour les exploiter.

Algorithme utilis lors de lapplication dune stratgie de scurit

Si lassemblage fait partie de la liste dassemblages auxquels la stratgie de scurit donne


son entire confiance, elle lui accorde la super permission nomme FullTrust que nous dcrivons un peu plus loin.

Sinon, lalgorithme commence parcourir larborescence des groupes de code selon les
rgles suivantes :

Lalgorithme vrifie si lassemblage est membre de chaque groupe de code racine.

Les groupes de code enfants dun groupe de code sont pris en compte par lalgorithme
seulement si lassemblage est membre du groupe de code parent.

Lensemble des permissions accordes lassemblage par une stratgie de scurit est lunion
des ensembles de permissions des groupes de code dont lassemblage est membre.

Chaque groupe de code peut tre marqu de faon finaliser la stratgie de scurit laquelle il appartient. Dans ce cas, si un assemblage appartient ce groupe de code le mcanisme dvaluation nira pas valuer les niveaux de stratgies de scurit suivantes.

Chaque groupe de code peut tre marqu comme exclusif. Dans ce cas, si un assemblage
appartient ce groupe de code il ne bnficiera que des permissions associes ce groupe.

Si un assemblage appartient deux groupes de code exclusifs de la mme stratgie de scurit,


aucune permission ne lui sera accorde.
Comprenez bien que nous avons parl ici de lalgorithme de lapplication dun seul niveau de
stratgie de scurit. Rappelez-vous quau final, lensemble de permissions accord lassemblage est lintersection des ensembles de permissions accords par chaque niveau de stratgie
de scurit. Une consquence est quun assemblage qui une stratgie de scurit donne son
entire confiance naura pas obligatoirement toutes les permissions. Il sut quau moins une
autre stratgie de scurit applique ne lui fasse pas entirement confiance.

Configuration par dfaut des stratgies de scurit


Par dfaut, la stratgie de scurit machine est configure avec larborescence de groupe de
code de la Figure 6-3. Comme vous pouvez le voir, il y a un groupe de code selon la zone do
provient lassemblage. On rappelle quune zone .NET dfinit la provenance dun assemblage
et que tout assemblage fournit une preuve quant la zone dont il est issu. Notez aussi que
par dfaut, la stratgie de scurit machine fait entirement confiance dans les assemblages
signs avec la cl prive correspondant au jeton de cl publique de Microsoft ou de lECMA.
Par dfaut les autres stratgies de scurit (entreprise, utilisateur et domaine dapplication) font
entirement confiance tous les assemblages. Donc par dfaut tous se passe comme sil ny avait
que la stratgie de scurit machine .

Configurer des stratgies de scurit


Il existe deux outils permettant de configurer les stratgies de scurit :

196

Chapitre 6 : La gestion de la scurit


Lgende :
Microsoft_Strong_Name
Nom du groupe de code
SN.PublicKey=MS
Preuve apporter par un
assemblage pour en tre membre

FullTrust

Nom de lensemble de permissions


accord aux assemblages membres

ECMA_Strong_Name
SN.PublicKey=ECMA
FullTrust

MyComputer_Zone
Zone=MyComputer
FullTrust

NetCodeGroup_1
All

LocalIntranet_Zone
Zone=Intranet local

Permission daccder au
site dorigine de lassemblage

LocalIntranet
NetCodeGroup_1
All_Code

Restricted_Zone

All

All

Zone=site non fiable

Nothing

Nothing

Permission daccder en
lecture seule aux fichiers
du rpertoire

Internet_Zone

NetCodeGroup_1

Zone=Internet

All

Internet

Permission daccder au
site dorigine de lassemblage

Trusted_Zone

NetCodeGroup_1

Zone=site de confiance

All

Internet
Permission daccder au
site dorigine de lassemblage

Figure 6 -3 : Configuration par dfaut de la stratgie de scurit machine

Loutil graphique de configuration : .NET Framework Configuration Tool mscorcfg.msc. Cet


outil gre aussi dautres aspects de .NET comme le Remoting. Vous pouvez lancer cet outil
comme ceci : Panneau de configuration  Outils dadministration  Microsoft .NET Framework
2.0 Configuration.

Un outil utilisable en ligne de commande : caspol.exe.

Au niveau de la scurit .NET, ces deux outils ont exactement la mme fonctionnalit : la configuration des stratgies de scurit entreprise , machine et utilisateur de la machine. La
stratgie de scurit dun domaine dapplication ne peut se faire que programmatiquement, en
utilisant les classes adquates du framework .NET.
La Figure 6 -4 prsente une vue gnrale de mscorcfg.msc qui permet de retrouver immdiatement les notions prsentes que lon vient dexposer :
Lorsque vous tes un administrateur, pour chaque stratgie de scurit vous pouvez (ou lorsquun utilisateur veut configurer la stratgie de scurit le concernant) :

Ajouter/modifier/supprimer des ensembles de permissions.

CAS : Accorder des permissions en fonction des preuves avec les stratgies de scurit

197

Paramtres de configuration ne
concernant pas la scurit

Paramtres de la stratgie de
scurit entreprise

Arborescences
des groupes
de code

Paramtres
de la stratgie
de scurit
machine

Liste des
ensembles de
permissions

Assemblages en
qui la politique
fait confiance
Paramtres de la stratgie de
scurit concernant lutilisateur
logu
Paramtres de configuration ne
concernant pas la scurit

Figure 6 -4 : Vue gnrale de loutil de configuration de .NET Framework

Ajouter/supprimer des assemblages en qui la politique fait confiance.

Ajouter/modifier/supprimer des groupes de code dans larborescence.

Exporter ou importer la stratgie de scurit dans un (ou partir dun) fichier de dploiement .msi. Ces fonctionnalits sont accessibles dans les menus Stratgies de scurit  Crer
un fichier de dploiement... et Stratgies de scurit  Ouvrir ...

Pour un assemblage donn, vous pouvez obtenir la liste des groupes de code (dune politique ou de toutes les politiques) dont il est membre, et la liste des permissions qui lui sont
accordes par la ou les stratgies de scurit concernes. Cette fonctionnalit est accessible
dans le menu Stratgies de scurit  Evaluer un assemblage...

Sur une machine donne, les paramtres dune stratgie de scurit sont stocks dans des fichiers
de configuration au format XML. Voici leur localisation :
Stratgie de scurit entreprise
Windows XP/2000/NT

%runtime install path%\vXXXX\Config\Enterprisesec.config

198

Windows 98/Me

Chapitre 6 : La gestion de la scurit

%runtime install path%\vXXXX\Config\Enterprisesec.config

Stratgie de scurit machine


Windows XP/2000/NT

%runtime install path%\vXXXX\Config\Security.config

Windows 98/Me

%runtime install path%\vXXXX\Config\Security.config

Stratgie de scurit utilisateur


Windows XP/2000/NT

%USERPROFILE%\Application
config\vXXXX\Security.config

data\Microsoft\CLR

security

Windows 98/Me

%WINDIR%\username\CLR security config\vXXXX\Security.config

Ces paramtres sont stocks pour chaque version du CLR. Ainsi, si plusieurs versions du CLR
cohabitent, chacune ses propres paramtres de scurit.
Le fait que ces paramtres de configuration soient stocks au format XML ore des possibilits
intressantes, comme limport de groupes de code ou densembles de permissions prsentes
au format XML. Les trois articles Importing a Permission Using an XML File, Importing
a Permission Set Using an XML File et Importing a Code Group Using an XML File des
MSDN traitent ce sujet en dtail.
Les directives de loutil caspol.exe accessibles en ligne de commande, sont dcrites dans les
MSDN larticle Code Access Security Policy Tool. travers les nombreuses directives de cet
outil, vous y retrouverez toutes les fonctionnalits prsentes ci-dessus.

CAS : La permission FullTrust


Dans la section prcdente, nous avons cit la permission particulire FullTrust que lon pourrait traduire par confiance aveugle. Cette permission permet de dsactiver les vrifications du
mcanisme CAS. Les assemblages qui ont cette permission ont par consquent toutes les permissions standard et personnalises. Du point de vue de CAS il y a deux types dassemblages : ceux
qui ont la permission FullTrust et ceux qui ne lont pas. CAS naccorde ces derniers quune
confiance partielle, (partially trusted assemblies en anglais). En particulier, CAS naccorde quun
confiance partielles aux assemblage qui ont lensemble de permission nomm Everything. En
eet, cet ensemble accorde par dfaut toutes les permissions standard mais ne prend pas en
compte les permissions personnalises.
Par dfaut, le code des assemblages signs ne peut tre invoqu que par des assemblages qui
ont la permission FullTrust. Cela vient du fait que seuls les assemblages signs peuvent tre
placs dans le GAC et peuvent donc tre exploits malicieusement par du code mobile. En eet,
les assemblages non signs dj prsents sur une machine ne peuvent pas tre exploits par du
code mobile puisque celui-ci ne peut pas anticiper lendroit o ils sont stocks, ni deviner les
fonctionnalits implmentes.
Cet aspect de la technologie CAS peut se rvler limitatif, aussi, vous avez la possibilit de le
dsactiver en marquant votre assemblage sign avec lattribut dassemblage System.Security.
AllowPartiallyTrustedCallersAttribute. Soyez conscient que vous exposez vos clients
de gros risques si vous distribuez largement une bibliothque de classes marques avec cet

CAS : Vrifier les permissions imprativement partir du code source

199

attribut. Il faut vraiment tre certain que le code distribu ne peut pas tre dtourn. Dailleurs,
seuls certains assemblages standard de Microsoft sont marqus avec cet attribut.
Enfin, soyez conscient que ne pas utiliser cet attribut ne reprsente pas une garantie ultime
contre le dtournement de votre code. En eet, votre code peut toujours tre invoqu partir
dun assemblage qui a la permission FullTrust qui lui mme est invoqu partir dun assemblage qui na pas la permission FullTrust.

CAS : Vrifier les permissions imprativement


partir du code source
Vrifier les permissions partir du code ne signifie absolument pas soctroyer des permissions
que les stratgies de scurit ne nous ont pas accord. Ceci est bien videmment impossible.
Nous allons commencer par exposer comment vrifier les permissions imprativement partir
du code (i.e en appelant des mthodes spcialises dans la vrification). Nous nous intresserons
ensuite aux attributs permettant de vrifier dclarativement les permissions (i.e le code dune
mthode marque par un tel attribut doit avoir telle permission). Nous conclurons par une
comparaison de ces deux faons de faire.

Les classes CodeAccessPermissions et PermissionSet


La classe CodeAccessPermission ainsi que ses classes drives permettent deectuer des
oprations sur les permissions accordes au code en cours dexcution principalement au
moyen des mthodes Demand(), Deny()/RevertDeny(), PermitOnly()/RevertPermitOnly() et
Assert()/RevertAssert().
Les instances de la classe System.Security.PermissionSet reprsentent des collections de permissions. Cette classe prsente aussi les quatre mthodes cites. Elle permet donc de faciliter les
oprations portant sur plusieurs permissions.

La mthode Demand()
La mthode Demand() vrifie que le code courant dispose des permissions reprsentes par le
jeu de permission. Une remonte de la pile des appels est alors dclenche afin de retrouver la
hirarchie de toutes les mthodes responsable de cet appel. Chaque mthode de cette pile est
galement teste pour le jeu de permissions. Si lune dentre elle ne dispose pas de toutes les
permissions requises, alors une exception de type SecurityException est leve. Lexcution du
code sarrte et la suite de la mthode lorigine de la remonte de la pile nest pas excute. Le
programme suivant sassure quil a la permission de lire un fichier avant de le faire :
Exemple 6-3 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
string sFichier = @"C:\data.txt" ;
CodeAccessPermission cap =
new FileIOPermission(FileIOPermissionAccess.Read, sFichier) ;

200

Chapitre 6 : La gestion de la scurit


try{
cap.Demand();
// Lis le fichier "C:\data.txt".
}
catch ( SecurityException ){
// Vous navez pas la permission de lire "C:\data.txt".
}
}
}

Lintrt de demander explicitement les permissions est danticiper un ventuel refus et dadapter le comportement de lapplication en consquence. La demande explicite permet galement
de mettre en place des stratgies plus sophistiques dans lesquelles le jeu de permissions testes
est ventuellement construit en fonction du contexte comme le rle de lutilisateur.

Les mthodes Deny() RevertDeny() PermitOnly()


et RevertPermitOnly()
La mthode Deny() des classes CodeAccessPermission et PermissionSet permet de signaler les
permissions dont notre code na pas besoin. Bien que la plupart des dveloppeurs aient dautres
tches faire que de signaler les permissions dont ils nont pas besoin, ceci constitue une bonne
pratique. La mthode Deny() permet de sassurer que du code tiers que lon appelle naura pas
certaines permissions. Cette pratique permet aussi davoir une vision globale de ce quune application utilise et permet ds le dpart dun projet de fixer des restrictions. Voici un exemple qui
montre comment refuser les permissions dangereuses de modification du rpertoire systme et
de la base des registres.
Exemple 6-4 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
PermissionSet ps = new PermissionSet(PermissionState.None) ;
ps.AddPermission( new FileIOPermission(
FileIOPermissionAccess.AllAccess,@"C:\WINDOWS")) ;
ps.AddPermission( new RegistryPermission(
RegistryPermissionAccess.AllAccess, string.Empty )) ;
ps.Deny();
// Ici on ne peut modifier les fichiers syst`
emes
// et les donnees de la base des registres.
CodeAccessPermission.RevertDeny();
}
}
Aucune exception ne sera leve si votre code ne disposait pas de ces permissions avant lappel
Deny().Il est a noter que les restrictions lies lappel de la mthode Deny() ne sont appliques
que dans la mthode qui linvoque ainsi que dans les mthodes appeles partir de celle-ci. Dans
tous les cas, la sortie de la mthode ayant invoque les restrictions de droits, les paramtres par

CAS : Vrifier les permissions imprativement partir du code source

201

dfaut sont restaurs. Il est aussi possible dannuler les limitations de droits avec le mthode
RevertDeny().
Une alternative existe au couple de mthodes Deny()/RevertDeny(). Le couple de mthode
PermitOnly()/RevertPermitOnly() permet aussi de modifier temporairement lensemble des
permissions accordes la mthode courante. La dirence entre ces deux manires est que
Deny() spcifie les permissions ne pas accorder alors que PermitOnly() spcifie les permissions
accorder.

Les mthodes Assert() et RevertAssert()


La mthode Assert() permet de spcifier quun appelant na pas besoin davoir une ou plusieurs
permissions. Pour cela, la mthode Assert() supprime le parcours de la pile dappel pour ces
permissions partir de l o elle est appele. La mthode qui appelle Assert() doit avoir la
mta-permission SecurityPermission(Assertion). En outre elle doit aussi avoir la/les permissions(s) concerne(s) pour que la suppression du parcours de la pile dappel ait eectivement
lieu. Dans le cas contraire lappel Assert() na aucun eet et aucune exception nest lance.
Lappel de la mthode Assert() peut introduire des vulnrabilits dans le code qui lappelle
mais dans certaines situations son utilisation se rvle absolument ncessaire. Par exemple, la
classe standard FileStream utilise en interne le mcanisme P/Invoke pour accder aux fichiers
et toutes ses mthodes suppriment le parcours de la pile pour la mta-permission SecurityPermission(UnmanagedCode). Sans cet artifice, tout code qui souhaiterait avoir accs un fichier devrait avoir la mta-permission SecurityPermission(UnmanagedCode) en plus de la permission
FileIOPermission, ce qui nest clairement pas acceptable.
Pour supprimer le parcours de la pile dappel pour vrifier que tous les appelants ont la
mta-permission SecurityPermission(UnmanagedCode), il est prfrable de marquer la mthode concerne ou sa classe avec lattribut System.Security.Suppress\-Unmanaged\-Code\
-Security\-Attribute plutt que dutiliser la mthode Assert(). En eet, cet attribut indique
au compilateur JIT quil ne faut pas produire le code pour vrifier que tous les appelants ont la
mta-permission SecurityPermission(UnmanagedCode) lors dun appel du code non gr.
Lexemple suivant supprime le parcours de la pile dappel pour la permission de lire la base des
registres :
Exemple 6-5 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
CodeAccessPermission cap = new RegistryPermission(
RegistryPermissionAccess.NoAccess, string.Empty ) ;
cap.Assert() ;
// Lis la base des registres.
RegistryPermission.RevertAssert() ;
}
}
Vous ne pouvez appeler Assert() plusieurs fois conscutivement dans une mme mthode. Ceci
provoque la leve dune exception. Pour appeler Assert() plusieurs fois conscutivement dans

202

Chapitre 6 : La gestion de la scurit

une mme mthode, il faut quentre chaque appel, la mthode statique CodeAccessPermission.RevertAccess() soit appele. Pour supprimer le parcours de la pile dappel pour plusieurs
permissions dans une mme mthode il est donc obligatoire dappeler Assert() sur une instance
de PermissionSet.
Par prudence il vaut mieux ninvoquer la mthode Assert() quau moment o lon en a besoin
et pas, par exemple en dbut de mthode. Il faut dans le mme esprit invoquer la mthode RevertAssert() le plus tt possible. Le mieux est en gnral de servir dune structure try/finally
Notez enfin que contrairement aux mthodes Deny()/RevertDeny(), PermitOnly()/RevertPermitOnly() et Assert()/RevertAssert(), la mthode Demand() est la seule qui ninflue pas sur
le droulement ultrieur des oprations.

Les mthodes FromXml() et ToXml()


Les mthodes FromXml() et ToXml() permettent de construire ou de sauver un ensemble de
permissions complexe laide de documents XML.

Linterface System.Security.IPermission
Linterface System.Security.IPermission permet de faire des oprations ensemblistes sur un ensemble de permission. Cette interface est implmente par la classe PermissionSet, la classe
CodeAccessPermission ainsi que ses classes drives. Voici sa dfinition :
public interface System.Security.IPermission {
IPermission Union(IPermission rhs) ;
IPermission Intersect(IPermission rhs) ;
bool
IsSubsetOf(IPermission rhs) ;
IPermission Copy() ;
void
Demand() ;
}
Avec la mthode IsSubsetOf() vous pouvez calculer des relations dinclusion entre permissions.
Par exemple la permission qui donne tous les accs au dossier "C:\MonRep" inclue la permission
qui donne tous les accs au dossier "C:\MonRep\patrick\".
Exemple 6-6 :
using System.Security ;
using System.Security.Permissions ;
class Program {
static void Main() {
string rep1 = @"C:\Monrep" ;
string rep2 = @"C:\Monrep\Patrick" ;
CodeAccessPermission cap1 = new FileIOPermission(
FileIOPermissionAccess.AllAccess, rep1) ;
CodeAccessPermission cap2 = new FileIOPermission(
FileIOPermissionAccess.AllAccess, rep2) ;
bool b = cap2.IsSubsetOf(cap1);
// Ici b vaut true.
}
}

CAS : Vrifier les permissions dclarativement partir du code source

203

Vous pouvez aussi calculer des nouvelles permissions partir dintersections ou dunions de
permissions avec les mthodes Union() et Intersect(). Vous pouvez ainsi rutiliser des permissions volues. Ces types doprations ensemblistes sur les permissions sont trs utiliss par le
gestionnaire de scurit. En pratique les dveloppeurs nont pas beaucoup doccasions de les
utiliser.

CAS : Vrifier les permissions dclarativement partir


du code source
Une alternative existe lutilisation imprative des classes PermissionSet et CodeAccessPermission pour manipuler les permissions directement partir du code source. Cette alternative utilise des attributs standard qui peuvent sappliquer ventuellement aux mthodes, aux types ou
lassemblage lui-mme. Chacune des classes drives de la classe CodeAccessPermission reprsentant un type de permission a un attribut standard qui lui correspond. Par exemple lattribut
RegistryPermissionAttribute reprsente les permissions relatives laccs de la base des registres,
exactement comme la classe RegistryPermission. LExemple 6-5 peut ainsi tre rcrit comme
ceci :
Exemple 6-7 :
using System.Security.Permissions ;
class Program{
[RegistryPermission(SecurityAction.Assert)]
static void Main(){
// Lis la base des registres.
}
}
Les valeurs de lnumration System.Security.Permissions.SecurityAction permettent de spcifier la manipulation souhaite. On peut citer les valeurs Demand, Deny, PermitOnly et Assert qui
ont les mmes eets que leurs mthodes homonymes expliques dans la section prcdentes.
Cependant, lnumration SecurityAction prsente des valeurs qui permettent de raliser des
oprations non prsentes par les classes CodeAccessPermission et PermissionSet :
Valeur de SecurityAction

Description de laction

InheritanceDemand

Lorsquun assemblage est charg, permet dimposer que les types drivs du type sur lequel sapplique cette action aient les permissions spcifies.

LinkDemand

Force le compilateur JIT vrifier quune ou plusieurs permissions sont


accordes la mthode, sans prendre en compte les permissions accordes aux mthodes appelantes. Cette action est plus permissive que Demand mais moins coteuse aussi, puisquelle nest vrifie qu la compilation de la mthode par le JIT et pas chaque appel.

204

Chapitre 6 : La gestion de la scurit

Manipuler les permissions au chargement de lassemblage


Lnumration SecurityAction prsente aussi les trois valeurs suivantes destines tre utiliss
au niveau dun assemblage entier. On les utilise pour signifier au CLR que lorsquil charge lassemblage, il doit procder des oprations sur lensemble des permissions :
Valeur de SecurityAction

Description de laction

RequestMinimum

Spcifie une ou plusieurs permissions sans lesquels lassemblage ne peut


tre charg.

RequestOptional

Spcifie une ou plusieurs permissions requises pour excuter correctement lassemblage. Cependant lassemblage est charg mme si ces permissions ne lui sont pas accordes. On parle de permission optionnelle.

RequestRefuse

Lorsquun assemblage est charg, spcifie une ou plusieurs permissions


qui ne doivent pas tre accordes lassemblage.

Par exemple, un assemblage qui ralise des accs une base de donnes besoin de la permission SqlClientPermission. Il peut avoir besoin de la permission RegistryPermission pour
rcuprer des paramtres mais ceci peut tre optionnel si il prvoit des paramtres par dfaut.
Enfin, il se peut quil nait absolument pas besoin de permissions telles que WebPermission ou
UIPermission. Il faudrait donc quil soit marqu par les attributs suivants :
Exemple 6-8 :

Program.cs

using System.Security.Permissions ;
using System.Data.SqlClient ;
[assembly: SqlClientPermission(SecurityAction.RequestMinimum)]
[assembly: RegistryPermission(SecurityAction.RequestOptional)]
[assembly: UIPermission(SecurityAction.RequestRefuse)]
[assembly: System.Net.WebPermission(SecurityAction.RequestRefuse)]
class Program { public static void Main() { } }
Loutil permview.exe vous permet de visualiser ces attributs. Loutil permcalc.exe dcrit en page
82 va plus loin et permet de calculer lensemble des permissions requis par un assemblage.

Imprative vs. Dclarative


Lutilisation dattributs .NET prsente les inconvnients suivants (par rapport la vrification
imprative des permissions) :

Lors de lchec dune demande ou dune assertion de permissions vous ne pouvez pas rattraper dexception lendroit o lerreur a eu lieu.
Les arguments passs aux permissions (comme le nom dun rpertoire pour grer les permissions daccs ce rpertoire) doivent tre connus la compilation. Plus gnralement,
vous ne pouvez pas mettre en place une logique de scurit dynamique (i.e base sur des
informations connues seulement qu lexcution telles que le rle de lutilisateur courant
par exemple).

CAS : Facilits pour tester et dboguer votre code mobile

205

Les avantages dutiliser des attributs pour manipuler des permissions sont :

La possibilit davoir accs ces attributs et aux paramtres de ces attributs au travers des
mtadonnes de lassemblage ou en utilisant loutil permview.exe.

La possibilit dutiliser certains de ces attributs sur lassemblage entier.

CAS : Facilits pour tester et dboguer votre code mobile


.NET 2.0 prsente de nouvelles facilits pour tester et dboguer votre code mobile. La classe,
System.Security.SecurityException prsente une dizaine de nouvelles proprits permettant
de rcolter beaucoup plus dinformation lorsque lon rattrape une exception de ce type. On peut
citer la proprit AssemblyName FailedAssemblyInfo{get;} qui contient le nom de lassemblage qui est la source de lchec des vrifications de scurit. Comprenez bien que cette facilit
est double tranchant : si un telle exception est analyse par un individu mal intentionn, elle
lui fourniront autant dinformation pour lui permettre dexploiter les vulnrabilits de votre
code.
En page 81 nous exposons les facilits prsentes par Visual Studio 2005 pour la prise en compte
des permissions CAS lors du dveloppement dune application.

CAS : La permission de faire du stockage isol


La prsente section a pour objectif dexpliquer une permission particulire que lon peut accorder ou non un assemblage. Cette permission de faire du stockage isol (isolated storage en
anglais) est la rponse la problmatique suivante :
Donner la permission une application daccder au disque dur tmoigne dune trs grande
confiance dans cette lapplication. A priori, peu dapplications dont le code est mobile peuvent
prtendre un tel degr de confiance. Cependant, la plupart des applications ont besoin de
stocker des donnes de manire persistantes, donc sur le disque dur. Ces donnes sont souvent
des journaux dactivit ou des prfrences utilisateur. Ne pas autoriser ces applications stocker
leurs donnes peut les rendre instables, et autoriser ces applications accder au disque dur est
trs dangereux.
Autoriser une application faire du stockage isol consiste lui permettre daccder un rpertoire qui lui est rserv sur le disque dur. Vous pouvez spcifier une taille maximale pour
ce rpertoire. Lapplication ne peut pas avoir accs aux fichiers qui ne sont pas situs dans ce
rpertoire. Deux applications direntes auront chacune leur rpertoire pour faire du stockage
isol. La responsabilit de nommer ou de localiser un tel rpertoire sur une machine o lapplication est installe incombe totalement .NET. Un tel rpertoire est parfois nomm bac sable
(sandbox en anglais).
En fait, le mcanisme de stockage isol va un peu plus loin que ce qui vient dtre dit. Le choix du
nom et de la localisation du rpertoire peut non seulement se faire sur lidentit de lassemblage,
mais aussi sur lidentit de lutilisateur excutant lapplication ou (non exclusif) sur lidentit
du domaine dapplication contenant lassemblage. Chacune de ces identits est appele porte
(scope en anglais). Concrtement lapplication aura plusieurs rpertoires de stockage isol, un
pour chaque condition dexcution (i.e un pour chaque valeur du produit cartsien des portes). Chaque fois que lapplication sexcute dans les mmes conditions, elle utilise le mme rpertoire. La classe System.IO.IsolatedStorage.IsolatedStorageFile prsente plusieurs m-

206

Chapitre 6 : La gestion de la scurit

thodes statiques telles que GetUserStoreForAssembly(), GetMachineStoreForAssembly(), GetMachineStoreForDomain() ou GetMachineStoreForApplication() permettant dobtenir le rpertoire correspondant la porte dsire.
Voici un exemple montrant comment accder au rpertoire de stockage isol :
Exemple 6-9 :
using System.IO ;
using System.IO.IsolatedStorage ;
class Program {
static void Main() {
// Obtient le repertoire en fonction de lutilisateur
// et de lassemblage courant.
IsolatedStorageFile isf =
IsolatedStorageFile.GetUserStoreForAssembly() ;
IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(
"pref.txt", FileMode.Create, isf) ;
StreamWriter sw = new StreamWriter(isfs) ;
sw.WriteLine("Mettre ici vos pr
ef
erences...") ;
sw.Close() ;
}
}
Le rpertoire utilis par ce programme pour faire du stockage isol sous mon identit est celui
de la Figure 6 -5:

Figure 6 -5 : Rpertoire pour faire du stockage isol

Support .NET pour les utilisateurs et rles Windows


La plupart des applications prsentent dirents niveaux dutilisations. Par exemple, dans une
banque, tous les caissiers utilisateurs dune application bancaire ne sont pas autoriss transfrer une somme de lordre du million deuros. De mme, tous les utilisateurs dune telle application ne sont pas autoriss configurer les protocoles daccs aux bases de donnes. Enfin

Support .NET pour les utilisateurs et rles Windows

207

chaque client peut tre autoris consulter son compte ( partir dinternet). Une telle application ncessite de sparer les utilisateurs entre les clients, les simples caissiers, les caissiers avec des
responsabilits et les administrateurs. Chacune de ces catgories est appele rle. Pour lapplication chaque utilisateur joue zro, un ou plusieurs rles. En fonction du cahier des charges de
lapplication bancaire, les dveloppeurs doivent pouvoir vrifier directement partir du code
le rle de lutilisateur courant. Dans le code, cette vrification se fait avant toutes les excutions
dune fonctionnalit critique. Dans ce contexte, pour une application, accorder de la confiance
un utilisateur revient dterminer quels sont les rles quil joue.

Introduction la scurit sous Windows


Les systmes dexploitation Windows 95/98/Me nont pas de contexte de scurit. En revanche,
sous les systmes dexploitation Windows NT/2000/XP/2003/Vista toute excution de code seectue dans un contexte de scurit. chaque processus Windows est associ une identit Windows
que lon nomme principal. Pour simplifier, vous pouvez considrer quun principal est un utilisateur Windows. Pour linstant, nous considreront quun thread sexcute dans le contexte du
principal de son processus. Nous verrons que sous certaines conditions cette rgle peut tre mise
en dfaut.
Windows associe chacune de ses ressources (fichiers, registre etc) un ensemble de rgles daccs.
Lorsque quun thread tente daccder une ressource Windows, Windows vrifie que le principal
associ au thread est autoris par les rgles daccs.
Windows prsente la notion de groupe dutilisateurs. Chaque utilisateur appartient un ou plusieurs groupes. Les rgles daccs aux ressources peuvent tre configures en fonction des utilisateurs et en fonction des groupes. Ainsi, un administrateur peut autoriser ou refuser laccs une
ressource tous les utilisateurs dun groupe en spcifiant un seul groupe plutt quen spcifiant
N utilisateurs.
Parmi les rles classiques Windows on peut citer le rle administrateur, utilisateur, invit etc. La
notion de groupe permet dimplmenter naturellement le concept de rle jou par un utilisateur. chaque rle correspond un groupe Windows et un utilisateur Windows joue un rle sil
fait partie du groupe correspondant.
chaque authentification dun utilisateur Windows cre une session logon. Une session logon
est matrialise par un jeton de scurit (security token en anglais). Le principal dun processus
est aussi matrialis par un jeton de scurit. Lorsquun processus cre un nouveau processus,
ce dernier hrite automatiquement du jeton de scurit de son crateur et volue donc dans le
mme contexte de scurit. Nous prcisons quen page 135 nous expliquons comment le framework .NET vous permet de crer un processus dans un contexte de scurit dirent de celui de
son processus parent.
Lors du dmarrage, Windows cre automatiquement trois sessions logon : la session systme, la
session locale et la session rseau. Ceci explique le fait que des applications peuvent sexcuter
sur une machine sans quun utilisateur nest t logu (notamment on utilise pour cela la notion
de service Windows). Cette possibilit est particulirement exploite dans le cas de serveurs qui
sont susceptibles dtre reboots automatiquement.
.NET prsente plusieurs espaces de noms et de nombreux types permettant dexploiter programmatiquement la scurit Windows. Comprenez bien que ces types ne font quencapsuler les fonctions et les structures Win32 ddies la scurit.

208

Chapitre 6 : La gestion de la scurit

Les interfaces IIdentity et IPrincipal


Le framework .NET prsente les deux interfaces suivantes qui permettent de reprsenter les notions proches didentit et de principal :
interface System.Security.Principal.IIdentity{
string
AuthenticationType{get;}
bool
IsAuthenticated{get;}
string
Name{get;}
}
interface System.Security.Principal.IPrincipal{
IIdentity
Identity {get;}
bool
IsInRole(string role) ;
}
Tandis que linterface IIdentity reprsente laspect authentification (qui on est ?) linterface
IPrincipal reprsente laspect autorisation (quest ce quon est autoris faire ?). Ces interfaces sont notamment utilises pour manipuler la scurit Windows avec les implmentations
System.Security.Principal.WindowsIdentity et System.Security.Principal.WindowsPrincipal.
Par exemple le programme suivant rcupre lidentit de lutilisateur associ au contexte de
scurit Windows sous-jacent :
Exemple 6-10 :
using System.Security.Principal ;
class Program{
static void Main(){
IIdentity id = WindowsIdentity.GetCurrent();
System.Console.WriteLine( "Nom : "+id.Name) ;
System.Console.WriteLine( "Authentifi
e ? : "+id.IsAuthenticated) ;
System.Console.WriteLine(
"Type dauthentification : "+id.AuthenticationType) ;
}
}
Ce programme ache :
Nom : PSMACCHIA\pat
Authentifie ? : True
Type dauthentification : NTLM
Le framework .NET prsente dautres implmentations des interfaces IIdentity et IPrincipal
relatives dautres mcanismes de scurit et vous pouvez fournir vos propres implmentations
pour dvelopper vos propres mcanismes. On peut ainsi citer la classe System.Web.Security.
FormsIdentity exploite par ASP.NET et la classe System.Web.Security.PassportIdentity exploite par le mcanisme Passport.
Le couple de classes System.Security.Principal.GenericIdentity et System.Security.Principal.GenericPrincipal peut tre utilis comme base limplmentation de vos propres mcanismes
dauthentification/autorisation mais rien ne vous empche dimplmenter directement les interfaces IIdentity et IPrincipal.

Support .NET pour les utilisateurs et rles Windows

209

Les identificateurs de scurit Windows


Pour identifier les utilisateurs et les groupes, Windows utilise des identificateurs de scurit (SID).
Les SID peuvent tre vus comme des gros nombres uniques dans le temps et dans lespace,
un peu comme les GUID. Les SID peuvent tre reprsents par une chane de caractres au
format SDDL (Security Descriptor Definition Language un format de reprsentation textuel non
XML) donnant quelques prcisions quant lentit reprsente. Par exemple on peut dduire
du SID suivant "S-1-5-21-1950407961-2111586655-839522115-500" quil reprsente un administrateur car il contient le nombre 500 en dernire position (501 aurait indiqu un invit).
Le framework .NET prsente les trois classes suivantes dont les instances reprsentent des SID :
System.Object
System.Security.Principal.IdentityReference
System.Security.Principal.NTAccount
System.Security.Principal.SecurityIdentifier
La classe NTAccount permet de reprsenter le SID sous une forme lisible par les humains tandis que la classe SecurityIdentifier est utilise pour communiquer un SID Windows. Chacune de ces classes prsente une mthode IdentityReference Translate(Type targetType)
permettant dobtenir une reprsentation dirente dun mme SID.
La classe WindowsIdentity prsente la proprit SecurityIdentifier User{get;} permettant de rcuprer le SID de lutilisateur Windows courant. Lnumration System.Security.
Principal.WellKnownSidType reprsente la plupart des groupes Windows fournis par dfaut.
Enfin, la classe SecurityIdentifier prsente la mthode bool IsWellKnown(WellKnownSidType) qui permet de tester si le SID courant appartient au groupe Windows prcis. Lexemple
suivant exploite tout ceci pour tester si lutilisateur courant est un administrateur :
Exemple 6-11 :
using System.Security.Principal ;
class Program {
static void Main() {
WindowsIdentity id = WindowsIdentity.GetCurrent() ;
SecurityIdentifier sid = id.User ;
NTAccount ntacc = sid.Translate(typeof(NTAccount)) as NTAccount ;
System.Console.WriteLine( "SID:
" + sid.Value) ;
System.Console.WriteLine( "NTAccount: " + ntacc.Value) ;
if ( sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) )
System.Console.WriteLine("...is administrator.") ;
}
}
Cet exemple ache :
SID:
S-1-5-21-1950407961-2111586655-839522115-500
NTAccount: PSMACCHIA\pat
...is administrator.

Impersonation du thread Windows


Par dfaut, un thread Windows volue dans le contexte de scurit de son processus. Nanmoins,
on peut, partir du code, associer le contexte de scurit dun thread avec un utilisateur en

210

Chapitre 6 : La gestion de la scurit

utilisant la fonction win32 WindowsIdentity.Impersonate(IntPtr jeton). Il faut au pralable


loguer lutilisateur et obtenir un jeton de scurit avec la fonction LogonUser(). Cet utilisateur
nest pas ncessairement lutilisateur du contexte de scurit du processus. Lorsque le contexte
de scurit dun thread est associ un utilisateur, on dit que le thread eectue un emprunt
didentit (impersonation en anglais). Cette technique est expose par lexemple suivant :
Exemple 6-12 :
using System.Runtime.InteropServices ;
using System.Security.Principal ;
class Program{
[DllImport("Advapi32.Dll")]
static extern bool LogonUser(
string
sUserName,
string
sDomain,
string
sUserPassword,
uint
dwLogonType,
uint
dwLogonProvider,
out
System.IntPtr Jeton);
[DllImport("Kernel32.Dll")]
static extern void CloseHandle(System.IntPtr Jeton) ;
static void Main(){
WindowsIdentity id1 = WindowsIdentity.GetCurrent() ;
System.Console.WriteLine( "Avant impersonation : " +id1.Name) ;
System.IntPtr pJeton ;
if( LogonUser(
"invite" ,
// login
string.Empty, // domaine Windows
"invitepwd" , // mot de passe
2,
// LOGON32_LOGON_INTERACTIVE
0,
// LOGON32_PROVIDER_DEFAUT
out pJeton) ) {
WindowsIdentity.Impersonate(pJeton) ;
WindowsIdentity id2 = WindowsIdentity.GetCurrent() ;
System.Console.WriteLine("Pendant impersonation : "+id2.Name) ;
// Ici, le thread Windows sous-jacent
// a lidentite de lutilisateur invit
e.
CloseHandle(pJeton) ;
}
}
}
Ce programme ache :
Avant impersonation : PSMACCHIA\pat
Pendant impersonation : PSMACCHIA\invit
e
La mthode WindowsIndentity.GetCurrent() accepte une surcharge prenant un paramtre un
boolen. Lorsque le boolen est true cette mthode nous retourne lidentit de lutilisateur
seulement si le thread est en cours demprunt didentit. Lorsque le boolen est false cette

Support .NET pour les contrles des accs aux ressources Windows

211

mthode nous retourne lidentit de lutilisateur seulement si le thread nest pas en cours demprunt didentit.

Support .NET pour les contrles des accs


aux ressources Windows
Introduction au contrle des accs aux ressources Windows
Aprs avoir introduit les notions de groupes et dutilisateurs Windows nous allons nous intresser la seconde partie de la scurit sous Windows : les contrles des accs aux ressources.
Une ressource Windows peut tre un fichier, un objet de synchronisation Windows (mutex, vnement...), une entre de la base des registres etc. Chaque type de ressource prsente des droits
daccs qui lui sont propres. Par exemple, on peut citer le droit daccs dajouter des donnes
un fichier et le droit daccs dobtenir la possession dun mutex. Aucun de ces droits daccs na
de sens sorti du contexte de son type de ressource.
Chaque ressource contient physiquement des informations qui permettent Windows de dduire quel utilisateur a quel droit daccs sur la ressource. Ces informations sont contenues dans
une structure associe la ressource que lon nomme descripteur de scurit (SD). Un SD contient
notamment un SID reprsentant lutilisateur qui a cr ou qui dtient la ressource et une liste
nomme Discretionary Access Control List (DACL). Bien que stock sous une forme binaire, un
SD peut tre reprsent par une chane de caractres au format SDDL.
Lorsquun thread qui sexcute dans le contexte de scurit dun utilisateur tente dobtenir un
ou plusieurs droits daccs sur une ressource, Windows dtermine sil peut obtenir les droits
partir de la DACL de la ressource.
Une DACL est une liste ordonne dAccess Control Element (ACE). Un ACE est une structure qui
associe un SID une liste de droits daccs. Une DACL contient deux types dACE :

les ACE qui accordent leurs droits daccs leur SID ;


les ACE qui refusent leurs droits daccs leur SID.

Lorsquun thread tente dobtenir des droits daccs une ressource, Windows tablit son verdict
partir du SID du thread et de la DACL de la ressource. Les ACE sont valus dans lordre dans
lequel ils sont stocks dans la DACL. Chaque ACE accorde ou retire des droits daccs lorsque
le SID du thread est inclus dans son SID. Lensemble des droits daccs demands est accord
ds que tous les droits daccs ont t accords durant lvaluation. Les droits daccs sont tous
refuss ds quun seul des droits daccs demands est refus par un ACE. Comprenez bien que
lordre de stockage des ACE dans la DACL importe et que Windows nvalue pas ncessairement
tous les ACE lors dune demande de droits daccs.
Pour certains types de ressources Windows permet lhritage de SD. Cette possibilit se rvle
essentielle par exemple pour un administrateur qui souhaiterait configurer les SD de milliers
de fichiers contenus dans un rpertoire en une seule manipulation.
Chaque SD dune ressource Windows contient une seconde liste dACE nomme System Access
Control List (SACL). Cette seconde liste est exploite par Windows pour auditer les accs une
ressource. linstar des ACE dune DACL les ACE dune SACL associent chacun un SID une
liste de droits daccs. Au contraire des ACE dune DACL les ACE dune SACL contiennent deux
informations binaires qui peuvent sinterprter comme ceci :

212

Chapitre 6 : La gestion de la scurit

lvnement un de mes droits daccs a t accord un SID inclus dans mon SID doit-il tre
logu ?

lvnement un de mes droits daccs a t refus un SID inclus dans mon SID doit-il tre logu ?

Clairement, lordre de stockage des ACE dans une SACL nimporte pas.
Le nouvel espace de noms System.Security.AccessControl dfinit des types permettant dexploiter les SD. Aprs avoir prsent les types ddis la manipulation des SD de ressources
spcifiques Windows nous prsenterons des types prvus pour exploiter les SD dune manire
gnrique (i.e indpendante du type de ressource Windows sous jacent).

.NET et la manipulation de SD spcifiques


Les types relatifs des ressources spcifiques consistent en une hirarchie de types reprsentant
les SD, une hirarchie de types reprsentant les ACE et des numrations reprsentant les droits
daccs. Les classes suivantes permettent de reprsenter les descripteurs de scurit :
System.Object
System.Security.AccessControl.ObjectSecurity
System.Security.AccessControl.DirectoryObjectSecurity
System.DirectoryServices.ActiveDirectorySecurity
System.Security.AccessControl.CommonObjectSecurity
Microsoft.Iis.Metabase.MetaKeySecurity
System.Security.AccessControl.NativeObjectSecurity
System.Security.AccessControl.EventWaitHandleSecurity
System.Security.AccessControl.FileSystemSecurity
System.Security.AccessControl.DirectorySecurity
System.Security.AccessControl.FileSecurity
System.Security.AccessControl.MutexSecurity
System.Security.AccessControl.RegistrySecurity
System.Security.AccessControl.SemaphoreSecurity
Ces classes acceptent des paramtres spcifiques reprsentant les ACE pour remplir les DACL et
SACL. Notez quil y a des classes qui reprsentent les ACE de la DACL (access rule) et des classes
qui reprsentent les ACE de la SACL pour laudit (audit rule) :
System.Object
System.Security.AccessControl.AuthorizationRule
System.Security.AccessControl.AccessRule
Microsoft.Iis.Metabase.MetaKeyAccessRule
System.Security.AccessControl.EventWaitHandleAccessRule
System.Security.AccessControl.FileSystemAccessRule
System.Security.AccessControl.MutexAccessRule
System.Security.AccessControl.ObjectAccessRule
System.DirectoryServices.ActiveDirectoryAccessRule
System.DirectoryServices.[*]AccessRule
System.Security.AccessControl.RegistryAccessRule
System.Security.AccessControl.SemaphoreAccessRule
System.Security.AccessControl.AuditRule
Microsoft.Iis.Metabase.MetaKeyAuditRule

Support .NET pour les contrles des accs aux ressources Windows

213

System.Security.AccessControl.EventWaitHandleAuditRule
System.Security.AccessControl.FileSystemAuditRule
System.Security.AccessControl.MutexAuditRule
System.Security.AccessControl.ObjectAuditRule
System.DirectoryServices.ActiveDirectoryAuditRule
System.Security.AccessControl.RegistryAuditRule
System.Security.AccessControl.SemaphoreAuditRule
Voici la liste des numrations reprsentant les droits daccs. Par exemple, lnumration
FileSystemRights contient une valeur AppendData tandis que lnumration MutexRights
contient une valeur TakeOwnership.
Microsoft.Iis.Metabase.MetaKeyRights
System.Security.AccessControl.EventWaitHandleRights
System.Security.AccessControl.FileSystemRights
System.Security.AccessControl.MutexRights
System.Security.AccessControl.RegistryRights
System.Security.AccessControl.SemaphoreRights
Enfin les dirents types du framework .NET reprsentant directement les ressources Windows
concernes (System.Threading.Mutex, System.IO.File etc) ont des nouveaux constructeurs acceptant des ACL et des mthodes Set/GetAccessControl permettant de positionner et dobtenir
les ACL dune instance. Voici un exemple illustrant tout ceci lors de la cration dun fichier avec
une DACL :
Exemple 6-13 :
using System.Security.AccessControl ;
using System.Security.Principal ;
using System.IO ;
class Program {
static void Main() {
// Cree la DACL.
FileSecurity dacl = new FileSecurity() ;
// Rempli la DACL avec un ACE.
FileSystemAccessRule ace = new FileSystemAccessRule(
WindowsIdentity.GetCurrent().Name,
FileSystemRights.AppendData | FileSystemRights.ReadData,
AccessControlType.Allow) ;
dacl.AddAccessRule(ace) ;
// Cree un nouveau fichier avec cette DACL.
System.IO.FileStream fileStream = new System.IO.FileStream(
@"fichier.bin" , FileMode.Create , FileSystemRights.Write ,
FileShare.None, 4096 , FileOptions.None, dacl ) ;
fileStream.Write(new byte[] { 0, 1, 2, 3 }, 0, 4) ;
fileStream.Close() ;
}
}
Vous pouvez visualiser les droits daccs aux fichiers fichier.bin comme ceci : Proprit du fichier fichier.bin  Scurit  Paramtres avancs  Autorisations  Modifier les autorisations spciales

214

Chapitre 6 : La gestion de la scurit

accordes au principal avec lequel vous avez excut le programme  lecture de donnes et ajout de
donnes.
Si longlet Scurit ne sache pas il faut eectuer cette manipulation : Panneau de configuration
 Options des dossiers  Achage  Utiliser le partage de fichiers simple (recommand).

.NET et la manipulation de SD gnriques


.NET prsente des types gnraux pour la manipulation des SD. La hirarchie de types suivante
permet de reprsenter des SD :
System.Object
System.Security.AccessControl.GenericSecurityDescriptor
System.Security.AccessControl.CommonSecurityDescriptor
System.Security.AccessControl.RawSecurityDescriptor
La hirarchie de type suivante permet de reprsenter des ACL, des DACL et des SACL :
System.Object
System.Security.AccessControl.GenericAcl
System.Security.AccessControl.CommonAcl
System.Security.AccessControl.DiscretionaryAcl
System.Security.AccessControl.SystemAcl
System.Security.AccessControl.RawAcl
La hirarchie de type suivante permet de reprsenter des ACE :
System.Object
System.Security.AccessControl.GenericAce
System.Security.AccessControl.CustomAce
System.Security.AccessControl.KnownAce
System.Security.AccessControl.CompoundAce
System.Security.AccessControl.QualifiedAce
System.Security.AccessControl.CommonAce
System.Security.AccessControl.ObjectAce
Lexemple suivant montre comment crer un SD, comment ajouter des ACE son DACL, puis
comment transformer ce SD en un SD de ressource spcifique Windows (le type mutex en loccurrence) :
Exemple 6-14 :
using System ;
using System.Security.AccessControl ;
using System.Security.Principal ;
class Program {
static void Main() {
// Cree un nouveau descripteur de s
ecurite.
CommonSecurityDescriptor csd = new CommonSecurityDescriptor(
false, false, string.Empty) ;
DiscretionaryAcl dacl = csd.DiscretionaryAcl ;
// Ajoute un ACE a son DACL.

Support .NET pour les contrles des accs aux ressources Windows

215

dacl.AddAccess(
AccessControlType.Allow, // Allow OU Deny.
WindowsIdentity.GetCurrent().Owner, // Utilisateur courant.
0x00180000, // Masque : TakeOwnerShip ET Synchronize

//
equivalent `
a
//(int) MutexRights.TakeOwnership | (int) MutexRights.Synchronize
InheritanceFlags.None, // D
esactive...
PropagationFlags.None); // ...lheritage dACE.
string sSDDL = csd.GetSddlForm( AccessControlSections.Owner ) ;
Console.WriteLine("Security Descriptor : " + sSDDL) ;
MutexSecurity mutexSec = new MutexSecurity() ;
mutexSec.SetSecurityDescriptorSddlForm(sSDDL);
AuthorizationRuleCollection aces = mutexSec.GetAccessRules(
true, true, typeof(NTAccount)) ;
foreach (AuthorizationRule ace in aces){
if (ace is MutexAccessRule){
MutexAccessRule mutexAce = (MutexAccessRule)ace ;
Console.WriteLine("-->SID : " +
mutexAce.IdentityReference.Value) ;
Console.WriteLine("
Type de droits dacc`
es : " +
mutexAce.AccessControlType.ToString()) ;
if (0xffffffff == (uint)mutexAce.MutexRights)
Console.WriteLine("
Tous les droits !") ;
else
Console.WriteLine("
Droits : " +
mutexAce.MutexRights.ToString()) ;
}
}
}
}
Cet exemple ache :

Security Descriptor : D:(A;;0xffffffff;;;WD)(A;;0x180000;;;LA)


-->SID : TOUT LE MONDE
Type de droits dacc`es : Allow
Tous les droits !
-->SID : PSMACCHIA\pat
Type de droits dacc`es : Allow
Droits : TakeOwnership, Synchronize

On remarque que par dfaut un DACL dun nouveau SD contient lACE qui donne tous
les droits tout le monde. Vous pouvez utiliser la mthode CommonAcl.Purge(SecurityIdentifier) pour supprimer les ACE relatives un SID dans un ACL.

216

Chapitre 6 : La gestion de la scurit

.NET et la notion de rle


De la mme faon quun thread Windows sexcute dans un contexte de scurit Windows, un
thread gr a la possibilit de sexcuter dans un contexte de scurit de votre choix. Vous pouvez
alors exploiter un mcanisme de scurit de type rle/utilisateur autre que celui de Windows,
tel que celui dASP.NET par exemple. Ceci est possible car la classe Thread prsente la proprit
IPrincipal CurrentPrincipal{get;set;}. Un principal peut tre associ un thread gr de
trois faons direntes :

Soit vous associez explicitement un principal un thread gr avec la proprit Thread.


CurrentPrincipal.
Soit vous dfinissez une politique de principal pour un domaine dapplication. Lorsquun
thread gr excutera le code du domaine dapplication, la politique de principal du domaine lui associera ventuellement un principal, sauf dans le cas o le thread a t associ
explicitement un principal, auquel cas celui-ci nest pas modifi.
Soit vous pouvez dcider que tous les threads qui sont crs dans un domaine ou qui pntrent dans un domaine sans avoir de principal .NET auront un principal particulier. Pour
cela il sut de prciser ce principal avec la mthode void AppDomain.SetThreadPrincipal
(IPrincipal).

Ces trois oprations ncessitent la mta-permission CAS SecurityPermissionFlag.ControlPrincipal pour tre menes bien.

Positionner la politique de principal dun domaine dapplication


Lexemple suivant positionne la politique de principal du domaine dapplication courant WindowsPrincipal. Cest--dire que lorsquun thread gr excute le code contenu dans le domaine
dapplication, sil ny a pas eu de principal associ explicitement ce thread gr, alors le principal associ au contexte de scurit Windows sous-jacent lui est associ :
Exemple 6-15 :
using System.Security.Principal ;
class Program{
static void Main(){
System.AppDomain.CurrentDomain.SetPrincipalPolicy(
PrincipalPolicy.WindowsPrincipal);
IPrincipal pr = System.Threading.Thread.CurrentPrincipal;
IIdentity id = pr.Identity ;
System.Console.WriteLine( "Nom : "+id.Name) ;
System.Console.WriteLine( "Authentifi
e ? : "+id.IsAuthenticated) ;
System.Console.WriteLine(
"Type dauthentification : "+id.AuthenticationType) ;
}
}
Ce programme ache :
Nom : PSMACCHIA\pat
Authentifie ? : True
Type dauthentification : NTLM

.NET et la notion de rle

217

Les autres politiques de principal possibles pour un domaine dapplication sont :

Pas de principal associ au thread (PrincipalPolicy.NoPrincipal). Dans ce cas la proprit


Thread.CurrentPrincipal vaut null par dfaut.

Un principal non authentifi associ au thread (PrincipalPolicy.UnauthenticatedPrincipal).


Dans ce cas le CLR associe une instance de GenericPrincipal non authentifie la proprit Thread.CurrentPrincipal.

Cette dernire alternative constitue la politique de principal prise par dfaut par tous les domaines dapplication.

Vrifier lappartenance un rle


Vous pouvez vrifier le rle dun principal dun thread gr de trois faons direntes :

Vous pouvez utiliser la mthode IsInRole() prsente par linterface IPrincipal.


Exemple 6-16 :
using System.Security.Principal ;
class Program{
static void Main(){
IPrincipal pr = System.Threading.Thread.CurrentPrincipal ;
if( pr.IsInRole( @"BUILTIN\Administrators" ) ){
// Ici, le principal est un administrateur.
}
else
System.Console.WriteLine(
"Lexecution de ce programme requiert les droits dAdministrateur") ;
}
}
Le lecteur attentif aura remarqu que dans lExemple 6-11 nous avons exploit une autre
technique pour vrifier quun utilisateur Windows est membre dun groupe Windows. Cette
technique, base sur lnumration WellKnownSidType, est prfrable dans le cas particulier
dutilisateurs et de rles Windows. La raison est que le nom dun groupe Windows dire selon la langue utilise en paramtre (par exemple Administrateurs en franais, Administrators
en anglais).

Vous pouvez utiliser la classe System.Security.Permissions.PrincipalPermission. Bien que


cette classe ne drive pas de la classe CodeAccessPermission, elle implmente linterface
IPermission. Cette classe prsente aussi les mthodes classiques permettant de manipuler
les permissions (FromXml() ToXml() etc). Cette technique ore lavantage pour les dveloppeurs, de grer les rles dune manire intgre par rapport la gestion des permissions.
Exemple 6-17 :
using System.Security.Permissions ;
class Program{
static void Main(){
try{
PrincipalPermission prPerm = new PrincipalPermission(

218

Chapitre 6 : La gestion de la scurit


null, @"BUILTIN\Administrators" );
prPerm.Demand();
// Ici, le principal est un administrateur.
}
catch(System.Security.SecurityException){
System.Console.WriteLine(
"Lexecution de ce programme requiert les droits dAdministrateur") ;
}
}
}
Un autre avantage de cette technique est quelle permet de vrifier plusieurs rles dun seul
coup :
Exemple 6-18 :
...
PrincipalPermission prPermAdmin = new PrincipalPermission(
null, @"BUILTIN\Administrators" ) ;
PrincipalPermission prPermUser = new PrincipalPermission(
null, @"BUILTIN\Users" ) ;
System.Security.IPermission prPerm = prPermAdmin.Union(prPermUser);
prPerm.Demand() ;
...
linstar de la gestion des permissions, vous pouvez utiliser lattribut .NET PrincipalPermission spcialement conu pour grer les rles .NET :
Exemple 6-19 :
using System.Security.Permissions ;
class Program{
[PrincipalPermission(
SecurityAction.Demand,
Role= @"BUILTIN\Administrators")]
static void Main(){
// Ici, le principal est un administrateur.
}
}

La comparaison entre la technique dutilisation de la classe PrincipalPermission et la technique


dutilisation dattributs .NET est faite dans la section page 204.

Scurit base sur les rles sous COM+


COM+ est une technologie Microsoft permettant une classe (.NET ou non) dutiliser des fonctionnalits appeles services dentreprise. Parmi ces services dentreprise, il existe un service
dentreprise de gestion de la scurit partir de rles. Pour chaque composant servi (i.e qui utilise COM+), vous pouvez associer les rles requis pour lexploitation du composant puis aecter
des rles chaque utilisateurs. Les rles COM+ peuvent ventuellement tre dirents des rles
Windows mais en pratique, on utilise souvent les rles Windows. Lorsquun assemblage contient
des composants servis, il peut vrifier lappartenance dun utilisateur un rle en utilisant

.NET et les algorithmes symtriques de cryptographie

219

lattribut System.EnterpriseServices.SecurityRole sur lassemblage tout entier ou seulement sur


certaines classes ou interfaces de lassemblage. Pour plus dinformation quant lexploitation
du service dentreprise de gestion des rles COM+ et quant lunification des notions de rle
Windows et de rle COM+ vous pouvez vous rfrer larticle Unify the Role-Based Security
Models for Enterprise and Application Domains with .NET de Juval Lowy disponible lURL
http://msdn.microsoft.com/msdnmag/issues/02/05/rolesec/ .

.NET et les algorithmes symtriques de cryptographie


Un peu de thorie
Nous allons expliquer comment Julien et Mathieu peuvent schanger des messages dune manire confidentielle en utilisant un algorithme symtrique dencryptions. Les algorithmes symtriques sont bass sur un systme de paire de cls. Avant de pouvoir encrypter un message M,
Julien et Mathieu doivent choisir un algorithme symtrique et se fabriquer une paire de cls
(S,P). Nommons P(M) un message M encrypt avec la cl P et S(M) un message M encrypt avec
la cl S. Les proprits dun algorithme symtrique sont les suivantes :

S(P(M)) = P(S(M)) = M
On ne peut obtenir M si lon dtient P(M) sans connatre la paire de cls (S,P).
On ne peut obtenir M si lon dtient S(M) sans connatre la paire de cls (S,P).

On voit que les cls S et P jouent un rle symtrique do le nom de ce type dalgorithme. Pour
que Julien envoi le message M Mathieu dune manire confidentielle, il doit lui envoyer une
des deux versions cryptes du message. Mathieu pourra alors obtenir le message original en
appliquant lalgorithme symtrique sur le message crypt avec les deux cls. En pratique, Julien
et Mathieu se seront mis daccord sur la cl utilise pour lencryption. Si un tiers intercepte la
version crypte du message, il ne pourra pas obtenir le message original puisquil ne dtient pas
la paire de cl (S,P). Tout ceci est rsum dans la figure suivante :
Rseau peu sr, seuls les
messages encrypts y circulent
Mathieu
Connat le couple
de cls (S,P)

S(M1)
S(M2)

Julien
Connat le couple
de cls (S,P)

Un tiers
Ne peut retrouver un message partir de sa version
encrypte car il ne possde pas la paire de cls (S,P)

Figure 6 -6 : Echange de messages crypts avec un algorithme symtrique


Les algorithmes symtriques ne sont pas secrets et sont largement dissqus dans de nombreuses
publications. Dans la mesure o la publication de lalgorithme permet des milliers de mathmaticiens den examiner les faiblesses ventuelles on peut mme dire que publier lalgorithme
de cryptographie participe la robustesse de lalgorithme.
Seules les cls doivent tre gardes confidentielles. Ceci est un principe de la cryptographie
apparu relativement rcemment, il y a une trentaine dannes. Cest vraiment une nouveaut

220

Chapitre 6 : La gestion de la scurit

puisque pendant des sicles, les algorithmes ont constitu la pierre angulaire de la cryptographie.

Le framework .NET et les algorithmes symtriques


Le framework .NET prsente une implmentation pour les algorithmes symtriques les plus
connus savoir les algorithmes DES, RC2, Rinjdael et Triple DES. Voici la hirarchie de classes.
Seules les classes qui ne sont pas en gras sont abstraites. Ainsi, vous pouvez dcider de fournir
votre propre implmentation de ces algorithmes.
System.Object
System.Security.Cryptography.SymmetricAlgorithm
System.Security.Cryptography.DES
System.Security.Cryptography.DSECryptoServiceProvider
System.Security.Cryptography.RC2
System.Security.Cryptography.RC2CryptoServiceProvider
System.Security.Cryptography.Rijndael
System.Security.Cryptography.RinjdaelManaged
System.Security.Cryptography.TripleDES
System.Security.Cryptography.TripleDESCryptoServiceProvider
Lalgorithme DES (Digital Encryption Standard) est lalgorithme symtrique le plus utilis.
Lexemple suivant illustre lutilisation de la classe DESCryptoServiceProvider pour crypter
et dcrypter une chane de caractre. En plus dune cl, nous fournissons cet algorithme un
vecteur dinitialisation (Initialization Vector en anglais IV). Un vecteur dinitialisation peut tre vu
comme un nombre en gnral choisi alatoirement et utilis pour initialiser lalgorithme :
Exemple 6-20 :
using System.Security.Cryptography ;
class Program{
static void Main() {
string sMsg = "Le message `
a encrypter !" ;
string sEnc, sDec ;
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
System.Text.Encoding utf = new System.Text.UTF8Encoding();
byte[] key = utf.GetBytes("12345678");
byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 };
ICryptoTransform encryptor = des.CreateEncryptor(key, iv);
ICryptoTransform decryptor = des.CreateDecryptor(key, iv);
{
byte[] bMsg = utf.GetBytes(sMsg) ;
byte[] bEnc = encryptor.TransformFinalBlock(bMsg, 0,
bMsg.Length);
sEnc = System.Convert.ToBase64String(bEnc) ;
}
{
byte[] bEnc = System.Convert.FromBase64String(sEnc) ;
byte[] bDec = decryptor.TransformFinalBlock(bEnc, 0,
bEnc.Length);

.NET et les algorithmes asymtriques de cryptographie (cl publique/cl prive)

221

sDec = utf.GetString(bDec) ;
}
System.Console.WriteLine("Message : " + sMsg) ;
System.Console.WriteLine("Encrypt
e : " + sEnc) ;
System.Console.WriteLine("Decrypt
e : " + sDec) ;
}
}
Cet exemple ache ceci :
Message : Le message a` encrypter !
Encrypte : iNnbHD1R3nci5tGElIvKIBapaTmfqHEV
Decrypte : Le message `a encrypter !
Dans lexemple prcdent, notez que les objets encryptor et decryptor implmentent tous les
deux linterface ICryptoTransform. Ceci est une consquence de laspect symtrique des algorithmes.
La classe DESCryptoServiceProvider peut aussi tre utilise pour construire une cl et un vecteur dinitialisation. Ainsi lexemple prcdent peut tre rcrit comme suit pour exploiter cette
possibilit :
Exemple 6-21 :
...
des.GenerateKey() ;
des.GenerateIV() ;
ICryptoTransform encryptor = des.CreateEncryptor() ;
ICryptoTransform decryptor = des.CreateDecryptor() ;
...

.NET et les algorithmes asymtriques de cryptographie


(cl publique/cl prive)
Un peu de thorie
Les algorithmes symtriques prsentent deux faiblesses :

La paire de cls doit tre connue des deux parties souhaitant schanger des messages. un
moment donn, il faut que cette paire de cls circule sur un canal de communication entre
les deux parties.

Une paire de cl nest valable quentre deux parties. Si Julien souhaite changer des messages crypts avec Mathieu et avec Sbastien il doit dtenir deux paires de cls : une pour
crypter les changes avec Mathieu et une pour crypter les changes avec Sbastien. On voit
donc apparatre un problme de gestion de cls.

Les algorithmes asymtriques rsolvent ces deux problmes. Un algorithme asymtrique a les
trois proprits dun algorithme symtrique que nous rappelons en reprenant les notations de
la section prcdente :

222

Chapitre 6 : La gestion de la scurit


S(P(M)) = P(S(M)) = M
On ne peut obtenir M si lon dtient P(M) sans connatre la paire de cls (S,P).
On ne peut obtenir M si lon dtient S(M) sans connatre la paire de cls (S,P).

En plus de ces trois proprits un algorithme asymtrique a les deux proprits suivantes :

Il est ais de calculer la cl S lorsque lon connat la cl P.


Il est trs dicile de calculer la cl P si lon connat la cl S.

Nous avons donc introduit une dissymtrie dans notre paire de cl do le nom de ce type dalgorithme.
On comprend maintenant comment Mathieu et Julien peuvent exploiter ce type dalgorithme
pour schanger des messages sans schanger de cls et sans avoir grer un grand nombre de
cls. Il sut quils calculent chacun une paire de cl que nous nommons (Sj,Pj) et (Sm,Pm).
Mathieu diuse la cl Sm tandis que Julien diuse la cl Sj. Toute personne souhaitant envoyer
un message M Mathieu dune manire confidentielle peut utiliser la cl Sm pour lencrypter. Si
Mathieu a pris le soin de garder prive la cl Pm, il est alors le seul pouvoir dcrypter Sm(M) en
calculant Pm(Sm(M)). Tout ceci est rsum dans la figure ci-dessous
Rseau peu sr, seuls les
messages encrypts y circulent
Mathieu
Connat le couple
de cls (Sm,Pm) ainsi
que la cl Sj

Sm(M1)
Sj(M2)

Julien
Connat le couple
de cls (Sj,Pj) ainsi
que la cl Sm

Un tiers
Connat les cles Sj et Sm. Ne peut retrouver un message partir de
sa version encrypte car il ne possde ni la cl Pj, ni la cl Pm

Figure 6 -7 : Echange de messages crypts avec un algorithme symtrique


On dit que S est la cl publique ou partage (S pour Shared en anglais). On dit que P est la cl prive
(P pour Private en anglais). On nomme parfois les algorithmes asymtriques les algorithmes cls
publiques/cls prives.
Un problme dauthentification subsiste nanmoins. Puisque la cl Sm est connu de tous un tiers
peut parfaitement envoyer un message crypt Sm(M) Mathieu en se faisant passer pour Julien.
Ce problme peut tre contourn en utilisant lastuce suivante : Julien peut envoyer le message
crypt Sm(Pj(M)) Mathieu. Mathieu peut alors dcrypter ce message puisquil dtient les cl
Pm et Sj et que Sj(Pm( Sm(Pj(M)) )) = Sj(Pj(M)) = M. En outre Mathieu peut tre certains
que Julien est lenvoyeur puisque seul Julien connat la cl Pj.

Notion de session scurise


Un autre problme subsiste : le cot des calculs des algorithmes asymtriques connus est environs 1000 fois plus lev que le cot des calculs des algorithmes symtriques connus. En pratique, les protocoles scuriss dchange de messages utilisent un algorithme asymtrique pour
changer une paire de cl dun algorithme symtrique puis utilise lalgorithme symtrique pour
crypter les messages. La paire de cl de lalgorithme symtrique nest alors valable que pour une
session dchange de messages. On parle alors de cl de session et de session scurise.

.NET et les algorithmes asymtriques de cryptographie (cl publique/cl prive)

223

Lalgorithme RSA
Lalgorithme RSA cr en 1977, est lheure actuelle lalgorithme asymtrique le plus utilis. La
protection des cartes bancaires ou des messages militaires lutilise. La plateforme .NET lutilise
aussi. Le nom de RSA provient du nom de ses inventeurs, R.L. Rivest, A. Shamir et L.M Adelman.
Lalgorithme RSA est bas sur une proprit des grands nombres premiers. Soit deux grands
nombres premiers A et B. Il est trs facile de calculer le produit de A et de B. En revanche, lorsque
lon ne connat que le produit AB, il est trs dicile de calculer les nombres A et B. Sans rentrer
dans les dtails de lalgorithme RSA, vous pouvez considrer que la paire de nombre (A,B) dfinit la cl prive tandis que le produit de A et de B dfinit la cl publique.
Tant que lon ne saura obtenir une paire de nombres premiers A et B partir de leur produit
quen temps polynomial, lalgorithme RSA restera extrmement fiable. En fait, on ne sait mme
pas prouver quil existe un algorithme en un temps meilleur que le temps polynomial. La plupart des mathmaticiens contemporains supposent que ce problme restera inaccessible pendant encore de nombreuses dcennies. Nanmoins, lhistoire a montr quil est quasiment impossible destimer quand un problme mathmatique sera rsolu.
Sachez enfin que lon utilise des algorithmes statistiques ecaces pour le calcul de grands
nombres premiers. Ce type dalgorithme permet de dterminer si un grand nombre est premier
un degr de certitude qui peut tre aussi grand que lon veut sans jamais tre gal 100%.

Algorithme asymtriques et signature numrique


En plus de la possibilit de crypter des donnes, les proprits des algorithmes asymtriques
peuvent tre utilises pour signer numriquement des donnes. Signer numriquement des
donnes signifie quun consommateur de donnes peut tre absolument certains que celui qui
a produit des donnes dtient une certaine cl prive. On parle dauthentification de donnes
si le consommateur peut en plus tre certains que seul celui qui a produit la donne dtient la
cl prive.
Pour comprendre ce qui va tre expos il est ncessaire de comprendre ce quest une valeur
de hachage. Une valeur de hachage est un nombre calcul partir dun ensemble de donnes.
Ce calcul a la particularit de fournir deux nombres dirents pour deux ensembles de donnes dirents, de manire quasiment sre. Le framework .NET prsente dans lespace de noms
System.Security.Cryptography les implmentations des principaux algorithmes de hachage.
On peut citer les algorithmes SHAx, RIPEMD160 ou MD5.
Supposons que Mathieu veuille convaincre Julien quil est lauteur du fichier FOO. Il lui faut
dabord calculer une valeur de hachage x du fichier. Ensuite Mathieu calcule Pm(x) partir de
sa cl prive. Enfin Mathieu intgre la valeur Pm(x) et sa cl publique Sm dans le fichier FOO (par
exemple au dbut du fichier).
Julien connat au pralable la cl publique Sm de Mathieu. Julien rcupre le fichier FOO. Il extrait Pm(x) et la cl publique du fichier. Il vrifie que cest bien la cl publique de Mathieu. Il
peut donc calculer x de deux manires direntes :

En calculant la valeur de hachage du fichier FOO (auquel il a t Pm(x) et la cl publique).

En calculant x partir de Pm(x) et de la cl publique Sm car Sm( Pm(x) )=x.

Si par ces deux calculs Julien obtient la mme valeur, il peut tre sr que lauteur de FOO dtient
la cl prive Pm. On dit que Pm(x) constitue une signature numrique du fichier FOO. Si Mathieu

224

Chapitre 6 : La gestion de la scurit

a pu convaincre Julien quil est le seul dtenteur de la cl prive Pm, Julien peut tre sr que
lauteur de FOO est Mathieu.
Une faille dans cet algorithme existe nanmoins. Nous avons dit que Le calcul de la valeur de
hachage la particularit de fournir de manire quasiment sre deux nombres dirents pour deux
fichiers dirents . Si un tiers parvient trouver une squence doctets qui produit la mme
valeur de hachage, il peut utiliser la signature numrique prcdente dans un fichier contenant
cette squence doctets. Julien naura aucun moyen de savoir que ce fichier na pas t produit
par un dtenteur de la cl prive Pm. Cependant la taille des valeurs de hachage est de lordre
de 20 octets. Il y a donc moins dune chance sur 10 puissance 48 pour quune squence doctets
prise au hasard fournisse une valeur de hachage donne.
En page 28 nous expliquons comment la plateforme .NET permet de signer numriquement les
assemblages. En page 230 nous verrons que cette technique est utilise dans les environnements
Windows depuis bien avant .NET pour authentifier des fichiers. Nous prsenterons alors une
technologie permettant de pouvoir convaincre quon est le seul dtenteur dune certaine cl
prive.

Le framework .NET et lalgorithme RSA


Le framework .NET prsente deux classes permettant dexploiter lalgorithme RSA : La classe
RSACryptoServiceProvider pour lencryption de donnes et la classe DSACryptoServiceProvider pour signer numriquement des donnes (DSA pour Digital Signature Algorithm).
Voici la hirarchie des classes :
System.Object
System.Security.Cryptography.AsymmetricAlgorithm
System.Security.Cryptography.DSA
System.Security.Cryptography.DSACryptoServiceProvider
System.Security.Cryptography.RSA
System.Security.Cryptography.RSACryptoServiceProvider
Lexemple suivant expose lutilisation de la classe RSACryptoServiceProvider pour crypter une
chane de caractres. La mthode ExportParameter(bool) permet de rcuprer la cl publique
ou la paire de cl publique/prive selon quelle est appele avec la valeur false ou true :
Exemple 6-22 :
using System.Security.Cryptography ;
class Program {
static void Main() {
string sMsg = "Le message `
a encrypter !" ;
string sEnc, sDec ;
System.Text.Encoding utf = new System.Text.UTF8Encoding() ;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters publicKey = rsa.ExportParameters(false);
RSAParameters publicAndPrivateKey = rsa.ExportParameters(true);
{
RSACryptoServiceProvider rsaEncryptor = new
RSACryptoServiceProvider();
rsaEncryptor.ImportParameters(publicKey);
byte[] bMsg = utf.GetBytes(sMsg) ;

LAPI de protection des donnes (Data Protection API)

225

byte[] bEnc = rsaEncryptor.Encrypt(bMsg, false);


sEnc = System.Convert.ToBase64String(bEnc) ;
}
{
RSACryptoServiceProvider rsaDecryptor = new
RSACryptoServiceProvider();
rsaDecryptor.ImportParameters(publicAndPrivateKey);
byte[] bEnc = System.Convert.FromBase64String(sEnc) ;
byte[] bDec = rsaDecryptor.Decrypt(bEnc, false);
sDec = utf.GetString(bDec) ;
}
System.Console.WriteLine("Message : " + sMsg) ;
System.Console.WriteLine("Encrypt
e : " + sEnc) ;
System.Console.WriteLine("Decrypt
e : " + sDec) ;
}
}
Cet exemple ache ceci :
Message : Le message `a encrypter!
Encrypte: WVswU5B1JMKdrGrESNngo+s7K/+kvz3o8UaxB5E0sjdejNDmjsuvGEKMP
P3q3OuRXB4k7B5yLwcnaJK2guVmK3ysN+OgmsheOX0UlqUBlzp2EzVsaqpzUQGHxe6k
toOBILR4PU1Jqyq1kESSTfMx9jfTDnMEJ3l1Op+wpQX5DFMs=
Decrypte: Le message `a encrypter!

La taille des cls


Traditionnellement, la taille des cls des algorithmes symtriques est de 40, 56 et 128 bits. La
taille des cls pour les algorithmes asymtriques a tendance tre plus leve. Pour vous donner
une ide, une cl de 40 bits ne rsiste que quelques minutes une attaque dtermine tandis
qu ma connaissance, aucune cl de 128 bits na encore t craque . Pour chaque implmentation du framework dun algorithme vous pouvez obtenir la taille des cls utilises. Certaines
implmentations vous permettent de fixer cette taille.
Bien entendu plus une cl contient de bits plus elle est sre. Lgalement, vous ne pouvez pas
utiliser nimporte quelle taille. Aussi, les implmentations des algorithmes dencryptions fournissent la proprit int[] LegalKeySizes[]{get;}.

LAPI de protection des donnes (Data Protection API)


LAPI de protection de donnes de Windows
Depuis Windows 2000, les systmes dexploitation Windows prsentent une API de cryptographie nomme DPAPI (Data Protection API). Cette API est implmente dans la DLL systme
crypt32.dll. Elle a ceci de particulier quelle se base sur les crdits accords au couple login/mot
de passe de lutilisateur courant pour grer les cls. Elle peut aussi se baser sur lidentit dun
processus, lidentit dune session Windows ou lidentit de la machine courante. En eet, bien
souvent nous souhaitons crypter des donnes pour garantir leur confidentialit au niveau dun
utilisateur, dun processus, dune session ou dune machine. Dans ces cas, lutilisation de DPAPI
nous vite davoir grer des cls.

226

Chapitre 6 : La gestion de la scurit

Cette API est capable de grer les modifications de mots de passe. Autrement dit, si vous stockez
des donnes en les encryptant pour un utilisateur donn, vous serez capable de les exploiter
mme lorsque le mot de passe de cet utilisateur aura t modifi. Ceci est possible grce un
systme de stockage des cls expires. Plus de dtails ce sujet sont disponibles dans larticle
Windows Data Protection des MSDN.

La classe System.Security.Cryptography.ProtectedData
Lexemple suivant montre comment utiliser la classe System.Security.Cryptography.ProtectedData pour protger des donnes au niveau dun utilisateur. Nous aurions pu utiliser la valeur DataProtectionScope.LocalMachine pour protger ces donnes au niveau de la machine
courante. Dans cet exemple, nous exploitons loption dajouter de lentropie lencryption. Cela
signifie quun processus sexcutant dans le contexte adquat (i.e sous le bon utilisateur ou sur la
bonne machine) naura pas la possibilit de dcrypter les donnes sil ne connat pas lentropie
utilise pour les encrypter. Vous pouvez donc considrer lentropie comme une sorte de cl
secondaire :
Exemple 6-23 :
using System.Security.Cryptography ;
class Program{
static void Main() {
string sMsg = "Le message `
a encrypter !" ;
string sEnc, sDec ;
System.Text.Encoding utf = new System.Text.UTF8Encoding() ;
byte[] entropy = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
{
byte[] bMsg = utf.GetBytes(sMsg) ;
byte[] bEnc = ProtectedData.Protect(
bMsg , entropy , DataProtectionScope.CurrentUser);
sEnc = System.Convert.ToBase64String(bEnc) ;
}
{
byte[] bEnc = System.Convert.FromBase64String(sEnc) ;
byte[] bDec = ProtectedData.Unprotect(
bEnc, entropy, DataProtectionScope.CurrentUser);
sDec = utf.GetString(bDec) ;
}
System.Console.WriteLine("Message : " + sMsg) ;
System.Console.WriteLine("Encrypt
e : " + sEnc) ;
System.Console.WriteLine("Decrypt
e : " + sDec) ;
}
}
Cet exemple ache ceci :
Message : Le message `a encrypter!
Encrypte: AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAA3uy2/pifMEGRALpANT44y
QAAAAACAAAAAAADZgAAqAAAABAAAAA4UMUfUFqt5Xrz5U6hAVJ1AAAAAASAAACg
AAAAEAAAAHMxZCILz7K6JJc4Mmd/4P8YAAAAiiE+UBJdtaReGyP9vMsmw6HsqHM

LAPI de protection des donnes (Data Protection API)

227

LksdXFAAAAMRqfALSa8aaMm1mkestfBudeX91
Decrypte: Le message `a encrypter!

La classe System.Security.Cryptography.ProtectedMemory
La classe System.Security.Cryptography.ProtectedMemory permet de protger des donnes
au niveau de portes plus fines que celles prsentes par la classe ProtectedData. Les options
prsentes par lnumration MemoryProtectionScope sont les suivantes :

SameProcess : Spcifie que seul du code invoqu dans le mme processus que celui qui a
encrypt les donnes sera mme de les dcrypter.
SameLogon : Spcifie que seul du code invoqu dans un processus dans le mme contexte
utilisateur que celui qui a encrypt les donnes sera mme de les dcrypter. Cela implique
notamment quil faut que les oprations dencryptions et de dcryptions dune mme donne aient lieux durant la mme session Windows.
CrossProcess : Spcifie que les donnes peuvent tre dcryptes par nimporte quel code
excut dans nimporte quel processus la condition que le systme dexploitation nait pas
t redmarr entre lopration dencryption et lopration de dcryption.

Lexemple suivant illustre lutilisation de cette classe. Il faut que les donnes encrypter soient
stockes sur un tableau doctets dune taille multiple de 16 :
Exemple 6-24 :
using System.Security.Cryptography ;
class Program {
static void Main() {
string sMsg = "01234567890123456789012345678901" ;
System.Text.Encoding utf = new System.Text.UTF8Encoding() ;
System.Console.WriteLine("Message : " + sMsg) ;
byte[] bMsg = utf.GetBytes(sMsg) ;
ProtectedMemory.Protect(bMsg,
MemoryProtectionScope.SameProcess);
System.Console.WriteLine("Encrypt
e : " + utf.GetString(bMsg)) ;
ProtectedMemory.Unprotect(bMsg,
MemoryProtectionScope.SameProcess);
System.Console.WriteLine("Decrypt
e : " + utf.GetString(bMsg)) ;
}
}
Cet exemple ache ceci :
Message : 01234567890123456789012345678901
Encrypte : m;SH<^"?vfn6b{m.Op%L
Decrypte : 01234567890123456789012345678901

La classe System.Security.SecureString
La manipulation des chanes de caractres avec la classe String prsente plusieurs vulnrabilits
si lon considre quun individu mal intentionn peut avoir accs aux pages mmoires dun
processus Windows :

228

Chapitre 6 : La gestion de la scurit


Une mme chane de caractres peut tre duplique dans la mmoire du processus du fait
que le ramasse miettes se rserve le droit de bouger les instances de types rfrence.
Les chanes de caractres sont stockes en mmoire sans aucune sorte dencryptions.
Le fait que les chanes de caractres soient immuables implique qu chaque modification
dune chane, son ancienne version rside en mmoire pendant une dure que lon ne matrise pas.
Une autre consquence du caractre immuable des chanes de caractres est que vous navez
pas la possibilit de nettoyer les octets utiliss pour la stocker lorsque vous nen avez plus
besoin ( moins dutiliser la technique dcrite en page 508).

Pour pallier cette vulnrabilit, le framework .NET prsente la classe System.Security.SecureString. Cette classe dont limplmentation se base sur les services DPAPI, permet de stocker en
mmoire une chane de caractre sous une forme encrypte.
Vous pouvez initialiser une instance de SecureString soit partir dun tableau doctets (que
vous pouvez par la suite mettre zro) soit en la construisant caractre aprs caractre. La classe
Marshal prsente plusieurs mthodes pour rcuprer une chane de caractres encrypte dans
une instance de SecureString.
Lexemple suivant montre comment utiliser une instance de SecureString pour stocker un mot
de passe saisi par un utilisateur sur la console. Nous achons ensuite cette chane de caractres
sur la console. Pour les besoins de lexemple, nous convertissons linstance de BSTR contenant
la chane de caractre dcrypte en une instance de String. En pratique, il faut viter cette manipulation. Fatalement, pour exploiter une chane de caractres scurise il faut un moment
ou un autre la dcrypter en mmoire. Il faut alors la stocker dans une zone mmoire que
lon matrise pour pouvoir la dtruire ds que possible et pour viter les problmes poss par
la classe String vus prcdemment :
Exemple 6-25 :
using System ;
using System.Runtime.InteropServices ;
class Program {
static void Main() {
System.Security.SecureString pwd = new
System.Security.SecureString();
ConsoleKeyInfo nextKey = Console.ReadKey(true) ;
while(nextKey.Key != ConsoleKey.Enter){
pwd.AppendChar(nextKey.KeyChar);
Console.Write("*") ;
nextKey = Console.ReadKey(true) ;
}
Console.WriteLine() ;
pwd.MakeReadOnly();
IntPtr bstr = Marshal.SecureStringToBSTR(pwd) ;
// Recup`ere une instance de la classe String
// pour les besoins de lexemple.
try{
Console.WriteLine( Marshal.PtrToStringAuto(bstr) ) ; }
finally{ Marshal.ZeroFreeBSTR(bstr) ; }
}
}

LAPI de protection des donnes (Data Protection API)

229

Protection des donnes dans vos configurations


La classe System.Configuration.Configuration permet de stocker des donnes encryptes
dans le fichier de configuration de lapplication. Lexemple suivant montre comment stocker
une version encrypte de la chane de caractres MonPassword dans un paramtre de configuration nomm TagPassword. Notez que seule la sauvegarde ncessite une manipulation spciale
pour prciser que lon souhaite stocker une version encrypte. La dcryption de la donne est
eectue dune manire transparente lors de son chargement :
Exemple 6-26 :
using System.Configuration ;
class Program {
static void Main() {
SavePwd("MonPassword") ;
System.Console.WriteLine(LoadPwd()) ;
}
static void SavePwd(string pwd) {
Configuration cfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
cfg.AppSettings.Settings.Add("TagPassword", pwd) ;
cfg.AppSettings.SectionInformation.ProtectSection(
"RsaProtectedConfigurationProvider" );
cfg.Save() ;
}
static string LoadPwd() {
Configuration cfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
return cfg.AppSettings.Settings["TagPassword"].Value ;
}
}
Voici le fichier de configuration gnr par lexemple prcdent. On voit quil contient une
section protectedData qui dclare que la section appSettings est encrypte et une section
appSetings qui contient les donnes encryptes :
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<protectedData>
<protectedDataSections>
<add name="appSettings"
provider="RsaProtectedConfigurationProvider"
inheritedByChildren="false" />
</protectedDataSections>
</protectedData>
<appSettings>
<EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">

230

Chapitre 6 : La gestion de la scurit


...
<CipherData>
<KeyName>Rsa Key</KeyName>
<CipherValue>EuZpuz7Rj(...)qalkG3VbQ=</CipherValue>
</CipherData>
...
</KeyInfo>
<CipherData>
<CipherValue>50cJLWVB(...)JHsGvLTY</CipherValue>
</CipherData>
</EncryptedData>
</appSettings>
</configuration>

Protection des donnes changes par lintermdiaire dun rseau


Dans le chapitre consacr au flot de donnes, nous prsentons en page 660 les dirents protocoles dchange scuris de donnes par lintermdiaire dun rseau.

Authentifier vos assemblages avec la technologie


Authenticode et les certificats X.509
Authenticode vs. Nom fort
Bien avant la distribution publique de la plateforme .NET, les systmes Windows exploitaient la
technologie connue sous le nom dAuthenticode pour authentifier les fichier excutables. Cette
technologie sapparente la technologie du nom fort qui fait lobjet de la section page 28 dans le
sens o elle permet de signer numriquement un assemblage. Cependant ces deux technologies
ont des buts dirents :

La technologie Authenticode doit tre utilise pour identifier lauteur ou lentreprise qui
a fabriqu le fichier excutable. Elle permet dexcuter un programme sans crainte dune
ventuelle falsification.
La technologie du nom fort doit tre utilise pour identifier un assemblage (i.e pour nommer dune manire unique chaque assemblage et mme, chaque version dun mme assemblage). Elle permet notamment le stockage dassemblages cte cte.

En outre ces deux technologies prsentent des dirences notables :

La technologie Authenticode permet de signer toutes sortes de fichiers excutables, les assemblages .NET comme les fichiers excutable Windows contenant du code natif. La technologie du nom fort ne se cantonne quant elle quaux assemblages.
La vrification de la validit dun fichier excutable par la technologie Authenticode nest
eectue par Windows quune seule fois, lorsque lon tlcharge le fichier ou lorsquon linstalle (par exemple partir dun CD). La vrification du nom fort est eectue par le CLR
chaque chargement dun assemblage sign.

Cependant, la dirence principale entre ces deux technologies rside dans le concept de certificat.

Authentifier vos assemblages avec la technologie Authenticode

231

Les certificats et les Certificate Authorities


Un certificat contient une cl publique et des informations sur un diteur. Il permet de garantir que la cl publique a bien t fabrique par lditeur rfrenc. Un certificat contient aussi
dautres informations telles que la date limite de validit. La technologie du nom fort ne prsente pas de certificat. Bien que certaines cls publiques soient bien connues et permettent donc
didentifier un diteur (comme la cl publique de Microsoft par exemple) il ny a pas de moyens
standard pour obtenir la certitude quune cl publique utilise dans la technologie du nom fort
a t fabrique par un diteur lambda.
Plusieurs standards de certificats existent. La technologie Authenticode utilise le standard X.509
qui stocke les certificats dans des fichiers dextension .cer. Le standard CMS/Pkcs7 qui stocke
les certificats dans des fichiers dextension .p7b est aussi trs rpandu car il est utilis par le
protocole Secure/Multipurpose Internet Mail Extensions (S/MIME) pour signer et crypter les mails.
Un fichier sign avec la technologie Authenticode contient un certificat X.509. Windows peut
alors demander une autorit extrieure si le certificat est valide. Ces autorits extrieures sont
nommes Certificate Authorities (CA) ou parfois aussi tiers de confiance. Ce sont des entreprises
dont le but est de valider ou non une certification. On compte parmi les CA les plus connus les
organismes VeriSign ou EnTrust. Concrtement, un diteur de logiciel paie un CA pour que ce
dernier valide son certificat auprs de ses clients. Le CA aura au pralable eectu un ensemble
de vrification sur le srieux de lditeur de logiciel.

Les certificats racines


En pratique, il existe un mcanisme permettant Windows de ne pas contacter systmatiquement un CA lors de la vrification dun certificat. En eet, chaque systme Windows maintient une liste de certificats (nomme Certificate Store en anglais). Les certificats contenus dans
cette liste sont nomms les certificats racines. La liste des certificats racines prsente par dfaut
par Windows est disponible dans larticle Microsoft Root Certificate Program Members des
MSDN.
Vous pouvez visualiser ajouter ou retirer des certificats dans cette liste avec loutil certmgr.exe.
Lorsquun diteur de logiciel demande un CA de publier son certificat, ce dernier signe le
certificat de lditeur avec son propre certificat. Si le certificat du CA est un certificat racine,
Windows na pas besoin deectuer un accs rseau pour demander au CA de valider le certificat de lditeur puisquil a mathmatiquement tous les lments ncessaires pour eectuer la
vrification lui mme.
On peut imaginer que le CA contact par lditeur est de moindre importance. Dans ce cas, le
certificat de ce CA nest probablement pas un certificat racine des systmes Windows. Cependant,
rien nempche ce CA de moindre importance de demander un CA bien connu de lui signer
son certificat. On voit donc samorcer une chane de certifications qui en pratique, vite aux
centaines de millions de machines utilisant Windows de contacter les CA lors de linstallation
de logiciels.

Les implications de la technologie Authenticode


Lorsque Windows installe ou tlcharge un logiciel qui na pas de certificat ou dont le certificat ne
peut tre vrifi, il ache une fentre de dialogue vous demandant si vous autorisez lexcution
du programme.

232

Chapitre 6 : La gestion de la scurit

Le mcanisme CAS est capable dobtenir une preuve partir dun assemblage sign avec un
certificat X.509 (voir page 189). Vous pouvez ainsi dcider que les assemblages signs avec un
certificat peuvent sexcuter avec plus (ou moins) de permissions que les autres.
Les dtails de la cration dun certificat, de la signature dun excutable par un certificat et de
la vrification dun certificat dpassent le cadre du prsent ouvrage. Tout ceci est dcrit dans
larticle Signing and Checking Code with Authenticode des MSDN.
En outre, sachez que le framework .NET prsente les espaces de noms System.Security.
Cryptography.X509Certificates et System.Security.Cryptography.Pkcs qui contiennent
des types spcialiss dans la manipulation de ces standards ainsi que dans la manipulation des
listes de certificats.

7
Rflexion, liens tardifs,
attributs

Nous avons parl page 17 des mtadonnes et de la faon dont elles sont physiquement stockes
dans les assemblages. Nous allons voir dans ce chapitre quelles constituent la base des mcanismes de rflexion et dattributs.

Le mcanisme de rflexion
Le mcanisme de rflexion dnomme lutilisation durant lexcution, des mtadonnes de type
dun assemblage. En gnral cet assemblage est charg explicitement lors de lexcution dun
autre assemblage mais il peut tre aussi construit puis charg dynamiquement.
Le mot rflexion est utilis pour montrer que lon utilise limage dun assemblage (comme une
image dans un miroir). Cette image est constitue par les mtadonnes de type de lassemblage.

Quand utilise-t-on le mcanisme de rflexion ?


Nous avons recens quelques catgories dutilisation du mcanisme de rflexion. Elles font lobjet des prochaines sections du prsent chapitre. Le mcanisme de rflexion peut tre utilis dans
les cas suivants :

Lors de la dcouverte des types dun assemblage lexcution par lanalyse dynamique de
ses mtadonnes de type. Par exemple, les outils ildasm.exe ou Reflector chargent explicitement un module dun assemblage et analyse son contenu (voir page 21).

Lors de lutilisation de liens tardifs. Cette technique consiste utiliser une classe situe dans
un assemblage qui nest pas connu la compilation. La technique du lien tardif est particulirement utilise par les langages interprts comme les langages de script.

Lorsque lon souhaite exploiter les informations contenues dans les attributs.

234

Chapitre 7 : Rflexion, liens tardifs, attributs

Lorsque lon souhaite accder aux membres non publics dune classe partir de lextrieur
de la classe. Bien videmment cette pratique est viter, mais il est ncessaire parfois dy
avoir recours, par exemple pour faire des tests unitaires qui ne peuvent se satisfaire des seuls
membres non publics.

Lors de la construction dynamique dun assemblage. Pour utiliser les classes dun assemblage construit dynamiquement on utilise la technique du lien tardif explicite.

Le mcanisme de rflexion est utilis par le CLR et le framework dans de multiples cas. Par
exemple, limplmentation par dfaut de la mthode Equals() sur un type valeur utilise la
rflexion pour comparer deux instances champs champs.
La rflexion est galement utilise par le CLR lors de la srialisation dun objet afin de connatre
les champs srialiser, ou encore par le ramasse-miettes, qui sen sert pour construire larbre de
rfrencement lors dune collecte dobjets.

Quest ce que le mcanisme de rflexion apporte de nouveau ?


Lide sous-jacente du mcanisme de rflexion nest pas nouvelle. Cela fait longtemps que lon
peut analyser dynamiquement le contenu dun excutable, notamment grce des informations dauto description. Le format TLB (prsent page 279) a t conu cet eet. Prcisons que
les donns au format TLB sont produites partir de donnes au format IDL (Interface Definition Language). Le langage IDL peut donc aussi tre vu comme un format dauto description.
Le mcanisme de rflexion propos par .NET va beaucoup plus loin que le format TLB et le
format IDL :

Il est trs simple utiliser grce certaines classes de base.

Il est plus abstrait que le format TLB et le langage IDL. Par exemple il nutilise pas dadresses
physiques. Ainsi il peut tre utilis la fois sur des machines 32 et 64 bits.

Contrairement aux mtadonnes du format TLB, les mtadonnes .NET sont toujours physiquement contenues dans le module quelles dcrivent.

Le niveau de dtail de description des donnes est beaucoup plus pouss que dans le format
TLB. Concrtement on peut trs simplement avoir toutes les informations possibles sur
nimporte quel lment dclar dans un assemblage (par exemple le type dun argument
dune mthode dune classe).

Le niveau de dtail sans prcdent de la rflexion .NET est d de nombreuses classes de base
du Framework .NET qui permettent dextraire et dutiliser les mtadonnes de type dun assemblage contenu dans un domaine dapplication. La plupart de ses classes se trouvent dans lespace
de noms System.Reflection. Il y a une classe pour chaque type dlment dun assemblage :

Il y a une classe dont les instances reprsentent des assemblages


(System.Reflection.Assembly) ;

une classe dont les instances reprsentent des classes (System.Type) ;

une classe dont les instances reprsentent les mthodes des classes
(System.Reflection.MethodInfo) ;

une classe dont les instances reprsentent les champs des classes
(System.Reflection.FieldInfo) ;

Le mcanisme de rflexion

235

une classe dont les instances reprsentent les paramtres des mthodes
(System.Reflection.ParameterInfo).

etc.

Finalement ces classes ne reprsentent rien dautre quun moyen de visualiser logiquement lensemble des mtadonnes de type. La visualisation nest pas physique dans le sens o certains lments utiliss pour lorganisation interne dun assemblage (comme les jetons de mtadonnes)
ne sont pas reprsents.
Toutes les classes de lespace de noms System.Reflection sutilisent dune manire trs logique. Par exemple, partir dune instance de System.Reflection.Assembly, on peut obtenir un tableau dinstances de System.Type. partir dune instance de System.Type on
peut obtenir un tableau dinstances de System.Reflection.MethodInfo. partir dune instance de System.Reflection.MethodInfo, on peut obtenir un tableau dinstances de System.
Reflection.ParameterInfo. Tout ceci est illustr par la Figure 7-1.
Byte[]
GetILAsByteArray()

GetModules()

MethodBody

GetMethodBody()

Module

ConstructorInfo

GetTypes()

GetTypes()

GetConstructor()

GetParameters()

ParameterInfo
GetParameters()

GetMethodBody()

Assembly

MethodInfo
GetMethods()

Type
GetEvents()

GetProperties()

GetFields()

FieldInfo

EventInfo

PropertyInfo

ReflectedType

Figure 7 -1 : Classes utilises par le mcanisme de rflexion


On peut regretter le fait que lon ne puisse pas descendre au niveau de linstruction IL mais
seulement au niveau de dun tableau doctets reprsentant le corps dune mthode en IL. Pour
cela, vous devez avoir recours certaines bibliothques telles que Cecil (dveloppe par JeanBaptiste Evain), ILReader (dveloppe par Lutz Roeder) ou Rail (dveloppe par luniversit de
Coimbra). Sachez cependant quen .NET 2005 la rflexion sait traiter les types gnriques, les
mthodes gnriques ainsi que les contraintes (voir page 498).

Analyser les assemblages contenus dans un domaine dapplication


Voici un exemple qui expose lanalyse des mtadonnes de type avec les classes de lespace de
noms System.Reflection. En fait, nous prsentons ici un assemblage qui analyse ses propres
mtadonnes de type. Pour obtenir une instance de la classe Assembly reprsentant cet assemblage, nous avons recours la mthode statique Assembly.GetExecutingAssembly() :

236

Chapitre 7 : Rflexion, liens tardifs, attributs

Exemple 7-1 :
using System ;
using System.Reflection ;
class Program{
public static void Main(){
Assembly assembly = Assembly.GetExecutingAssembly() ;
foreach (Type type in assembly.GetTypes()){
Console.WriteLine("Classe : " + type) ;
foreach (MethodInfo method in type.GetMethods()){
Console.WriteLine(" M
ethode : " + method) ;
foreach (ParameterInfo param in method.GetParameters())
Console.WriteLine("
Param : " +
param.GetType()) ;
}
}
}
}
Voici lachage de ce programme :
Classe : Program
Methode : Void Main()
Methode : System.Type GetType()
Methode : System.String ToString()
Methode : Boolean Equals(System.Object)
Param : System.Reflection.ParameterInfo
Methode : Int32 GetHashCode()

Extraire des informations partir des mtadonnes


Lobjet de la prsente section est de prsenter un petit programme qui exploite le mcanisme
de rflexion pour acher lensemble des classes dexceptions contenues dans les assemblages
System et mscorlib. Nous nous sommes bass sur le fait que toutes les classes dexception drivent de la classe System.Exception. Les types dexceptions qui ne drivent pas directement de
la classe System.Exception sont marqus avec une apostrophe.
On pourrait simplement modifier ce programme pour acher lensemble des classes dattribut du Framework en se basant sur le fait que toutes ces classes drivent de la classe System.
Attribute.
Exemple 7-2 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
// Fabrique le nom fort de lassemblage System.
// Le numero de version est le m
eme pour tous les asm syst`
emes.
// Cest celui de mscorlib qui contient System.Object.
string systemAsmStrongName = "System, Culture = neutral, " +

Le mcanisme de rflexion

237

"PublicKeyToken=b77a5c561934e089, Version=" +
typeof(System.Object).Assembly.GetName().Version.ToString() ;
// Charge explicitement lassemblage System.
// Nul besoin de charger lassemblage mscorlib car il est
// automatiquement et implicitement charg
e par le CLR.
Assembly.ReflectionOnlyLoad(systemAsmStrongName) ;
// Pour chaque assemblage du domaine dapplication courant...
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()){
Console.WriteLine("\nAssemblage:" + a.GetName().Name) ;
// Pour chaque type de cet assemblage...
foreach (Type t in a.GetTypes()){
// Seules les classes publiques sont retenues...
if (!t.IsClass || !t.IsPublic) continue ;
bool bDeriveDException = false ;
bool bDeriveDirectement = true ;
// System.Exception est-il un type de base de ce type ?
Type baseType = t.BaseType ;
while (baseType != null && !bDeriveDException){
// Pour trouver les classes dattributs remplacer cette
// ligne par if( baseType == typeof(System.Attribute))
if (baseType == typeof(System.Exception))
bDeriveDException = true ;
else bDeriveDirectement = false ;
baseType = baseType.BaseType ;
}// end while
//
//
//
if

Affiche le nom de la classe si elle repr


esente un
attribut. Place une apostrophe si la classe ne
derive pas directement de System.Excception.
(bDeriveDException)
if (bDeriveDirectement)
Console.WriteLine(" " + t) ;
else
Console.WriteLine(" " + t) ;
}// end foreach(Type...
}// end foreach(Assembly...
}// end Main
}

Notez lutilisation de la mthode Assembly.ReflectionOnlyLoad() dans lexemple prcdent.


Cette mthode permet de spcifier au CLR que lassemblage charg ne sera utilis que par le
mcanisme de rflexion. En consquence, le CLR interdira lexcution du code dun assemblage
charg avec cette mthode. En outre, le chargement dun assemblage en mode Reflection Only
est sensiblement plus rapide car le CLR saranchit des vrifications relatives la scurit. La

238

Chapitre 7 : Rflexion, liens tardifs, attributs

classe Assembly prsente la proprit bool ReflectionOnly{get;} qui permet de savoir quun
assemblage a t charg par cette mthode.

Les liens tardifs


Avant daborder cette section, il est prfrable davoir de bonnes bases en programmation
objet, notamment en ce qui concerne le mcanisme de polymorphisme. Ce sujet est trait
dans le chapitre 12.

Que veut dire crer un lien avec une classe ?


Tout dabord, il faut sentendre sur ce que veut dire crer un lien avec une classe (to bind a
class en anglais). On va parler de couches logicielles plutt que dassemblages car ce qui va tre
dit est valable pour dautres technologies.
Un lien avec une classe est cr entre la couche logicielle utilisatrice de la classe (qui instancie
la classe puis utilise les instances) et la couche logicielle qui dfinit la classe. Concrtement, ce
lien est la correspondance entre les appels aux mthodes de la classe dans la couche logicielle
utilisatrice, et lemplacement physique des mthodes dans la couche logicielle qui dfinit la
classe. Cest ce lien qui permet au thread daller continuer sa course dans le code de la mthode
dune classe lorsque celle-ci est appele.
En gnral, on distingue trois types de liens avec une classe : les liens prcoces (early bind en
anglais) crs pendant la compilation, les liens dynamiques (dynamic bind en anglais) dont une
partie est cre la compilation et lautre lexcution, et les liens tardifs (late binding en anglais)
crs pendant lexcution.

Liens prcoces et liens dynamiques


Les liens prcoces sont crs par le compilateur, lors de la fabrication dun assemblage partir du
code source crit dans un langage .NET. On ne peut donc pas crer de liens prcoces avec une
mthode virtuelle ou abstraite. En eet, le mcanisme de polymorphisme dcide lexcution
quel est le corps dune mthode virtuelle ou abstraite appeler, partir de la classe relle de
lobjet sur lequel la mthode est appele. Dans ce cas, on qualifie le lien de lien dynamique. Dans
la premire dition du prsent ouvrage ainsi que dans dautres documents, les liens dynamiques
sont parfois nomms lien tardif implicite puisquils sont crs implicitement par le mcanisme
de polymorphisme et puisquils sont finaliss tardivement, durant lexcution.
Intressons-nous ici aux liens prcoces, ceux crs avec des mthodes statiques ou avec des mthodes dinstances ni virtuelles ni abstraites. Si lon sen tient la dfinition de crer un lien
avec une classe de la section prcdente, il nexiste pas de liens prcoces en .NET. En eet, il
faut attendre que le corps dune mthode soit JIT compile en langage machine avant de
connatre son emplacement physique dans lespace dadressage du processus. Cette information
demplacement physique du corps de la mthode ne peut donc pas tre connue du compilateur
qui cre lassemblage. Nous avons vu page 111 que pour rsoudre ce problme, le compilateur
qui cre lassemblage insre le jeton de mtadonnes correspondant la mthode appeler,
lendroit du code IL o lappel a lieu. Lorsque le corps de la mthode est JIT compile lexcution, le CLR stocke en interne la correspondance entre la mthode et lemplacement physique

Les liens tardifs

239

du corps de la mthode en langage machine. Cette information est physiquement stocke dans
une zone mmoire associe la mthode, appele stub.
Cette constatation est trs importante puisque dans un langage comme C++, lorsquune mthode nest ni virtuelle ni abstraite (i.e ni virtuelle pure en terminologie C++), le compilateur
calcule lemplacement physique du corps de la mthode en langage machine. Ensuite, le compilateur insre un pointeur vers cet emplacement chaque appel de la mthode concerne.
Cette dirence confre un avantage certains .NET, puisque les compilateurs nont plus se
soucier de dtails techniques tels que la reprsentation dune adresse mmoire. Le code IL est
totalement indpendant de la couche physique qui lexcute.
En ce qui concerne les liens dynamiques, presque tout se passe comme pour les liens prcoces. Le
compilateur insre le jeton de mtadonne correspondant la mthode virtuelle (ou abstraite)
appeler, lendroit du code IL o lappel a lieu. On parle ici du jeton de mtadonne de la
mthode dfinie dans le type de la rfrence sur laquelle lappel lieu. Cest ensuite au CLR
de dterminer lexcution vers quelle mthode se brancher en fonction de limplmentation
exacte de lobjet rfrenc. Cest le polymorphisme.
Cette technique dinsertion par le compilateur du jeton de mtadonne pour les liens prcoces
et dynamiques est utilise dans les trois cas suivant :

Lorsque le code contenu dans un module appelle une mthode dfinie dans le mme module.

Lorsque le code contenu dans un module appelle une mthode dfinie dans un autre module du mme assemblage.

Lorsque le code contenu dans un module dun assemblage appelle une mthode dfinie
dans un autre assemblage rfrenc la compilation. Si ce dernier assemblage nest pas dj
charg lors de lappel de la mthode, le CLR le charge implicitement.

Liens tardifs
En .NET, le code dun assemblage A peut instancier et utiliser lexcution un type dfini dans
un assemblage B non rfrenc la compilation de A. On qualifie ce type de lien lien tardif. Dans
la premire dition du prsent ouvrage ainsi que dans dautres documents, les liens tardifs sont
parfois nomms liens tardifs explicites. Le lien est tardif dans le sens o, comme nous allons le
voir, il est cr aprs la compilation, durant lexcution. Le lien est explicite dans le sens o le
nom de la mthode appeler doit tre prcis explicitement dans une chane de caractres.
Lide de lien tardif nest pas nouvelle dans le monde du dveloppement Microsoft. Tout le mcanisme dit dAutomation dans la technologie COM, qui utilise linterface IDispatch, nest rien
dautre quune usine gaz faite pour pouvoir crer des liens tardifs partir de langages de
scripts ou peu typs, comme VB. La notion de liens tardifs existe aussi en Java.
Le concept de lien tardif fait partie des ides que les dveloppeurs habitus au langage C++ ont
du mal assimiler. En eet, en C++ seul les liens prcoces et les liens dynamiques existent. Le
problme de comprhension vient du fait suivant : on comprend bien que les informations ncessaires pour crer un lien (i.e les jetons de mtadonnes) sont dans lassemblage B contenant la
classe appeler, mais on ne comprend pas pourquoi le dveloppeur ne profite pas de la capacit
du compilateur crer des liens prcoces et dynamiques en rfrenant B la compilation de
A. Il existe plusieurs raisons direntes :

240

Chapitre 7 : Rflexion, liens tardifs, attributs

La raison la plus courante est que pour certains langages il ny a pas de compilateur ! Dans
un langage type script, les instructions sont interprtes une une. Dans ce cas il ne peut y
avoir que des liens tardifs. Les liens tardifs permettent donc dutiliser des classes compiles et
contenues dans des assemblages, partir de langages interprts. Le fait que la technique des
liens tardifs soit trs facile utiliser en .NET, fait que lon peut facilement crer des langages
interprts propritaires (tel que le langage IronPython http://www.ironpython.com/).

On peut souhaiter utiliser la technique de liens tardifs partir de programmes crits en


langages compils, tels que C  . Lide est que lemploi dun lien tardif permet dintroduire
un degr de flexibilit dans larchitecture gnrale de votre application. Cette technique
est en fait un design pattern populaire nomm plugin que nous prsenterons la fin de la
prsente section.

Certaines applications ont pour vocation dappeler le code de nimporte quel assemblage
qui lui est propos. Lexemple typique est loutil open-source NUnit qui permet de tester le
code de nimporte quel assemblage en invoquant ses mthodes. Nous approfondissons ce
sujet lors de la construction dun attribut personnalis un peu plus loin dans ce chapitre.

On doit utiliser des liens tardifs entre le code dun assemblage A et les classes dun assemblage B si B nexiste pas lors de la compilation de A. Cette situation est dcrite un peu plus
loin dans ce chapitre o lon y parle de construction dynamique dassemblages.

Certains prconisent lutilisation de liens tardifs la place du polymorphisme. En eet, puisque


au moment de lappel seul le nom et la signature de la mthode sont pris en compte, le type de
lobjet sur lequel la mthode est appele importe peu. Il sut que limplmentation de lobjet
prsente une mthode avec ce nom et cette signature. Nous dconseillons nanmoins cette utilisation car elle est trs permissive et ne force pas les concepteurs de lapplication se focaliser
sur des interfaces abstraites.
part les cas qui viennent dtre exposs, vous naurez pas utiliser de liens tardifs explicites.
Ne les utiliser pas pour le plaisir davoir des liens tardifs explicites dans vos applications car :

Vous perdrez lnorme bienfait de la vrification syntaxique du compilateur.

Les performances des liens tardifs explicites sont beaucoup moins bonnes que celles des
liens prcoces et des liens tardifs implicites (mme si vous utilisez les optimisations prsentes un peu plus loin).

On ne peut crer un lien tardif avec une classe obfusque. En eet, lors de lobfuscation, le
nom de la classe est chang dans lassemblage qui la contient. Or, la technique du lien tardif
retrouve la classe avec laquelle le lien tardif doit tre cr grce son nom. Comme nous
allons le voir, ce nom est stock du cot du consommateur de la classe dans une chane de
caractres. Il y a donc incompatibilit entre obfuscation et lien tardif.

Cration dune instance dune classe non connue la compilation


Si une classe ou une structure nest pas connue la compilation, on ne peut linstancier avec
loprateur new. Le framework .NET prvoit donc des classes qui permettent dinstancier des types
non connus la compilation.

Prciser un type
Intressons-nous aux direntes techniques qui permettent de prciser un type :

Les liens tardifs

241

Certaines mthodes de certaines classes acceptent une chane de caractres qui contient le
nom complet du type (i.e avec son espace de noms).

Dautres acceptent une instance de la classe System.Type. Dans un domaine dapplication,


chaque instance de System.Type reprsente un type et il ne peut y avoir deux instances
reprsentant le mme type. Pour obtenir une instance de System.Type, il existe de nombreuses faons :

En C  on utilise souvent le mot-cl typeof() qui prend en paramtre un type, et retourne linstance de System.Type adquate.

Vous pouvez aussi utiliser une des surcharges de la mthode statique GetType() de la
classe System.Type.

Si un type est encapsul dans un autre type, vous pouvez utiliser une des surcharges des
mthodes non statiques GetNestedType() ou GetNestedTypes() de la classe System.
Type.

Vous pouvez aussi utiliser les mthodes non statiques GetType() GetTypes() ou GetExportedTypes() de la classe System.Reflection.Assembly.

Vous pouvez aussi utiliser les mthodes non statiques GetType() GetTypes() ou FindTypes() de la classe System.Relection.Module.

Supposons maintenant que le programme suivant soit compil dans un assemblage Foo.dll.
Nous allons exposer plusieurs manires permettant de crer une instance de la classe NMFoo.
Calc partir dassemblages qui ne rfrencent pas Foo.dll.
Exemple 7-3 :

Code de lassemblage Foo.dll

using System ;
namespace NMFoo {
public class Calc {
public Calc() {
Console.WriteLine("Calc.Constructeur appel
e") ;
}
public int Sum(int a, int b) {
Console.WriteLine("Methode Calc.Sum() appel
ee") ;
return a + b ;
}
}
}

Utilisation de la classe System.Activator


La classe System.Activator prsente les deux mthodes statiques CreateInstance() et CreateInstanceFrom() qui permettent de crer une instance dune classe non connue la compilation. Par exemple :
Exemple 7-4 :
using System ;
using System.Reflection ;
class Program {
static void Main() {

242

Chapitre 7 : Rflexion, liens tardifs, attributs


Assembly assembly = Assembly.Load("Foo.dll") ;
Type type = assembly.GetType("NMFoo.Calc") ;
object obj = Activator.CreateInstance(type);
// obj est une reference vers une instance de NMFoo.Calc.
}
}

Chacune de ces mthodes se dcline en de nombreuses versions surcharges (et parfois gnriques) avec les arguments suivants :

Une classe, sous forme dune chane de caractres ou dune instance de System.Type ;

ventuellement le nom de assemblage qui contient la classe ;

ventuellement une liste darguments pour le constructeur.

Si lassemblage qui contient la classe nest pas prsent dans le domaine dapplication, lappel CreateInstance() ou CreateInstanceFrom() dclenche le chargement de cet assemblage. Pour raliser ce chargement, une des mthodes System.AppDomain.Load() ou System.AppDomain.LoadFrom() est appele en interne, selon que lon appelle CreateInstance()
ou CreateInstanceFrom(). Un constructeur de la classe est choisi, partir des arguments passs.
Une instance de la classe ObjectHandle, renfermant un objet marshall par valeur est retourne.
Dans le chapitre 22 relatif .NET Remoting, nous prsentons une autre utilisation de ces
mthodes dans le cadre dapplications distribues.
Les surcharges de CreateInstance() o le type est prcis sous la forme dune instance de
System.Type, retournent directement une rfrence vers un objet.
La classe System.Activator prsente aussi la mthode CreateComInstanceFrom() utilise pour
crer des instances dobjets COM et la mthode GetObject() utilise pour crer des objets distants.

Utilisation de la classe System.AppDomain


La classe System.AppDomain prsente les quatre mthodes non statiques CreateInstance(),
CreateInstanceAndUnWrap(), CreateInstanceFrom() et CreateInstanceFromAndUnwrap() qui
permettent de crer une instance dune classe non connue la compilation. Par exemple :
Exemple 7-5 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc");
// obj est une reference vers une instance de NMFoo.Calc.
}
}
Ces mthodes sont similaires aux mthodes de la classe System.Activator, prsentes prcdemment. Cependant, elles permettent de choisir le domaine dapplication dans lequel lobjet
doit tre cr. De plus, les versions AndUnwarp() fournissent directement une rfrence vers
lobjet. Cette rfrence est obtenue partir de linstance de ObjectHandle.

Les liens tardifs

243

Utilisation de la classe System.Reflection.ConstructorInfo


Une instance de la classe System.Reflection.ConstructorInfo rfrence un constructeur. La
mthode Invoke() de cette classe construit en interne un lien tardif vers le constructeur, puis
linvoque au moyen de ce lien. Elle permet donc de crer une instance du type du constructeur.
Par exemple :
Exemple 7-6 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
Assembly assembly = Assembly.Load ("Foo.dll") ;
Type type = assembly.GetType("NMFoo.Calc") ;
ConstructorInfo constructorInfo =
type.GetConstructor( new Type[0] ) ;
object obj = constructorInfo.Invoke(new object[0]) ;
// obj est une reference vers une instance de NMFoo.Calc.
}
}

Utilisation de la classe System.Type


La mthode non statique InvokeMember() de la classe System.Type permet de crer une instance dune classe non connue la compilation, la condition quelle soit appele avec la valeur
CreateInstance de lnumration BindingFlags. Par exemple :
Exemple 7-7 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
Assembly assembly = Assembly.Load("Foo.dll") ;
Type type = assembly.GetType("NMFoo.Calc") ;
Object obj = type.InvokeMember(
null, // pas besoin de nom pour invoquer le constructeur
BindingFlags.CreateInstance,
null, // pas besoin de binder
null, // pas besoin dun objet cible car on le cr
ee
new Object[0]) ; // pas de param`
etre
// obj est une reference vers une instance de NMFoo.Calc.
}
}

Cas particuliers
Avec les mthodes prsentes, vous pouvez crer une instance de pratiquement nimporte
quelle classe ou structure. Deux cas particuliers sont noter :

244

Chapitre 7 : Rflexion, liens tardifs, attributs

Pour crer un tableau il faut appeler la mthode statique CreateInstance() de la classe


System.Array.

Pour crer un dlgu il faut appeler la mthode statique CreateDelegate() de la classe


System.Delegate.

Utiliser les liens tardifs avec le framework


Maintenant que nous savons crer des instances de types inconnus la compilation, intressonsnous la cration de liens tardifs vers les membres de ces types afin de pouvoir utiliser ces instances. L aussi, plusieurs faons existent.

La mthode Type.InvokeMember()
Revenons vers la mthode Type.InvokeMember() qui nous a servi prcdemment crer une
instance dun type inconnu la compilation en invoquant un de ces constructeurs. Cette mthode accomplit en interne trois tches :

Elle cherche un membre du type sur lequel elle est appele qui correspond aux informations quon lui passe.

Si le membre est trouv, elle cre un lien tardif.

Elle utilise le membre (invocation pour une mthode, cration dobjet puis invocation
pour un constructeur, obtention ou dfinition de la valeur pour un champ...).

Lexemple suivant montre comment invoquer la mthode Sum() sur notre instance de la classe
NMFoo.Calc (notez que lors de la phase de dboguage, le dbogueur est capable de continuer sa
course dans le corps de la mthode invoquer grce un lien tardif) :
Exemple 7-8 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc") ;
Type type = obj.GetType() ;
object[] parametres = new object[2] ;
parametres[0] = 7 ;
parametres[1] = 8 ;
int result = (int)type.InvokeMember(
"Sum",
// Nom de la m
ethode.
BindingFlags.InvokeMethod,
null,
// Pas besoin de binder.
obj,
// Lobjet cible.
parametres); // Les param`
etres.
// Ici, result vaut 15.
}
}
La surcharge la plus couramment utilise de la mthode Type.InvokeMember() est :

Les liens tardifs


public object InvokeMember(
string
name,
BindingFlags invokeAttr,
Binder
binder,
object
target,
object[]
args
) ;

245

//
//
//
//
//

Le nom du membre.
Quel membre doit
etre pris en compte.
Les r`
egles de recherche du membre.
Lobjet sur lequel invoquer le membre.
Les arguments de lappel.

Le paramtre invokeAttr est un indicateur binaire qui signale sur quel type de membre la recherche va se porter. Pour chercher sur les mthodes, nous utilisons lindicateur BindingFlags.
InvokeMethod. Les dirents indicateurs sont dcrits en dtail dans les MSDN, dans larticle
BindingFlags Enumeration.
Le paramtre binder indique un objet de type Binder qui va orienter InvokeMember() sur la
faon dont elle va eectuer ses recherches. La plupart du temps vous positionnerez ce paramtre
null pour indiquer que vous souhaitez utiliser la proprit System.Type.DefaultBinder. Un
objet de type Binder fournit ce genre dinformations :

Il indique quelles conversions de types sont acceptes ou non pour les arguments. Dans
lexemple prcdent, nous aurions pu fournir deux arguments de type double. Grce au
DefaultBinder, lappel de la mthode aurait march car il supporte la conversion du type
double vers le type int.

Il indique si nous utilisons les paramtres optionnels dans notre liste de paramtres.

Tout ceci (notamment la table de conversion de type) est dcrit en dtail dans les MSDN lentre Type.DefaultBinder Property. Vous pouvez crer vos propres objets Binder en drivant
de la classe Binder. Nanmoins une instance de DefaultBinder sut dans la plupart des cas et
cette possibilit est trs rarement utilise.
Si une exception est lance durant lappel du membre, InvokeMember() intercepte lexception
et relance une exception de type System.Reflection.TargetInvocationException. Naturellement lexception lance dans la mthode est rfrence par la proprit InnerException de lexception relance.
Notez enfin que lorsque vous crez un lien tardif, vous ne pouvez pas accder, a priori,
aux membres non publics. Lexception System.Security.SecurityException est alors, en
gnral, lance. Nanmoins si le bit TypeInformation de System.Security.Permissions.
ReflectionPermissionFlags (accessible par une instance de la classe System.Security.
Permissions.ReflectionPermission) est positionn, vous avez accs aux membres non publics.
Si le bit MemberAccess est positionn, vous avez accs aux types non visibles (i.e encapsuls dans
dautres types, dune manire non publique).

Lier une fois, invoquer N fois


De mme que nous avons vu quune instance de la classe ConstructorInfo permet de se lier
tardivement et dinvoquer un constructeur, une instance de la classe System.Reflection.
MethodInfo permet de se lier tardivement et dinvoquer nimporte quelle mthode. Lavantage dutiliser la classe MethodInfo plutt que la mthode Type.InvokeMember() rside dans
lconomie de la recherche du membre chaque invocation, do une lgre optimisation.
Lexemple suivant illustre tout ceci :

246

Chapitre 7 : Rflexion, liens tardifs, attributs

Exemple 7-9 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc") ;
Type type = obj.GetType() ;
// Creation du lien tardif.
MethodInfo methodInfo = type.GetMethod("Sum") ;
object[] parametres = new object[2] ;
parametres[0] = 7 ;
parametres[1] = 8 ;
int result ;
// 10 invocations de Sum au travers du lien tardif.
for (int i = 0 ; i < 10 ; i++)
result = (int)methodInfo.Invoke(obj, parametres) ;
}
}

Le compilateur VB.NET cre des liens tardifs


En eet, faisons une petite parenthse VB.NET et observons le fait que ce langage utilise en secret des liens tardifs lorsque loption Strict est positionne Off. Par exemple le programme
VB.NET suivant...
Exemple 7-10 :

VB.NET et les liens tardifs

Option Strict Off


Module Module1
Sub Main()
Dim obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc")
Dim result As Integer = obj.Sum(7, 8)
End Sub
End Module
...est quivalent au programme C  suivant :
Exemple 7-11 :
using System ;
using System.Reflection ;
using Microsoft.VisualBasic.CompilerServices;
class Program {
static void Main() {
object obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"Foo.dll", "NMFoo.Calc") ;
object[] parametres = new object[2] ;
parametres[0] = 7 ;
parametres[1] = 8 ;

Les liens tardifs

247

int result = (int)LateBinding.LateGet(obj, null, "Sum",


parametres, null, null) ;
}
}
Cette facilit proposer aux dveloppeurs VB.NET peut vite se rvler catastrophique la fois
pour les performances et si le programme est obfusqu. Do lintrt de positionner loption
Strict On.

La bonne faon dutiliser les liens tardifs avec des interfaces


Il existe une autre manire totalement dirente que celles que nous avons expos pour exploiter une classe ou une structure non connue la compilation avec des liens tardifs. Cette faon
prsente lnorme avantage de ne pas dgrader les performances par rapport lutilisation dun
lien prcoce ou dynamique. En revanche, vous devez vous contraindre une certaine discipline
pour appliquer cette recette (qui est en fait un design pattern connu sous le nom de plugin).
Lide est quil faut faire en sorte que le type inconnu la compilation implmente une interface
qui elle, est connue la compilation. Pour cela, nous sommes contraints de crer un troisime
assemblage spcialement pour hberger cette interface. Rcrivons notre exemple avec la classe
Calc au moyen de 3 assemblages :
Exemple 7-12 :

Code de lassemblage contenant linterface (InterfaceAsm.cs)

namespace NMFoo {
public interface ICalc {
int Sum(int a, int b) ;
}
}
Exemple 7-13 :

Code de lassemblage contenant la classe cible (ClassAsm.cs)

using System ;
namespace NMFoo {
public class CalcAvecInterface : ICalc {
public CalcAvecInterface() {
Console.WriteLine("Calc.Constructeur appel
e") ;
}
public int Sum(int a, int b) {
Console.WriteLine("Methode Calc.Sum() appel
ee") ;
return a + b ;
}
}
}
Exemple 7-14 :
Code de lassemblage client de la classe cible, non connue `
a la
compilation (ProgramAsm.cs)
using
using
using
class

System ;
System.Reflection ;
NMFoo ;
Program {

248

Chapitre 7 : Rflexion, liens tardifs, attributs


static void Main() {
ICalc obj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
"ClassAsm.dll", "NMFoo.CalcAvecInterface") as ICalc ;
int result = obj.Sum(7, 8);
}
}

Soyez attentif au transtypage explicite en ICalc de lobjet retourn par la mthode CreateInstanceAndUnwrap() qui nous permet dutiliser ensuite un lien dynamique sur la mthode Sum().
On aurait pu viter ce transtypage en utilisant la surcharge gnrique de la mthode Activator.CreateInstance<ICalc>().
La figure suivante reprend lorganisation ainsi que les liens entre nos trois assemblages :
Assemblage contenant la classe
Program

Rfrence la compilation pour pouvoir


utiliser linterface ICalc

Chargement explicite lors de


AppDomain.CurrentDomain.
CreateInstanceFromAndUnWrap()
Assemblage contenant la classe
CalcAvecInterface

Assemblage contenant
linterface ICalc

Rfrence la compilation pour pouvoir


utiliser linterface ICalc

Figure 7 -2 : Organisation des assemblages dans le design pattern plugin


Dans la philosophie du design pattern plugin, les donnes ncessaires la mthode CreateInstanceAndUnwrap() pour crer une instance (en loccurrence les deux chanes de caractres "Foo.dll" et "NMFoo.CalcAvecInterface") sont en gnral stockes dans un fichier de
configuration. Tout lintrt est alors de pouvoir choisir une implmentation simplement en
modifiant un fichier de configuration et sans aucune recompilation.
Une variante du plugin consiste utiliser une classe abstraite plutt quune interface.
Enfin, sachez quil est aussi possible dutiliser un dlgu pour pouvoir se lier tardivement
vers une mthode. Bien que cette technique soit plus performante que lutilisation de la classe
MethodInfo, on lui prfre en gnral le plugin.

Les attributs
Quest ce quun attribut ?
Un attribut est une information qui se rapporte un lment du code tel quune classe ou une
mthode. Par exemple, le framework .NET fournit lattribut System.ObsoleteAttribute qui
peut tre utilis pour marquer une mthode comme ceci (notez la syntaxe avec les crochets []) :
[System.ObsoleteAttribute()]
void Fct() { }
Linformation la mthode Fct() est marque avec lattribut System.ObsoleteAttribute est
insre dans lassemblage lors de la compilation. Cette information peut alors tre exploite
par le compilateur C  . Lorsquil rencontre un appel cette mthode, il peut mettre un avertissement indiquant quil vaut mieux viter dinvoquer une mthode obsolte, qui risque de

Les attributs

249

disparatre dans les prochaines versions. Sans attribut, vous seriez oblig de commenter et de
documenter lobsolescence de la mthode Fct(). La faiblesse de cette dmarche est que vous
nauriez aucune garantie que vos clients soient au courant de cette obsolescence.

Quand a t-on besoin des attributs ?


Tout lintrt dutiliser un attribut rside dans le fait que linformation quil contient est insre
dans lassemblage. Cette information peut alors tre consomme divers moments par divers
consommateurs pour divers desseins :

Un attribut peut tre consomm par le compilateur. Lattribut System.ObsoleteAttribute


que nous venons de voir constitue un bon exemple de consommation dattribut par le compilateur. Certains attributs standard destins ntre consomms que par le compilateur
ne sont pas stocks dans lassemblage. Par exemple, lattribut SerializationAttribute ne
marque pas directement un type mais indique au compilateur que ce type est srialisable.
En consquence, le compilateur positionne certains drapeaux sur le type concern qui seront consomms par le CLR lexcution. De tels attributs sont nomms pseudo-attributs.

Un attribut peut tre consomm par le CLR lexcution. Par exemple, le framework .NET
prsente lattribut System.ThreadStaticAttribute. Lorsquun champ statique est marqu
avec cet attribut, le CLR fait en sorte qu lexcution il existe une version de ce champ par
thread.

Un attribut peut tre consomm par un dbuggeur lexcution. Ainsi lattribut System.
Diagnostics.DebuggerDisplayAttribute permet de personnaliser lachage dun lment du code (ltat dun objet par exemple) lors du dboguage.

Un attribut peut tre consomm par un outil. Par exemple, le framework .NET prsente
lattribut System.Runtime.InteropServices.ComVisibleAttribute. Lorsquune classe est
marque avec cet attribut, loutil tlbexp.exe gnre un fichier qui permettra ultrieurement cette classe dtre consomme comme si elle tait dfinie avec la technologie COM.

Un attribut peut tre consomm par votre propre code lexcution, en ayant recours au
mcanisme de rflexion pour accder linformation. Ainsi, il peut tre intressant de dfinir lintgrit des champs de vos classes en marquant leurs champs avec des attributs. Tel
champ entier doit tre dans telle plage de valeur. Tel champ de type rfrence ne doit jamais tre nul. Tel champ doit rfrencer une chane dau plus 100 caractres etc. Grce au
mcanisme de rflexion, il est ais dcrire du code capable de valider ltat de nimporte
quelle classe dont les champs sont marqus. Nous dtaillons un peu plus loin un exemple
de consommation dattributs lexcution par du code propritaire.

Un attribut peut tre consomm par un utilisateur qui analyse un assemblage avec un outil
tel que ildasm.exe. Ainsi, nous pouvons imaginer un attribut qui permettrait dassocier
une chane de caractres un lment du code source. Cette chane de caractres tant insre dans lassemblage, il devient alors possible de consulter des commentaires sans avoir
besoin du code source.

Quelques prcisions sur les attributs

Un attribut est ncessairement dfinit par une classe qui drive de la classe System.
Attribute.

250

Chapitre 7 : Rflexion, liens tardifs, attributs

Une classe dattribut nest instancie que lorsque le mcanisme de rflexion accde un
de ses reprsentant. Selon lutilisation, une classe dattribut nest donc pas forcment instancie ( linstar de la classe System.ObsoleteAttribute qui na pas tre utilis avec le
mcanisme de rflexion).

Le Framework .NET met votre disposition de nombreux attributs. Certains sont destins
tre consomms par le CLR. Dautres sont consomms par le compilateur ou des outils
fournis par MS.

Vous avez la possibilit de crer vos propres classes dattributs. Ils seront alors ncessairement consomms par vos propres programmes ou outils puisque vous ne pouvez pas modifier ni le compilateur ni le CLR.

Par convention, le nom dune classe dattribut est sux par Attribute. Cependant, un attribut nomm XXXAttribute peut en C  tre utilis la fois avec lexpression XXXAttribute
mais aussi avec lexpression XXX lorsquil marque un lment du code.

Dans le chapitre consacr la gnricit, nous prsentons en page 497 les rgles relatives au
recoupements entre les deux notions dattribut et de gnricit.

Elments du code source sur lesquels sappliquent les attributs


Les attributs sappliquent des lments de votre code source. Voici exactement quels sont ces
lments. Ils sont dfinis par les valeurs de lnumration AttributeTargets.
Nom des lments

Champ dapplication de lattribut

All

Tous les lments du code source savoir : lassemblage lui-mme,


les classes, les membres des classes, les dlgus, les vnements, les
champs, les interfaces, les mthodes, les modules, les paramtres, les
proprits, les valeurs de retour et les structures.

Assembly

Lassemblage lui-mme.

Class

Les classes.

Constructor

Les constructeurs.

Delegate

Les dlgus.

Enum

Les numrations.

Event

Les vnements.

Field

Les champs.

GenericParameter

Les paramtres gnriques.

Interface

Les interfaces.

Method

Les mthodes.

Module

Les modules.

Les attributs

251

Parameter

Les paramtres des mthodes.

Property

Les proprits des classes.

ReturnValue

Les valeurs de retour des mthodes.

Struct

Les structures.

Quelques attributs standard du Framework .NET


La bonne comprhension dun attribut dcoule de la bonne comprhension de la situation
dans laquelle il faut lutiliser. Aussi, chaque attribut standard est dcrit dans le chapitre qui le
concerne.
Quelques attributs standard relatifs la gestion de la scurit sont prsents page 203.
Quelques attributs standard relatifs au mcanisme P/Invoke sont prsents dans la section page
265.
Quelques attributs standard relatifs lutilisation de COM partir dapplications. NET sont
prsents dans la section page 281.
Quelques attributs standard relatifs au mcanisme de srialisation sont prsents page 790.
Quelques attributs standard relatifs au mcanisme de srialisation XML sont prsents page 780.
Quelques attributs standard relatifs aux assemblages sont prsents page 25.
Quelques attributs standard permettant de personnaliser le comportement dun dbogueur
sont prsents en page 617.
Lattribut System.Runtime.Remoting.Contexts.Synchronization qui permet dimplmenter
un mcanisme de synchronisation est prsent page 160.
Lattribut ConditionalAttribute qui permet dindiquer au compilateur C  sil doit compiler
ou non certaines mthodes est prsent page 314.
Lattribut ThreadStaticAttribute qui sert modifier le comportement des threads vis--vis des
champs statiques est prsent page 176.
Lattribut CLSCompliantAttribute qui sert indiquer au compilateur sil doit faire certaines
vrifications est prsent page 131.
Lattribut ParamArrayAttribute qui sert implmenter le mot cl C  params est prsent page
405.
Lattribut CategoryAttribute est prsent page 685.

Exemple dun attribut personnalis


Un attribut personnalis (custom attribute en anglais) est un attribut que vous crez vous-mme
en faisant driver une de vos classes de la classe System.Attribute. linstar des attributs validateurs de champs dcris au dbut de cette section, on peut imaginer de multiples situations
o lon peut bnficier dattributs personnaliss. Lexemple que nous prsentons ici sinspire de
limplmentation de loutil open-source NUnit.

252

Chapitre 7 : Rflexion, liens tardifs, attributs

Loutil NUnit permet dexcuter et donc de tester, nimporte quelle mthode de nimporte quel
assemblage. Comme il ny a pas de sens excuter toutes les mthodes dun assemblage, NUnit
nexcute que les mthodes marques avec un attribut de type TestAttribute.
Pour implmenter une version simplifie de ce comportement, nous nous imposons les
contraintes suivantes :

Le test dune mthode est considr comme concluant si celle-ci nenvoie aucune exception
non rattrape.

Nous dfinissions un attribut TestAttribute qui peut sappliquer sur les mthodes. Lattribut peut tre paramtr par le nombre de fois que la mthode doit tre excute (proprit
int TestAttribute.nTime). Lattribut peut aussi tre paramtr pour permettre dignorer
une mthode marque (proprit bool TestAttribute.nTime).

La mthode Program.TestAssembly(Assembly) permet dexcuter toutes les mthodes


contenues dans lassemblage pass en rfrence et marques avec lattribut TestAttribute.
Pour simplifier, nous supposerons que ces mthodes sont publiques, non statiques et ne
prennent pas darguments. Nous sommes contraint dutiliser un lien tardif pour invoquer
les mthodes marques.

Le programme suivant satisfait ce cahier des charges.


Exemple 7-15 :
using System ;
using System.Reflection ;
[AttributeUsage(AttributeTargets.Method,AllowMultiple=false)]
public class TestAttribute : System.Attribute {
public TestAttribute() {
Console.WriteLine("TestAttribute.ctor() par d
efaut.") ;
}
public TestAttribute(int nTime) {
Console.WriteLine("TestAttribute.ctor(int).") ;
m_nTime = nTime ;
}
private int m_nTime = 1 ;
private bool m_Ignore = false ;
public bool Ignore { get { return m_Ignore ; }
set { m_Ignore = value ; } }
public int nTime { get { return m_nTime ; }
set { m_nTime = value ; } }
}
class Program{
static void Main(){
// Le programme sanalyse lui-m
eme
TestAssembly(Assembly.GetExecutingAssembly()) ;
}
static void TestAssembly(Assembly assembly) {
// Pour toutes les methodes de tous les types de assembly

Les attributs
foreach(Type type in assembly.GetTypes() ){
foreach(MethodInfo method in type.GetMethods() ){
// Obtient les attributs de type TestAttribute
// qui marquent la m
ethode r
ef
erenc
ee par method.
// Declenche lappel `
a TestAttribute.ctor().
object[] attributes = method.GetCustomAttributes(
typeof(TestAttribute),false) ;
if( attributes.Length == 1 ){
// Obtient une ref
erence de type TestAttribute.
TestAttribute testAttribute =
attributes[0] as TestAttribute ;
// Si la methode nest pas `
a ignorer.
if( ! testAttribute.Ignore ){
object [] parameters = new object[0] ;
object instance = Activator.CreateInstance(type) ;
// Invoque la m
ethode nTime fois.
for(int i=0;i< testAttribute.nTime ; i++){
try{
//Invocation de la m
ethode avec un lien tardif.
method.Invoke(instance,parameters) ;
} catch(TargetInvocationException ex) {
Console.WriteLine(
"La m
ethode {" + type.FullName + "." +
method.Name +
"} a lanc
e une exception de type " +
ex.InnerException.GetType() +
" lors de lex
ecution " + (i+1) + ".") ;
}// end catch(...
}// end for(...
}// end if( ! attribute.Ignore )
}// end if( attributes.Length == 1 )
}// end foreach(MethodInfo...
}// end foreach(Type...
}
}
class Foo {
[Test()]
public void Plante() {
Console.WriteLine("Plante()") ;
throw new ApplicationException() ;
}
int state = 0 ;
[Test(4)]
public void PlanteLaDeuxiemeFois() {
Console.WriteLine("PlanteLaDeuxiemeFois()") ;
state++ ;
if (state == 2) throw new ApplicationException() ;
}

253

254

Chapitre 7 : Rflexion, liens tardifs, attributs


[Test()]
public void NePlantePas() {
Console.WriteLine("NePlantePas()") ;
}
[Test(Ignore = true)]
public void PlanteMaisIgnore() {
Console.WriteLine("PlanteMaisIgnore()") ;
throw new ApplicationException() ;
}
}

Ce programme ache :
TestAttribute.ctor() par defaut.
Plante()
La methode {Foo.Plante} a lance une exception de type
System.ApplicationException lors de lex
ecution 1.
TestAttribute.ctor(int).
PlanteLaDeuxiemeFois()
PlanteLaDeuxiemeFois()
La methode {Foo.PlanteLaDeuxiemeFois} a lanc
e une exception de type
System.ApplicationException lors de lex
ecution 2.
PlanteLaDeuxiemeFois()
PlanteLaDeuxiemeFois()
TestAttribute.ctor() par defaut.
NePlantePas()
TestAttribute.ctor() par defaut.
Plusieurs remarques simposent :

On marque notre classe TestAttribute avec un attribut de type AttributeUsage.


On se sert de la valeur Method de lnumration AttributeTarget pour signifier au compilateur que lattribut TestAttribute ne peut sappliquer quaux mthodes.
On positionne false la proprit AllowMultiple de la classe AttributeUsage pour signifier quune mthode ne peut accepter plusieurs attributs de type TestAttribute. Notez la
syntaxe particulire pour initialiser la proprit AllowMultiple. On dit que AllowMultiple
est un paramtre nomm.
On se sert aussi de la syntaxe du paramtre nomm dans la dclaration de lattribut Test
qui marque la mthode Foo.PlanteMaisIgnore().
On se sert du fait que lorsquune exception est lance et non rattrape lors de lexcution dune mthode invoque au moyen dun lien tardif, cest une exception de type
TargetInvocationException qui est rcupre par lappelant. Lexception initiale est alors
rfrence par la proprit InnerException de lexception rcupre.
Pour viter davoir clater le code sur plusieurs assemblages, ce programme se teste luimme (en fait seules les mthodes de la classe Foo sont teste puisque ce sont les seules
mthodes marques avec lattribut TestAttribute). Voici lorganisation des assemblages
que lon aurait si le code tait clat :
La ressemblance de ce schma avec celui de la Figure 7 -2 nest pas fortuite. Dans les deux
cas, nous exploitons un lment inconnu la compilation par lintermdiaire dun lment
connu de tous la compilation (un attribut dans ce cas, une interface prcdemment).

Construction et utilisation dynamique dun assemblage


Assemblage contenant la
mthode TestAssembly()

Rfrence la compilation pour pouvoir


utiliser la classe TestAttribute

Chargement explicite, par exemple


avec la mthode Assembly.Load()
Assemblage contenant la classe
F(x) tester

255

Assemblage contenant la classe


TestAttribute

Rfrence la compilation pour pouvoir


utiliser la classe TestAttribute

Figure 7 -3 : Organisation des assemblages

Les attributs conditionnels


C  2 introduit la notion dattribut conditionnel. Un attribut conditionnel nest pris en compte par
le compilateur que si un symbole est dfini. Lexemple suivant illustre cette notion dattribut
conditionnel au travers dun projet constitu de trois fichiers :
Exemple 7-16 :
[System.Diagnostics.Conditional("_TEST")]
public class TestAttribute : System.Attribute { }
Exemple 7-17 :
#define _TEST
[Test] // Le compilateur marque la classe Foo1 avec lattribut Test.
class Foo1 { }
Exemple 7-18 :
#undef _TEST
[Test] // Le compilateur ne marque pas la classe Foo2
// avec lattribut Test.
class Foo2 { }
Dans lexemple de la section prcdente, les attributs conditionnels pourraient tre exploits
pour gnrer une version de test et une version destine la production partir du mme code.
En page 314 nous dcrivons une autre utilisation de lattribut Conditional.

Construction et utilisation dynamique dun assemblage


Lespace de noms System.Reflection.Emit prsente un ensemble de classes permettant de fabriquer lexcution dun assemblage, de nouveaux assemblages. Cest en fait de la gnration
de code, mis part que le code nest pas gnr sous la forme de fichiers sources C  ou C++, mais
bel et bien sous la forme dun assemblage prt tre excut. Vous avez ensuite la possibilit
de sauver lassemblage dune manire persistante. Le mot Emit sapparente au fait dmettre
du code.

256

Chapitre 7 : Rflexion, liens tardifs, attributs

Pourquoi construire dynamiquement un assemblage ?


Lorsque vous fabriquez un assemblage avec les classes de lespace de noms System.Reflection.
Emit, le code est mit en langage IL. Ce langage est brivement prsent dans la section page
41. Bien que le langage IL soit fondamental, il est matris par beaucoup moins de dveloppeurs
que le langage C  ou le langage VB.NET. Pour la plupart des dveloppeurs, il est donc beaucoup
plus fastidieux de gnrer du code en langage IL, que de gnrer un fichier source C  qui sera
compil ultrieurement. En fait, il est toujours prfrable de gnrer du code en un langage
structur comme C  ou VB.NET.
Mais alors, pourquoi donc gnrer dynamiquement des assemblages ?
Il existe en fait au moins trois cas de figure, prsents dans les MSDN larticle Reflection Emits
Application Scenarios :

Lexcution dun script dans un navigateur web : lide est quun script dans un page Web
construit dynamiquement un assemblage qui est sauv de faon persistante chez le client.

Lexcution dun script dans une page ASP.NET : lide est quun script dans un page
ASP.NET, construit dynamiquement un assemblage qui est sauv de faon persistante
dans le cache du serveur. Ainsi, seule la premire visite de la page provoque la cration de
lassemblage.

La compilation dune expression rgulire fournie lexcution. Le problme de lvaluation dune expression rgulire est reprsentatif de cette classe de problmes qui admettent
une solution globale lente, et des solutions particulires rapides. Lide est de construire
une solution particulire lexcution, lorsque lexpression rgulire est fournie, plutt que
dimplmenter au dveloppement la solution globale lente. Le framework .NET permet la
compilation des expressions rgulires et tout ceci est expliqu page 625.

Tout ceci peut paratre abstrait aussi allons-nous prsenter un exemple concret qui a la mrite
davoir une utilit potentielle dans lindustrie.

Un problme concret
Prsentation du problme
Lexemple prsent ici est bas sur lvaluation dun polynme P coecients entiers, dfini
sur les entiers. Imaginez une application o ou un tel polynme est fourni lexcution (par
exemple par un utilisateur). Supposons que lapplication doive, par la suite, valuer ce polynme pour un trs grand nombre de valeurs. Nous allons montrer que ce problme concret
admet une solution trs optimise, utilisant la fabrication lexcution dun nouvel assemblage.
Par la suite nous allons supposer que le polynme P saisi par lutilisateur durant lexcution de
lapplication est :
P(x) = 66x3 + 83x2 13 735x + 30 139
Pour la petite histoire, ce polynme dcouvert par les mathmaticiens Dress et Landreau en
1999, a la particularit de ne prendre pour valeurs que des nombres premiers pour x entre 26
et 19 (inclus). Nous aurions pu prendre nimporte quel autre polynme coecients entiers
pour illustrer notre exemple.

Construction et utilisation dynamique dun assemblage

257

Pour nos tests de performance, nous valuerons P pour chacune de ces valeurs 10 millions de
fois. On ne travaillera quavec des entiers sur quatre octets signs (le type int en C  , qui est aussi
le type System.Int32). Pour ceux qui ont oubli leurs cours de mathmatique nous rappelons
que la manire la plus conomique en terme doprations dvaluer ce polynme est de lcrire
sous cette forme :
P(x) = 30 139 + x(13 735 + x(83 + 66x))
Cette astuce sappelle la mthode de Hrner. Seulement trois multiplications et trois additions
sont ncessaires pour valuer P pour une valeur de x.

Solution1 : La mthode globale


Fort de cette connaissance, la solution immdiate qui vient lesprit est :
Exemple 7-19 :
using System ;
using System.Diagnostics;
class Program {
static int Evalue(int x,int[] Coefs) {
int tmp = 0;
int degre = Coefs.GetLength(0);
for(int i=degre-1 ; i>= 0 ; i--)
tmp = Coefs[i]+x*tmp;
return tmp;
}
static void Main() {
Stopwatch sw = Stopwatch.StartNew();
int [] Coefs = {30139,-13735,83,66};
for( int x = -26 ; x<= 19 ; x++ )
for( int i = 0 ; i<10000000 ; i++)
Evalue(x,Coefs);
Console.WriteLine("Duree :" + sw.Elapsed );
}
}
Puisque nous focalisons nos tests sur la performance, il ny a pas lieu ici de vrifier la primarit
des entiers retourns par la mthode Evalue().

Solution 2 : La mthode particulire


Si nous navions pas eu la contrainte de dfinir les coecients du polynme hors de la mthode
Evalue() nous aurions pu crire le programme de cette faon :
Exemple 7-20 :
using System ;
using System.Diagnostics;
class Program {
static int Evalue(int x) {

258

Chapitre 7 : Rflexion, liens tardifs, attributs


return 30139-x*(13735-x*(83+x*66));
}
static void Main() {
Stopwatch sw = Stopwatch.StartNew();
for( int x = -26 ; x<= 19 ; x++ )
for( int i = 0 ; i<10000000 ; i++)
Evalue(x);
Console.WriteLine("Duree :" + sw.Elapsed );
}
}

Comparaison des performances des deux solutions


La dure obtenue sur une machine de rfrence pour la premire solution (la mthode globale)
est de 17,81 secondes. La dure obtenue sur la mme machine pour la seconde solution (la mthode particulire) est de 3,87 secondes soit un gain de 4,6 par rapport la premire mthode.
Beaucoup dinstructions machines, comme le passage par rfrence du tableau, la recherche des
coecients dans le tableau et la gestion de la boucle, ont disparu dans la seconde solution.
Ces rsultats montrent bien que lvaluation dun polynme particulier est beaucoup plus rapide que lutilisation dun algorithme global dvaluation dun polynme.
Deux problmes potentiels pourraient ventuellement fausser ces tests de performance.

Problme potentiel 1 : Le ramasse-miettes pourrait se dclencher dans un des tests et pas


dans lautre. Nous avons vrifi que le ramasse-miettes ne se dclenche pas, tant donne
la faible quantit de mmoire demande par ces programmes.

Problme potentiel 2 : Le compilateur JIT pourrait tre assez intelligent pour sapercevoir que ce nest pas la peine dappeler la mthode Evalue() puisquon nutilise pas ses rsultats. Nous avons pu tablir que ce nest pas le cas en vrifiant que si la mthode Evalue()
incrmente un compteur global, les rsultats sont sensiblement quivalents.

Une solution performante grce la cration dynamique


dun assemblage
Le problme de la mthode particulire par rapport la mthode globale est que les coecients
dans la mthode Evalue() doivent tre connus la compilation. Le polynme ne peut pas
tre fourni au moment de lexcution, mais seulement au moment de la compilation. Grce
au mcanisme de construction dun assemblage lexcution, nous allons pouvoir utiliser la
mthode particulire, plus rapide, tout en acceptant le polynme lexcution. En eet, il sut
de gnrer le code IL de la mthode Evalue() de la mthode particulire.
Pour le polynme donn en exemple dans la section prcdente, le code IL gnr par le compilateur C  dans la seconde solution est celui-ci :
.method private hidebysig static int32 Calc(int32 x) cil managed
{
// Code size
28 (0x1c)
.maxstack 7
.locals init ([0] int32 CS$00000003$00000000)

Construction et utilisation dynamique dun assemblage


IL_0000:
IL_0005:
IL_0006:
IL_000b:
IL_000c:
IL_000e:
IL_000f:
IL_0011:
IL_0012:
IL_0013:
IL_0014:
IL_0015:
IL_0016:
IL_0017:
IL_0018:
IL_001a:
IL_001b:
} // end of

259

ldc.i4
0x75bb // coef 30139 mis sur le haut de la pile
ldarg.0
// valeur de x mise sur le haut de la pile
ldc.i4
0xffffca59// coef -13735 mis sur le haut de la pile
ldarg.0
// valeur de x mise sur le haut de la pile
ldc.i4.s
83
// coef 83 mis sur le haut de la pile
ldarg.0
// valeur de x mise sur le haut de la pile
ldc.i4.s
66
// coef 66 mis sur le haut de la pile
mul
//
add
//
mul
//
Evaluation du polyn
ome en x
add
//
avec trois additions et
mul
//
trois multiplications
add
//
stloc.0
br.s
IL_001a
ldloc.0
ret
method Program::Calc

Il sut maintenant de produire ce code IL pour nimporte quel polynme, avec les classes de
lespace de noms System.Reflection.Emit.

Dans cet exemple, linstruction IL ldarg est utilise avec le paramtre 0 pour charger le
premier argument. Si la mthode navait pas t statique ldarg.0 aurait reprsent le pointeur this et il aurait fallu utiliser ldarg.1 pour accder au premier argument. Par la suite
la mthode ne sera pas statique, aussi nous utiliserons ldarg.1 pour accder au premier
lment.
Voici le code :
Exemple 7-21 :
using
using
using
using
using

System ;
System.Reflection ;
System.Reflection.Emit ;
System.Threading ;
System.Diagnostics ;

public interface IPolynome {


int Evalue(int x) ;
}
class Polynome {
public IPolynome polynome ;
public Polynome(int[] coefs) {
Assembly asm = BuildCodeInternal(coefs) ;
polynome = (IPolynome)asm.CreateInstance("PolynomeInternal") ;
}
private Assembly BuildCodeInternal(int[] coefs) {
AssemblyName asmName = new AssemblyName() ;

260

Chapitre 7 : Rflexion, liens tardifs, attributs


asmName.Name = "EvalPolAsm" ;
// Construit un assemblage dynamique.
AssemblyBuilder asmBuilder =
Thread.GetDomain().DefineDynamicAssembly(
asmName, AssemblyBuilderAccess.Run) ;
// Construit un module dans lassemblage.
ModuleBuilder modBuilder =
asmBuilder.DefineDynamicModule("MainMod") ;
// Ajoute la classe PolynomeInternal dans le module.
TypeBuilder typeBuilder = modBuilder.DefineType(
"PolynomeInternal", TypeAttributes.Public) ;
typeBuilder.AddInterfaceImplementation(typeof(IPolynome)) ;
// Implemente la methode int Evalue(int)
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
"Evalue",
MethodAttributes.Public | MethodAttributes.Virtual,
typeof(int),
// type retour
new Type[] { typeof(int) }) ; // argument
// Gen`ere le code IL `a partir du tableau des coefficients.
ILGenerator ilGen = methodBuilder.GetILGenerator() ;
int deg = coefs.GetLength(0) ;
for (int i = 0 ; i < deg - 1 ; i++) {
ilGen.Emit(OpCodes.Ldc_I4, coefs[i]) ;
ilGen.Emit(OpCodes.Ldarg, 1) ;
}
ilGen.Emit(OpCodes.Ldc_I4, coefs[deg - 1]) ;
for (int i = 0 ; i < deg - 1 ; i++) {
ilGen.Emit(OpCodes.Mul) ;
ilGen.Emit(OpCodes.Add) ;
}
ilGen.Emit(OpCodes.Ret) ;
// Indique que la methode Evalue() ...
// ... implemente celle de linterface IPolynome.
MethodInfo methodInfo = typeof(IPolynome).GetMethod("Evalue") ;
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo) ;
typeBuilder.CreateType() ;
return asmBuilder ;
}
}
class Program {
static void Main() {
Stopwatch sw = Stopwatch.StartNew();

Construction et utilisation dynamique dun assemblage

261

int[] coefs = { 30139, -13735, 83, 66 } ;


Polynome p = new Polynome(coefs) ;
for (int x = -26 ; x <= 19 ; x++)
for (int i = 0 ; i < 10000000 ; i++)
p.polynome.Evalue(x) ;
Console.WriteLine("Duree :" + sw.Elapsed);
}
}

Comparaison avec les deux mthodes prcdentes


La dure obtenue sur la machine de rfrence pour cette troisime solution est de 4.36 secondes.
Cest 1.12 fois plus lent quavec la deuxime solution, mais la cration de lassemblage ne rentre
pratiquement pas en ligne de compte, puisquelle consomme entre 2 et 4 centimes de seconde.
On peut supposer que cette petite baisse de performance est due lutilisation dune mthode
non statique, qui oblige passer une rfrence vers une instance chaque appel de Evalue().
De plus lappel une mthode dfinie dans une interface est plus coteux que lappel une
mthode non virtuelle et non abstraite. En eet, linstruction IL callvirt est utilise pour lappel une mthode dfinie dans une interface, alors que linstruction IL call est utilise pour
lappel une mthode statique. Or, le compilateur JIT gnre du code machine consommant
moins de cycle horloge pour linstruction IL call que pour linstruction IL callvirt. Nous
verrons un peu plus tard, que cette petite baisse de performance est aussi due au fait que nous
ne tenons pas compte que certains coecients du polynme peuvent tre stocks sur un octet
(en loccurrence, les coecients 83 et 66). Or, le compilateur C  profite de cette situation pour
utiliser des instructions IL spcialement optimises pour manipuler des entiers sur un octet.
En revanche cette troisime solution est 4.1 fois plus rapide que la premire solution, tout en
respectant les mmes contraintes (i.e les coecients du polynmes sont dfinies hors de la mthode Evalue()). Cest un gain norme, rsultant directement de la cration dynamique dun
assemblage. Une optimisation est encore possible si lon tient compte des coecients nuls.

Description technique du code


Les types intressants dans le code prcdent sont :
AssemblyName
AssemblyBuilder
ModuleBuilder
TypeBuilder
MethodBuilder
ILGenerator
MethodInfo
Ils sont gnralement utiliss dans cet ordre, de la mme faon que dans cet exemple. Il existe
de nombreux autres types dtaills dans les MSDN. Ces autres types prennent notamment en
charge la gestion des exceptions, la gestion des branchements, la gestion des autres lments
dune classe (proprits, vnements).

Possibilit de sauver lassemblage sur le disque


Si vous aviez voulu sauver lassemblage sur le disque il aurait fallu :

262

Chapitre 7 : Rflexion, liens tardifs, attributs

Nommer le module (par exemple MainMod.dll).

Utiliser AssemblyBuilderAccess.RunAndSave au lieu de AssemblyBuilderAccess.Run.

Sauver lassemblage avec la ligne de code suivant, juste avant le retour de la mthode BuildCodeInternal() :
TheAsm.Save("MainMod.dll") ;

Nous pouvons comparer le code IL produit par le compilateur C  et le code IL produit par notre
propre programme :
Code IL produit par le compilateur C 
.method private hidebysig static
int32 Calc(int32 x) cil managed
{
// Code size 28 (0x1c)
.maxstack 7
.locals init ([0]
int32 CS$00000003$00000000)
IL_0000: ldc.i4 0x75bb
IL_0005: ldarg.0
IL_0006: ldc.i4 0xffffca59
IL_000b: ldarg.0
IL_000c: ldc.i4.s 83
IL_000e: ldarg.0
IL_000f: ldc.i4.s 66
IL_0011: mul
IL_0012: add
IL_0013: mul
IL_0014: add
IL_0015: mul
IL_0016: add
IL_0017: stloc.0
IL_0018: br.s IL_001a
IL_001a: ldloc.0
IL_001b: ret
} // end of method CCalc::Calc

Code IL produit dynamiquement par notre programme


.method public virtual instance
int32 Evalue(int32 A_1) cil managed
{
.override [DynamicAssembly3]
IPolynome::Evalue
// Code size 30 (0x1e)
.maxstack 7
IL_0000: ldc.i4 0x75bb
IL_0005: ldarg.1
IL_0006: ldc.i4 0xffffca59
IL_000b: ldarg.1
IL_000c: ldc.i4 0x53
IL_0011: ldarg.1
IL_0012: ldc.i4 0x42
IL_0017: mul
IL_0018: add
IL_0019: mul
IL_001a: add
IL_001b: mul
IL_001c: add
IL_001d: ret
} // end of method CPolynomeInternal::Evalue

Les dirences sont dues aux faits suivants :

Nous avons produit dynamiquement une mthode non statique.

Nous nutilisons pas de variables locales, contrairement au code produit par le compilateur
C.

Nous nutilisons pas linstruction optimise ldc.i4.s qui charge sur la pile une valeur dun
octet (i.e entre -128 et 127) dans quatre octets. Nous pourrions optimiser notre programme
en utilisant cette instruction.

Construction et utilisation dynamique dun assemblage

263

Conclusion
Vous avez vu ici un exemple reprsentatif des possibilits de la cration dynamique dassemblages. On pourrait ladapter simplement dautres domaines aussi utiles que le calcul vectoriel (composition avec une matrice, valuation dune forme quadratique etc). Il arrive souvent quune mme matrice, inconnue avant lexcution, soit compose des millions de fois (par
exemple pour calculer les dplacements des points dune scne 3D).
La majorit des dveloppeurs nauront jamais construire dassemblages dynamiquement, mais
certains projets ont beaucoup gagner utiliser cette possibilit.

8
Interoprabilit .NET
code natif / COM / COM+

La plateforme .NET prsente plusieurs techniques pour faire interoprer du code gr avec du
code natif et pour faire cooprer des objets grs avec des objets COM. Ce besoin est particulirement prsent lors dun processus de migration dune application C++ ou VB6 vers .NET. En
eet, linteroprabilit permet de migrer vos projets petit petit, composant aprs composant.

Le mcanisme P/Invoke
Le CLR utilis conjointement avec certaines classes de base du framework .NET, permet du
code gr dappeler des fonctions compiles en code natif. Cette possibilit est nomme Platform
Invoke ou P/Invoke. Vous pouvez lutiliser pour appeler les fonctions de vos propres DLLs natives.
Microsoft exploite aussi le mcanisme P/Invoke pour appeler les fonctions de ses propres DLLs
natives.
Notons lexistence du mcanisme internal call qui a la mme finalit que P/Invoke. Ce mcanisme consiste implmenter les artefacts des appels aux fonctions natives directement (en dur)
dans le code du CLR. Il est donc plus performant que P/Invoke mais nest utilisable que par
Microsoft.

Lattribut DllImport
Les classes qui permettent dutiliser P/Invoke se trouvent dans lespace de noms System.
Runtime.InteropServices. Pour appeler une fonction dune DLL native partir dun programme C  , il faut dabord dclarer cette fonction dans une classe C  :

La dclaration de cette fonction doit tre marque avec lattribut System.Runtime.InteropServices.DllImport qui indique le nom de la DLL.

266

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Utiliser les mots-cls static et extern devant la dclaration de la mthode.

Garder le mme nom de fonction que celui spcifi dans la DLL.

Donner un nom chaque argument.

Un exemple
Le programme suivant appelle la fonction Beep() dfinie dans la DLL native et standard
Kernel32.dll.
Exemple 8-1 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll")]
public static extern bool Beep(uint iFreq, uint iDuration);
static void Main() {
bool b = Beep(100, 100);
}
}
Dans certains cas rares, vous ne pouvez pas spcifier le nom de la fonction dfinie dans la DLL
car vous avez dj une mthode qui a ce nom. Il est possible de changer le nom dune fonction
implmente dans une DLL native comme ceci :
Exemple 8-2 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll", EntryPoint = "Beep")]
public static extern bool MonBeep(uint iFreq, uint iDuration) ;
static void Main() {
bool b = MonBeep(100, 100) ;
}
}

Convention dappel
Les fonctions implmentes dans les DLLs natives supportent plusieurs conventions dappel.
Ces conventions dappel indiquent au compilateur comment doit se comporter le passage dargument. Si vous devez utiliser une fonction qui a une convention dappel particulire, il faut
utiliser lattribut DllImport comme ceci :
[DllImport("MaDLL.dll", CallingConvention=XXX)]
XXX est une valeur de lnumration System.Runtime.InteropServices.CallingConvention.
Les significations des valeurs de cette numration sont exposes dans les MSDN larticle
CallingConvention Enumeration. Par dfaut cette valeur vaut StdCall qui est la convention
dappel standard sur les systmes dexploitation Microsoft (mis part Windows CE qui admet la
convention dappel standard Cdecl).

Le mcanisme P/Invoke

267

Impact au niveau des performances


Lors de chaque appel dune fonction dune DLL native, deux tapes sont eectues en interne.
Lensemble de ces tapes est appel P/Invoke Marshalling :

Il faut parcourir la pile pour vrifier que tous les appelants ont la permission UnmanagedCode.
Afin damliorer les performances, vous pouvez supprimer cette tape en utilisant lattribut
System.Security.SuppressUnmanagedCodeSecurity sur une mthode P/Invoke ou sur
une classe qui contient des mthodes P/Invoke . Cependant, lutilisation de cet attribut
peut tre compromettante pour la scurit.

Il faut crer une fentre de pile (stack frame en anglais) qui soit compatible avec les fentres
de pile utilises dans le code non gr. Cette tape est en gnral trs lgre.

Puisque la seconde tape est lgre et puisque la premire tape peut tre supprime si la scurit ne fait pas partie de vos considrations, vous pouvez faire en sorte que lutilisation de
P/Invoke ait un faible impact sur les performances.

Infrer les dfinitions des fonctions standard


Microsoft ne fournit pas les dfinitions gres de ses fonctions natives. Pour obtenir ces dfinitions, nous vous conseillons de consulter le site www.pinvoke.net.

Tableau de conversion des types


Le prototype de Beep() dans la DLL Kernel32.dll est le suivant :
BOOL Beep(DWORD dwFreq,DWORD dwDuration) ;
Pour appeler Beep(), nous avons d convertir le type BOOL win32 en bool .NET et le type DWORD
win32 en uint .NET. Voici le tableau de conversion des types win32 dans les types .NET.
Equivalent C 

Type win32

Type .NET

LPSTR, LPCSTR, LPWSTR,


LPCWSTR

System.StringSystem.
StringBuilder

string

BYTE

System.Byte

Byte

SHORT

System.Int16

Short

WORD

System.UInt16

ushort

DWORD, UINT,ULONG

System.Int32

uint

INT, LONG

System.UInt32

uint

BOOL

System.Bool

bool

CHAR

System.Char

char

FLOAT

System.Single

float

DOUBLE

System.Double

double

268

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

On dit quun type est blittable si la reprsentation binaire de ses instances est identique en mode
gr et en mode natif. Dans le cadre de linteroprabilit, lutilisation des types blittable est donc
plus performante. Dans le tableau prcdent, seuls les types de chanes de caractres ne sont
pas blittables. Les tableaux monodimensionnels dlments de type blittable ainsi que les types
seulement composs de champs de types blittables sont aussi des types blittable.

Passage darguments par pointeurs


En page 119 nous expliquons que le ramasse-miettes est amen dplacer les objets de type
rfrence en mmoire tout moment. Si vous passez ladresse dun objet de type rfrence par
lintermdiaire dun pointeur du code non gr, vous vous exposez priori au risque que
le ramasse-miettes bouge cet objet en mmoire durant lappel. Heureusement le mcanisme
P/Invoke est capable de dtecter cette situation. Le cas chant, il pinglera lobjet concern en
mmoire et fournira un pointeur au code natif (la notion dpinglage dobjet en mmoire est
expose en page 505). Lobjet sera automatiquement dfix lors du retour de lappel. Un
autre problme se pose alors. Certaine fonctions natives stockent les pointeurs qui leurs sont
passs de faon les utiliser dune manire asynchrone, aprs lappel. Par exemple la fonction
win32 ReadFileEx() dfinie comme suit...
BOOL ReadFileEx(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
) ;
...lit les donnes dun fichier dune manire asynchrone. Pour cela, elle stocke les pointeurs
lpBuffer et lpOverlapped. Si vous invoquez cette fonction partir de votre code gr, il est
absolument ncessaire dpingler explicitement les buers passs durant toute la dure de la
lecture asynchrone et pas seulement durant lappel la fonction.
Il faut donc tre particulirement vigilant car les problmes engendrs par la non fixation dun
objet utilis dune manire asynchrone par du code gr se manifestent rarement et de manire
alatoires.

Passage de chanes de caractres


Pour passer une chane de caractres lors de lappel dune mthode native par lintermdiaire
du mcanisme P/Invoke, il vous sut dutiliser le type System.String. Sachez cependant que
les fonctions natives ayant des paramtres dentrs ou de retours de type chanes de caractres
existent chacune en deux versions : une version ANSI suxe par un A et une version UNICODE suxe par un W. Pour choisir entre ces deux versions, lattribut DllImport prsente
le paramtre nomm CharSet qui peut prendre les valeurs Auto, Unicode ou Ansi. La valeur
Auto est quivalente la valeur Ansi sur les systmes dexploitation Windows 98 et Windows
Me. Lexemple suivant montre comment passer deux chanes de caractres lors de lappel de la
mthode MessageBox() :

Le mcanisme P/Invoke

269

Exemple 8-3 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(System.IntPtr hWnd,
string text, string caption, uint type) ;
static void Main() {
MessageBox(System.IntPtr.Zero, "hello", "caption text", 0) ;
}
}

Rcuprer une chane de caractre


Nous allons montrer ici comment exploiter une chane de caractres retourne par une fonction
implmente dans une DLL native. Dans du code non gr il y a deux faons de procder :

Soit le code appelant fournit une mmoire tampon et la taille de cette mmoire tampon.
Dans ce cas la fonction appele remplie la mmoire tampon avec la chane de caractres
retourner.
Soit le code appelant sattend recevoir un pointeur partir de la fonction appele. Dans
ce cas, la fonction appele alloue la zone mmoire contenant la chane de caractres.

Il faut absolument tenir compte de ces dirences lorsque lon appelle au moyen de P/Invoke
une fonction dont le code est non gr. La fonction GetCurrentDirectory() de la DLL
Kernel32.dll est une bonne candidate pour illustrer le premier cas.
DWORD GetCurrentDirectory(
DWORD
nBufferSize,
LPTSTR lpBuffer) ;
En eet, cette fonction admet un pointeur vers une zone de mmoire tampon et sa taille en
argument. Voici le code C  permettant dappeler cette fonction. On ne peut utiliser la classe
string car les instances de cette classe sont immuables. Il faut donc se servir de la classe System.
Text.StringBuilder.
Exemple 8-4 :
using System.Text ;
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern uint GetCurrentDirectory(
uint Taille,
StringBuilder sTmp);
static void Main() {
uint Taille = 255 ;
StringBuilder sTmp = new StringBuilder( (int) Taille);
uint i = GetCurrentDirectory(Taille, sTmp) ;
System.Console.WriteLine(sTmp) ;
}
}

270

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

La fonction GetCommandLine() de la DLL Kernel32.dll est une bonne candidate pour illustrer
le second cas.
LPTSTR GetCommandLine() ;
Cette fonction retourne un pointeur vers la chane de caractres fournie en ligne de commande.
Si nous utilisons le type string comme type de retour, le P/Invoke marshaller copiera la chane
de caractres retourne dans une mmoire tampon alloue par ses soins. Ensuite le P/Invoke
marshaller dsallouera la chane de caractres originale. Or, cette chane de caractres originale
ne doit pas tre dsalloue, car elle existait avant lappel de la fonction GetCommandLine() et
sera srement utilise ultrieurement, dans dautres appels de fonctions. Il faut donc utiliser la
classe IntPtr, qui permet de copier la chane de caractres retourne dans une instance de la
classe string, sans dsallouer la chane de caractres originale :
Exemple 8-5 :
using System.Runtime.InteropServices ;
class Program {
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern System.IntPtr GetCommandLine() ;
static void Main() {
System.IntPtr ptr = GetCommandLine() ;
string sTmp = Marshal.PtrToStringAuto(ptr);
System.Console.WriteLine(sTmp) ;
}
}

Passage de structures et dunions


Lors de lutilisation dune structure dans un appel P/Invok , il faut tenir compte de la faon dont les champs sont stocks dans les instances de la structure. La plupart du temps les
champs sont stocks de manire squentielle dans lordre de leur dclaration. Cest--dire quils
sont stocks les uns aprs les autres, et le calcul de la position dun champ par le compilateur se fait en sommant la taille des champs dclars avant ce champ. Cependant, dans le cas
dune union, les champs sont stocks au mme emplacement, ils ont tous la mme position. Les
unions nexistant pas dans .NET, il faut utiliser conjointement les attributs System.Runtime.
InteropServices.StructLayout System.Runtime.InteropServices.FieldOffset lors de la dclaration dune structure destine tre utilise dans des appels de fonctions implmentes dans
des DLLs natives.
Par exemple la structure Point peut tre dclare en C  comme ceci :
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x ;
public int y ;
}
La structure Point peut aussi tre dclare en C  comme cela :
[StructLayout(LayoutKind.Explicit)]
public struct Point {

Le mcanisme P/Invoke

271

[FieldOffset(0)] public int x ;


[FieldOffset(4)] public int y ;
}
Lunion _Union dclare comme ceci en C :
union _Union {
int
i ;
char c ;
float f ;
}
...peut tre dclare en C  comme cela :
[StructLayout(LayoutKind.Explicit)]
public struct Point {
[FieldOffset(0)] public int i ;
[FieldOffset(0)] public char c ;
[FieldOffset(0)] public float f ;
}
Notez quavec lattribut System.Runtime.InteropServices.MarshalAs, vous pouvez indiquer
au P/Invoke marshaller comment transformer certains types. Par exemple la structure win32 suivante :
typedef struct {
int
wStructSize ;
int
x ;
int
y ;
int
dx ;
int
dy ;
int
wMax ;
TCHAR rgchMember[2];
} HELPWININFO ;
...pourrait tre dclare en C  comme cela :
[StructLayout(LayoutKind.Sequential)]
public struct HELPWININFO {
int
wStructSize ;
int
x ;
int
y ;
int
dx ;
int
dy ;
int
wMax ;
[MarshalAs(UnmanagedType.ByValArry, SizeConst =2)]
public char[] rgchMember;
}

Attribut de direction
Lors de la dclaration dune mthode P/Invoke , vous pouvez utiliser les attributs System.
Runtime.InteropServices.In et System.Runtime.InteropServices.Out devant chaque argu-

272

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

ment de la fonction. Cela permet dindiquer au P/Invoke marshaller de ne convertir largument


qu lentre ou qu la sortie de la fonction. Par dfaut tous les arguments sont convertis
lentre et la sortie.

Dlgus et pointeur non gr de fonction


Vous pouvez invoquer une fonction dfinie dans une DLL native par lintermdiaire dun dlgu fabriqu partir dun pointeur non gr de fonction. En eet, grce aux mthodes statiques
GetDelegateForFunctionPointer() et GetFunctionPointerForDelegate() de la classe Marshal
les notions de dlgus et de pointeurs sur fonction sont interchangeables :
Exemple 8-6 :
using System ;
using System.Runtime.InteropServices ;
class Program {
internal delegate bool DelegBeep(uint iFreq, uint iDuration) ;
[DllImport("kernel32.dll")]
internal static extern IntPtr LoadLibrary(String dllname) ;
[DllImport("kernel32.dll")]
internal static extern IntPtr GetProcAddress(IntPtr hModule,
String procName) ;
static void Main() {
IntPtr kernel32 = LoadLibrary( "Kernel32.dll" );
IntPtr procBeep = GetProcAddress( kernel32, "Beep" );
DelegBeep delegBeep = Marshal.GetDelegateForFunctionPointer(
procBeep , typeof( DelegBeep ) ) as DelegBeep;
delegBeep(100,100);
}
}

Introduction linteroprabilit avec C++/CLI


En plus dexposer des facilits pour exploiter le mcanisme P/Invoke dune manire transparente, le langage C++/CLI prsente la particularit unique de pouvoir fabriquer des assemblages
contenant la fois du code natif et du code gr. Il est alors opportun de raliser une petite
incursion dans ce langage afin de vous donner les cls pour dcider si vos besoins dinteroprabilit en justifient lutilisation.

Le mcanisme It Just Works (IJW)


La finalit du mcanisme IJW (acronyme pour It Just Works qui pourrait se traduire par a marche
tout simplement !) du langage C++/CLI est la mme que celle du mcanisme P/Invoke prsent
dans la section prcdente : permettre dappeler des fonctions natives partir du code gr. En
apparence, ces deux mcanismes sont dirents puisque IJW nutilise pas dattributs. Du point
de vue du CLR en revanche, les mcanismes IJW et P/Invoke sont une seule et mme possibilit.
Le code IL gnr pour un appel avec le mcanisme P/Invoke est donc quivalent au code IL
gnr avec le mcanisme IJW. Pour sen convaincre, rcrivons en C++/CLI lExemple 8-1 :

Introduction linteroprabilit avec C++/CLI

273

Exemple 8-7 :
// compile with: /clr
#include "stdafx.h"
#include "windows.h"
int main(){
bool b = Beep(100, 100) ;
}
Le compilateur C++/CLI peut se passer de lattribut DllImport car il connat la dfinition de la
mthode que lon souhaite appeler grce linclusion du fichier dentte adquat. Dans notre
exemple, la mthode Beep() est dfinie dans le fichier dentte windows.h. La visualisation de
lassemblage produit par le compilateur C++/CLI avec loutil ildasm.exe nous rvle que la mthode statique suivante a t fabrique :
.method public static pinvokeimpl(lasterr stdcall)
int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvStdcall)
Beep(
uint32 modopt([Microsoft.VisualC]Microsoft.VisualC.IsLongModifier) A_0,
uint32 modopt([Microsoft.VisualC]Microsoft.VisualC.IsLongModifier) A_1)
native unmanaged preservesig {
.custom instance void
[mscorlib]System.Security.SuppressUnmanagedCodeSecurityAttribute::
.ctor() = ( 01 00 00 00 )
// Embedded native code
// Disassembly of native methods is not supported.
// Managed TargetRVA = 0x00001D92
} // end of method Global Functions::Beep
La visualisation de lassemblage produit par le compilateur C  pour lExemple 8-7 expose cette
dfinition :
.method public hidebysig static pinvokeimpl("Kernel32.dll" winapi)
bool Beep(uint32 iFreq,uint32 iDuration) cil managed preservesig{}
Du point de vue du CLR, les deux versions de la mthode Beep() sont invoquer avec le mcanisme P/Invoke puisquelles ont toutes les deux le drapeau pinvokeimpl. Nanmoins, on remarque que le compilateur C++/CLI a gnr du code natif pour localiser la mthode Beep()
contenue dans la DLL kernel32.dll tandis que la version C  compte sur le CLR pour raliser cet
opration. Dautres dirences sont noter comme lutilisation dans la version C++/CLI de lattribut SuppressUnmanagedCodeSecurity (dcrit page 201) et du modificateur IsLongModifier
qui rsout les problmes dus au fait que les mots-cls C++/CLI long et int se rfrrent tous deux
au type Int32.

Types grs et types non grs


Dans la version 2 du langage C++/CLI, un type marqu avec un des mots-cls ref, value,
interface ou enum est un type gr tandis quun type non marqu par un de ces mots-cls
est natif. En outre, une mthode dfinie aprs le #pragma managed sera compile en langage
IL tandis quune mthode dfinie aprs le #pragma unmanaged sera compile en code natif. Le
langage C++/CLI permet :

274

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

La dfinition de types non grs avec des mthodes dont le corps contient du code natif.

La dfinition de types non grs avec des mthodes dont le corps contient du code IL.

La dfinition de types grs avec des mthode dont le corps contient du code IL.

Les #pragma managed et unmanaged doivent tre dfinis hors dun type pour prendre eet. Un
type ne peut donc avoir la fois des mthodes natives et gres. En outre, le langage C++/CLI ne
permet pas la dfinition de types grs avec des mthodes dont le corps contient du code natif.
Tout cela est illustr par le programme suivant :
Exemple 8-8 :
// compile with: /clr
#include "stdafx.h"
#using <mscorlib.dll>
#pragma unmanaged
class TypeNatifCodeNatif{
public:
TypeNatifCodeNatif(int state){ m_State = state ; }
int GetEtat(){ return m_State ; }
private:
int m_State ;
} ;
#pragma managed
class TypeNatifCodeIL {
public:
TypeNatifCodeIL(int state){ m_State = state ; }
int GetEtat(){ return m_State ; }
private:
int m_State ;
} ;
ref class TypeGereCodeIL {
public:
TypeGereCodeIL(int state){ m_State = state ; }
int GetEtat(){ return m_State ; }
private:
int m_State ;
} ;
int main(){
TypeNatifCodeNatif* o1 = new TypeNatifCodeNatif(1) ;
int i1 = o1->GetEtat() ;
delete o1 ;
TypeNatifCodeIL* o2 = new TypeNatifCodeIL(2) ;
int i2 = o2->GetEtat() ;
delete o2 ;
TypeGereCodeIL^ o3 = gcnew TypeGereCodeIL(3) ;
int i3 = o3->GetEtat() ;
return 0 ;
}

Introduction linteroprabilit avec C++/CLI

275

Si vous compilez ce code et que vous analysez lassemblage produit avec loutil ildasm.exe
vous vous apercevrez que le compilateur a prvu deux types valeurs grs TypeNatifCodeIL
et TypeNatifCodeNatif pour permettre dutiliser les types natifs sous-jacent partir du code
gr. Ces deux types grs ne prsentent aucun membre. Leurs tats sont stocks par les types
natifs. Les types natifs sont stocks dans des sections binaires de lassemblage qui ne sont pas
visualisables partir des outils ildasm.exe et Reflector.
On remarque la prsence des quatre mthodes gres publiques statiques TypeNatifCodeIL.GetEtat(...), TypeNatifCodeIL.{ctor}(...), TypeNatifCodeNatif.GetEtat(...) et
TypeNatifCodeNatifL.{ctor}(...). Il ny a pas despace de noms TypeNatifCodeNatif ou
TypeNatifCodeIL. Le langage IL accepte les noms de mthodes contenant un point. Il est intressant de remarquer que les deux mthodes .GetEtat() prennent un paramtre reprsentant
la rfrence this. En outre, les deux mthodes relatives TypeNatifCodeIL contiennent du code
IL alors que les deux mthodes relatives TypeNatifCodeNatif ont le drapeaux pinvokeimpl
indiquant quelle appelle une mthode native au moyen de P/Invoke.
Enfin, si vous visualisez le code IL de la mthode main(), vous verrez que le compilateur
C++/CLI utilise les types de lespace de noms System.Runtime.CompilerServices pour rendre
possible la magie du code IL qui manipule des types natifs.

Utiliser des objets grs partir du code natif


Le problme fondamental lors de lutilisation dun objet gr par du code natif est que vous ne
pouvez pas utiliser de pointeur vers lobjet gr moins de le fixer en mmoire. Cependant, le
ramasse-miettes perd beaucoup en ecacit lorsquil y a des objets fixs. Il a donc fallu trouver
une autre notion que celle de pointeur pour rfrencer les objets grs partir du code natif.
Le ramasse-miettes implmente un systme de pointeurs logiques, aussi nomms handles. Un
handle est un numro sur quatre octets. Le ramasse-miettes fait en sorte que chaque objet gr
ait son propre handle. Le code natif peut rfrencer un objet gr partir de son handle.
Un handle vers une instance dun type gr T est reprsente en C++/CLI avec lexpression T.
Le framework .NET prsente la structure gre System.Runtime.InteropServices.GCHandle qui
permet de faire la passerelle entre les objets grs et leurs handles. Cependant, le code natif na
pas directement accs aux types grs. Vous navez donc pas la possibilit dutiliser la structure
gre GCHandle() ni la syntaxe T partir du code natif.
Pour rfrencer un objet gr de type T partir du code natif, vous devez utiliser une instance de
gcroot<T>. gcroot<T> est une structure native qui utilise le mcanisme de template de C++/CLI
et dont les mthodes sont en IL. Il est instructif de regarder la dfinition de gcroot<T> dans le
fichier gcroot.h. Vous y verrez :

Un champ _handle de type void*. Ce champ ne peut tre de type GCHandle. En eet, a
structure gcroot<T> est native et ne peut avoir de champs de type gr tels que GCHandle.
Les mthodes de gcroot<T> peuvent utiliser des instances de GCHandle de puisquelles sont
compiles en IL. La passerelle entre une instance de GCHandle et le champ _handle est assure par des oprateurs statiques de conversion de la structure GCHandle. Pour simplifier
ces oprations, le fichier gcroot<T> utilise les deux macros suivantes :
__GCHANDLE_TO_VOIDPTR(x)
((GCHandle::operator System::IntPtr(x)).ToPointer())
__VOIDPTR_TO_GCHANDLE(x)
(GCHandle::operator GCHandle(System::IntPtr(x)))

276

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


Le destructeur de la structure gcroot<T> libre le handle. Lorsque le code natif appelle
ce destructeur il signifie donc au ramasse-miettes que lobjet gr sous-jacent pourra tre
dtruit si il ny a plus dautres rfrences vers celui-ci.

Dans lexemple suivant, la classe TypeNatifCodeNatif garde une rfrence vers une chane de
caractre gre. Comme vous pouvez le voir, le recours gcroot est rduit au strict ncessaire
et vous ne voyez pas apparatre la structure gre GCHandle :
Exemple 8-9 :
// compile with: /clr
#include "stdafx.h"
#include <vcclr.h>
using namespace System ;
#pragma unmanaged
class TypeNatifCodeNatif {
public:
TypeNatifCodeNatif( gcroot<String^> s ) {m_s = s;}
gcroot<String^> m_s ;
} ;
#pragma managed
int main() {
TypeNatifCodeNatif* obj = new TypeNatifCodeNatif("Bonjour") ;
Console::WriteLine( obj->m_s ) ;
delete obj ;
}
Pour exploiter un objet gr partir du code natif vous devez dvelopper vous-mme un type
natif avec du code gr qui fait la passerelle entre les deux mondes. Lexemple suivant montre
comment du code natif qui dtient une rfrence (en fait un handle) sur une instance gre de
type string peut invoquer la mthode get_Length() sur cette instance :
Exemple 8-10 :
// compile with: /clr
#include "stdafx.h"
#include <vcclr.h>
using namespace System ;
#pragma managed
class TypeNatifCodeIL {
public:
static int Lentgh(gcroot<String^> s){
return s->Length;
}
};
#pragma unmanaged
class TypeNatifCodeNatif {
public:
TypeNatifCodeNatif(gcroot<String^> s) {
m_Length = TypeNatifCodeIL::Lentgh(s) ;
}

.NET et les Handles win32

277

int m_Length ;
} ;
#pragma managed
int main() {
TypeNatifCodeNatif* obj = new TypeNatifCodeNatif("Bonjour") ;
Console::WriteLine( obj->m_Length ) ;
delete obj ;
}

.NET et les Handles win32


Les handles du ramasse-miettes prsents dans la section prcdente sont conceptuellement proches des handles win32 que nous allons voir. Cependant, ces deux sortes de
handles appartiennent des domaines compltement disjoints.

Introduction
Une application excute sous un systmes Windows a la possibilit dexploiter des ressources
systmes telles que les fichiers, le registre, les pilotes, les processus, les threads, les mutex, les
pipes nomms, les sokets, les fentres etc. Une mme ressource systme peut tre exploite
simultanment par plusieurs processus (on peut citer par exemple les mutex nomms). Une
ressource systme ne peut donc pas tre rfrence par un pointeur puisquelle nappartient
pas lespace dadressage dun processus. En consquence, les processus Windows accdent aux
ressources systmes par lintermdiaire de pointeurs logiques nomms handles.
Toutes les fonctions win32 permettant la manipulation dune ressource systme acceptent un
paramtre en entr de type HANDLE. Un handle est un numro cod sur quatre octets. Chaque
processus Windows maintient en interne une table dassociation entre les handles et les ressources systmes utilises. Deux ressources systmes ne peuvent donc pas tre rfrence par
le mme handle au sein dun processus. Deux handles rfrenant une mme ressource partir
de deux processus distincts peuvent tre deux entiers dirents.
Une ressource systme est cre lors de lappel de certaines fonctions win32 telles que CreateFile(), CreateMutex() ou CreateThread(). Ces fonctions ont la particularit de retourner un
handle. Lappel une de ces fonctions ne cre pas ncessairement une ressource. Par exemple
vous pouvez rcuprer un handle vers un mutex existant en appelant la fonction CreateMutex() paramtre avec le nom du mutex. La fonction win32 CloseHandle() permet de signifier
Windows que le processus appelant na plus besoin de la ressource systme rfrence. Windows
dtruit une ressource systme lorsque plus aucun processus ne maintient de handle vers elle.
Le compteur de performance Windows Processus/Nombre de handles vous permet de connatre
le nombre de handles couramment dtenus par un processus. La colonne Handles du gestionnaire des tches vous permet aussi de visualiser ce nombre en temps rel.

La classe HandleCollector
Une instance de la classe System.Runtime.InteropServices.HandleCollector permet de fournir au CLR une estimation du nombre de handles actuellement dtenu par le processus. Vous

278

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

pouvez prciser un seuil initial et un seuil maximum partir duquel le ramasse-miettes lancera
une collecte. En eet, le ramasse-miettes na aucune connaissance de la quantit de mmoire
non gre maintenue par des ressources systmes et cette classe permet de pallier cette faiblesse.
Une instance de HandleCollector peut tre nomme de faon ntre concerne que par les
handles vers un certain type de ressource.

Les classes SafeHandle et CriticalHandle


Avant la version 2.0 de .NET, un handle tait rfrenc par une instance du type IntPtr. Cela
posait plusieurs problmes :

Il ny avait pas de vrification de type la compilation. Par exemple, rien ne vous empchez
de communiquer un handle sur une fentre (de type win32 HWND) une fonction win32 qui
a besoin dun handle sur un fichier (de type win32 HFILE).

Vous naviez pas de garanties quant la libration dun handle. Une exception de type
ThreadAbortException ou OutOfMemory pouvait compromettre cette opration.

Vous tiez expos une situation de comptition (race condition) quant la fermeture du
handle. Rien nempchait un thread dutiliser un handle pendant quun autre thread tait
en train de le fermer. Cela pouvait mme mener un problme de scurit connu sous
le nom de handle-recycling attack o du code malveillant exploitait une ressource en train
dtre ferme.

Le framework .NET 2.0 ore un moyen de pallier ces problmes avec les classes abstraites
System.InteropServices.CriticalHandle et System.InteropServices.SafeHandle. Lide
est de prvoir une classe non abstraite drive dune de ces classes pour grer le cycle de
vie dun type de handle. Cette classe doit tre drive de SafeHandle pour implmenter
un type de handle qui supporte un compteur de rfrences. Sinon cette classe doit driver
de CriticalHandle. Les deux classes SafeHandle et CriticalHandle drivent de la classe
System.Runtime.ConstrainedExecution.CriticalFinalizer. Et ont donc un finaliseur critique. Les finaliseurs critiques sont dcrits en page 129. De ce fait, on obtient certaine garantie
de fiabilit sur lopration de fermeture dun handle.
Vous pouvez aussi driver des classes abstraites CriticalHandleMinusOneIsInvalid, SafeHandleMinusOneIsInvalid et SafeHandleZeroOrMinusOneIsInvalid dont les noms sont loquents
quant aux services de dure de vie oerts. Ces classes sont dans lespace de noms Microsoft.
Win32.SafeHandles.

Utilisation de COM partir de .NET


Avec le temps, la technologie COM est devenue de plus en plus complique utiliser et comprendre, tel point que certaines entreprises se sont spcialises dans le dveloppement de composants COM. Au dbut du projet .NET, Microsoft avait le souhait de construire une nouvelle
technologie en saranchissant des contraintes de compatibilit ascendante avec les technologies existantes. Cependant, Microsoft ayant prconis lutilisation de la technologie COM depuis
plusieurs annes, il existe maintenant des millions de composants COM travers le monde.
Il ntait pas envisageable dobliger les entreprises re-dvelopper tous leurs composants sous
.NET, et il tait obligatoire de ne pas construire .NET au-dessus de COM. La seule solution
tait de construire un ensemble de classes et doutils qui permettraient dutiliser les composants
COM sans les recompiler, partir du code dvelopp sous .NET. Cest ce que nous allons exposer.

Utilisation de COM partir de .NET

279

Mtadonnes de types et bibliothque de types


Bien que rpondant au mme besoin, la cration de liens prcoces avec les classes COM et la
cration de liens prcoces avec les classes .NET sont techniquement parlant, trs dirents. La
dirence principale est que le code gr .NET a besoin de consulter les mtadonnes de
types avant de crer un lien avec une mthode dune classe .NET (que le lien soit prcoce
ou tardif). Cela vient du fait que le code IL utilise la notion de jeton de mtadonnes la place
de la notion dadresse. Voici quelques autres dirences :

Les classes COM ne se manipulent quau travers dinterfaces COM.

Le passage des arguments ne se fait pas de la mme manire.

Les classes COM nadmettent pas de constructeurs avec paramtres.

Les composants COM (i.e les quivalents dans la technologie COM des assemblages, des DLLs en
gnral mais aussi des excutables) prsentent optionnellement des mtadonnes, contenues
dans ce que lon appelle une bibliothque de types (type library en anglais). Une bibliothque de
types peut tre contenue directement dans le composant COM ou dans un fichier part, dextension tlb. En plus du fait quun composant COM na pas obligatoirement une bibliothque
de types, le formatage binaire des mtadonnes au sein des bibliothques de types est totalement
dirent du formatage binaire des mtadonnes dans les assemblages .NET.

Assemblages inter-oprables et classes Runtime Callable Wrapper


Microsoft a cr loutil type library importer (tlbimp.exe) pour construire un assemblage .NET
directement partir dun composant COM dont la bibliothque de types est disponible. Un
assemblage cr partir de tlbimp.exe est qualifi dassemblage interoprable (interop assembly
en anglais). Nous verrons comment utiliser partir de .NET des classes COM dun composant
COM dont la bibliothque de types nest pas accessible, dans la prochaine section. la fin de la
prsente section, nous verrons que lutilisation de Visual Studio peut viter davoir manipuler
loutil tlbimp.exe.
Voici un fichier IDL (Interface Definition Langage) qui dcrit un composant COM nomm
COMComposant. Il est clair que ce composant COM admet une bibliothque de types qui
contient la classe COM CClassCOM. Cette classe admet une interface propritaire qui sappelle
IClasseCOM. Cette interface prsente la fonction CalcSomme() qui calcule la somme de ses deux
premiers paramtres et la retourne dans le troisime paramtre, qui est un paramtre de sortie.
Exemple :

COMComposant.idl

[
object,
uuid(947469B1-61EB-4010-AE29-8380C2D577E9),
dual,
helpstring("IClasseCOM Interface"),
pointer_default(unique)
]
interface IClasseCOM : IDispatch{
HRESULT CalcSomme([in]int a, [in]int b, [out,retval] int *pResult) ;
} ;

280

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


[ version(1.0),
uuid(8A4F713C-910F-4EE0-8A26-B870FBE58596),
custom(a817e7a1-43fa-11d0-9e44-00aa00b6770a,
"{23391CA9-529E-43CF-9BF6-C636BF96BF26}"),
helpstring("COMComposant 1.0 Type Library") ]
library COMComposant {
importlib("stdole2.tlb") ;
importlib("olepro32.dll") ;
[
version(1.0),
uuid(1E3B6413-7E63-42B5-874D-E0A27A42190C),
helpstring("CClasseCOM Class")
]
coclass CClasseCOM{
interface IClasseCOM;
};
}

Voici la ligne de commande utiliser pour obtenir un assemblage interoprable partir de ce


composant COM. tlbimp.exe nous ore la possibilit dajouter un espace de noms avec la directive /namespace. Cet outil prsente de nombreuses autres directives dcrites dans les MSDN
larticle Type Library Importer (tlbimp.exe).
>tlbimp.exe COMComposant.dll /out:AsmCOMComposant.dll
/namespace:TestInterop
Si on analyse lassemblage AsmCOMComposant.dll avec ildasm.exe, on saperoit quil contient
entre autres une classe nomme CClasseCOMClass et une interface nomme IClassCOM. Linterface IClassCOM prsente la mthode CalcSomme() et la classe CClasseCOMClass implmente
linterface IClassCOM.
Limplmentation de la mthode CalcSomme() dans la classe CClasseCOMClass ne contient
que le code pour transformer lappel dune mthode dune type .NET, en un appel vers la
mthode COM IClasseCOM.CalcSomme(). Dans le jargon .NET on dit que la classe .NET
CClasseCOMClass est une classe Runtime Callable Wrapper (RCW). En terminologie design patterns
(Gof), on dit dune telle classe quelle est un adaptateur, car elle adapte linterface de lobjet COM
une interface que le client peut appeler. On peut aussi dire que CClasseCOMClass est une classe
proxy, dans le sens ou tous les appels sont dabord intercepts, pour eectuer un traitement sur
les arguments. Aprs interception, les appels sont eectus directement sur la classe COM sousjacente.
Voici le code dun programme C  qui utilise lassemblage interoprable AsmCOMComposant.dll.
Naturellement il faut qu la compilation de ce programme, on prcise une rfrence vers
AsmCOMComposant.dll :
Exemple 8-11 :
using TestInterop ;
class Program {
static void Main() {
IClasseCOM foo = new CClasseCOMClass() ;
int result = foo.CalcSomme(2, 3) ;

DotNETClient.cs

Utilisation de COM partir de .NET


Processus

281
Assemblage inter-oprable

CLR
IInterface1
Client
.NET

IInterface2

IInterface1
RCW

IInterface2

Objet
COM

IUnknown

Figure 8 -1 : Utilisation dune classe COM partir dun client .NET


System.Console.WriteLine("Resultat :{0}", result) ;
}
}
Notez qu la dirence dune classe COM, une classe RCW peut tre directement appele, sans
passer par aucune interface. On aurait pu crire :
...
CClasseCOMClass foo = new CClasseCOMClass() ;
int Result = foo.CalcSomme(2,3) ;
...
Si le classe COM supporte plusieurs interfaces propritaires, tlbimp.exe gnrera autant dinterfaces .NET. En outre, toutes les mthodes de ces interfaces seront dclares et accessibles publiquement dans la classe RCW.
Lutilisation de Visual Studio peut viter davoir manipuler loutil tlbimp.exe. Il sut dajouter
une rfrence au projet directement vers le composant COM, exactement comme si vous rajoutiez une rfrence vers un autre assemblage (voir la Figure 8 -2). Lassemblage interoprable
contenant les classes RCW sera automatiquement construit et plac dans le mme rpertoire
que lassemblage concern.
Les assemblages interoprables et les composants COM dune application .NET font bien videmment partie des fichiers dployer avec votre application .NET.

Accder aux classes dun composant COM sans utiliser


de bibliothque de types
Le framework .NET prsente la possibilit de crer ses propres classes et interfaces RCW pour accder aux instances dune classe COM sans utiliser la bibliothque de types et loutil tlbimp.exe.
Cette fonctionnalit est essentiellement utilise lorsquun composant COM na pas de bibliothque de types, ou lorsquun composant COM a une bibliothque de types non disponible au
moment du dveloppement.
Cette fonctionnalit est assez fastidieuse utiliser. Il faut redfinir entirement chaque interface
COM et chaque classe COM que vous utilisez. Pour chaque argument et chaque valeur de retour
de chaque mthode il faut utiliser lattribut System.Runtime.InteropServices.MarshalAs.
Pour le composant COM de la page 279, il faudrait crire :

282

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Figure 8 -2 : Ajout dune rfrence vers un composant COM partir de Visual Studio .NET
Exemple 8-12 :
using System ;
using System.Runtime.InteropServices ;
[
ComImport,
Guid("947469B1-61EB-4010-AE29-8380C2D577E9"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IClasseCOM {
[return : MarshalAs(UnmanagedType.I4)]
int CalcSomme(
[In,MarshalAs(UnmanagedType.I4)] int a,
[In,MarshalAs(UnmanagedType.I4)] int b,
[Out,MarshalAs(UnmanagedType.I4)]out int c) ;
}
[
ComImport,
Guid("1E3B6413-7E63-42B5-874D-E0A27A42190C")
]
public class CClasseCOM {}
class Program {
static void Main() {
IClasseCOM foo = new CClasseCOM () as IClasseCOM ;
int result ;
foo.CalcSomme(2, 3, out result) ;
System.Console.WriteLine("R
esultat :{0}", result) ;
}
}

Utilisation de COM partir de .NET

283

Import dun ActiveX avec Visual Studio .NET


Un ActiveX est le terme employ pour dcrire une classe COM graphique. On peut aussi utiliser
le terme contrle ActiveX ou le terme OCX. Les contrles ActiveX sont stocks dans des composants COM dextension .dll ou dextension .ocx. Il existe un trs grand nombre de contrles ActiveX disponibles gratuitement ou non sur internet. Il existe des OCX pour visionner des images,
des animations, des OCX pour parcourir les rpertoires, pour diter des numros de tlphones
etc. De plus chaque technologie graphique (Flash de Macromedia, MapPoint de Microsoft etc) est
en gnral fournie avec un contrle ActiveX permettant de lutiliser. Comme vous le voyez il
existe un trs vaste choix de contrles ActiveX. La bonne nouvelle est que Visual Studio permet
de les utiliser dans vos programmes .NET aussi simplement que sils taient des contrles graphiques .NET. Dans le chapitre consacr la technologie Windows Form nous exposons comment utiliser des contrles graphiques .NET mais intressons-nous ici limport de contrles
ActiveX dans vos formulaires .NET grce Visual Studio.
Il est prfrable de prparer le terrain, et dinsrer un onglet spcial pour nos imports de
contrles ActiveX dans la bote outils (toolbox) des composants graphiques de Visual Studio.
Pour cela cliquez droit sur cette boite outils et choisissez le menu Ajouter un onglet (Add
Tab) et donnez un nom votre nouvel onglet (par exemple ActiveX ). Votre bote outils
doit ressembler maintenant celle de la Figure 8-3.

Figure 8 -3 : Prparation de la bote outils pour importer des OCX


Vous pouvez maintenant ajouter un contrle ActiveX en cliquant droit sur longlet ActiveX
et en choisissant le menu Personnaliser la bote outils (Customize Toolbox...). Vous arrivez sur
la fentre de la Figure 8-4 qui vous permet dajouter soit un contrle ActiveX soit un contrle
graphique .NET dans votre onglet.
Ici nous avons choisi le contrle ActiveX Microsoft Rich Textbox Control qui permet dacher un
document au format RTF. Lorsque nous ajoutons un tel contrle dans une WinForm, nous avons
accs aux fentres de proprits de ce contrle. En interne, un assemblage interoprable a t
cr, contenant les classes RCW adquates.

Spcificits de COM prendre en compte


en manipulant une classe RCW
Gestion du cycle de vie dun objet COM partir de .NET
Le cycle de vie dun objet COM suit des rgles trs prcises qui sont compltement encapsules dans les classes RCW. Notamment les appels la fonction CoCreateInstance(), qui permet de crer un objet COM, sont encapsuls. Les appels aux mthodes de linterface IUnknow
(QueryInterface() qui permet de naviguer entre les interfaces, AddRef() et Release() qui permettent de grer le compteur interne de rfrences) sont aussi encapsuls.

284

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Figure 8 -4 : Ajout dun OCX dans la bote outils


Une classe RCW est une classe gre. Vous ne pouvez dcider exactement quand elle sera dtruite, puisque cette responsabilit incombe au ramasse-miettes. Or, une classe RCW ne dtruit
son objet COM sous-jacent que lorsquelle est elle-mme dtruite. A priori, vous ne pouvez donc
pas dcider du moment de la destruction dun objet COM dans vos programmes .NET. Cette
situation est gnante car en gnral, larchitecture dune classe COM prend en compte le fait
que le client peut choisir le moment de la destruction des instances de cette classe COM.
La classe System.Marshal.InteropServices.Marshal prsente la mthode int ReleaseComObject(object) pour pallier ce problme. Cette mthode prend en paramtre un object qui doit
tre lobjet RCW concern. En interne, un appel cette mthode provoque lappel la mthode
Release() de lobjet COM et ne fait donc que dcrmenter dune unit le compteur interne
de rfrence de lobjet COM. La nouvelle valeur de ce compteur interne est retourne, et vous
pouvez donc tester si lobjet COM est eectivement dtruit en vrifiant que cette valeur vaut
bien 0. Une fois quun objet COM est dtruit, nous vous conseillons de mettre immdiatement
nul les rfrences vers lobjet RCW concern, car toute utilisation de cet objet provoque lenvoi
de lexception InvalidComObjectException. Par exemple :
...
IClasseCOM foo = new CClasseCOMClass() ;
int result = foo.CalcSomme(2,3) ;
System.Runtime.InteropServices.Marshal.ReleaseComObject(foo);
foo = null;
...

Gestion des types de donnes COM partir de .NET


La conversion entre les types de donnes COM et les types de donnes .NET ne pose pas de
problme. Il est nanmoins utile de prciser les conversions suivantes :

Les classes RCW convertissent les Basic String (BSTR) de COM en des instances de la classe
System.String.

Utilisation de COM partir de .NET

285

Les classes RCW convertissent les VARIANT de COM en des instances dune classe drive de
la classe object. La classe .NET de lobjet sous-jacent dpend naturellement du type sousjacent du VARIANT et vous pouvez transtyper cet objet .NET avec loprateur as de C  .

Les classes RCW convertissent les SAFEARRAY de COM en des tableaux grs du type adquat. Par exemple un argument de type SAFEARRAY(BSTR) dans une mthode COM devient un argument de type System.String[] dans la mthode de la classe RCW.

Gestion des erreurs COM partir de .NET


La gestion des erreurs est radicalement dirente entre .NET et COM :

.NET gre les erreurs au moyen dexceptions, gres par le CLR.

COM gre les erreurs au moyen de la valeur de retour de chaque mthode de chaque interface COM. Cette valeur de retour est toujours de type HRESULT. Un HRESULT est une valeur
code sur quatre octets qui prcise si une erreur sest produite lors de lappel dune mthode
sur un objet COM. Le cas chant, le HRESULT contient le type de lerreur et ventuellement
des informations sur la couche logicielle qui a gnr lerreur.

Si lappel une mthode dun objet COM renvoie un HRESULT derreur, une exception de type
COMException est automatiquement leve par le CLR. La proprit ErrorCode de cette exception
contient la valeur du HRESULT.

Gestion des apartments COM partir de .NET


COM permet de grer les anits entre les objets COM et les threads au moyen dapartments
COM. Concrtement, vous pouvez vous assurer que les mthodes des instances dune classe
COM ne peuvent tre excutes que par certains threads. Un processus peut contenir plusieurs
apartments COM. Chaque objet COM est contenu dans un processus et appartient un apartment COM. Chaque thread du processus appartient un apartment COM. Si un thread dun
apartment COM doit eectuer lappel dune mthode dun objet COM qui nest pas dans le
mme apartment COM (voire dans un autre processus), linterface COM concerne doit tre
marshalle de faon pouvoir tre utilise par le thread client. Cette opration de marshalling est ncessaire car le code des mthodes de la classe dun objet COM est toujours excut
par le ou les threads de lapartment COM de lobjet.
Il existe deux sortes dapartment COM les Single Thread Apartment (STA) et les Multi Thread Apartment (MTA) qui contiennent respectivement un thread ou plusieurs threads. Un processus peut
contenir plusieurs STA mais ne peut contenir plus dun MTA. Durant lvolution de la technologie COM, la notion de STA est apparue avant la notion de MTA. Le but de STA tait de dcharger
les dveloppeurs du souci des accs concurrents aux ressources dans une classe COM. En eet,
dans le mode STA, cest le mme thread qui gre les dirents appels aux direntes instances
dune mme classe COM. La gestion STA sest rvle insusante pour certaines applications
et il a fallu introduire la notion de MTA, qui a redonne au dveloppeur la responsabilit de
grer les accs concurrents aux ressources.
Les threads grs de .NET connaissent cette notion dapartment COM. Plus exactement, un
thread gr sait si son thread Windows sous-jacent est STA ou MTA. Vous pouvez positionner
la proprit ApartmentState dune instance de la classe Thread avec une des valeurs de lnumration System.Threading.ApartmentState, savoir STA, MTA ou Unknown (qui est la valeur
prise par dfaut). Cette proprit ne peut tre modifie quune seule fois dans lexistence dun
thread gr.

286

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Vous pouvez aussi spcifier lapartment COM dun thread en utilisant lun des attributs System.
STAThread ou System.MTAThread sur la mthode qui constitue le point dentre du thread. Par
exemple :
...
[MTAThread]
public static void Main(){
}
...

Lien tardif explicite sur les classes COM


Bien avant la venue de .NET et de son mcanisme de rflexion, la technique de lien tardif explicite tait disponible sur les classes COM dont les interfaces tendaient linterface IDispatch.
IDispatch prsente notamment la mthode GetDispID() qui permet davoir la position de la
dclaration dune mthode dans une interface, en fonction du nom de la mthode. Cette position sappelle DISPID. IDispatch prsente aussi la mthode Invoke() qui permet dappeler une
mthode en connaissant son DISPID. Lutilisation de ces deux mthodes permet respectivement
de crer des liens tardifs explicites avec des classes COM et dinvoquer la mthode avec un tel
lien.
Vous pouvez exploiter le mcanisme de liens tardifs explicites IDisptach de COM, partir du
mcanisme de liens tardifs explicites de .NET. Pour utiliser cette possibilit avec la classe du
composant COM de la page 279 il faudrait crire :
Exemple 8-13 :
using System ;
using System.Reflection ;
class Program {
static void Main() {
Type type = Type.GetTypeFromProgID("COMComposant1.CClasseCOM");
// On peut aussi ecrire :
// Type type = Type.GetTypeFromCLSID(
//
new System.Guid("1E3B6413-7E63-42B5-874D-E0A27A42190C")) ;
Object obj = Activator.CreateInstance(type) ;
Object[] args = new Object[] { 3, 4 } ;
int retVal = (int) type.InvokeMember(
"CalcSomme",
BindingFlags.InvokeMethod,
null,
obj,
args) ;
Console.WriteLine(retVal) ;
}
}
Notez lobtention du type partir de son PROGID ou de son CLSID, grce lune des mthodes
statiques de la classe Type, GetTypeFromProgID() ou GetTypeFromCLSID().

Utilisation de COM partir de .NET

287

Utiliser une classes COM sans lenregistrer dans le registre


Windows XP ainsi que les versions postrieures prsentent la possibilit dutiliser une classe COM
sans lenregistrer dans la base de registres. Lide est de fournir un fichier qui contient lassociation entre les CLSIDs et les DLLs COM des classes COM utilises pour chaque application. Lors
du lancement de lapplication, Windows XP charge les donnes de cette table. Durant lexcution, lorsquune classe COM doit tre charge, Windows XP consulte au pralable cette table. Si
la classe COM nest pas trouve par cette technique, le processus classique de localisation du
composant COM contenant la classe par consultation de la base des registre est alors enclench.
Vous pouvez exploiter cette technique nomme registration free COM (ou reg free COM) partir
dun assemblage .NET. Pour cela, il sut de fournir un fichier ayant le mme nom que lassemblage avec lextension .manifest contenant les donnes dassociation entre les CLSIDs et les
DLLs COM au format XML comme ceci (MyAsm.exe est le nom de lassemblage en loccurrence,
donc le nom du fichier est MyAsm.exe.manifest) :
Exemple :

MyAsm.exe.manifest

<?xml version="1.0" encoding="utf-8"?>


<assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1
assembly.adaptive.xsd"
manifestVersion="1.0"
xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ... >
<assemblyIdentity name="MyAsm.exe" version="1.0.0.0" type="win32" />
<file name="[Chemin relatif ou absolu]/MyCOMComponent.dll"
asmv2:size="20480">
<typelib tlbid="{ea995c49-e5d0-4f1f-8489-31239fc9d9d0}"
version="2.0" helpdir=""
resourceid="0" flags="HASDISKIMAGE" />
<comClass clsid="{97b5534f-3b96-40a4-88b8-19a3bf4eeb2e}"
threadingModel="Apartment"
tlbid="{ea995c49-e5d0-4f1f-8489-31239fc9d9d0}"
progid="MyCOMComponent.MyClass" />
<comClass ... />
</file>
<file ... />
...

Visual Studio 2005 permet dexploiter simplement cette technique. Pour cela, il faut que
vous positionniez lattribut Isolated dune rfrence COM true. Cela fonctionne aussi si
la rfrence COM est un OCX. La compilation du projet fait alors en sorte de crer le fichier
dextension .manifest dans le rpertoire de sortie. Elle copie aussi les DLLs composants
COM dans ce rpertoire. Reg free COM est particulirement utile si vous dsirez avoir recours des classes COM dans un projet dploy la XCopy, par exemple avec la technologie
ClickOnce.
Prcisons que pour exploiter cet attribut il faut que les classes COM soient enregistres dans
la base des registres de la machine qui ralise la compilation. En outre, il vaut mieux tester
ce genre dapplication sur une machine vierge. En eet, une utilisation dfectueuse de reg
free COM ne serait pas dtecte sur une machine sur laquelle les composants COM utiliss
sont enregistrs.

288

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Sachez que vous pouvez vous passer de reg free COM en eectuant le travail de localisation, de
chargement et dinstanciation de la classe COM vous-mme. Cette technique est utile si votre
dploiement seectue potentiellement sur des versions antrieures Windows XP. Pour chaque
classe et composant COM il faut :

Localiser la DLL composant COM.

La charger en mmoire avec la fonction win32 LoadLibrary().

Localiser la fonction DllGetClassObject() exporte par la DLL composant COM. Pour


cela, il faut avoir recours la fonction win32 GetProcAddress().

Appeler cette fonction DllGetClassObject() pour obtenir un objet COM implmentant


linterface COM IClassFactory.

Appeler la mthode IClassFactory.CreateInstance() pour crer un objet COM.

Encapsuler une classe .NET dans une classe COM


Notion de CCW
Dans un processus de migration dune application vers .NET, il peut arriver que certains composants soient migrs avant dautres. Il se peut que du code non gr doive instancier et utiliser une
classe .NET. cette fin, le framework .NET nous permet dencapsuler une classe .NET dans un
COM Callable Wrapper (CCW). Un CCW est un objet COM cr et gr automatiquement par le
CLR. Un tel objet COM encapsule un objet .NET, pour faire en sorte que ce dernier puissent tre
accessible comme un objet COM. Un CCW est cr durant lexcution par le CLR, partir des
mtadonnes de la classe .NET (et non partir dune quelconque bibliothque de types COM).
Il existe au plus un CCW par objet .NET, quel que soit le nombre de clients souhaitant lutiliser
comme un objet COM. Un objet .NET ayant un CCW peut aussi tre utilis par des clients .NET.
Dans ce cas, le fait que lobjet .NET ait un CCW est compltement transparent pour les clients
.NET. La Figure 8 -5 rsume linteraction entre le client non gr et lobjet .NET au moyen dun
CCW.
Processus
IInterface
Code
client
dobjets
COM

CLR

IInterface

IDispatch

Objet
.NET

CCW
IUnknown
Client .NET

Figure 8 -5 : Utilisation dun objet .NET par lintermdiaire dun CCW


Il est important de noter que le modle objet propos par COM est assez restrictif par rapport
au modle objet propos par .NET. Pour tre utilise comme une classe COM normale par
lintermdiaire dun CCW, une classe .NET doit satisfaire les contraintes suivantes :

Encapsuler une classe .NET dans une classe COM

289

La classe .NET doit avoir un constructeur sans arguments, appel aussi constructeur par
dfaut. En eet, COM ne supporte pas la notion de constructeur avec arguments. Parmi les
constructeurs de la classe .NET, seul ce constructeur sans argument pourra tre appel par
lintermdiaire du CCW.
Seules les classes publiques dun assemblage peuvent tre encapsules dans un CCW.
La classe .NET peut avoir des membres statiques, mais ils ne seront pas utilisables par lintermdiaire dun CCW puisque COM ne supporte pas cette notion de membres statiques.
Les surcharges dune mthode de la classe .NET seront renommes, car COM ne supporte
pas cette notion. Concrtement, un blanc soulign est mis avant le nom de chaque mthode
surcharge, et un numro est mis la fin du nom de la mthode.

Produire une bibliothque de types COM dcrivant les classes CCW


partir dun assemblage .NET
Loutil Type Library Exporter tlbexp.exe permet de fabriquer une bibliothque de types COM
dcrivant les classes et interfaces .NET publiques contenues dans un assemblage. Montrons comment utiliser tlbexp.exe au moyen dun exemple simple. Voici du code C  :
Exemple 8-14 :

dotNET2COM.cs

namespace Test {
public interface ICalc {
int CalcSomme(int a, int b) ;
}
public class CCalc : ICalc {
public int CalcSomme(int a, int b) {
return a + b ;
}
}
}
Pour produire lassemblage dotNET2COM.dll partir de ce fichier C  , il sut de taper la ligne
de commande suivante :
>csc.exe -t:library dotNET2COM.cs
Pour produire une bibliothque de types COM dcrivant le CCW qui encapsule la classe CCalc
et linterface ICalc, il sut de taper la ligne de commande suivante :
>tlbexp.exe dotNET2COM.dll /out:dotNET2COM.tlb

Comprenez bien que le CCW est produit lexcution par le CLR. Durant cette opration
le CLR na pas besoin dune bibliothque de types. La bibliothque de type nest utile que
pour les clients qui souhaitent eectuer un lien prcoce COM avec le CCW.
Nous pouvons visualiser la bibliothque de types dotNET2COM.tlb au moyen de loutil OLE
Viewer (oleview.exe). Cet outil est accessible par le menu Outils de Visual Studio .NET. Il est
aussi accessible en ligne de commande. OLE Viewer comporte le menu Fichier  Visualiser une
bibliothque de types... . Rappelons que les bibliothques de types sont dcrites dans un format
binaire, et quil est donc ncessaire de disposer dun tel outil pour les visualiser :

290

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


[
uuid(824A9F5F-39AE-35BB-8741-7C4C8F7DB26C),
version(1.0),
custom(90883F05-3D28-11D2-8F17-00A0C9A6186D,
dotNET2COM,
Version=0.0.0.0,
Culture=neutral,
PublicKeyToken=null)
]
library dotNET2COM{
importlib("mscorlib.tlb") ;
importlib("stdole2.tlb") ;
// Forward declare all types defined in this typelib
interface ICalc ;
interface _CCalc ;
[
odl,
uuid(72E3095D-B243-37F0-A95A-41ABBE13E029),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.ICalc)
]
interface ICalc : IDispatch {
[id(0x60020000)]
HRESULT CalcSomme(
[in] long a,
[in] long b,
[out, retval] long* pRetVal);
};
[
uuid(A51C81A3-4892-39EC-981A-AF77FB4CFD36),
version(1.0),
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.CCalc)
]
coclass CCalc
[default]
interface
interface
};

{
interface _CCalc;
_Object;
ICalc;

[
odl,
uuid(84181003-CCB9-3219-B373-629AF4E3B246),
hidden,
dual,

Encapsuler une classe .NET dans une classe COM

291

oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test.CCalc)
]
interface _CCalc : IDispatch {
} ;
} ;
Plusieurs remarques peuvent tre faites :

La classe COM CCalc implmente les interfaces COM suivantes :

ICalc : Cette interface COM reprsente linterface .NET ICalc.

_Object : Cette interface reprsente les mthodes de la classe System.Object dont drive toutes les classes .NET. Linterface _Object est dfinie dans la bibliothque de types
mscorlib.tlb.

_CCalc : Cette interface COM est linterface de classe (class interface en anglais) de la
classe .NET CCalc. Une interface de classe est sense prsenter tous les membres
publiques non statiques dune classe .NET (y compris les membres des types dont
la classe est drive). Cependant, dans notre exemple cette interface de classe ne
contient aucun membre. Lutilisation dinterfaces de classes tant dconseille, le
comportement par dfaut de tlbexp.exe est de ne pas insrer de membres dans
une interface de classe quil produit. Lutilisation dinterfaces de classes est dconseille car elle couple les clients non grs avec des classes gres et non avec des
interfaces. Vous pouvez nanmoins prciser tlbexp.exe que vous souhaitez que
linterface de classe soit correctement construite en utilisant dans votre code C  lattribut System.Runtime.InteropServices.ClassInterface avec la valeur AutoDual. Par
exemple :
...
[ClassInterface(ClassInterfaceType.AutoDual)]
public class CCalc : ICalc{
...
}
...

Les champs publics dune classe admettant une interface de classe, sont matrialiss
dans linterface de classe sous forme de deux accesseurs. De mme, les accesseurs des
proprits des interfaces publiques, sont prsents comme des mthodes dans les interfaces COM gnres par tlbexp.exe.

Une classe ID a t produite automatiquement pour la classe COM CCalc. On aurait pu utiliser lattribut System.RunTime.InteropServices.Guid pour spcifier notre propre attribut
dans le code C  :
...
[Guid("A51C81A3-4892-39EC-981A-AF77FB4CFD36")]
public class CCalc : ICalc{
...
}
...

292

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Une interface ID a t produite automatiquement pour linterface COM ICalc. L aussi,


on aurait pu spcifier notre propre interface ID avec lattribut Guid.

Linterface COM ICalc tend linterface COM IDispatch qui permet de crer des liens tardifs explicites sur les classes COM.

Vous avez la possibilit dutiliser dans votre code .NET lattribut .NET System.RunTime.
InteropServices.COMVisible sur une interface, une classe, une mthode ou un vnement
afin dindiquer tlbexp.exe que lentit concerne ne soit pas visible dans la bibliothque de
types construite. Par exemple :
...
[ComVisible(false)]
public class CCalc : ICalc{
...
}
...
Les structures publiques sont prises en compte par tlbexp.exe. Elles pourront ainsi tre passes
comme arguments des mthodes des classes et des interfaces.

Enregistrer les CCW sur un systme dexploitation Microsoft


Nous avons vu dans la section prcdente comment fabriquer une bibliothque de types COM
partir dun assemblage .NET. Nous allons voir maintenant comment enregistrer cette bibliothque de types dans la base des registres dun systme dexploitation Microsoft. Rappelons que
pour tre utilisable, une classe COM doit imprativement tre enregistre dans la base des registres. Cest principalement linformation dassociation entre le classe ID de la classe COM et
le fichier (DLL ou excutable) contenant eectivement limplmentation de la classe, qui est
sauvegarde dans la base des registres. En eet, une application qui instancie une classe COM
ne connat la classe COM que par son classe ID et na aucune information quant au fichier qui
contient limplmentation de la classe. Il faut donc aller consulter la base des registres pour localiser limplmentation dune classe COM, au moins la premire fois que celle-ci est instancie
dans une application.
Pour enregistrer (ou ds-enregistrer ) les classes COM dun composant COM classique, il suffit dutiliser loutil regsvr32.exe. Pour enregistrer (ou dsenregistrer ) les classes .NET dun
assemblage comme des classes COM, il faut utiliser loutil Assembly Registration Tool regasm.exe
spcialement conu pour cette tche. Par exemple :
>regasm.exe dotNET2COM.dll
Cet outil est paramtrable, avec plusieurs options dcrites dans les MSDN larticle Assembly Registration Tool (Regasm.exe). Loption /regfile permet dindiquer regasm.exe quil
faut produire le fichier de mise jour de la base des registres, dextension .reg. Lutilisation de
loption /regfile entrane que la base des registres nest pas modifie. Lexcution dun fichier
.reg met jour la base des registres. Dans un fichier .reg, les informations de mise jour sont
encodes dans un format texte. Il est ainsi ais de les consulter et de les modifier. Par exemple :
>regasm.exe dotNET2COM.dll /regfile:dotNET2COM.reg
Voici le fichier produit :

Encapsuler une classe .NET dans une classe COM


Exemple :

293
dotNET2COM.reg

[HKEY_CLASSES_ROOT\Test.CCalc]
@="Test.CCalc"
[HKEY_CLASSES_ROOT\Test.CCalc\CLSID]
@="{A51C81A3-4892-39EC-981A-AF77FB4CFD36}"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}]
@="Test.CCalc"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}
\InprocServer32]
@="C:\WINDOWS\System32\mscoree.dll"
"ThreadingModel"="Both"
"Class"="Test.CCalc"
"Assembly"="dotNET2COM, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null" "RuntimeVersion"="v1.0.3705"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}\ProgId]
@="Test.CCalc"
[HKEY_CLASSES_ROOT\CLSID\{A51C81A3-4892-39EC-981A-AF77FB4CFD36}
\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
La cl InprocServer32, qui est sense contenir le chemin et le nom du fichier qui contient limplmentation de la classe COM, est gale mscoree.dll. Cette DLL, appele DLL cale, permet
le chargement du CLR dans un processus. Les quatre composantes du nom fort de lassemblage
qui contient limplmentation de la classe .NET sont spcifies dans la sous-cl Assembly. Lorsquune application aura besoin de la classe COM qui a pour ProgID Test.Calc, elle chargera
dabord le CLR (si ce nest dj fait pour ce processus) puis elle utilisera le mcanisme de localisation dassemblage, pour localiser lassemblage dotNET2COM.
La sous cl ThreadingModel vaut both, cest--dire que les instances de la classe COM CCW supportent indiremment les modes STA et MTA. Ceci est la consquence du fait que les modles
STA et MTA ne sont pas pris en compte dans .NET. Les objets .NET nont pas danit avec les
threads.
Par dfaut le ProgID de la classe COM produite est {espace de nom}.{nom de la classe}. Vous
pouvez toutefois spcifier un autre ProgID en utilisant lattribut .NET System.RunTime.
InteropServices.ProgId dans votre code .NET. Par exemple :
...
[ProgID("dotNET.CCalc")]
public class CCalc : ICalc{
...
Loutil regasm.exe prsente loption /tlb qui permet de fabriquer une bibliothque de type,
exactement comme avec loutil tlbexp.exe.
>regasm.exe dotNET2COM.dll /tlb:dotNET2COM.tlb

294

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Les outils regasm.exe et tlbexp.exe peuvent tre utiliss indpendamment sur le mme assemblage. On pourrait craindre que les identifiants uniques des classes et des interfaces ne soient pas
les mmes dans les fichiers produits par regasm.exe et dans les fichiers produits par tlbexp.exe.
Heureusement il nen est rien, ce qui nous amne penser que les valeurs de ces identifiants
uniques sont calcules dune manire dterministe partir de lassemblage. Empiriquement, les
identifiants uniques ne changent pas mme si les membres des classes ou des interfaces concernes varient. En revanche, les identifiants uniques changent pour les classes ou les interfaces
dont le nom change.

Utiliser un assemblage .NET comme un composant COM


Nous prsentons ici un exemple de code C++ non gr qui utilise la classe .NET CCalc comme
une classe COM. Grce limport de la bibliothque de types dotNET2COM.tlb, produite partir
de lassemblage dotNET2COM.dll avec tlbexp.exe ou regasm.exe, le code C++ peut eectuer un
lien prcoce avec la classe COM CCalc. Voici le code :
Exemple 8-15 :
#include "stdafx.h"
#import "dotNET2COM.tlb"
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0 ;
// Initialise le thread courant pour quil puisse utiliser COM.
HRESULT hr = ::CoInitialize(NULL);
dotNET2COM::ICalcPtr pI;
// Cree lobjet COM qui contient notre objet .NET.
hr = pI.CreateInstance( __uuidof(dotNET2COM::CCalc));
// Appel dune methode (grace au lien pr
ecoce produit par limport
// de la biblioth`eque de type).
int result = pI->CalcSomme(2,3);
// Lib`ere lobjet COM.
pI = NULL;
// Desinitialise le thread courant pour quil ne puisse
// plus utiliser COM.
::CoUninitialize();
return nRetCode ;
}
Comme lassemblage dotNET2COM.dll que nous avons produit ne peut pas tre plac dans le
GAC (car il na pas de nom fort puisquil nest pas sign), il faut obligatoirement quil soit dans le
rpertoire de lapplication. Noubliez pas denregistrer la classe CCalc avec regasm.exe (comme
dcrit dans la section prcdente) pour excuter cet exemple.

Exception .NET et CCW


Supposons quune lexception soit lance partir du code gr dun objet .NET utilis par du
code natif comme un objet COM. Dans ce cas, le CLR a la responsabilit de grer la transition
entre le code gr et le code non gr. Pour accomplir cette tche, il convertit au mieux les
informations de lexception en un code derreur dans un HRESULT. Le CLR utilise des rgles

Introduction COM+

295

de conversion trs prcises pour convertir les direntes classes dexception et les dirents
HRESULT. Par exemple HRESULT COR_E_DIVIDEBYZERO est converti en une exception de type
DivideByZeroException. Tout ceci fait lobjet de larticle HRESULTs and Exceptions des
MSDN.

Grer le cycle de vie


Nous prcisons ici une faiblesse du mcanisme dutilisation dune classe .NET comme une classe
COM. Lorsquun tel objet nest plus utilis, cest--dire que lorsque le compteur interne de rfrences vers un objet COM CCW arrive 0, lobjet COM CCW est dtruit mais pas lobjet .NET
sous-jacent. Cet objet .NET sera dtruit par le ramasse-miettes ultrieurement, un moment
indtermin.
Contrairement au mcanisme dutilisation dun objet COM partir de .NET, il nexiste pas de
mcanisme spcial pour forcer la destruction dun objet .NET encapsul dans un objet COM
CCW. Ce comportement peut ne pas tre souhaitable, car lobjet .NET peut possder de prcieuses ressources quil faudrait librer ds quil nest plus utilis.
Un problme encore plus gnant peut survenir si lobjet .NET contient des rfrences vers des
objets COM, et que le dveloppeur du code non gr suppose quil ny a plus dobjet COM dans
le processus. Aprs la destruction de lobjet COM CCW le dveloppeur du code non gr peut
appeler la fonction CoUninitialize() pour indiquer quil na plus besoin de la bibliothque
COM dans le thread courant. Cependant, si ce thread est amen ultrieurement dtruire les
objets COM possds par lobjet .NET, il aura un comportement indtermin qui provoquera
srement un crash.
Pour viter les problmes de gaspillage de ressources et ce scnario catastrophe, il est conseill
de prvoir une mthode dans la classe .NET, spcialement prvue pour librer les ressources
dtenues par lobjet .NET. Cette mthode devra tre explicitement appele par le code non gr.

Introduction COM+
Quest ce que COM+ ?
COM+ est le terme dsignant les services dentreprise dans le contexte des applications destines
tre excutes sous les systmes dexploitation Microsoft. COM+ est donc la technologie Microsoft
pour construire des serveurs dapplications. Un service dentreprise de COM+ est une fonctionnalit volue qui peut tre ajoute une classe COM. Ces fonctionnalits sont axes autour du
dveloppement dapplications distribues transactionnelles. On verra la liste de ces fonctionnalits dans la prochaine section mais on peut dj citer le pooling dobjets ou la passerelle avec
des milieux transactionnels non Microsoft.
COM+ 1.0 est apparu avec Windows 2000. COM+ ntait pas une nouvelle version de COM mais
une nouvelle version de la technologie MTS (Microsoft Transaction Server) qui permettait, entre
autres, de raliser des transactions distribues. Par rapport la technologie MTS, on obtenait de
bien meilleures performances avec COM+ 1.0. COM+ 1.5 est apparu avec Windows XP et ajoute
quelques services dentreprise COM+ 1.0.

296

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

COM+ via .NET : les services dentreprises


Il tait ncessaire que le framework .NET ait une technologie de serveur dapplications. Pendant les dernires annes, Microsoft a mobilis beaucoup de ressources pour le dveloppement
du framework .NET. Paralllement, la technologie COM+ orait satisfaction, et beaucoup de
ressources avaient dj ts utilises pour mettre au point cette technologie. Microsoft a donc
prfr capitaliser sur COM+ plutt que de re-dvelopper une nouvelle technologie de serveurs
dapplication.
Chaque service dentreprise COM+ est exploitable via une classe .NET. Le framework .NET
prsente un ensemble dattributs, de classes et doutils permettant vos classes .NET dutiliser
les services dentreprise COM+. Ces attributs et ses classes sont sous lespace de noms System.
EnterpriseServices. Ils sont implments dans lassemblage System.EnterpriseServices.
dll qui se trouve dans le GAC. Lide principale est que le dveloppeur na pas connatre
COM+ dans les dtails pour pouvoir utiliser les services dentreprise COM+ dans ses classes
.NET.

Prsentation des services dentreprise COM+


La liste complte des services dentreprise COM+
La prsentation dtaille de tous les services dentreprise COM+ dpasse le cadre de cet ouvrage.
Voici la liste des services dentreprise de COM+ que vous pouvez assigner vos classes .NET :

Pooling dobjets ;
Lide du pooling dobjet est de recycler les objets pour quils puissent chacun servir plusieurs clients conscutivement. Les cots de la construction et de la destruction dun objet
sont alors diviss par le nombre de clients servis par lobjet. Le pool reprsente le conteneur
des objets qui ne sont pas en train de servir un client.

Activation juste temps (JITA Just In Time Activation) ;


Lide de ce mcanisme est de dsactiver un objet aprs les appels de certaines mthodes,
afin de le rendre utilisable par dautres clients. Rappelons que dsactiver un objet signifie
que COM+ appelle la mthode Deactivate() sur lobjet, puis le met dans le pool dinstances
du composant servi concern. Si un tel pool dobjets nexiste pas, la mthode Dispose() est
appele sur lobjet et il sera dtruit ultrieurement par le ramasse-miettes. Pour indiquer
que les instances dun composant servi supportent le mcanisme JITA, il sut dutiliser
lattribut System.EnterpriseServices.JustInTimeActivation dans la dclaration du composant servi concern.

Transactions distribues ;
COM+ permet dexploiter le serveur transactionnel de Windows nomm MS DTC (Distributed
Transaction Coordinator) afin de raliser des transactions distribues.

BYOT (Bring Your Own Transaction)


Ce mcanisme permet dassocier une transaction existante un composant qui a priori ne
fait pas partie de cette transaction. La transaction peut tre gre par MS DTC (le gestionnaire de Windows de transactions distribues), mais aussi par un autre milieu transactionnel
comme TIP (Transaction Internet Protocol).

Prsentation des services dentreprise COM+

297

COM Transaction Integrator (COMTI)


Ce mcanisme vous permet dintgrer les mainframes dvelopps avec les technologies CICS
(Customer Information Control System) et IMS (Information Management System) de IBM, dans
des objets COM classiques. Sous le terme COMTI sont regroups les outils et les classes
permettant cette intgration.

Compensating Resource Manager (CRMs)


La thorie des transactions sapplique, entre autres, aux bases de donnes relationnelles. Cependant, il ne faut pas oublier quelle constitue un cadre thorique gnral la protection
de lintgrit de ressources quelconques. Un composant servi CRM est un composant servi
propritaire qui gre des ressources (par exemple des fichiers). Un CRM peut participer
au sein dune transaction distribue gre par MS DTC (le gestionnaire Microsoft de transactions distribues). Lorsquun CRM participe une transaction, il doit pouvoir stocker
temporairement et de manire persistante ltat de ses ressources avant leur modification
de manire pouvoir revenir en arrire si la transaction choue. Le CRM doit tre capable
de valider les changements (commit) ou de les annuler (rollback) mme si le processus qui
le gre est dtruit durant la transaction. Si ce processus est eectivement dtruit durant la
transaction, les oprations de commit ou de rollback doivent pouvoir tre eectues lorsque
le processus est relanc. Contrairement ce que lon pourrait penser, crire un CRM est une
tche relativement aise. La plupart de la complexit est encapsule dans les classes fournies
par le framework .NET.

Interoprabilit XA
Ce mcanisme permet dencapsuler, au sein dune transaction gre par le modle de transaction Microsoft (OLE Transaction), les accs une base de donnes qui supporte le modle
transactionnel XA (X/Open Distributed Transaction Processing (DTP).

vnements coupls (Loosely Coupled Events)


Ce mcanisme permet un client dune COM+ application dtre averti par le serveur lorsquun vnement se produit. Cela vite au client daller demander rgulirement au serveur
si lvnement sest produit.

Passage dune chane de caractres la construction dun objet


Ce mcanisme permet de pallier partiellement labsence darguments des constructeurs
des classes COM. Cependant la chane de caractres est la mme pour toutes les instances
dun composant servi. Vous avez la possibilit de modifier cette chane par lintermdiaire
de loutil Services de composants . La principale utilit de cette chane est de fournir une
chane de connexion vers une base de donnes.

Composants privs
Tous les composants servis dune COM+ application ne sont pas ncessairement accds par
les clients de la COM+ application. Il existe souvent des composants servis qui ne doivent
tre utiliss que par dautres composants servis, existant dans le mme processus et appartenant la mme COM+ application. Pour empcher un client dutiliser un tel composant
servi, il faut le dclarer comme un composant servi priv.

Appels asynchrones dconnects (Queued Components)


Ce mcanisme dappel asynchrone va plus loin que le mcanisme dappel asynchrone de
.NET. Contrairement ce mcanisme, les requtes des clients ne sont pas traites directement. Elles sont stockes dans une file dattente sous forme de messages. Durant cette opration, aucun objet serveur nest cr. Le client et le serveur peuvent mme tre physiquement dconnects. Tout ceci est transparent pour le client qui dialogue avec un objet proxy.

298

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


Tout ceci est transparent pour le serveur qui ultrieurement rcuprera ces requtes et les
traitera lorsquil sera connect avec la machine cliente. Notez que le mcanisme interne de
gestion de file dattente est Microsoft Message Queue (MSMQ). MSMQ doit tre installe la
fois sur la machine client et la machine serveur.

Scurit Role-Based
COM+ prsente son propre mcanisme de scurit, bas sur le rle que joue lutilisateur
appelant. Ce mcanisme est dirent du mcanisme de scurit .NET bas sur les rles, et
du mcanisme Windows bas sur les rles.

Services SOAP
Ce mcanisme permet de publier un composant servi comme un service web. On peut toujours continuer y accder comme un composant COM+.

Synchronisation / activit COM+


Une activit COM+ est un arbre logique dappels entre instances de composants servis. Ce
mcanisme permet de synchroniser laccs des instances de composants servis entre plusieurs activits COM+. Le mcanisme dinterception dappels de COM+ fait en sorte quil
ne puisse y avoir plus dune activit COM+ qui utilise une instance dun composant servi
la fois.

Avez-vous besoin des services dentreprise COM+ ?


Parce que COM+ nest pas intgr .NET, il vaut mieux viter de lutiliser quand cela est possible. En eet, certaines fonctionnalits sont redondantes entre les briques logicielles du framework .NET et les services dentreprise COM+. Voici quelques-unes de ces fonctionnalits redondantes.
.NET 2.0 prsente un framework de dveloppement transactionnel qui est toujours prfrable
la gestion des transactions avec COM+. Ce framework fait lobjet du chapitre sur les transactions.
Bien souvent, le pooling dobjets COM+ est utilis pour viter davoir construire et dtruire
une connexion avec une base de donnes, pour chaque client. Soyez conscient que la fonctionnalit de pool de connexions est en gnral prvue par les fournisseurs de donnes ADO.NET
(voir page 718).
La fonctionnalit COM+ dactivation JITA dun objet ( Just In Time Activation ou activation juste temps ) permet de repousser la cration (ou lactivation) dun objet jusquau moment de sa premire utilisation relle par un client. Or, le mode WKO simple appel de .NET
Remoting fonctionne sur ce modle dactivation (voir page 795).
La notion dappels asynchrones de COM+ est un peu plus gnrale que celle de .NET, prsente
page 171. Elle est plus gnrale dans le sens o elle permet au client deectuer lappel mme si
le serveur nest pas joignable au moment de lappel. Dans ce cas, lappel est stock dans une file
dattente ct client, et sera eectu automatiquement lorsque le serveur redeviendra joignable.
Si vous navez pas besoin de cette fonctionnalit, mieux vaut utiliser les appels asynchrones
.NET.
La notion de rle dans le domaine de la scurit est la fois prsente en COM+, Windows et
.NET. Vous pouvez en savoir plus ce sujet page 216.
La possibilit de nautoriser au plus quun client utiliser un composant un instant donn,
est possible aussi bien avec COM+ quavec .NET (voir page 160).

Utiliser les services COM+ dans des classes .NET

299

Utiliser les services COM+ dans des classes .NET


Notion de composant servi (serviced component)
Un composant servi (serviced component en anglais) est une classe .NET utilisant un ou plusieurs
services dentreprise COM+. Pour utiliser au moins un service dentreprise COM+, une classe
.NET doit imprativement driver de la classe System.EnterpriseServices.ServicedComponent.
Un composant servi doit imprativement avoir un constructeur sans argument, aussi appel
constructeur par dfaut.
La classe ServicedComponent implmente linterface IDisposable et sa mthode Dispose(). La
classe ServicedComponent prsente aussi la mthode virtuelle protge Dispose(bool). Si le
boolen est positionn true, cela signifie quil faut librer les ressources gres et non gres.
Si le boolen est positionn false, cela signifie quil ne faut librer que les ressources non
gres.
Voici donc quoi ressemble le squelette dun composant servi :
Exemple 8-16 :
using System ;
using System.EnterpriseServices ;
public class SystemeBancaire : ServicedComponent {
public SystemeBancaire() {
}
new public void Dispose() {
Dispose(true) ;
GC.SuppressFinalize(this) ;
}
protected override void Dispose(bool bDisposing){
// Lib`ere les ressources non g
er
ees.
if( bDisposing ){
// Lib`ere les ressources g
er
ees.
}
base.Dispose(bDisposing) ;
}
}

Dclarer les services dentreprise utiliss dans un composant servi


Pour signaler au compilateur quune classe drivant de ServicedComponent utilise tel ou tel
service dentreprise, il faut que la classe soit dclare avec les attributs .NET correspondant
aux services dentreprise souhaits. Ces attributs sont tous dans lespace de noms System.
EnterpriseServices. Par exemple lattribut Transaction permet de paramtrer le mode transactionnel utilis dans une classe. Lattribut ObjectPooling permet dindiquer que les instances
de la classe font partie dun pool. Pour utiliser conjointement ces deux services dentreprise et
les paramtrer dans notre classe SystemeBancaire, il faudrait la dclarer comme ceci :

300

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+


...
[Transaction(TransactionOption.Required)]
[ObjectPooling(MinPoolSize=5,MaxPoolSize=30)]
public class SystemeBancaire : ServicedComponent {
...

Contexte COM+ et utilisation des services dentreprise


dans un composant servi
Un contexte COM+ est un conteneur logique dans un processus, qui hberge les objets qui utilisent les mmes services dentreprise COM+.

Attention, bien que conceptuellement proche de la notion de contexte .NET prsente


page 842, la notion de contexte COM+ est dirente. Certains documents parlent de
contextes grs (.NET) et de contextes non grs (COM+).
La plomberie interne pour utiliser un service dentreprise peut tre assez lourde. La notion
de contexte permet de partager cette plomberie entre plusieurs objets afin damliorer les performances. La gestion des contextes est transparente pour le dveloppeur. Lors de la cration
dune instance dun composant servi, COM+ soccupe de vrifier sil existe un contexte dans le
processus qui utilise les mmes services dentreprise. Si tel est le cas, linstance du composant
servi utilisera ce contexte, sinon un nouveau contexte avec les services dentreprise adquats est
cr.
Sachez que si le client dune instance dun composant servi nest pas dans le mme contexte
COM+, COM+ cre un objet proxy pour le client. Lintrt est que chaque appel au composant
servi est dabord intercept en interne par COM+ avant dtre excut. Chaque retour dappel
est aussi intercept par COM+. Ce que fait COM+ durant cette interception dpend compltement des services dentreprise utiliss. Le mcanisme JITA, dcrit un peu plus loin, constitue
un exemple intressant de ce que peut faire COM+ durant ces interceptions. Notez que ce mcanisme dobjets proxy et dinterception est transparent pour lutilisateur.
Du point de vue du dveloppeur dun composant servi .NET, le contrle de lutilisation
des services dentreprise se fait par lintermdiaire de la classe System.EnterpriseServices.
ContextUtil. Cette classe ne contient que des membres statiques. Supposons quun composant
servi utilise le service dentreprise de gestion des transactions et que vous souhaitiez avorter
la transaction courante dans le code dune mthode. Il sut dappeler la mthode ContextUtil.SetAbort(). On ne peut pas dire quun composant servi .NET appartient un contexte
COM+, car le composant servi est gr mais pas le contexte. On dit plutt quun composant
servi utilise un contexte pour avoir accs ses services dentreprise. La Figure 8-6 rsume tout
ceci :

Notion de COM+ application


Une COM+ application est une collection de composants servis. Les composants servis dune
COM+ application peuvent tre dans dirents assemblages. En revanche, tous les composants

Utiliser les services COM+ dans des classes .NET

301

Processus
CLR
Composant servi
1 (contexte A)

Composant servi
2 (contexte A)

Composant servi
3 (contexte B)

ContextUtil

COM : Contexte A

COM : Contexte B

Gestion interne des services dentreprise

Figure 8 -6 : Contextes COM+ et composants servis .NET

servis dun mme assemblage appartiennent la mme COM+ application. Les COM+ applications reprsentent ce que lon appelle communment les serveurs dentreprises. Les clients utilisent les instances des composants servis dune COM+ application, et les composants servis utilisent les services dentreprise COM+ pour eectuer leurs tches.
Plusieurs attributs dassemblage ont t conus pour que vous puissiez configurer les paramtres
de la COM+ application qui contiendra lassemblage que vous dveloppez. Voici les principaux :

ApplicationName : prcise le nom de la COM+ application. Par exemple :


[assembly : ApplicationName("Serveur bancaire.")]

ApplicationID : prcise lidentificateur unique de la COM+ application.


[assembly : ApplicationID("301E31A3-E011-432b-9D7E-5643253EEE89")]

Description : donne une description de la COM+ application.


[assembly : Description("Permet dacc
eder aux informations bancaires.")]

ApplicationAccessControl : permet de configurer des paramtres de scurit de la COM+


application.

ApplicationQueuing : Permet de configurer lutilisation de files de messages dans la COM+


application.

ApplicationActivation : Il existe deux modes dactivation dune COM+ application, le


mode dactivation librairie et le mode dactivation serveur. La description de ces modes fait
lobjet de la prochaine section.

Si plusieurs assemblages configurent la mme COM+ application, un paramtre donn de la


COM+ application prendra la valeur spcifie dans le dernier assemblage install. Si un paramtre de la COM+ application nest positionn par aucun assemblage, il sera positionn une
valeur par dfaut, lors de la cration de la COM+ application.

302

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Le catalogue COM+
Le catalogue COM+ est une base de donnes prsente dans tous les systmes dexploitation Microsoft depuis Windows 2000. Le catalogue COM+ contient les informations de configuration des
COM+ applications qui rsident sur la machine. Le catalogue COM+ est physiquement stock
sur deux supports :

Une partie des informations du catalogue COM+ est stocke dans la base des registres, directement avec les informations des classes COM.

Lautre partie des informations du catalogue COM+ est stocke dans une srie de fichiers
bibliothques de composants (extension .clb). Ces fichiers sont stocks dans le rpertoire
<< %systemroot%\Registration >>.

Le format des fichiers .clb nest pas document. Il est fortement dconseill dessayer de modifier ou de visualiser les donnes du catalogue COM+ par lintermdiaire dun diteur tel que
regedt32.exe ou en modifiant les fichiers .clb. Nous prsenterons un peu plus loin, un outil
spcialement conu pour visualiser et modifier les donnes du catalogue COM+, de faon
pouvoir crer et paramtrer des applications COM+ sur une machine. La Figure 8 -7 expose les
direntes relations existant entre le catalogue COM+, les COM+ applications et les composants
servis, installs sur une machine.
OS Microsoft (Windows 2000 ou XP)
Systme de fichiers

Fichiers .clb

Base des
registres

COM+ Catalogue
Assemblage C
Assemblage
B
Composant
servi
Composant servi
Assemblage
A
Composant
servi
Composant servi
Composant servi
Composant servi
Composant servi

COM+ Application
Nom
ID
Paramters pour les
services dentreprise
utiliss
Liste des composants

Figure 8 -7 : Catalogue COM+ et COM+ applications

Les modes dactivation dune COM+ application


Chaque COM+ application a un mode dactivation qui est soit librairie soit serveur .

Mode dactivation librairie


Le mode dactivation librairie indique que les assemblages contenant les composants servis
dune COM+ application doivent tre chargs dans le processus du client de la COM+ application. La Figure 8 -8 expose larchitecture gnrale mise en uvre dans le mode dactivation
librairie .

Utiliser les services COM+ dans des classes .NET

303

Processus client

Client non gr

CLR
Client .NET

Proxy

Instance dun
composant servi

Proxy

Client Callable Wrapper


COM+ contexte
COM+ interception

Figure 8 -8 : Mode dactivation librairie dune application COM+

Mode dactivation serveur


Le mode dactivation serveur indique que les assemblages contenant les composants servis
dune COM+ application doivent tre chargs dans un processus prvu pour hberger les COM+
applications. Ce processus est lanc automatiquement par le systme lors de la premire utilisation dune COM+ application. Lexcutable dllhost.exe est utilis pour lancer ce processus.
On dit dun tel processus quil est subrog (surrogate en anglais). Pour quune COM+ application
puisse tre utilise par des clients distants (i.e qui ne sont pas sur la mme machine) il faut
naturellement quelle ait le mode dactivation serveur . La Figure 8 -9 expose larchitecture
gnrale mise en uvre dans le mode dapplication serveur .
Processus dun client non gr
Client non gr
Proxy

Processus dun client gr


CLR

Client .NET

Runtime Callable Wrapper

Cette ligne reprsente ventuellement un rseau dans le cas de clients distants

Processus client (dllhost.exe)


CLR
Instance dun
composant servi

Client Callable Wrapper


COM+ contexte
COM+ interception

Figure 8 -9 : Mode dactivation serveur dune application COM+

Installation des composants servis dans une COM+ application


Linstallation des composants servis contenus dans un assemblage dans une COM+ application
a besoin que lassemblage ait un nom fort. Cest--dire que lassemblage doit tre sign numriquement avec loutil sn.exe. Au moment de lexcution, le CLR trouvera cet assemblage soit
dans le GAC, soit dans le rpertoire courant de lapplication.

Les tapes de linstallation


Linstallation des composants servis contenus dans un assemblage, dans une COM+ application,
contient les quatre tapes suivantes :

304

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Enregistrer les composants servis dans la base des registres, pour quils puissent tre accds
comme des objets COM. La description de cette tape fait lobjet de la section prcdente.

Construire une bibliothque de types dcrivant les composants servis contenus dans lassemblage. Cette bibliothque de types contient les informations spcifies par les attributs
COM+ contenus dans lassemblage.

Trouver la COM+ application qui doit contenir ces composants servis. Si la COM+ application nest pas trouve alors crer une nouvelle COM+ application.

Configurer la COM+ application partir des informations de la bibliothque de types, issues des attributs COM+ de lassemblage. Les paramtres non positionns par ces valeurs
prennent des valeurs par dfaut. De plus, si la COM+ application existait dj, il se peut
que les valeurs de ses paramtres changent avec linstallation de ces nouveaux composants
servis.

Les direntes faons de raliser linstallation


La classe System.EnterpriseServices.RegistrationHelper permet dinstaller (ou de dsinstaller) les composants servis dun assemblage. Elle permet dexcuter les quatre tapes vues cidessus dans une mme transaction. Aprs avoir utilis la classe RegistrationHelper vous pouvez donc tre certain que soit toutes les tapes ont t eectues avec succs, soit rien na t
modifi car une des tapes a chou.
Concrtement, il existe trois faons dinstaller les composants servis dun assemblage. Chacune
de ces faons utilise la classe RegistrationHelper dune manire masque ou non. Quelle que
soit la faon utilise, seul un utilisateur ayant les droits dadministrateur peut raliser cette opration.

La faon manuelle :
Il vous sut dutiliser loutil Services Installation Utility (regsvcs.exe) en ligne de commande, avec pour argument lassemblage qui contient les composants servis. Par exemple :
>regsvcs.exe SystemeBancaire.dll
Les options de loutil regsvcs.exe sont dcrites dans les MSDN larticle .NET Services
Installation Tool (Regsvcs.exe). Si le nom de la COM+ application nest pas spcifi dans
lassemblage ou partir de loption /appname de regsvcs.exe, le nom de la COM+ application sera gal au nom de lassemblage (sans extension de fichier).

La faon programme :
Il vous sut dutiliser la classe RegistrationHelper au sein dune mthode dune classe
dun assemblage. La classe RegistrationHelper prsente la mthode InstallAssembly()
prvue cet eet. Par exemple :
Exemple 8-17 :
using System.EnterpriseServices ;
public class Program {
static void Main() {
RegistrationHelper rh = new RegistrationHelper() ;
string sCOMPlusAppName = "Serveur bancaire." ;
string sTypeLibName = "SystemeBancaire.tlb" ;
rh.InstallAssembly(

Utiliser les services COM+ dans des classes .NET

305

"SystemeBancaire.dll", // Nom de lassemblage.


ref sCOMPlusAppName,
ref sTypeLibName,
InstallationFlags.CreateTargetApplication) ;
}
}
Largument CreateTargetApplication indique quune nouvelle COM+ application doit
tre cre. Si ce nest pas possible lexception RegistrationException est lance.

La faon automatique :
Quand un client a besoin quun composant servi soit instanci, le CLR vrifie si le composant servi est eectivement prsent dans une COM+ application. Si tel nest pas le cas,
le CLR installe automatiquement lassemblage dans une nouvelle COM+ application. Bien
que sduisante, cette faon de faire doit tre vite car vous pouvez dicilement tre certain
que le premier client a les droits dadministrateur qui sont ncessaires pour raliser cette
opration.

Visualisation et manipulation du catalogue COM+


Vous pouvez visualiser et manipuler le catalogue COM+, et donc les COM+ applications prsentes sur une machine, en utilisant le composant logiciel enfichable (snap-in en anglais) Services
de composants (component services en anglais). La Figure 8-10 prsente une copie dcran de cet
outil :

Figure 8 -10 : Loutil Services de composants


Loutil services de composants permet un administrateur de modifier les paramtres dune
COM+ application. Cependant, il est conseill de ne modifier que les paramtres relatifs linstallation dune COM+ application, comme les paramtres de scurit.

306

Chapitre 8 : Interoprabilit .NET code natif / COM / COM+

Si une COM+ application est en cours dutilisation, loutil services de composants vous lindique avec une petite animation. Il vous permet aussi de stopper une COM+ application utilise.
Si vous changez les paramtres dune COM+ application, il faudra stopper la COM+ application
pour que les changements soient pris en compte lors de la prochaine utilisation.

Concevoir un client dun composant servi


Du point de vue dun client gr, il ny a aucune dirence entre lutilisation dune instance
dune classe normale et lutilisation dune instance dun composant servi. Il est conseill dutiliser la syntaxe C  avec le mot-cl using pour appeler la mthode Dispose() dune manire
implicite. Par exemple :
public class Program {
static void Main(){
using( SystemeBancaire sys = new SystemeBancaire() ){
//... utilise sys
} //appelle sys.Dispose() dune mani`
ere implicite
}
}
Du point de vue dun client non gr, lutilisation dune classe composant servi passe par lutilisation de la classe COM dfinie par le Com Callable Wrapper.

Le langage C  2.0 et la
comparaison C  2.0/C++

9
Les concepts fondamentaux
du langage

Organisation du code source


C++ C  Malgr une syntaxe relativement proche de celle du C++, les espaces de noms
jouent un rle plus important dans C  .
En C++ on les utilisait pour empcher les collisions entre identificateurs.
En C  , les espaces de noms remplacent la directive prprocesseur #include. En eet, en C  il ny
a plus de fichiers den-tte (.h). Pour utiliser une ressource contenue dans un espace de noms
et dclare dans un autre fichier source C  , il sut de dclarer quon utilise lespace de noms
concern. Ce fichier source peut ventuellement faire partie dun autre assemblage que celui
dans lequel on utilise la ressource.
Cette possibilit dutilisation de ressources dclares dans un autre assemblage est aussi exploite dans lutilisation de ressources dfinies dans des bibliothques. Par exemple toutes les classes
de base du framework .NET sont accessibles au travers de lespace de noms System.
Une autre grosse dirence est que lon ne peut sparer la dclaration et la dfinition (le corps)
dune mthode ou dune classe, comme on pouvait le faire en C++ grce loprateur de rsolution de porte.
Autre dirence importante : il nexiste plus de fonctions globales. Il ne peut y avoir que des
mthodes, cest--dire des fonctions dclares lintrieur dun type (classe ou structure).
En C  , lutilisation des espaces de noms pour empcher les collisions entre identificateurs est
toujours dactualit. De plus pour tendre le nom dun identificateur en le faisant prcder du
nom de lespace de noms, on nutilise plus loprateur de rsolution de porte du C++ (loprateur :: qui a une autre application en C  2) mais un point.

310

Chapitre 9 : Les concepts fondamentaux du langage

Enfin les possibilits de dfinir des alias de noms despace de noms, et dimbriquer des espaces
de noms, sont prsentes en C  .

Les espaces de noms


Une ressource dans le code source peut tre dclare et dfinie lintrieur dun espace de noms.
Si une ressource nest dclare dans aucun espace de noms elle fait partie dun espace de noms
global et anonyme. Un espace de noms nomm Foo par exemple, se dclare comme suit :
namespace Foo{
// Ici, la definition delements.
}
On a la possibilit dimbriquer les espaces de noms :
namespace Foo1{
// Ici, la definition delements.
namespace Foo2{
// Ici, la definition del
ements.
// La qualification compl`
ete de Foo2 est Foo1.Foo2.
}
// Ici, la definition delements.
}
On a la possibilit de dcouper un espace de noms. Par exemple, pour taler des ressources qui
doivent tre dans le mme espace de noms sur plusieurs fichiers sources. Pour cela il sut de dclarer un ou plusieurs espaces de noms avec le mme nom pour chaque fichier source concern.
Notez quavec les types partiels de C  2, une ressource a maintenant la possibilit dtre dclare
sur plusieurs fichiers sources.
La technique de dcoupage dun espace de noms est dautant plus puissante que le dcoupage
peut se faire sur les fichiers sources de dirents modules (du mme assemblage ou non).
Les espaces de noms constituent un moyen de partitionner le code source dans des blocs distincts et organiss, selon une hirarchie que les architectes du projet ont tabli.
Enfin, mentionnons que les documentations anglo saxonnes utilisent souvent les expressions
Foo et Bar pour nommer les entits quelconques de leurs exemples (un peu comme nous utilisons parfois toto). Ces expressions sont issues de lacronyme FUBAR (Fucked Up Beyond All
Repairs) qui peut se traduire par cest irrparable, cest foutu. Cet acronyme a vraisemblablement
t invent lors de la seconde guerre mondiale pour dsigner une situation dsespre.

Utilisation des ressources contenue dans un espace de noms


Les types de ressources dclars lintrieur dun espace de noms sont :

Organisation du code source

311

Type de ressource

Mot-cl C 

espace de noms

namespace

classe

class

interface

interface

structure

struct

numration

enum

dlgu

delegate

Dans une moindre mesure on peut considrer que les commentaires constituent une sorte de
ressources dont la caractristique principale est de ne pas tre pris en compte par le compilateur.
Pour utiliser une ressource dfinie dans lespace de noms de nom B, il faut que celui-ci soit
dclar en tte du fichier source ou dans lespace de noms o lon souhaite utiliser la ressource,
avec le mot-cl using. Par exemple :
Exemple 9-1 :
using B;
namespace A{
class Program{
static void Main() {}
ClasseFoo f ; // la classe ClasseFoo peut
etre utilis
ee.
}
}
namespace B{
using A;
class ClasseFoo{Program p;} // la classe Program peut
etre utilis
ee.
}
Toutes les ressources dclares dans un mme espace de noms sont accessibles partir du code
contenu dans cet espace de noms. Y compris celles qui sont dclares dans le mme espace de
noms dans dautres fichiers sources, dautres modules ou assemblages rfrencs la compilation.
Comprenez bien que le mot-cl using nest quune commodit lusage du compilateur C  2
pour lui permettre de faire le lien entre les ressources et les noms des ressources qualifis sans
leurs espaces de noms. Lquivalent en Java et en VB.NET est le mot cl Imports/imports. Dans
aucun de ces trois langages ce mot-cl nimporte quoi que ce soit au sens o quelque chose est
dplac.
On peut utiliser le mot-cl using pour dfinir un alias despace de noms. En gnral, cela permet
dviter davoir nommer les espaces de noms imbriqus. Par exemple :
using SysWinForm = System.Windows.Forms ;
Il faut tre conscient que dans ce cas on est oblig dutiliser lalias devant les identificateurs de cet
espace. Ce nest pas le cas lorsquil ny a pas dalias et quil ny a pas de collisions didentificateurs.
Enfin, il nexiste pas en C  la syntaxe Java suivante :

312

Chapitre 9 : Les concepts fondamentaux du langage


using Foo.* ;

Structure dun projet C 


Le code source dun projet crit en C  est rpartit sur un ou plusieurs fichiers texte dont lextension est .cs. Prcisons quun projet C  est lensemble des fichiers sources C  concerns dans
la fabrication dun assemblage par le compilateur C  . Lorganisation dun projet C  peut tre
rsume par la Figure 10 -1 :
Projet

Namespace 1

Namespace 2

fichier1.cs

...

Namespace 3

Ressource D
Ressource E

fichier2.cs

Ressource C

...

Ressource A

fichier3.cs

Ressource B

Figure 9 -1 : Les lments structurels dun projet C 


En tenant compte des remarques suivantes :

Le mme espace de noms peut se trouver sur plusieurs fichiers.

Les ressources hors de tout espace de noms sont dans lespace de noms anonyme.
Comme expos par la ressource E, depuis la version 2.0 de C  une classe une structure ou

une interface a la possibilit dtre dclare sur plusieurs fichiers sources dun mme projet,
mais dans le mme espace de noms.

Organisation assemblages / espaces de noms


Une bonne pratique consiste nommer un assemblage avec le nom de lespace de noms racine
quil contient. Microsoft applique cette rgle pour nommer ses assemblages standard. Ainsi, lorganisation des classes de bases standard apparat immdiatement la lecture des noms des assemblages : System.dll, System.Drawing.dll, System.Drawing.Design.dll, System.Runtime.
Remoting.dll, System.Security.dll etc.

Les tapes de la compilation


C++ C  Attention, ltape de la cration de liens (link) du C++ nexiste plus en C  . En eet
le compilateur C  ne se sert pas de fichiers intermdiaires dextension .obj.
En revanche les tapes du prprocessing et la compilation elle-mme sont toujours l. En C 
beaucoup de directives prprocesseur du C++ disparaissent, et quelques nouvelles voient le jour.
C  La compilation consiste fabriquer un assemblage partir de fichiers sources crits en
C  . La notion dassemblage fait lobjet du chapitre 2 Assemblages, modules, langage IL . La

Le prprocesseur

313

compilation est prise en charge par le compilateur csc.exe dcrit un peu plus loin. Ce compilateur peut tre appel soit directement en ligne de commande, soit par lenvironnement
de dveloppement Visual Studio. Les options de compilation sont dfinies soit dans la ligne de
commande, soit par lintermdiaire de lenvironnement de dveloppement.
Il y a principalement deux tapes la compilation :

Prprocessing des fichiers sources : cette tape gnre de nouveaux fichiers sources, modifis
selon des directives prprocessing qui peuvent tre dfinies soit dans les fichiers sources eux
mme, soit directement en ligne de commande du compilateur. Ces directives sont dcrites
un peu plus loin.

Compilation des fichiers sources rsultant du prprocessing : le produit de cette tape est soit :

un assemblage, contenu dans un fichier dont lextension est .exe ou .dll,

un module contenu dans un fichier dextension .netmodule. Rappelons que lenvironnement Visual Studio ne sait pas grer les modules.

Le prprocesseur
C++ C  En C  la directive #define ne peut plus dfinir une constante remplacer. On ne
lutilise plus que pour changer simplement le code source, avec laide des directives #if #elif
#else et #endif.
Les constantes prdfinies ( __LINE__ ,__FILE__, __DATE__...) nexistent plus.
Les macros avec paramtres nexistent plus.
Lenvironnement de dveloppement Visual Studio est capable de mettre en gris les lignes qui ne
seront pas prises en compte lors de la compilation.
La trs clbre directive #include nexiste plus, puisque lorganisation des fichiers sources est
trs dirente en C  .
Les oprateurs # et ## de manipulation de chanes de caractres par le prprocesseur nexistent
plus.
La directive #warning a t ajoute en C  et fonctionne sur le mme principe que la directive
#error.
Les directives #line #region et #endregion apparaissent et sont spcifiques C  .
C  Toute compilation dun fichier source est prcde dune phase de mise en forme du
fichier. Celle-ci est eectue par un prprocesseur. Il neectue que des traitements simples de
manipulation textuelle. En aucun cas le prprocesseur nest charg de la compilation. Toutes les
directives prprocesseur sont prcdes par le caractre #.
Le prprocesseur reconnat les directives suivantes :
#define
#undef
#if
#elif
#else
#endif
#error
#warning #line
#region #endregion
#pragma warning disable
#pragma warning restore

314

Chapitre 9 : Les concepts fondamentaux du langage

Constantes symboliques et compilation conditionnelle


Vous avez la possibilit de dfinir une entit avec la directive #define. Selon la prsence ou non
de lentit, le code source peut tre modifi avec lutilisation des directives #if #elif #else et
#endif :
Exemple 9-2 :
#define MACRO1
public class Program {
public static void Main() {
#if (MACRO1)
System.Console.WriteLine("MACRO1 d
efinie.") ;
#elif (MACRO2)
System.Console.WriteLine("MACRO2 d
efinie et MACRO1 non d
efinie") ;
#else
System.Console.WriteLine("MACRO2 et MACRO1 non d
efinies") ;
#endif
}
}
Lenvironnement de dveloppement Visual Studio met en gris les lignes qui ne seront pas prises
en compte lors de la compilation. En outre, les constantes symboliques doivent tre dfinies
avant toute utilisation de la directive C  using.
On a aussi la possibilit de dfinir une constante symbolique lors de la compilation en ligne.
Par exemple :
>csc.exe prepross.cs /define:MACRO2
La directive #undef annule la dfinition dune constante symbolique, par exemple :
Exemple 9-3 :
#define MACRO1
#undef MACRO1
public class Program {
public static void Main() {
#if (MACRO1)
System.Console.WriteLine("MACRO1 d
efinie.") ;
#elif (MACRO2)
System.Console.WriteLine("MACRO2 d
efinie et MACRO1 non d
efinie") ;
#else
System.Console.WriteLine("MACRO2 et MACRO1 non d
efinies") ;
#endif
}
}

Constantes symboliques et lattribut ConditionalAttribute


Il existe une alternative lgante (mais restrictive) lutilisation de #if #elif #else et #endif.
Cette alternative est lattribut ConditionalAttribute qui permet de dfinir une mthode en

Le prprocesseur

315

fonction de la dfinition dune constante symbolique. Lavantage par rapport lutilisation des
directives prprocesseur #if #elif #else et #endif, est quil nest pas ncessaire daller commenter les appels la mthode lorsque celle-ci nest plus dfinie. Linconvnient est quun certain nombre de contraintes doivent sappliquer la mthode. Par exemple la mthode doit retourner le type void. La liste exhaustive de ces contraintes se trouve larticle The Conditional
Attribute dans les MSDN. Voici un exemple dutilisation de lattribut ConditionalAttribute :
Exemple 9-4 :
//#define __TRACE__
class Program {
[System.Diagnostics.Conditional("__TRACE__")]
public static void Trace(string s) {
System.Console.WriteLine(s) ;
}
static void Main() {
Trace("Hello") ;
System.Console.WriteLine("Bye") ;
}
}
Ce code ache ceci si la constante symbolique __TRACE__ nest pas dfinie.
Bye
Ce code ache cela si la constante symbolique __TRACE__ est dfinie.
Hello
Bye

Les directives #error et #warning


La directive #error peut prvenir des conflits de dfinitions de constantes symboliques la compilation.
Le programme suivant ne compilera pas, et le compilateur achera lerreur suivante : MACRO1
et MACRO2 ne peuvent etre definies en m
eme temps
Exemple 9-5 :
#define MACRO1
#define MACRO1
#define MACRO2
#if MACRO1 && MACRO2
#error MACRO1 et MACRO2 ne peuvent
etre d
efinies en m
eme temps
#endif
public class Program {
public static void Main() {
#if (MACRO1)
System.Console.WriteLine("MACRO1 d
efinie. ") ;
#elif (MACRO2)
System.Console.WriteLine("MACRO2 d
efinie et MACRO1 non d
efinie") ;

316

Chapitre 9 : Les concepts fondamentaux du langage


#else
System.Console.WriteLine("MACRO2 et MACRO1 non d
efinies") ;
#endif
}
}

La directive #warning fonctionne sur le mme principe, mis part quelle narrte pas la compilation mais gnre un avertissement.

Les directives #pragma warning disable et restore


Les directives #pragma warning disable et #pragma warning restore permettent de dsactiver la production davertissements du compilateur C  . Leur syntaxe dutilisation est illustre
par lexemple suivant :
Exemple 9-6 :
class Program {
static void Main() {
#pragma warning disable 105
// Desactive lavertissement CS0105 dans ce bloc.
#pragma warning restore 105
#pragma warning disable
// Tous les avertissements sont d
esactiv
es dans ce bloc.
#pragma warning restore
#pragma warning disable 105, 251
// Les avertissements CS0105 et CS0251 sont d
esactiv
es dans ce bloc.
#pragma warning restore 105
// Seul lavertissement CS0251 est d
esactiv
e dans ce bloc.
#pragma warning restore
// Tous les avertissements sont activ
es dans ce bloc.
#pragma warning disable
// Desactive tous les avertissements jusqu`
a la fin du fichier.
}
}

La directive #line
La directive #line permet au dveloppeur de modifier la ligne et ventuellement le fichier o
une erreur est dclare par le compilateur. Lexemple suivant ache que les erreurs de compilations trouves sont la ligne 1 dans le fichier M
ethode Main() :
Exemple 9-7 :
class Program {
public static void Main() {
#line 1 "Methode Main()"

Le compilateur csc.exe
int i == 0 ;

317
// <- erreur ici : pas le droit dutiliser ==

}
}
Attention lutilisation de cette directive car elle peut mettre en dfaut certaines directives de
lenvironnement de dveloppement. Notamment, lenvironnement nest plus capable de retrouver lerreur puisquil ne dispose plus ni du fichier, ni de la ligne valide.

Les directives #region et #endregion


Les directives #region et #endregion sont trs pratiques car elles permettent de plier/dplier par
un simple clic, une rgion de code. Cette possibilit est notamment utilisable dans les IDE Visual
Studio et SharpDevelop. La Figure 9 -2 illustre ces eets.

Figure 9 -2 : Eet des directives prprocesseur #region et #endregion


#endregion peut optionnellement rappeler le mme texte que #region. Il est galement possible dimbriquer ces directives.

Le compilateur csc.exe
Le compilateur csc.exe peut tre appel soit directement en ligne de commande, soit par lenvironnement de dveloppement Visual Studio, soit par des scripts de compilation type MSBuild
ou NAnt. Les options de compilation sont dfinies soit dans la ligne de commande, soit par
lintermdiaire de lenvironnement soit au sein des scripts.
La Figure 9 -3 illustre les entres possibles et les types de fichiers de sortie possibles, de csc.exe.
Un seul fichier est produit par compilation :
Prsentons les options de compilation les plus utilises laide de quelques exemples :

Compile fichier.cs et produit fichier.exe (notez que pour produire un excutable il faut
quau moins une mthode statique Main() existe dans au moins une classe).
>csc.exe fichier.cs

Compile fichier.cs et produit fichier.dll (une mthode statique Main() nest pas requise ici).
>csc.exe /target:library fichier.cs

318

Chapitre 9 : Les concepts fondamentaux du langage


Produit un module si
loption /target:module
est utilise

xx
Fichier1.cs
Modules rfrencs avec loption
/addmodule :

Module.netmodule

xx
Module1.netmodule

Produit un assemblage
librairie si loption
/target:library est utilise

Librairies rfrences avec loption


/reference : ou /r :

Librairie.dll

xx
Librairie1.dll
Excutable rfrenc avec loption
/reference : ou /r :. Au plus un
excutable rfrenc par
compilation. Si la sortie est un
xecutable, ne pas rfrencer
dexcutable

et

Executable.exe

csc.exe

ou
Produit un assemblage
excutable si loption
/target:exe est utilise
Executable.exe

Figure 9 -3 : Entres et sorties du compilateur C  csc.exe

Compile fichier.cs et produit prog.exe. La mthode Main() qui sert de point dentre est
celle de la classe Prog qui est dans lespace de noms Namespace001.
>csc.exe /out:prog.exe /main:Namespace001.Prog fichier.cs

Compile avec optimisation tous les fichiers dextension .cs dans le rpertoire courant, dfinit la constante symbolique MACRO1 pour chacun de ces fichiers et produit prog.exe.
>csc.exe /out:prog.exe /define:MACRO1 /optimize *.cs

Compile en mode debug tous les fichiers dextension .cs dans le rpertoire courant, ne gnre pas davertissement et produit le fichier mod.netmodule.
>csc.exe /target:module /out:mod.netmodule /warn:0 /debug *.cs

Compile en tenant compte des rfrences aux assemblages dont les modules avec le manifeste sont les fichiers lib1.dll et lib2.dll.
Le module mod.netmodule sera un module de lassemblage dont le module contenant le
manifeste est prog.exe.
Pour la compilation, les fichiers lib1.dll et lib2.dll peuvent se trouver dans le rpertoire
courant ou dans le rpertoire de chemin C:\.
>csc.exe /lib:c:\ /r:lib1.dll;lib2.dll /addmodule:mod.netmodule
/out:prog.exe fichier.cs

Nous avons illustr ici les options suivantes :

Le compilateur csc.exe

319

Option

Description

/target /t

Spcifie le format du produit de la compilation. Le format peut


tre :/target:library Une librairie (extension .dll)/target:module
Un module (extension .netmodule)/target:exe Un excutable en mode
console (extension .exe). Cest le format pris par dfaut si loption target
nest pas prcise./target:winexe Un excutable prsent dans une
fentre (de type Windows Form). Lextension du fichier sera aussi .exe.

/out

Spcifie le nom du fichier produit par la compilation.

/main /m

Spcifie la classe qui contient la fonction Main() qui servira de point dentre. Attention, le nom de la classe doit comprendre les espaces de nom
et doit respecter la casse. Cette option ne peut tre utilise que si lon fabrique un excutable.

/define /d

Dfinie une constante symbolique.

/optimise /o

Enclenche la compilation optimise (compatible avec loption debug).

/warn /w

Rgle le niveau davertissement. Ce niveau varie entre 0 (pas davertissement) et 4 (tous les avertissements sont achs).

/debug

Gnre des informations ncessaires au dboguage, dans un fichier


qui porte le mme nom que le fichier produit, avec lextension pdb
(programme database). (Compatible avec loption optimise).

/addmodule

Rfrence les modules utiliss par le produit de la compilation (un assemblage ou un module). Rappelons qu lexcution, tous les modules dun
assemblage doivent se trouver dans le mme rpertoire que lassemblage.

/reference/r

Rfrence les assemblages (excutable ou librairies) utiliss par le produit


de la compilation. On peut utiliser les ressources dun assemblage rfrenc, dans le code de lassemblage rfrenant. Dans ce cas, lassemblage
rfrenc sera charg par le CLR lors de la premire utilisation de lune
de ses ressources.Si le produit est un assemblage excutable, il ne faut pas
rfrencer un assemblage excutable. Si le produit nest pas un excutable,
on peut rfrencer au plus un assemblage excutable.

/lib

Spcifie les rpertoires o le compilateur peut chercher les fichiers rfrencs par loption /reference ou /r. Ces fichiers sont cherchs dans
lordre suivant :
Dans le rpertoire courant ;
dans le rpertoire du Common Langage Runtime ;
dans les rpertoires spcifis par /lib ;
dans les rpertoires spcifis par la variable denvironnement LIB.

320

Chapitre 9 : Les concepts fondamentaux du langage

/resource/
linkresource

Ajoute des ressources lassemblage. Lutilisation de ces options est dtaille page 37.

/unsafe

Indique que votre code peut contenir des zones de code non vrifiables
par le CLR (voir page 501).

/doc

Cette option permet de produire un fichier XML contenant les informations prsentes dans les commentaires /// du code source. Un exemple de
lutilisation de cette option est prsent un peu plus loin dans ce chapitre.

/help /?

Ache laide.

Une trentaine doptions sont disponibles. Nous vous avons prsent ici les plus courantes. Larticle C  Compiler Options Listed by Category des MSDN fournit la liste exhaustive des options.
Toutes ces options sont aussi disponibles dans les proprits dun projet dans lenvironnement
de dveloppement Visual Studio.

Les alias
Alias sur les espaces de noms et sur les types
Le mot cl using peut tre utilis pour dfinir un alias vers un espace de nom ou vers un type.
La porte dun tel alias est le fichier courant si il est dfini hors de tout espace de noms. Dans le
cas contraire, la porte dun alias est le fichier courant intersection lespace de nom dans lequel
il est dfinit.
Exemple 9-8 :
//Definition de lalias C vers le type System.Console.
using C = System.Console;
class Program{
static void Main(){
C.WriteLine("Hello 1");
}
}

Qualificateur dalias despace de noms


Il peut y avoir un conflit entre un espace de noms et un alias. Les trois assemblages suivants
illustrent ce cas :
Exemple 9-9 :

Code de Asm1.dll

namespace Foo.IO{ public class Stream{} }


Exemple 9-10 :
namespace Custom.IO{ public class Stream{} }

Code de Asm2.sll

Les alias
Exemple 9-11 :

321
Code de Program.exe qui r
ef
erence Asm1.dll et Asm2.dll

using FooIO = Foo.IO;


using CusIO = Custom.IO;
class Program{
static void Main(){
FooIO.Stream stream1 = new FooIO.Stream();
CusIO.Stream stream2 = new CusIO.Stream();
}
}
Grce lutilisation des alias FooIO et CusIO vous pouvez utiliser les deux types Stream dans
Program.exe sans avoir rcrire les espaces de noms en entier. Cependant ce programme ne
compile plus si vous modifiez le code de Asm2.dll comme ceci :
Code de Asm2.dll

Exemple 9-12 :
namespace Custom.IO{ public class Stream{} }
namespace FooIO{ public class Stream{} }

En eet, le compilateur sera incapable de dterminer si FooIO.Stream dsigne le type FooIO.


Stream dfini dans Asm2.dll ou le type Foo.IO.Stream de Asm1.dll alias FooIO.Stream..
Pour viter que lvolution dune bibliothque empche votre code de compiler vous pouvez
avoir recours au qualificateurs dalias despace de noms ::. Le compilateur infre que lidentificateur gauche du qualificateur dalias despace de noms est un alias. Dans notre exemple, il faut
donc rcrire Program.cs comme ceci :
Exemple 9-13 :

Code de Program.exe qui r


ef
erence Asm1.dll et Asm2.dll

using FooIO = Foo.IO;


using CusIO = Custom.IO ;
class Program {
static void Main(){
FooIO::Stream stream1 = new FooIO::Stream();
CusIO.Stream stream2 = new CusIO.Stream() ;
}
}
Cette technique du qualificateur dalias despaces de noms est ecace car la porte dun alias est
au plus le fichier dans lequel il est dfinit. Une volution dans un assemblage externe ne peut
donc pas introduire subrepticement une erreur de compilation.

Le qualificateur global
Dans certains projets volumineux il se peut que vous ayez un conflit entre le nom dun espace
de noms et le nom dune ressource. Le programme suivant ne compile pas :
Exemple 9-14 :
using System ;
class Program {
class System { }

322

Chapitre 9 : Les concepts fondamentaux du langage


const int Console = 691 ;
static void Main() {
// KO : Le compilateur essaye dacc
eder `
a Program.Console.
Console.WriteLine("Hello 1") ;
// KO : Le compilateur essaye dacc
eder Program.System.
System.Console.WriteLine("Hello 2") ;
}

}

C 2 introduit le qualificateur global qui, plac devant un qualificateur dalias despaces de
noms, indique au compilateur que lon souhaite utiliser un espace de noms. Lexemple prcdent doit donc tre rcrit comme suit :
Exemple 9-15 :
using System ;
class Program{
class System { }
const int Console = 691 ;
static void Main(){
global::System.Console.WriteLine("Hello 1");
global::System.Console.WriteLine("Hello 2");
}
}

Les alias externes


Les alias externes servent utiliser simultanment deux types dclars dans deux assemblages
dirents mais qui ont le mme nom et qui sont dans le mme espace de noms. Cette situation
peut par exemple survenir si vous devez utiliser dans le mme programme deux versions dun
mme assemblage. La syntaxe des alias externes est illustre par cet exemple :
Code de Asm1.dll

Exemple 9-16 :
namespace FooIO{ public class Stream{} }

Code de Asm2.sll

Exemple 9-17 :
namespace FooIO{ public class Stream{} }
Exemple 9-18 :

Code de Program.exe qui r


ef
erence Asm1.dll et Asm2.dll

extern alias AliasAsm1;


extern alias AliasAsm2;
class Program{
static void Main(){
AliasAsm1::FooIO.Stream stream1 = new AliasAsm1::FooIO.Stream();
AliasAsm2::FooIO.Stream stream2 = new AliasAsm2::FooIO.Stream();
}
}
Il faut alors compiler Program.exe comme ceci :

Commentaires et documentation automatique

323

>csc.exe /r:AliasAsm1=Asm1.dll /r:AliasAsm2=Asm2.dll Program.cs


Vous pouvez aussi prciser vos alias externes dans Visual Studio grce la proprit Aliases dans
les proprits des rfrences vers les assemblages bibliothques. Vous y remarquerez quune rfrence vers un assemblage peut supporter plusieurs alias.
Enfin, prcisons que le meilleur moyen dutiliser ces qualificateurs et autres alias est de ne pas
en avoir besoin en essayant dviter les conflits de noms des entits de vos programmes. En tant
quartefacts avancs de C  2, la majorit des dveloppeurs ne les matrisent pas et auront ainsi
des dicults relire du code qui les utilise.

Commentaires et documentation automatique


C++ C  Les commentaires en C  et C++ sont dclars de la mme faon. Cependant C 
accepte un nouveau type de commentaires destin la documentation du code. Ces nouveaux
commentaires sont dclars avec la balise ///.
La nouveaut dans les identificateurs (i.e les noms de variables, les noms des mthodes, les noms
des classes...) est que lon peut utiliser des lettres accentues.

Les commentaires
C

Il existe trois faons de commenter du texte dans un fichier source C  :

Le texte plac entre les balises /* suivie de */ est comment. Ces balises peuvent ventuellement se trouver sur deux lignes direntes.

Si une ligne contient la balise // alors le texte de cette ligne qui suit cette balise est comment.

Si une ligne contient la balise /// alors le texte de cette ligne qui suit cette balise est comment. De plus ce texte fera partie de la documentation automatique du code source, prsente plus loin.

Un commentaire de type /*...*/ ne peut tre imbriqu dans un autre commentaire /*...*/.
En revanche, un commentaire de type // ou /// peut tre imbriqu dans un commentaire de
type /*...*/.
En consquence, une bonne ligne de conduite est dutiliser les commentaires de type // ou
/// pour commenter le code, et dutiliser les commentaires de type /*...*/ pour dsactiver
temporairement une rgion du code.

Utiliser la liste des tches de Visual Studio


Bien souvent, en tant que dveloppeur nous sommes obligs de repousser lcriture dune portion de code. Sous Visual Studio, si un commentaire de type // ou /* */ commence avec lun des
mots TODO, HACK ou UNDONE il est automatiquement ajout dans la liste des tches. Pour avoir accs cette fonctionnalit, il faut slectionner le menu Acher  liste des tches puis slectionner
Commentaires dans la combo box de la liste des tches qui apparat.

324

Chapitre 9 : Les concepts fondamentaux du langage

La documentation automatique
C  ore la possibilit de produire un document partir des commentaires dun code source
marqus avec la balise ///. Cette possibilit est trs intressante car :

Ds quun projet atteint une certaine taille, les seuls commentaires dans le code ne susent
pas donner une vue densemble du projet. On est oblig davoir de la documentation
associe au projet.

Documenter du code est une tche longue et fastidieuse. Lexprience montre quavec le
temps les dveloppeurs ngligent la maintenance de la documentation technique dun projet. Seules les entreprises qui peuvent se permettre davoir un dpartement ddi la documentation technique parviennent maintenir correctement la documentation dun projet.

Les dveloppeurs sont bien souvent les personnes les mieux places pour commenter leur
code.

Le fait que la documentation technique dun projet soit un processus parallle au dveloppement du projet, implique la dsynchronisation inluctable de la documentation. La possibilit
de gnrer automatiquement la documentation partir du code source rsout ce problme
puisque la documentation technique est intgre au dveloppement du projet. Concrtement,
ds que le dveloppeur dclare une classe ou une mthode, il cre la documentation technique
associe au mme endroit (et au mme moment).
La production automatique de la documentation technique se fait en deux tapes :

Il faut dabord extraire et hirarchiser les informations prcises par les commentaires ///
du code source. Ces informations sont alors stockes dans un document XML. Ces commentaires contiennent eux-mmes des balises XML qui se retrouveront directement dans
le document XML gnr. Notez que Visual Studio 2005 vous aide dans la production de
ces balises avec lintellisense. Prcisons que les commentaires issus de la documentation
automatique se retrouvent aussi dans les tooltips de Visual Studio concernant les entits commentes.

Appliquer une feuille de style au fichier XML afin dobtenir une prsentation adapte la
lecture. Cette feuille de style est en gnral une transformation XSLT. La prsentation finale
est en gnral une arborescence de fichiers HTML.

La Figure 9 -4 rsume ceci :


Solution
Projet 1
Fichier1.c

document
XML

Arborescence
HTML
reprsentant la
documentation
automatique

Fichier2.c
Projet 2
Fichier3.c

Extraction et
Transformation
hirarchisation des
XSLT
commentaires ///
(durant la compilation
avec loption /doc)

Figure 9 -4 : Production automatique de la documentation technique


Illustrons ceci avec un exemple. Le fichier de code source C  suivant...

Commentaires et documentation automatique

325

Exemple 9-19 :
namespace MonEspaceDeNoms {
/// <summary>
/// MaClass illustre la production automatique de
/// la documentation technique
/// </summary>
class MaClass {
/// <summary>
/// Le point dentree de lapplication
/// </summary>
static void Main() {}
/// <summary>
/// La fonction f(int)
/// </summary>
/// <param name="i">Un entier</param>
static void f(int i){}
}
}
...produit le fichier XML suivant :
<?xml version="1.0"?>
<doc>
<assembly>
<name>AutomaticDocTest</name>
</assembly>
<members>
<member name="T:MonEspaceDeNoms.MaClass">
<summary>
MaClass illustre la production_automatique de
la documentation technique
</summary>
</member>
<member name="M:MonEspaceDeNoms.MaClass.Main(System.String[])">
<summary>
Le point dentree de lapplication
</summary>
</member>
<member name="M:MonEspaceDeNoms.MaClass.f">
<summary>
La fonction f(int)
</summary>
<param name="i">Un entier</param>
</member>
</members>
</doc>
Notez la prsence des balises <summary> et <param> la fois dans le code source C  et dans le
fichier XML. Vous avez deux manires de produire ce fichier XML :

326

Chapitre 9 : Les concepts fondamentaux du langage


Soit vous utilisez loption /doc du compilateur C  csc.exe :
>csc.exe MonFichier.cs /doc:MonFichierDoc.XML

Soit vous prcisez dans les proprits du projet dans Visual Studio que vous souhaitez produire un fichier XML de documentation lors de la compilation. Pour cela il faut prciser
le nom du fichier XML dans loption Fichier de documentation XML de la fentre Gnrer des
proprits du projet.

partir du fichier XML, vous pouvez appliquer une feuille de style pour produire la documentation technique au format que vous souhaitez. Visual Studio 2003 prsentait un outil de
cration de pages HTML partir de tels documents XML Avec Visual Studio 2005, nous vous recommandons dutiliser des outils spcialiss eectuer cette tche. Citons notamment lexcellent
outil NDoc qui est open source et tlchargeable gratuitement.

Les identificateurs
Les identificateurs sont des noms choisis par le dveloppeur qui nomment des ressources telles
que des espaces de noms, des classes, des mthodes de classes, des champs de classes et en fait,
tout ce qui peut tre nomm dans le code source.
Un identificateur doit obir aux rgles suivantes :

Le premier caractre est soit une lettre (A Z ou a z ou une lettre accentue UNICODE)
soit le caractre de soulignement _ soit le caractre @. Le premier caractre ne peut tre un
chire.
Les autres caractres sont dans lensemble des caractres cits ( part @), unis avec lensemble des chires (0 9).
Un identificateur ne peut contenir plus de 255 caractres.
Un identificateur ne peut tre un mot-cl C  .

Le compilateur C  tient compte de la casse (i.e la dirence majuscule/minuscule).

Convention de nommage CamelCase et PascalCase


La convention de nommage PascalCase encourage le dveloppeur nommer ses identificateurs
avec des lettres minuscules sauf pour les premires lettres des mots. Par exemple : MaVariable,
UneFonction, UnIdentificateurPascalCase.
La convention de nommage CamelCase est similaire PascalCase mis part que le premier caractre est en minuscule. Par exemple : maVariable, uneFonction, unIdentificateurPascalCase.
Le nom CamelCase provient des bosses provoque par les caractres majuscules qui rapplent celles dun chameau (camel veut dire chameau en anglais). Il ny a pas de relation avec le
langage de programmation Camel.
En C  , il est conseill par Microsoft dutiliser la convention de nommage PascalCase pour les
identificateurs reprsentant des noms de mthodes, dvnements, de champs, de proprits,
de constantes, despaces de noms, de classes, de structures, de dlgations, dnumrations, dinterfaces et dattributs.
La convention CamelCase doit alors sappliquer aux noms de variables et de paramtres de mthodes.

Les structures de contrle

327

Il est aussi conseill de faire commencer le nom dun champ priv par m_ et de faire commencer
le nom dune interface par un I majuscule.

Les structures de contrle


C++ C 
et C  .

En ce qui concerne les structures de contrle, il y a peu de dirences entre C++

Lutilisation de linstruction switch a un peu chang. Le type de boucle, foreach, spcialement


adapt au parcours dlments dun tableau, fait son apparition.
C  Une structure de contrle est un lment du programme qui change le comportement
par dfaut de lunit dexcution (du thread). Rappelons que ce comportement par dfaut est
dexcuter les instructions les unes la suite des autres. En programmation, les structures de
contrle se dclinent gnralement en trois familles :

Les conditions, qui excutent (ou pas) un bloc de code qu une certaine condition, portant
gnralement sur les tats de variables et dobjets.

Les boucles, qui excutent en boucle un bloc dinstructions. Le programmeur a le choix


entre terminer de boucler aprs un certain nombre ditrations, ou terminer de boucler
une certaine condition, voire ne jamais terminer (boucle infinie).

Les branchements ou sauts, qui permettent de rediriger directement vers une instruction
particulire lunit dexcution. Cependant ce type de structures de contrle est proscrire
car il complexifie grandement la maintenance du code. De plus il est dmontr quon peut
toujours se passer de branchements dans du code source C  .

Les appels de mthodes modifient eux aussi le comportement par dfaut de lunit dexcution,
dexcuter les instructions les unes la suite des autres. Cela peut sassimiler un saut, la
dirence fondamentale qu la fin de la mthode, lunit dexcution est capable de retourner
linstruction situe juste aprs lappel de mthode. Ce comportement fait quen gnral on ne
classe pas un appel de mthode dans les structures de contrles.

Les conditions (if/else, ?:, switch)


C++ C  En C  il nest pas possible dcrire if(i=1) au lieu de if(i==1). Loprateur daffectation ne renvoie pas un boolen. Cette erreur est particulirement dangereuse en C++, car
elle est non dtectable par le compilateur.
Linstruction switch subit aussi quelques dirences expliques plus loin.
Aucun autre changement nest noter sur les tests et conditions de type if/else entre C/C++
et C  .

Utilisation de if/else
C

Une condition se prsente sous cette forme :


if ( expression retournant un bool
een )
Bloc dinstructions `a executer si lexpression retourne true
else
Bloc dinstructions `a executer si lexpression retourne false

328

Chapitre 9 : Les concepts fondamentaux du langage

Lensemble else et son bloc dinstructions est optionnel. Un bloc dinstructions peut tre une
seule instruction ou plusieurs instructions, auquel cas il faut placer les accolades qui dfinissent
le bloc dinstructions :
if ( expression retournant un bool
een )
i = j*5 ;
else{
// Commencement du bloc dinstructions `
a ex
ecuter
// si la condition est fausse.
i = j*2 ;
j++ ;
} // Fin du bloc dinstructions `
a ex
ecuter si la condition est fausse.
Pour les lecteurs non habitus, il faudra faire attention lors de la lecture du code, au cas o il
ny aurait quune instruction. Vous pouvez utiliser les accolades, mme dans le cas o il ny a
quune instruction. En fait, il est conseill de toujours utiliser les accolades pour amliorer la
lisibilit du code.
Une condition est considre comme une instruction. Il est donc tout fait possible dimbriquer
les conditions :
if( expression1 retournant un bool
een )
if( expression2 retournant un bool
een )
Bloc `a executer si expression1 et expression2 sont true
else // se rapportant `a lexpression2
Bloc `a executer si expression1 true et expression2 false
else // se rapportant `a lexpression1
if( expression3 retournant un bool
een ) // pas de bloc else pour ce if
Bloc dinstructions `a executer si expression1 false et expression3 true
Ceci nuit gravement la qualit du code. De plus les conditions imbriques sont, en gnral,
issues dune mauvaise conception.

Expressions qui retournent un boolen


Voici quelques exemples dexpressions, qui retournent un boolen :
bool b = true ; int
if( b )
if( !b )
if( b == true )
if( b == false)
if( i )
if( !i )
if( i == 4)
if( i != 4 )
if( i < 4)
if( i <= 4)
if( i < 4 && j > 6)

i = 5 ; int j = 8 ;
// si b vaut true alors...
// si b vaut false alors...
// si b vaut true alors...
// si b vaut false alors...
// si i diff
erent de 0 alors...
// si i egal `
a 0 alors...
// si i egal 4 alors...
// si i diff
erent de 4 alors...
// si i strictement inf
erieur `
a 4 alors...
// si i inf
erieur ou
egal `
a 4 alors...
// si i strictement inf
erieur `
a 4 et j strictement
// superieur `
a 6 alors...
if( i >= 4 && i<= 6)
// si i dans lintervalle ferm
e [4,6] alors...
if( i < 4 || j > 6)
// si i strictement inf
erieur `
a 4 ou j strictement

Les structures de contrle

if( i < 4 || i > 6)


if( i != 4 || b )

329
` 6 alors...
// superieur a
// si i hors de lintervalle ferm
e [4,6] alors...
// si i diff
erent de 4 ou b est true alors...

La facilit dcriture ?:
Une facilit dcriture est propose :
condition ? Val retournee si condition true : Val retourn
ee si condition false ;
On parle doprateur ternaire ?:. En eet, ces le seul oprateur pour lequel trois oprandes sont
prises en compte.
Voici quelques exemples dutilisation :
bool b = true ;
int i = 5 ;
int j = 8 ;
// k1 = i si b true, sinon k = j
int k1 = b ? i : j ;
// k2 = 6 si i different de j, sinon k2 = 7
int k2 = (i!=j) ? 6 : 7 ;
// s="bonjour" si i strictement inf
erieur `
a j, sinon s="hello"
string s = i<j ? "bonjour" : "hello" ;
// k3 = i*2 si i superieur ou egal `
a j, sinon k3 = i*3
int k3 = i>=j ? i*2 : i*3 ;

Linstruction switch
C++ C  Programmeur C/C++ ATTENTION ! Il y a de subtiles modifications en ce qui
concerne lutilisation du mot-cl switch :

Vous pouvez toujours switcher sur une variable de type entier, boolen, numration.
La nouveaut C  est que vous pouvez switcher sur une chane de caractres.

La continuation vers le mot-cl case suivant, ne se fait pas automatiquement, donc le motcl break est obligatoire. La continuation vers le mot-cl case suivant se fait automatiquement lorsquil ny a pas dinstructions pour le cas prsent.

Vous pouvez dclarer des variables lintrieur dun bloc dinstructions, dans un bloc dinstructions case, mme si celui-ci nest pas entre accolades.

C  Tout comme les mots-cls if/else, le mot-cl switch permet de modifier le cours du
programme en fonction de la valeur dune variable. Cependant, lutilisation de switch est particulirement adapte aux types valeurs discrtes (entiers, numrations, string) et sa syntaxe
permet de traiter plusieurs cas de valeurs, plus facilement que les mots-cls if/else. Voici un
exemple :

330

Chapitre 9 : Les concepts fondamentaux du langage

Exemple 9-20 :
class Program {
static void Main() {
int i = 6 ;
switch (i){
case 1:
System.Console.WriteLine("i vaut 1") ;
break ;
case 6:
System.Console.WriteLine("i vaut 6") ;
break ;
default:
System.Console.WriteLine("i ne vaut ni 1 ni 6") ;
break ;
}
}
}
Deux mots-cls apparaissent dans cet exemple, en plus des mots-cls switch et case :

break : lorsque lunit dexcution rencontre linstruction break, elle continue son cours
directement la fin du switch. Notez que si un bloc de code switch contient au moins une
instruction, il doit se terminer soit par linstruction break soit par une instruction goto soit
par une instruction return. Dans le cas contraire, le compilateur signale une erreur.

default : lunit dexcution se branche sur le bloc dinstructions default si la valeur de la


variable est dirente de toutes les valeurs spcifies dans les blocs case. Notez que le bloc
default nest pas ncessairement en dernire position, bien que la plupart des dveloppeurs
lutilisent comme cela.

Vous avez la possibilit dexcuter le mme bloc dinstructions pour plusieurs valeurs. Par
exemple :
Exemple 9-21 :
class Program {
static void Main() {
int i = 6 ;
switch (i){
case 1:
case 3:
case 6:
System.Console.WriteLine("i vaut 1 ou 3 ou 6") ;
break ;
default:
System.Console.WriteLine("i ne vaut ni 1 ni 3 ni 6") ;
break ;
}
}
}

Les structures de contrle

331

Dans ce cas, il est impratif quaucune instruction napparaisse aprs case 1: ou case 3:.
Enfin on peut utiliser aussi linstruction de branchement goto (dcrite un peu plus loin) mais
ceci est compltement proscrire. Le cas suivant montre que lon peut faire du code dicilement comprhensible en quelques lignes :
Exemple 9-22 :
class Program {
static void Main() {
int i = 6 ; int j = 7 ;
switch (i){
case 1:
System.Console.WriteLine("passage par case 1") ;
goto default ;
case 6:
System.Console.WriteLine("passage par case 6") ;
if (j > 2) goto case 1 ;
break ;
default:
System.Console.WriteLine("passage par default") ;
break ;
}
}
}
Ce programme ache :
passage par case 6
passage par case 1
passage par default
Enfin, sachez que les types possibles de la variable sur laquelle agit un switch sont :

Les types entiers sbyte byte short ushort int uint long ulong.

Les boolens, le type bool.


Les numrations.

Les chanes de caractres, le type string. Notez que dans ce cas, si une instruction switch
a plus de 6 blocs case le compilateur C  2 utilise une table de hachage pour viter de trop
nombreuses comparaisons de chanes de caractres.

Les boucles (do,while,for,foreach)


C++ C  Un nouveau type de boucle apparat, les boucles foreach, spcialement adaptes
pour parcourir les lments dun tableau. Cependant, toutes les possibilits sduisantes de lutilisation de foreach ne sont vraiment dtailles qu la section page 563.
Les trois autres types de boucles en C/C++, do/while , while et for ont une utilisation similaires
en C  , de mme que pour les mots-cls continue et break.
C  On parle de boucles (loop en anglais), lorsquun bloc dinstruction (entre accolades) ou
une instruction (pas forcment entre accolades) est excut plusieurs fois conscutivement.

332

Chapitre 9 : Les concepts fondamentaux du langage

Les boucles de type while et do/while


Dans ces deux types de boucles, le fait que le programme sorte de la boucle dpend dune condition (similaire celles vues dans la section prcdente). Par exemple :
Exemple :

Boucle while

Boucle do/while

int i=0;int j=8 ;


while( i < 6 && j > 9) {
i++;j-- ;
}

int i=0;int j=8 ;


do {
i++;j-- ;
}
while( i < 6 && j > 9)

La seule dirence entre ces types de boucle est mise en vidence dans cet exemple. Les boucles
do/while excutent au moins une fois le bloc dinstructions. Si la condition est fausse en entrant
dans le while, les boucles de type while nexcutent pas le bloc dinstructions.

Les boucles for


Les boucles for se prsentent ainsi :
for(Instruction(s) dinitialisation (s
epar
ees par des virgules) ;
Condition de sortie de boucle (sort si false) v
erifi
ee `
a chaque
debut de boucle ;
Instruction(s) effectuee(s) `
a chaque fin de boucle(s
epar
ee par
des virgules))
Bloc dinstructions `a executer `
a chaque boucle
La condition est similaire celles vues dans la section prcdente. Voici quelques exemples :
for(int i = 1 ; i<=6 ; i++) ...
//-------------------------------------------------------------------int i = 3;int j=5 ;
for( ; i<7&& j>1 ;i++ , j--)...
//-------------------------------------------------------------------for( int i =6 ; i<9 ; ) { ... i++;}
Les variables dclares dans les instructions dinitialisation de la boucle for doivent avoir des
noms dirents des variables extrieures et intrieures la boucle. Ces variables ne seront plus
visibles ds la sortie de la boucle.

Les boucles foreach


Les boucles de type foreach sont spcialement adaptes au parcours des lments dun tableau
ou dune collection. Par consquent nous dtaillons ceci lors de la prsentation des tableaux,
dans la section page 563.

Les instructions break et continue


Deux instructions existent pour modifier le cours dune boucle (de type while do/while for
ou foreach).

Linstruction continue fait passer directement litration suivante de la boucle.

Les structures de contrle

333

Linstruction break fait quitter la boucle. Cette instruction fait aussi quitter un bloc switch
comme on la vue prcdemment.

Dans le cas de boucles imbriques, ces deux instructions sappliquent la boucle la plus proche
deux.
Exemple 9-23 :
class Program {
static void Main() {
for (int i = 0 ; i < 10 ; i++){
System.Console.Write(i) ;
if (i == 2) continue ;
System.Console.Write("C") ;
if (i == 3) break ;
System.Console.Write("B") ;
}
}
}
Ce programme ache :
0CB1CB23C
Les instructions break et continue ont tendance compliquer la lisibilit du code. Le rsultat
de lexemple prcdent, nest vident pour personne. Il faut donc les utiliser le moins souvent
possible.
Linstruction break peut aussi tre utilise pour interrompre ce quon appelle des boucles infinies, cest--dire des boucles de type for, do/while et while, dont la condition de sortie est
toujours vraie. Voici des exemples de boucles infinies :
for(;;) {...}
for(;true;) {...}
while(true) {...}
do{...}
while(true) {...}

Boucles et optimisations
Du fait que le code contenu dans une boucle est potentiellement sujet un grand nombre
dexcutions, il peut tre ecace dessayer de loptimiser. Voici quelques conseils :

Si vous dtectez des appels des mthodes qui ncessitent beaucoup de passages darguments, il peut tre ecace de copier le corps de la mthode dans la boucle (on parle
dinlining). Remarquez quen page 112, nous expliquons que parfois le compilateur JIT du
CLR est capable deectuer une telle optimisation.

Si vous accdez une proprit dun objet dont vous savez que la valeur retourne restera
constante durant toute la boucle, il est ecace de stocker au pralable ces valeurs constantes
dans des variables locales.

Penser utiliser la classe StringBuilder plutt que la classe String pour fabriquer une
chane de caractres dans une boucle.

334

Chapitre 9 : Les concepts fondamentaux du langage

Si vous avez le choix, ayez recours des boucles plutt qu la rcursivit.

Si vous avez tester plusieurs conditions de sortie dune boucle, il est ecace de tester en
premier la condition de sortie la plus probable.

Bien quen gnral moins pratique dutilisation, les boucles for sont lgrement plus ecaces que les boucles foreach.

Lorsque vous ralisez des optimisations, essayez surtout de quantifier le gain de performance apport. En eet, dans un environnement gr par le CLR, certaines de vos optimisations peuvent
gner le CLR et son compilateur JIT pour finalement savrer contre productives.

Les branchements (goto)


Linstruction de branchement goto a eu son heure de gloire il y a bien longtemps. Lutilisation
de linstruction goto ne doit se faire que dans certains cas trs particuliers. Par exemple, linstruction goto peut se rvler fort utile dans le code dun compilateur ou dans du code gnr.
Certains prnent lutilisation de goto pour coder une machine tat. Dans ce cas il vaut mieux
ne pas lutiliser et faire une machine tat partir dune utilisation de switch dans une boucle
infinie.
Linstruction goto force lunit dexcution continuer sur les instructions aprs une tiquette.
Cette tiquette et le goto doivent tre absolument dans la mme mthode. De plus ltiquette
doit tre visible du goto, ce qui ne signifie pas quelle doit tre dans le mme bloc dinstructions.
Par exemple, le code suivant compile :
Exemple 9-24 :
class Program {
static void Main() {
int i = 0 ;
goto label2;
label1:
i++ ;
goto label3;
label2:
i-- ;
goto label1;
label3:
System.Console.WriteLine(i) ;
}
}
Il se peut quune mauvaise utilisation de goto amne un cas de variable non initialise. Le
compilateur C  dtecte ceci et produit une erreur.

La mthode Main()
C++ C  Comme en C/C++, en C  le point dentre dun assemblage excutable est une
mthode appele Main().

La mthode Main()

335

Comme en C/C++, en C  , la mthode Main() peut retourner int ou void et accepte ventuellement un tableau de chanes de caractres reprsentant les arguments en ligne de commande de
lexcutable. En C  , il nest plus besoin de prciser la taille du tableau.
la dirence de C/C++ le tableau de chanes de caractres ne contient pas le nom de lexcutable en premire occurrence, mais directement le premier argument.
la dirence de C/C++ le m de Main est une majuscule.
la dirence de C/C++ Main est une mthode statique dune classe et non une fonction globale.
C  Chaque assemblage directement excutable (i.e dont le module principal a une extension
.exe) possde au moins une mthode statique Main() dans une de ses classes. Cette mthode
reprsente le point dentre du programme, cest--dire que juste aprs le lancement et linitialisation dune application .NET, le thread principal va commencer par excuter le code de cette
mthode. Lorsque cette mthode retourne, le processus est dtruit la condition quil ny ait pas
de threads de premier plan (thread foreground) qui soient toujours en cours dexcution. Si ces
notions de thread ou de thread foreground vous sont trangres, sachez quelles sont prsentes
au dbut du chapitre 5.
Un mme assemblage peut avoir ventuellement plusieurs mthodes Main() (chacune dans une
classe dirente). Le cas chant, il faut prciser au compilateur quelle mthode Main() constitue le point dentre du programme. Ce qui peut se faire soit avec loption /main en ligne de
commande du compilateur csc.exe, soit dans les proprits du projet sous Visual Studio, Application  startup object. Cette facilit est extrmement utile pour dboguer une classe particulire.
Une mthode Main() est statique et sa signature suit les rgles suivantes :

Elle retourne le type void ou int.

Elle accepte un tableau de chanes de caractres facultatif en argument. Le cas chant,


les chanes contiennent les arguments en ligne de commande de lexcutable. La premire
chane de caractres reprsente le premier argument, la deuxime chane de caractres reprsente le deuxime argument ...

Voici direntes dfinitions possibles pour la mthode Main() :


static
static
static
static

void Main() {/*...*/}


int Main() {/*...*/}
void Main(string[] args) {/*...*/}
int Main(string[] args) {/*...*/}

Par exemple le programme suivant ajoute les nombres passs en arguments en ligne de commande, et ache le rsultat. Sil ny a pas dargument, le programme le signale :
Exemple 9-25 :
class Program {
static void Main(string[] args) {
if (args.Length == 0)
System.Console.WriteLine("Entrez des nombres `
a ajouter.") ;
else{
long result = 0 ;
foreach (string s in args)
result += System.Int64.Parse(s) ;

336

Chapitre 9 : Les concepts fondamentaux du langage


System.Console.WriteLine("Somme de ces nombres :{0}", result) ;
}
}
}

Les informations communiques en ligne de commande (ainsi que les valeurs des variables
denvironnement) peuvent tre aussi rcupres grce aux mthodes string[] GetCommandLineArgs() et IDictionary GetEnvironmentVariables() de la classe System.Environment.

10
Le systme de types

C++ C 

Ce chapitre contient la plupart des grosses dirences entre C/C++ et C  .

C  C  est un langage typ, cest--dire que chaque objet a un type et un seul. Ce type est
compltement dfini au moment de la cration de lobjet, lexcution. En C  chaque variable
doit tre initialise, sinon le compilateur produira une erreur lors de son utilisation.

Stockage des objets en mmoire


Les concepts de threads (i.e unit dexcutions) et de processus (i.e tches ou espace dadressage)
sont requis pour comprendre cette section, et plus gnralement ce chapitre. Ces concepts sont
introduits au dbut du chapitre 5.

Allocation/dsallocation
Lors de lexcution dun programme, le fait de rserver une zone mmoire pour quelle
contienne les donnes relatives un objet est nomm allocation. Lopration inverse de restitution de la zone mmoire est appele dsallocation. La taille de cette zone mmoire, spcifie
en octets, doit tre au moins gale au nombre doctets ncessaires pour stocker ltat de lobjet.
Ce nombre doctets est fonction de limplmentation de lobjet.
Un processus peut un instant donn contenir un ou plusieurs threads. En tant quespace
dadressage, le processus contient toutes les zones mmoires alloues pour tous les objets du
programme. En tant quunits dexcution, seuls les threads peuvent utiliser les objets.

La pile
Chaque thread Windows a une zone mmoire prive que lon nomme la pile (stack en anglais).
Cette zone de mmoire est prive dans le sens o elle ne devrait pas tre accessible par les autres
threads (bien que ceci soit possible sous certaines conditions spciales). Cette zone mmoire est

338

Chapitre 10 : Le systme de types

contenue dans lespace dadressage du processus du thread. Le thread se sert de sa pile principalement pour :

stocker les valeurs des arguments de la fonction couramment excute ;


stocker ladresse (dans le code machine natif) laquelle il faudra se brancher lorsque la
fonction retournera ;
stocker certains objets (mais pas tous).

Chaque thread a un accs privilgi sa pile. En eet, les jeux dinstructions machine contiennent
des instructions optimises pour accder la pile. En outre le langage IL contient de nombreuses
instructions ddies la gestion de la pile. Notez quune pile est de taille variable et borne en
gnral par une grandeur de lordre du Mo. Cette limite peut tre dfinie lors de la construction
du thread.

Le tas
Un processus a gnralement un seul (mais parfois plusieurs) tas (heap en anglais). Cest une
zone mmoire contenue dans lespace dadressage du processus. Cette zone mmoire est accessible par tous les threads du processus. Donc, contrairement aux piles des threads dun processus, le tas nest pas spcifique un thread particulier. Le tas est principalement utilis pour
stocker des objets et, linstar des piles, sa taille peut varier au cours du temps. Cependant la
taille maximale du tas est beaucoup plus grosse que le Mo. En fait, il est assez singulier que le
bon droulement dune application soit limit par la taille maximale du tas.

Comparaison pile/tas
Un objet peut donc tre stock soit dans une pile dun thread soit dans un tas dun processus.
Les notions de pile et de tas doivent coexister car chacune a ses avantages :

Lavantage du tas est quil peut tre beaucoup plus gros quune pile. De plus il est accessible
par tous les threads du processus mais ceci nest pas toujours un avantage.
Lavantage de la pile est que laccs aux donnes est plus rapide quavec le tas. Ce gain est d
des instructions IL spciales. Ce gain est aussi d au fait que laccs la pile na pas tre
synchronis.

Il serait donc judicieux dutiliser le tas pour stocker les objets volumineux et dutiliser la pile
pour stocker les objets de petite taille. Nous allons voir que cest exactement ce choix qui a t
fait par les concepteurs de .NET.

Allocations statiques et allocations dynamiques


C++ C  Un point commun de C++ et de C  est que les objets peuvent tre allous soit
dans la pile dun thread (on parle dallocation statique) soit dans le tas du processus (on parle
dallocation dynamique). La dirence entre C++ et C  est la faon dont le mode dallocation
(statique ou dynamique) est choisi :

En C++ le choix du mode dallocation dune variable (dun objet) est laiss au dveloppeur.
Lallocation statique est utilise lorsque lobjet est directement dclare dans le code (par
exemple int i=0;) Lallocation dynamique est utilise lorsque lobjet est alloue avec loprateur new (par exemple int * pi = new int(0);).

Type valeur et type rfrence

339

En C  le choix du mode dallocation dun objet est fonction de son implmentation.. En


eet, nous allons voir quil existe deux sortes de types. Les types valeur dont les instances
sont alloues statiquement, et les types rfrence, dont les instances sont alloues dynamiquement.

Une autre dirence importante entre C++ et C  est la responsabilit de la dsallocation des
variables dynamiques. En C++ cette responsabilit incombe au dveloppeur alors quen C  elle
incombe une couche logicielle fournie par lenvironnement dexcution .NET. Cette couche
est nomme ramasse-miettes et elle fait lobjet de la section 116. Dans tous les cas, cette responsabilit est lourde car si les variables alloues dynamiquement, devenues inutiles, ne sont pas
rgulirement dsalloues, la taille du tas crot en permanence et finira srement par causer
des problmes. Ce type de problme est connu sous le nom de fuite de mmoire (memory leak en
anglais).
La dsallocation des variables alloues statiquement est dans tous les cas sous la responsabilit
du thread qui possde la pile concerne. Retenez surtout quen C  la responsabilit du dveloppeur est allge par rapport au C++ :

Il na pas choisir le type dallocation de ses variables.

Il na pas se soucier de la dsallocation de ses variables.

Type valeur et type rfrence


C++ C  Si vous tes programmeur C++ vous risquez dtre interpell par ces notions de
types valeur/rfrence. Nous allons voir quen C  loprateur new peut tre utilis pour allouer
dynamiquement, mais aussi statiquement une variable. Une consquence est que lon ne peut
plus fournir les arguments du constructeur directement aprs la dclaration dune variable. Rappelons quen C++ lutilisation de loprateur new est rserve pour allouer dynamiquement une
variable.

C  La notion de type valeur/rfrence est fondamentale, quelque soit le langage .NET


que vous utilisez.
Chaque type en C  est soit un type valeur (value type en anglais) soit un type rfrence (reference
type en anglais). Chaque objet est donc soit linstance dun type valeur, soit linstance dun type
rfrence. Une instance dun type valeur est alloue sur la pile du thread (allocation statique),
une instance dun type rfrence est alloue sur le tas du processus (allocation dynamique).
On ne manipule jamais directement des objets de type rfrence. On manipule ces objets par
lintermdiaire de rfrences. En revanche, on manipule toujours directement un objet de type
valeur. Voici un exemple pour illustrer ce fait (nous anticipons un peu la notion de structures,
qui dfinissent toujours des types valeur, et la notion de classes, qui dfinissent toujours des
types rfrence) :

340

Chapitre 10 : Le systme de types

Exemple 10-1 :
// TypeVal est un type valeur, car cest une structure.
struct TypeVal {
public int m_i ;
public TypeVal( int i ) { m_i = i ; }
}
// TypeRef est un type reference, car cest une classe.
class TypeRef {
public int m_i ;
public TypeRef( int i ) { m_i = i ; }
}
class Program {
static void Main() {
TypeVal v1 = new TypeVal(6);
TypeVal v2 = v1; // Une nouvelle instance du type TypeVal est
// creee et le champ v2.i est aussi
egal `
a 6.
// Neanmoins v1 et v2 sont deux instances diff
erentes
// de type TypeVal.
v2.m_i = 9;
// Ici v1.i vaut 6, il y a bien deux instances du type TypeVal.
System.Diagnostics.Debug.Assert( v1.m_i == 6 && v2.m_i == 9 ) ;
TypeRef r1 = new TypeRef(6);
TypeRef r2 = r1; // Il ny a pas de nouvelle instance de la
// classe TypeRef. r2 et r1 sont deux r
ef
erences de la
// meme instance de la classe TypeRef.
r2.m_i = 9;
// Ici r1.i vaut 9, il ny a quune seule instance de la
// classe TypeRef.
System.Diagnostics.Debug.Assert( r1.m_i == 9 && r2.m_i == 9 );
}
}
On saperoit dans lexemple prcdent que loprateur new peut tre ventuellement utilis
pour les allocations dobjets de type valeur mais ne modifie en rien le caractre statique de lallocation. Dans ce cas, cet oprateur sert cependant communiquer des paramtres au constructeur.

Contrairement au C++, lors dune allocation statique (i.e dun type valeur) en C  on ne
peut fournir des arguments au constructeur sans utiliser loprateur new. En C++ ceci tait
accept par le compilateur :
MyType v1(6) ;
En C  il faut crire :
MyType v1 = new MyType(6) ;
Dans le cas dune allocation dynamique, donc de lallocation dun objet de type rfrence, lutilisation de loprateur new est obligatoire (y compris si le constructeur ne prend pas de paramtres). Nous allons dtailler par la suite quels sont les types valeur et quels sont les types

Type valeur et type rfrence

341

rfrence, mais nous pouvons dj prciser que les types valeur sont les types primitifs de C 
(dclars avec les mots-cls int,double...) les structures (dclares avec le mot-cl struct) et
les numrations (dclares avec le mot-cl enum) alors que les types rfrence sont les classes
(dclares avec le mot-cl class) et les dlgations (qui sont des classes particulires dclares
avec le mot-cl delegate).
Les instances des types valeur ne sont pas toujours stockes sur la pile. En eet, lorsquun champ
dune instance de classe est de type valeur, il est stock au mme endroit que linstance de la
classe, cest--dire sur le tas. En revanche, les objets de types rfrence sont toujours stocks sur
le tas. Lorsquun champ dune instance de structure est de type rfrence, seule la rfrence est
stocke au mme endroit que linstance de la structure (sur la pile ou sur le tas selon les cas).

Notion de rfrence sur une instance de classe


C++ C  La notion de rfrence en C  est mi-chemin entre la notion de pointeur et de rfrence de C++. Comme une rfrence C++, une rfrence C  rfrence un objet, et les membres
publics de lobjet sont accessibles avec loprateur . .
linverse dune rfrence C++, et comme un pointeur C++, une rfrence C  peut tre nulle.
linverse dune rfrence C++, et comme un pointeur C++, une rfrence C  peut tre modifie. Cest--dire que pour une rfrence donne, lobjet rfrenc (point) nest pas ncessairement le mme au cours du temps.
linverse dun pointeur C++, et comme une rfrence C++, une rfrence C  ne donne pas
accs ladresse physique de lobjet et ne permet aucune manipulation dadresse (comme lincrmentation vers lobjet suivant) de type arithmtique des pointeurs .
C  Toute classe est un type rfrence et tout type rfrence est une classe ou une interface.
Toutes les classes hritent de la classe System.Object que nous allons bientt dcrire.
Il peut aussi y avoir une certaine ambigut entre les termes type, classe et implmentation. La
signification du terme type varie selon le contexte. On parle de type dune rfrence pour dsigner la classe ou linterface qui type une rfrence. On parle de type dun objet pour dsigner
son implmentation. Limplmentation dun objet dsigne la classe ou la structure dont il est
instance. Le terme type est donc plus gnral et plus abstrait que le terme implmentation qui lui
mme est plus gnral que le terme classe.
La notion de classe est un vaste sujet, qui fait lobjet du chapitre suivant. Nous allons nous intresser ici au fait quen tant que type rfrence, les instances des classes, sont EXCLUSIVEMENT
manipuls au travers de rfrences. Etudions lexemple suivant :
Exemple 10-2 :
class Personne {
public int m_Age ;
public string m_Nom ;
public Personne(int Age, string Nom) { m_Age = Age ; m_Nom = Nom ; }
}
class Program {
static void Main() {
Personne ref1 = null ; // ref1 ne r
ef
erence personne.
Personne ref2 = new Personne(50, "Raymond") ;

342

Chapitre 10 : Le systme de types


Personne ref3 = new Personne(48, "Josiane") ;
ref1 = ref2 ;
// ref1 r
ef
erence Raymond.
ref1.m_Age += 10 ;
// Raymond est vieilli de 10 ans !
ref1 = ref3 ;
// ref1 r
ef
erence Josiane.
ref1.m_Age += 10 ;
// Josiane est vieillie de 10 ans !
// ici ref2.m_Age == 60 , Raymond a 60 ans.
// ici ref3.m_Age == 58 , Josiane a 58 ans.
}
}

Une classe est dclare avec le mot-cl class. Avant tout, comprenez bien que ref1 ref2 et ref3
sont trois rfrences vers des objets, instances de la classe Personne. Au dpart ref1 est initialise
avec le mot-cl null. Cela signifie quaucun objet nest rfrenc par ref1. ce stade, aucun
membre de Personne ne peut tre utilis sur ref1.
Deux objets Personne sont alors allous (donc sont allous dynamiquement sur le tas du processus puisquils sont de type rfrence). Appelons-les Raymond et Josiane. Contrairement ce
que lon a vu pour les types valeur, dans le cas de type rfrence, il est obligatoire dutiliser loprateur new pour crer Raymond et Josiane. ce stade nous disposons des deux objets Raymond et
Josiane qui instancient la classe Personne. Nous disposons aussi de trois rfrences, ref1 qui est
nulle, ref2 qui rfrence Raymond et ref3 qui rfrence Josiane. ref1 rfrence alors Raymond.
Raymond est vieilli de 10 ans. ref1 rfrence alors Josiane. Josiane est vieillie de 10 ans.
Au final, les objets Raymond et Josiane ont tous les deux t modifis sans passer par les rfrences ref2 et ref3. De plus les objets Raymond et Josiane ne sont jamais manipuls directement. La syntaxe de C  ne permet de manipuler des instances de types rfrence que par lintermdiaire de rfrences.
Laccolade de fin de la mthode Main() implique que les objets Raymond et Josiane ne sont
plus rfrencs (en eet les rfrences ref1, ref2 et ref3 nexistent plus). Raymond et Josiane ne
seront plus jamais utiliss puisquils ne sont plus rfrencs. Ainsi ces deux objets seront marqus comme non actifs par le prochain dclenchement du ramasse-miettes. Ils seront dsallous
ultrieurement.

Le CTS (Common Type System)


Les types .NET sont indpendants du langage
Un aspect trs intressant de .NET est que les types utiliss sont indpendants du langage dans
lequel nous crivons notre code source. Ceci est possible grce un systme de types commun
tous les langages .NET nomm le CTS (Common Type System). Le CTS est une spcification qui
dcrit les direntes caractristiques des dirents types connus par le CLR.
Lexistence du CTS rsout de trs nombreux problmes bien connus de ceux qui ont dj dvelopp des modules devant communiquer entre eux dans des langages dirents. Par exemple
les chanes de caractres en VB sont reprsentes par des instances du type BSTR et en C++ par
des pointeurs vers un tableau de char (et encore, ne mentionnons pas le type string de la
STL et le type CString des MFC). La consquence est quun module crit en C++, utilis par
un programme crit en VB, doit manipuler des instances de BSTR et utiliser des fonctions de
conversion. Cela rajoute de la complexit aux programmes et a donc un cot non ngligeable
(voire prohibitif si en plus on mlange des formats dencodages tels que ASCII ou UNICODE).

Le CTS (Common Type System)

343

Vue densemble du CTS


Le CTS se dcline en un ensemble de types valeur et rfrence. Le CTS vous laisse lopportunit
de dfinir vos propres types :
Types valeurs

Types rfrences

System.Object

Interfaces

Classes

System.ValueType

System.String

System.SByte

System.Byte

System.Int16

System.UInt16

System.Int32

System.UInt32

System.Int64

System.UInt64

System.Char

System.Boolean

System.Double

System.Single

System.MultiCastDelegate

System.Enum

System.Decimal

Dlgations

numrations

Structures

System.Array

Tableaux

System.Delegate

Pointeurs

Pointeurs non grs

Pointeurs non grs

Pointeurs de fonction non grs

Lgende

A drive de B

Type utilisateur Type connu du CLR Type connu du


CLR, non CLS

Figure 10 -1 : Vue densemble du CTS


Le reste de ce chapitre est ddi ltude des dirents types du CTS que lon peut classer comme
ceci :

Les types primitifs (appels aussi types lmentaires) : Ces types reprsentent les entiers, les
nombres virgules, les caractres et les boolens. Ce sont des types valeur et en gnral
les langages dfinissent des alias pour faciliter leur utilisation. Ainsi le type System.Int16
correspond lalias short en C  et Short en VB.NET.
Les numrations : Ces types sont de type valeur et sont utiliss pour typer des ensembles de
valeurs.
Les structures : Ces types sont de type valeur. Les structures et les classes ont des similitudes
et des dirences.
Les classes : Ces types sont de type rfrence. Notez que le type reprsentant les chanes de
caractres et le type reprsentant les tableaux sont respectivement les classes System.String

344

Chapitre 10 : Le systme de types


et System.Array (dcrites respectivement page 370 et page 570). Notez que lutilisation de
la classe Array fait lobjet dune syntaxe particulire en C  et en VB.NET.

Les dlgations : Ces types sont des classes particulires dont les instances sont utilises pour
rfrencer des mthodes.

Les pointeurs : Ces types sont trs spciaux et utilisables seulement sous certaines conditions.
Nous dtaillons ce sujet dans la section en page 503.

La classe System.Object
C++ C  En C  toutes les classes et toutes les structures drivent de la classe Object. Les
mthodes de la classe Object, utilisables par toutes les classes et structures, ajoutent des fonctionnalits de hachage dobjet et de RTTI (Run Time Type Information). La fonctionnalit la plus
utilise est assurment la possibilit de redfinir la mthode Object.ToString() qui est cense
retourner une chane de caractres dcrivant lobjet, un peu comme loprateur C++ dans les
flots de donnes.
C  Le mot cl object du langage C  est un alias vers System.Object. La vue densemble du
CTS montre que mis part les interfaces et les types pointeurs, tous les types drivent automatiquement et implicitement, directement ou indirectement de la classe Object. En ce qui
concerne les interfaces on peut toujours convertir un objet rfrenc par une interface en une
rfrence de type Object car une interface est toujours implmente par un type rfrence ou
valeur. Ainsi, la classe Object joue un rle prpondrant dans larchitecture de la plateforme
.NET. De nombreuses mthodes standard acceptent leurs arguments sous forme de rfrences
types par la classe Object.
La classe Object prsente plusieurs mthodes. Chacune de ces mthodes peut donc sappliquer
tous les objets. Nanmoins il est logique de redfinir certaines des mthodes virtuelles pour
pouvoir les utiliser. Ces mthodes virtuelles peuvent tre redfinies dans le cadre dune classe,
mais aussi dans le cadre dune structure. Dans le cas dune numration, seule la mthode virtuelle ToString() est automatiquement redfinie par le compilateur. Les mthodes de la classe
Object sont :

public Type GetType()


Renvoie le type dun objet.

public virtual String ToString()


Renvoie une chane de caractres dcrivant lobjet. Le comportement par dfaut est de retourner le nom du type, ce qui nest pas ce que souhaite le dveloppeur la plupart du temps.
Voici un exemple de redfinition et dutilisation de cette mthode :
Exemple 10-3 :
class Personne { // On aurait pu mettre struct `
a la place de class.
string m_Nom ;
int m_Age ;
public Personne(string Nom, int Age) { m_Nom = Nom ; m_Age = Age ; }
public override string ToString() {
return "Nom:" + m_Nom + " Age:" + m_Age ;
}
}

Comparer des objets

345

class Program {
static void Main() {
Personne raymond = new Personne("Raymond", 50) ;
// WriteLine() appelle automatiquement Raymond.ToString()
System.Console.WriteLine(raymond) ;
}
}
Ce programme ache :
Nom:Raymond Age:50
Console.WriteLine() appelle automatiquement la mthode virtuelle ToString() des objets dont elle doit acher ltat sur la console. Si cette mthode nest pas redfinie, cest limplmentation par dfaut de la mthode ToString() de la classe Object qui est invoque.

public virtual void Finalize()


Cette mthode est appele lorsque lobjet est dtruit par le ramasse-miettes. En C  il est
impossible de redfinir explicitement cette mthode. Pour la redfinir, il faut utiliser une
syntaxe particulire explique en page 423.

protected object MemberwiseClone()


Cette mthode est explique un peu plus loin lors de la prsentation du clonage dobjet.

public static bool ReferenceEquals(object objA, object objB)

public virtual bool Equals(object obj)

public virtual int GetHashCode()


Ces trois mthodes sont expliques dans la section suivante.

Comparer des objets


Comparaison entre objets : identit vs. quivalence
Par dfaut il existe trois faons de comparer deux instances dun mme type rfrence : lutilisation dun des deux oprateurs == ou =! , lutilisation de la mthode dinstance Object.Equals() ou lutilisation de la mthode statique Object.ReferenceEquals(). Dans ce dernier cas, la comparaison se fait sur les rfrences et non sur ltat des objets. Deux rfrences sont
gales si elles rfrencent le mme objet. Cest la comparaison selon lidentit.
Par dfaut vous ne pouvez comparer deux instances dun mme type valeur quen utilisant la
mthode virtuelle Object.Equals(). Limplmentation par dfaut de cette mthode compare
les instances champs champs en ayant recours au mcanisme de rflexion. Deux instances dun
type valeur sont considres comme gales si leurs champs sont gaux deux deux. Cest la
comparaison selon lquivalence. Lexemple suivant illustre tout ceci :
Exemple 10-4 :
using System.Diagnostics ;
class TypeRef { public int state;}
struct TypeVal { public int state;}
public class Program {
public static void Main() {

346

Chapitre 10 : Le systme de types


// Comparaison selon lidentit
e.
TypeRef ref1 = new TypeRef() ; ref1.state = 3 ;
TypeRef ref2 = new TypeRef() ; ref2.state = 3 ;
Debug.Assert( ref1 != ref2 );
Debug.Assert( ! ref1.Equals(ref2) );
Debug.Assert( ! object.ReferenceEquals(ref1, ref2 ));
ref2 = ref1;
Debug.Assert( ref1 == ref2 );
Debug.Assert( ref1.Equals(ref2) );
Debug.Assert( object.ReferenceEquals(ref1, ref2) );
// Comparaison selon lequivalence.
TypeVal val1 = new TypeVal() ; val1.state = 3 ;
TypeVal val2 = new TypeVal() ; val2.state = 3 ;
Debug.Assert(val1.Equals(val2));
val1.state = 4;
Debug.Assert(!val1.Equals(val2));
}
}

Nous allons maintenant nous intresser aux possibilits oertes par le framework .NET pour
personnaliser la comparaison des instances de vos types.

Personnaliser le test dgalit entre deux objets


Si vous ne souhaitez que redfinir le test dgalit entre deux instances, il vaut mieux la fois rcrire la mthode Object.Equals() et redfinir les oprateurs == et != . Limplmentation
de vos oprateur doit alors appeler seulement la mthode Object.Equals(). Plus dinformation
concernant la redfinition des oprateurs sont disponibles en page 433.
Il est conseill de redfinir la mthode Equals() et les oprateurs dgalit pour tous les types
valeurs qui ont besoin davoir leurs instances compares. Vous y gagnerez en performance car
limplmentation par dfaut de la mthode Equals() pour les types valeur utilise la rflexion
et nest donc pas ecace.
Il est conseill que limplmentation de Object.Equals() dfinisse ce que lon appelle une relation dquivalence sur les instances du type concern. Pour cela il faut que limplmentation soit
rflexive (i.e x.Equals(x) retourne true pour tout instance x) symtrique (i.e x.Equals(y) est
gal y.Equals(x) pour toutes instances x et y) et transitive (i.e si x.Equals(y) et y.Equals(z)
sont gales true, alors x.Equals(z) est gale true pour toutes instances x, y et z).
Il est aussi conseill que la rcriture de la mthode Equals() dans une classe D drive de la
classe B, appelle la rcriture de la mthode Equals() dfinie dans la classe B si celle-ci existe.
En gnral, on dcide de personnaliser le test dgalit entre deux objets de type rfrence pour
obtenir un comportement de comparaison par quivalence. Cette pratique est notamment illustre par la faon dont les instances de la classe System.String sont compares. En eet, nous
verrons un peu plus loin que les chanes de caractres se comparent selon lquivalence.

Cloner des objets

347

Possibilit de stocker vos objets dans une table de hachage


Si vous ne redfinissez que la mthode Object.Equals() vous vous apercevrez que le compilateur C  2 met un avertissement vous conseillant de rcrire la mthode Object.GetHashCode().
Cet avertissement est mis indpendamment du fait que vous rcrivez ou non les oprateurs
dgalit. En fait, vous ne devez suivre ce conseil que si les instances du type concern peuvent
potentiellement servir de cls dans une table de hachage. Plus dinformation ce sujet sont
disponibles en page 584.

Personnaliser lordonnancement de vos objets


En plus de pouvoir comparer lgalit de vos instances, vous pouvez aussi dsirer pouvoir les
ordonner, par exemple pour les stocker dans une liste trie. Dans ce cas, vous avez le choix entre
implmenter linterface System.IComparable<T> sur le type des objets comparer ou implmenter linterface System.Collections.Generic.IComparer<T> dans une classe spcialise. Si
vous implmentez une de ces interfaces, il vaut mieux aussi surcharger les oprateurs == ,
!= , < , > , <= et >= de faon ce que les implmentations de ces surcharges applent limplmentation que vous avez fourni pour linterface.

Cloner des objets


Nous avons souvent besoin dobtenir une copie dun objet existant, cest--dire, de cloner un
objet. Il est plus rigoureux de dire que lon copie ltat de lobjet dans un autre objet. Pour
certaines classes, cloner une instance na pas de sens. Par exemple il ny a pas de sens copier
une instance de la classe Thread.
Pour les instances de types valeur, loprateur daectation = copie ltat de lobjet source
dans lobjet destination. La copie est ralise octet par octet. Par exemple, lopration de boxing
se sert de cette copie.
Pour les instances de types rfrence, loprateur daectation = copie la rfrence, et
non lobjet. Il est donc ncessaire de prvoir une mthode pour copier ltat dun objet
de type rfrence. Vous pouvez implmenter la mthode object Clone() de linterface
System.ICloneable, prvue cet eet. Cette interface ne prsente que cette mthode. Voici
un exemple qui illustre limplmentation de cette interface :
Exemple 10-5 :
class Article {
public string Description ;
public int Prix ;
}
class Commande : System.ICloneable {
public int Quantite ;
public Article Article ;
public override string ToString() {
return "Commande : " + Quantite + " x " + Article.Description +
" Cout total : " + Article.Prix * Quantite ;
}
public object Clone() {
Commande clone = new Commande();

348

Chapitre 10 : Le systme de types


// Copie superficielle
clone.Quantite = this.Quantite;
clone.Article = this.Article;
return clone;
}
}
class Program {
static void Main() {
Commande commande = new Commande() ;
commande.Quantite = 2 ;
commande.Article = new Article() ;
commande.Article.Description = "Chaussure" ;
commande.Article.Prix = 80 ;
System.Console.WriteLine(commande) ;
Commande commandeClone = commande.Clone() as Commande ;
commandeClone.Article.Description = "Veste" ;
System.Console.WriteLine(commande) ;
}
}

Cet exemple ache ceci :


Commande : 2 x Chaussure
Cout total : 160
Commande : 2 x Veste
Cout total : 160
Clairement, la modification faite sur larticle de la commande clone a t rpercute sur larticle de la commande originale. Cela est tout fait normal puisque dans ce programme il nexiste
quune seule instance de la classe Article. Elle est rfrence par les deux instances de la classe
Commande. On dit que la commande originale a subit une copie superficielle (shallow copy en anglais). La classe Object prsente la mthode protge MemberwiseClone() qui permet de raliser
une copie superficielle. Ainsi on peut rcrire lexemple prcdent comme ceci sans en modifier
la smantique :
Exemple 10-6 :
...
class Commande : System.ICloneable {
...
public object Clone() {
// Copie superficielle
return this.MemberwiseClone();
}
}
...
La notion de copie superficielle induit des bugs car en gnral il nest pas considr comme
normal quune modification sur un graphe dobjets clons soit rpercute sur le graphe original.
On peut lui prfrer la notion de copie en profondeur (deep copy en anglais) qui comme son nom
lindique, clone la totalit du graphe. Adaptons notre exemple la copie en profondeur :

Cloner des objets

349

Exemple 10-7 :
...
class Article : System.ICloneable {
...
public object Clone() {
// Copie superficielle = Copie en profondeur.
return this.MemberwiseClone() ;
}
}
class Commande : System.ICloneable {
...
public object Clone() {
// Copie en profondeur.
Commande clone = new Commande() ;
clone.Quantite = this.Quantite ;
clone.Article = this.Article.Clone() as Article;
return clone ;
}
}
...
Lachage de ce programme est maintenant ceci :
Commande : 2 x Chaussure
Commande : 2 x Chaussure

Cout total : 160


Cout total : 160

Remarquez que dans lexemple prcdent nous prcisons quen ce qui concerne la classe Article
la copie superficielle est quivalente la copie en profondeur. On pourrait tre tent darmer
que pour une classe donne, la copie superficielle est quivalente la copie en profondeur si
et seulement si tous ses membres sont de type valeur. Or la classe Article admet un champ de
type string qui est un type rfrence. Nous expliquons un peu plus loin dans ce chapitre (en
page 370) que la classe String prsente certaines proprits dont limmuabilit de ses instances,
qui font que souvent, les instances de cette classe peuvent tre considres comme des instances
dun type valeur.
Linterface ICloneable est souvent critique principalement parce quelle ne permet pas ses
implmentations de communiquer clairement leurs clients sil sagit dune copie en profondeur ou dune copie superficielle, voire dune copie en profondeur incomplte. Les concepteurs
du framework ont failli rendre cette interface obsolte lors du passage .NET 2.0. La raison principale qui a sauve cette interface de lobsolescence est quelle est implmente par de nombreuses classes standard ou propritaires. Aussi, il reste dconseill de limplmenter moins
de fournir une documentation rigoureuse. Une alternative est par exemple un constructeur de
copie qui prend en paramtre un boolen prcisant quelle genre de copie lon souhaite :
Exemple 10-8 :
...
class Commande {
public int Quantite ;
public Article Article ;
public override string ToString() {

350

Chapitre 10 : Le systme de types


return "Commande : " + Quantite + " x " + Article.Description +
" Cout total : " + Article.Prix * Quantite ;
}
// Constructeur par defaut.
public Commande() { }
// Constructeur de copie param`
etrable.
public Commande( Commande original , bool bDeepCopy) {
this.Quantite = original.Quantite;
if( bDeepCopy )
this.Article = original.Article.Clone() as Article;
else
this.Article = original.Article;
}
}
class Program {
static void Main() {
...
Commande commandeClone = new Commande( commande , true ) ;
...
}
}

Un autre argument qui plaide en faveur de linterface ICloneable est quelle permet dimplmenter naturellement le design pattern prototype qui permet de crer de nouveaux objets en
clonant un objet prototype rfrenc par une rfrence de type ICloneable.

Boxing et UnBoxing
C++ C  Rien de semblable nexiste en C/C++. Tout ceci dcoule directement du fait que
tous les types drivent de la classe Object. Il nexiste pas de telle classe en C/C++.
C  Les instances de type valeur, locales une mthode, sont directement stockes dans la
pile du thread. Le thread na donc pas besoin de pointeurs ni de rfrence vers les instances de
types valeur.
Beaucoup de mthodes ont besoin darguments sous forme dune rfrence de type Object.
Comme tous les types, les types valeur drivent de la classe Object. Cependant les instances de
types valeur nayant pas de rfrences, il a fallu trouver une solution pour pouvoir obtenir une
rfrence vers une instance dun type valeur lorsque lon en a besoin. Cette solution fait lobjet
de la prsente section : cest le boxing (appel compartimentation en franais. Ce terme est peu
usit, aussi nous conserverons le terme boxing dans cet ouvrage).

Opration de Boxing
Voici un exemple concret pour exposer la problmatique. La mthode f() accepte une rfrence
de type object. A priori, on ne peut donc pas lappeler avec un argument qui nadmet pas de
rfrence, par exemple un entier de type int :

Boxing et UnBoxing

351

Exemple 10-9 :
class Program {
static void f( object o ) { }
public static void Main() {
int i = 9 ;
f( i ) ;
}
}
Cependant le petit programme prcdent compile et fonctionne. La magie du boxing a permis
dobtenir une rfrence vers une instance qui nen avait pas ! Lopration de boxing seectue en
interne en trois tapes :

Une nouvelle instance du type valeur est cre et alloue sur le tas.

Cette instance est initialise avec ltat de linstance alloue sur le tas. Dans le cas de notre
entier, une copie de quatre octets est eectue. On peut dire que notre instance initiale a
t clone.

Une rfrence vers la nouvelle instance est utilise la place de linstance alloue sur le tas.

Le code IL de cette mthode Main() est le suivant :


.locals init ([0] int32 i)
IL_0000: ldc.i4.s
9
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: box
[mscorlib]System.Int32
IL_0009: call
void Program::f(object)
IL_000e: ret
On voit que linstruction box du langage IL est prvue spcialement pour lopration de boxing.
Cette instruction place la rfrence vers la nouvelle instance sur le haut de la pile.

On profite de cet exemple pour souligner que le mot cl C  int est un alias vers le type
System.Int32.

Une optimisation dangereuse de lutilisation du boxing


Si vous tes concern par les performances, il faut savoir que lopration de boxing a un cot non
nul et non ngligeable. Le programme de lExemple 10-10 utilise deux fois le boxing alors que le
programme de lExemple 10-11 nutilise quune seule fois le boxing, ce qui le rend plus optimis.
Malgr les apparences, ces programmes nont pas le mme comportement car le premier ache
Rfrences direntes et le second ache Mmes rfrences . Cette optimisation est donc
assez dangereuse puisquelle modifie le comportement dune manire non vidente. Nous vous
conseillons de ne pas lutiliser.

352

Chapitre 10 : Le systme de types

Exemple 10-10 :
class Program {
static void f(object o1, object o2) {
if (o1 == o2)
System.Console.WriteLine("M
emes r
ef
erences") ;
else
System.Console.WriteLine("R
ef
erences diff
erentes") ;
}
public static void Main() {
int i = 9 ;
f(i, i) ;
}
}
Exemple 10-11 :
class Program {
static void f(object o1, object o2) {
if (o1 == o2)
System.Console.WriteLine("M
emes r
ef
erences") ;
else
System.Console.WriteLine("R
ef
erences diff
erentes") ;
}
public static void Main() {
int i = 9 ;
object o = i;
f(o, o) ;
}
}

Opration de UnBoxing
Lopration inverse du boxing existe et sappelle le UnBoxing (le terme dcompartimentation existe
en franais mais nous continuerons utiliser le terme unboxing). Voici un exemple o le unboxing est mis en uvre :
Exemple 10-12 :
class Program {
public static void Main() {
int i = 9 ;
object o = i;
// i est box
e.
int j = (int)o; // o est unbox
e.
}
}
Le code IL de cette mthode Main() est le suivant :
.locals init ([0] int32 i,
[1] object o,

Les types primitifs

IL_0000:
IL_0002:
IL_0003:
IL_0004:
IL_0009:
IL_000a:
IL_000b:
IL_0010:
IL_0011:
IL_0012:

353

[2] int32 j)
ldc.i4.s
9
stloc.0
ldloc.0
box
[mscorlib]System.Int32
stloc.1
ldloc.1
unbox
[mscorlib]System.Int32
ldind.i4
stloc.2
ret

On voit que linstruction unbox du langage IL est prvue spcialement pour lopration de unboxing. Sans entrer dans les dtails internes, sachez que linstruction unbox place un pointeur
sur la pile vers lobjet box qui se trouve sur le tas. Cest linstruction IL ldind qui charge sur la
pile, la valeur de lobjet rfrence.
Linstance ne peut tre unboxe vers le type spcifi si elle nest pas exactement de ce type. Lexception InvalidCastException peut donc tre lance par une opration de unboxing. De plus
si la rfrence unboxer est nulle lexception NullReferenceException est lance.
En C  , les oprations de boxing et dunboxing sont implicites. Cest--dire que cest le compilateur qui va gnrer les appels aux instructions IL box et unbox lorsque cest ncessaire. Il nen va
pas ncessairement de mme pour tous les langages .NET.

Les types primitifs


C++ C 

Les types prsents ici sont assez similaires aux types C++.

Le mot-cl unsigned nexiste pas en C  . On utilise les types byte, ushort, uint et ulong pour
typer les entiers non signs.
La taille dun long/ulong est de huit octets en C  .
En C  , le type decimal (sur 16 octets), reprsente les rels dune manire exacte, dans la limite
de 28 chires significatifs.
En C  , le type bool naccepte que les valeurs true et false. Aucune conversion avec des types
entiers nest permise.
En C  le type char est maintenant cod sur deux octets et respecte la norme UNICODE. Rappelons quen C++ ce type est cod sur un octet et respecte la norme ASCII.
C  Le langage C  possde plusieurs types primitifs, qui sont tous de type valeur. Chaque type
primitif de C  correspond exactement un type du CTS. Les types primitifs sont dfinis par
les mots-cls : bool, byte, sbyte char, short, ushort, int, uint, long, ulong, float,
double, decimal. Les correspondances entre ces mots-cls et les types du Framework .NET sont
dfinies ci-aprs.
Chaque type primitif permet lcriture de constantes. En eet le dveloppeur a souvent besoin
dinitialiser ses variables de type primitif avec des valeurs de ce type.

354

Chapitre 10 : Le systme de types

Les types concernant la reprsentation des nombres entiers


Les types
mots-cls C 

Types du CTS correspondants

Taille
bits

en

Intervalle de valeur

byte

System.Byte

[ 0 ; 255 ]

sbyte

System.SByte

[ -128 ; 127 ]

short

System.Int16

16

[ -32768 ; 32767 ]

ushort

System.UInt16

16

[ 0 ; 65635 ]

int

System.Int32

32

[-2,1x109 ; 2.1x109 ]

uint

System.UInt32

32

[ 0 ; 4.2x109 ]

long

System.Int64

64

[-9,2x1018 ; 9,2x1018]

ulong

System.UInt64

64

[ 0 ; 1,8x1019]

Les types non signs ushort, uint et ulong ainsi que le type sign sbyte ne sont pas CLS
compliant (cest--dire quils ne sont pas conformes au CLS dfini page 129). Concrtement
les autres langages utilisant .NET ne sont pas tenus de les implmenter. Il ne faut donc pas
utiliser ces types dans les arguments de mthodes susceptibles dtre appeles par un autre
langage.

Dfinition des constantes de ces types


Par dfaut une constante dun de ces types scrit simplement en base 10 (dcimal). Par exemple :
int i = 1024 ;
Cependant vous avez la possibilit dcrire une telle constante en base 16 (hexadcimale) :
int i = 0X00000400 ;
Voici un petit problme :
int i = 1000000000 ;
long j = 10 * i ;

// i = 1 milliard
// j nest pas
egal `
a 10 milliards

Quelle est la valeur de j ? Elle nest pas de 10 milliards. En eet le calcul stant eectu avec des
int, (limit deux milliards et quelque en valeur absolue) le rsultat est de type int. Ensuite
le rsultat est finalement copi dans une variable de type long. Le problme principal est que
rien nest signal au dveloppeur par le compilateur (sauf sil utilise le mot-cl checked). Pour
rsoudre ces problmes, on doit signaler au compilateur que lon veut utiliser des valeurs sur
huit octets en ajoutant L la constante entire :

Les types primitifs

355

int i = 1000000000 ; // i = 1 milliard


long j= 10L * i ;
// j egal `
a 10 milliards

Les types concernant la reprsentation des nombres rels


Motscls C 

Types du CTS
correspondants

Taille
en
bits

Intervalle de valeur

Plus petite
valeur possible

Prcision

float

System.Single

32

[-3,4x1038 ;
3,4x1038 ]

1,4x10-45

7 dcimales

double

System.Double

64

[-1,8x10308 ;
1,8x10308]

4,9x10324

15 dcimales

decimal

System.
Decimal

128

[ -8x1028 ; 8x1028
]

1x10-28

28 dcimales

Particularit des types float et double


Le type float (respectivement double) est dfini par la norme ANSI IEEE 754. Toutes les valeurs
de 32 (respectivement 64) bits ne reprsentent pas forcment un rel correct. Ces valeurs non
correctes sont reprsentes par le champ statique constant System.Single.Nan (respectivement
System.Double.Nan). Nan sinterprte par Not A Number .
Le type float (respectivement double) admet aussi deux autres valeurs trs pratiques reprsentant linfini positif et linfini ngatif. Lide est que les oprations et les fonctions usuelles
des rels vers les rels se comportent logiquement aux infinis. Les cas indtermins (comme
zro multipli par linfini) retourne la valeur Nan. Ces valeurs sont reprsentes par les champs
statiques constants PositiveInfinity et NegativeInfinity
Les types Single et Double prsentent les mthodes suivantes :
Mthodes

Description

public static float/double


Parse(string s)

Mme fonctionnalit que pour les types entiers.

public string ToString()

Mme fonctionnalit que pour les types entiers.

public static bool IsInfinity(float/double d)

Cette mthode statique retourne true si lentre est


infinie dans le type primitif concern.

public static bool IsNan(float/double d)

Cette mthode statique retourne true si lentre


nest pas valide dans le type primitif concern.

Les types single et double prsentent aussi les champs statiques constants suivants :

356

Chapitre 10 : Le systme de types

Champs

Description

Epsilon

Plus petite valeur positive non nulle reprsentable avec le type primitif concern.

MaxValue

Plus grande valeur positive reprsentable avec le type primitif


concern (existe aussi pour le type decimal).

MinValue

Oppos de MaxValue (existe aussi pour le type decimal).

Le type decimal
Avec ses 28 dcimales, le type dcimal est trs utile dans les applications financires ou chaque
dcimale compte. Il vaut mieux lviter pour des applications de calcul intensif qui ne ncessitent pas une telle prcision car il est coteux de manipuler des instances de decimal.

Dfinition de constantes
Par dfaut les constantes relles sont de type double. Voici quelques exemples :
double
double
double
double
double

d1
d2
d3
d4
d4

=
=
=
=
=

10 ;
10.0 ;
10E3 ;
10.1E3;
1.1E-1;

//
//
//
//
//

OK,
OK,
OK,
OK,
OK,

d1 vaut dix.
d2 vaut dix.
notation scientifique d3 vaut dix milles.
notation scientifique d4 vaut dix milles cent.
notation scientifique d5 vaut z
ero virgule onze.

Pour que la constante soit convertie en float, il faut la suxer avec f ou F.


float
float
float
float

d1 = 1.2; // KO, la constante est un double qui ne peut


etre
// convertie implicitement en float (perte dinformation)
d2 = 1.2f ;
// OK
d3 = (float)1.2 ; // OK la conversion est explicite.
d4 = 12E-1F ;
// OK la conversion est explicite.

Pour que la constante soit convertie en decimal, il faut la suxer avec m ou M.


decimal d1 = 1.2 ; // KO, la constante est un double qui ne peut
etre
// convertie implicitement en decimal.
decimal d2 = 1.2m ;
// OK
decimal d3 = (decimal)1.2 ; // OK la conversion est explicite.
decimal d4 = 12E-1m ;
// OK la conversion est explicite.

Le type boolen
Le mot-cl C  pour ce type est bool. Une variable de ce type ne peut prendre que deux valeurs :
true ou false. Le fait que la valeur true soit reprsente par un 1 ou un 0 par le CLR lexcution ne concerne en aucun cas le dveloppeur. Si vous avez un tableau de boolens, il est
prfrable dutiliser un des types prvus cet eet dcrit en page 573, qui optimise le stockage
des valeurs en mmoire en stockant 8 boolens dans un octet.

Les types primitifs

357

Le mot-cl bool est un alias pour le type du CTS System.Boolean. Cette structure prsente les
deux mthodes public static bool Parse(string s) et public string ToString()
dcrite un peu plus haut. Notons que Sytem.Boolean prsente aussi les deux champs statiques
constants de type string, FalseString (gal "False") et TrueString (gal "True"). Ces deux
chanes de caractres sont les images (respectivement les antcdents) des valeurs false et true
par la mthode ToString() (respectivement Parse()).

Le type reprsentant un caractre


Le mot-cl C  pour ce type est char. Une instance de ce type reprsente un caractre respectant
la norme UNICODE. Voici quelques exemples de constantes de type char :
char
char
char
char

c1
c2
c3
c4

=
=
=
=

A ;
//
\x41 ; //
(char)65 ; //
\u0041 ; //

Lettre A
Le 65`eme
Le 65`eme
Le 65`eme

en UNICODE.
caract`
ere UNICODE A(41 est en Hexa).
caract`
ere UNICODE A.
caract`
ere UNICODE A(41 est en Hexa).

Le mot-cl char reprsente le type du CTS System.Char. Cette structure prsente notamment
des mthodes statiques pour dterminer si un caractre est une lettre majuscule, une lettre minuscule, un chire etc. En voici quelques-unes :
Mthodes statiques de la classe
System.Char

Description

bool IsLower(char ch )

Renvoie true si ch est une minuscule (les minuscules


accentues sont aussi considres comme des minuscules).

bool IsUpper(char ch )

Renvoie true si ch est une majuscule.

bool IsDigit(char ch )

Renvoie true si ch est un chire.

bool IsLetter (char ch )

Renvoie true si ch est une lettre.

bool IsLetterOrDigit(char ch)

Renvoie true si ch est une lettre ou un chire.

bool IsPunctuation(char ch )

Renvoie true si ch est un caractre de ponctuation.

bool IsWhiteSpace(char ch )

Renvoie true si ch est un espace.

char ToUpper(char ch )

Renvoie la majuscule de la lettre ch.

char ToLower(char ch )

Renvoie la minuscule de la lettre ch.

Conversions entre nombres entiers et chanes de caractres


Les types primitifs prsentent chacun les trois mthodes suivantes :

public static [le type primitif concern


e].Parse(string s)
Cette mthode statique parse la chane de caractres en entre de faon retourner la valeur
dans le type primitif concern. Deux exceptions peuvent tre lances :

358

Chapitre 10 : Le systme de types

-FormatException si la chane ne contient pas que des chires avec ventuellement un


des caractres + ou - en premire position.

-OverflowException si le nombre reprsent nentre pas dans lintervalle de valeurs du


type primitif concern.

public bool [le type primitif concern


e].TryParse(string s, out [le type primitif concerne])
Cette mthode statique parse la chane de caractres en entre de faon retourner la valeur
au moyen du paramtre out dans le type primitif concern. Cette mthode retourne un
boolen true si elle a pu eectivement parser une valeur dans le type concern, sinon elle
retourne false.
public string [le type primitif concern
e].ToString()
Cette mthode crit la valeur entire dans une chane de caractres. Cette mthode est la
rcriture de la mthode ToString() de la classe Object.

Lexemple suivant ache deux fois la chane de caractres "-234" :


Exemple 10-13 :
class Program {
static void Main() {
string s1 = "-234" ;
int i = System.Int32.Parse(s1);
string s2 = i.ToString();
System.Console.WriteLine(s1) ;
System.Console.WriteLine(s2) ;
}
}

Oprations sur les types primitifs


C++ C  En C  , le modulo sapplique aussi aux types rels. De plus le langage C  dispose
du mot-cl checked qui permet de forcer la vrification de toutes les conversions et oprations.
Une exception est lance si un problme est rencontr.

Oprations arithmtiques sur un mme type primitif


C

Les cinq oprations arithmtiques sont :

Oprateur

Opration

Laddition.

La soustraction.

La multiplication.

La division.

Le modulo (i.e le reste de la division)

Oprations sur les types primitifs

359

Ces oprations sappliquent tous les types primitifs, entiers ou rels. Chacune de ces oprations
accepte deux oprandes. Une facilit dcriture est propose avec les cinq oprateurs suivants :
Facilit dcriture

Equivalence

i += j;

i = i+j;

i -= j;

i = i-j;

i *= j;

i = i*j;

i /= j;

i = i/j;

i %= j;

i = i%j;

Gestion de la division par zro


En ce qui concerne la division par zro sur un type entier et le type decimal, lexception
DivideByZeroException est lance.
En ce qui concerne la division par zro sur les types double et float, la variable prend la valeur
particulire NaN (Not a Number) (et non pas une valeur infinie comme on pourrait sy attendre).

Gestion des dpassements de capacit


Un dpassement de capacit est le fait dobtenir une valeur hors de lintervalle de valeurs autorises la suite dune opration. Un dpassement de capacit provoque les consquences suivantes :

Sur des variables de type decimal lexception OverflowException est lance.

Sur les variables de type double et float, la variable prend la valeur particulire NaN (Not
a Number).

Sur des variables de types entiers les bits significatifs qui dpassent ne sont pas pris en
compte. On obtient donc une valeur errone. Pour pallier ce comportement par dfaut
pour le moins dangereux, vous pouvez utiliser le mot-cl checked :
Exemple 10-14 :
class Program {
static void Main() {
byte i = 255 ;
checked {
i += 1 ; // Une exception de type
// System.OverflowException est lanc
ee.
}
}
}
Le mot-cl checked peut aussi sappliquer directement une expression.

360

Chapitre 10 : Le systme de types


Exemple 10-15 :
...

...

byte i = 255 ;
i = checked( (byte)(i+1) ) ; // Une exception de type
// System.OverflowException est lanc
ee.

Le mot-cl checked ne peut sappliquer une classe ou une mthode. Dans un bloc de
code vrifi par le mot-cl checked, vous pouvez utiliser le mot-cl unchecked pour ponctuellement revenir au comportement par dfaut.

Priorit des oprateurs


En ce qui concerne la priorit des oprateurs elle est dfinie comme suit :

+ - * / sont prioritaires sur %.

+ et --- sont de priorit gale.

* et / sont de priorit gale.

* et / sont prioritaires sur + et -.

Lorsquil y a mme priorit loprateur le plus gauche dans lexpression est prioritaire.
Mais cela na pas dimportance puisque les oprations de base avec la mme priorit sont
commutatives.

On peut toujours rendre une opration prioritaire en la mettant entre parenthses.

Par exemple :
Exemple 10-16 :
...
int
int
int
int
int
int
int

a = 3 ;
b = 10 ;
c = 4 ;
r1 = b+c %a ;
r2 = (b+c)%a ;
r3 = a*b+c ;
r4 = a*b%c ;

//
//
//
//

r1
r2
r3
r4

=
=
=
=

11
2
34
2 (30 modulo 4)

...

Oprateurs de pr et post incrmentation et dcrmentation


C++ C  Une dirence avec C++ est quen C  , ces oprations sappliquent aussi aux types
rels. De plus, dans le cas dun type entier on peut grer le dpassement de capacit avec le motcl checked.
C  On peut incrmenter ou dcrmenter dune unit, une variable de type primitif entier
ou rel, au moyen des oprateurs ++ et --. Par exemple :

i++ ; quivaut i = i+1;

i-- ; quivaut i = i-1;

Oprations sur les types primitifs

361

Toute la dicult vient du fait que ces deux oprateurs peuvent se placer soit avant soit aprs
la variable, ce qui change lordre dvaluation des expressions. Par exemple :
Exemple 10-17 :
...
int
int
int
int

i
j
k
l

=
=
=
=

3 ;
i++ ; // j vaut 3 et i vaut 4
3 ;
++k ; // l vaut 4 et i vaut 4

...
Lorsque lon utilise ces oprateurs avec des types entiers, il se peut que lon provoque un dpassement de capacit. Comme pour tout dpassement de capacit sur des variables de type entier
une exception nest pas lance...
Exemple 10-18 :
...
byte i = 255 ;
i++ ; // i vaut 0
...
... moins que lon utilise le mot-cl checked :
Exemple 10-19 :
...

...

byte i = 255 ;
checked{ i++ ; } // Une exception de type
// System.OverflowException est lanc
ee.

Oprations arithmtiques entre types primitifs dirents


C++ C  Les types primitifs invoqus tant dirents, les comportements des oprations
arithmtiques entre types primitifs dirents ne sont pas les mmes entre C++ et C  .
C  Lorsquune opration arithmtique seectue avec deux oprandes de types primitifs diffrents, le rsultat a un type dont lintervalle de valeur est au moins gal ou plus grand que
ceux des types des oprandes. Certains mariages de type sont interdits par le compilateur. Voici
le rsultat de mes propres essais, exposs dans un tableau :
Un des deux types

Lautre des deux types

Type
retourn

sbyte byte short ushort

sbyte byte short ushort

int

sbyte byte short ushort

int

int

sbyte byte short ushort int long

uint

long

Oprations entre types primitifs entiers

362

Chapitre 10 : Le systme de types

int long uint

int

long

ulong

int

ERREUR de
compilation

long

long

long

ulong

ulong

ulong

ulong

long

ERREUR de
compilation

Oprations entre types primitifs entiers et rels


sbyte byte short ushort int uint
long ulong

float

float

sbyte byte short ushort int uint


long ulong

double

double

sbyte byte short ushort int uint


long ulong

decimal

decimal

float double

decimal

ERREUR de
compilation

double

float

double

float

float

float

double

double

double

decimal

decimal

decimal

Oprations entre types primitifs rels

De plus les transtypages (cast en anglais) sont autoriss de nimporte quel type entier ou rel vers
nimporte quel type entier ou rel. Bien entendu ceci reste dangereux puisque les intervalles de
valeurs de tous ces types sont tous dirents. Le dveloppeur peut toujours utiliser le mot-cl
checked. Ainsi si une valeur doit tre convertie dans un type o elle nest pas reprsente, une
exception de type System.OverflowException sera lance.
Exemple 10-20 :
...
int i = -5 ;
checked {
byte b = (byte)i ; // Exception lanc
ee. Le type byte ne
}
// repr
esente pas de nombres n
egatifs.
...

Oprations sur les types primitifs

363

Notez cependant, quil est possible darrondir un type rel en un type entier. Par exemple, on
peut convertir le double 3.1415 en un byte de 3. Cest la valeur entire se trouvant avant la
virgule lors de lcriture du rel qui est alors choisie.
Lorsque plusieurs oprateurs sont utiliss dans une expression, ils sont excuts selon leur ordre
de priorit (dcrit un peu plus haut). Si les types primitifs des variables sont dirents, les rsultats intermdiaires suivent les rgles nonces.

Oprations binaires (bit bit)


C++ C  Rien de nouveau par rapport au C/C++ si ce nest que tout ceci est disponible en
64 bits grce aux types long et ulong.
C  Des oprations binaires (i.e bit bit) peuvent tre eectues entre oprandes de type int,
long , uint, ulong . Dans le cas dune opration binaire, les variables de type sbyte, short, byte,
ushort sont converties en type int :
Opration

Aussi nomme

Oprateur

AND

Et logique

&

OR

Ou logique

XOR

Ou exclusif logique

NOT

Inversion des bits

Seul loprateur agit sur un seul oprande, contrairement aux autres qui agissent sur deux
oprandes. Rappelons ces oprations :
Bit A

Bit B

A AND B

A OR B

A XOR B

Des oprations de dcalages peuvent tre eectues sur des variables de type int, long, uint et
ulong grce aux oprateurs :

<< dcalage gauche.

>> dcalage droite.

Rappelons quun dcalage gauche dune position en notation dcimale provoque une multiplication par 10. De mme, un dcalage gauche dune position en notation binaire provoque

364

Chapitre 10 : Le systme de types

une multiplication par deux. Un dcalage droite dune position en notation binaire provoque
une division entire par deux.
Exemple 10-21 :
...
uint a = 11 ;
uint b = a << 2 ; // a est inchang
e, b vaut 44
uint c = a >> 2 ; // a est inchang
e, c vaut 2
...
Prcisons que :

Si le dcalage se fait sur une variable dun type sign (i.e int ou long) le dcalage est arithmtique, cest--dire que le bit de signe est inchang et nest pas dcal.

Si le dcalage se fait sur une variable dun type non sign (i.e uint ou ulong) le dcalage est
binaire, et le rsultat reste positif.

Les structures
C++ C  En C++, les structures et les classes sont des notions similaires. En C  la dirence
de base est que les structures dfinissent des types valeur et les classes dfinissent des types rfrence.
En C  toutes les structures drivent de la classe Object.
Contrairement au C++, les champs dune structure sont privs par dfaut. De plus tous les
champs que lon veut dclarer publics doivent tre prcds du mot-cl public.
Contrairement au C++, les structures ne peuvent driver daucune classe ou structure, et ne
peuvent servir de base pour aucune classe ou structure drive.
En C  , une structure peut avoir un ou plusieurs constructeurs, mais le dveloppeur na pas le
droit de dfinir un constructeur par dfaut (i.e sans argument). De plus la syntaxe dinitialisation des champs dans le constructeur ( : nom_dechamp(valeur)...) nexiste plus. Enfin le constructeur par dfaut initialise 0 les champs.
Une structure peut avoir des mthodes publiques et prives, mais les mthodes doivent tre
compltement dfinies lintrieur de la dclaration de la structure (idem pour les classes). Il
ny a donc plus doprateur de rsolution de porte :: .
En C  , la possibilit de mettre des champs de bits dans une structure nexiste plus.
En C  , il nest pas obligatoire de mettre un ; la fin de la dclaration dune structure (idem
pour les classes).
Enfin, mais ceci est une dirence gnrale, en C  , au sein dun mme espace de nom, il nest
pas ncessaire davoir dj dfini une structure pour lutiliser.
C  En C  , une structure est dclare avec le mot-cl struct. La notion de structure est assez
proche de celle dune classe. Les points communs sont :

Une structure peut avoir des champs, des proprits, des mthodes, des dlgus, des vnements et des types. Si lun de ces membres nest pas dclar avec le mot-cl public, il nest
pas accessible hors de la classe.

Les structures
...
struct Employee{
int salaire ;
public string nom ;
}
...

365

// Ce champ est priv


e.
// Ce champ est public.

Les membres dune structure sont accessibles hors de la dfinition dune structure avec
loprateur point . .
...
Employee raymond ;
raymond.nom = "Raymond" ;
raymond.salaire = 3000 ;
...

// OK Le champ nom est public.


// KO Le champ salaire est priv
e.

Toute structure drive de la classe Object.

Une structure peut tre dfinie :

lintrieur dune autre structure. Elle peut cependant tre instancie hors de la structure encapsulante, si son niveau de visibilit le permet.

lintrieur dune classe. Elle peut cependant tre instancie hors de la classe encapsulante, si son niveau de visibilit le permet.

lextrieur de toute classe et de toute structure. On peut donc instancier cette structure partout dans lespace de noms courant, et dans les zones du code utilisant cet espace de nom.

Une structure ne peut tre dfinie lintrieur dune mthode.

Une structure peut avoir un ou plusieurs constructeurs mais le dveloppeur na pas le droit
de dfinir un constructeur par dfaut (i.e sans arguments). De plus le compilateur sattend
ce que tous les champs de la structure soient initialiss dans tous les constructeurs.
...
struct Employee {
int salaire ;
public string nom ;
Employee (int _salaire,string _nom){
salaire = _salaire ;
nom = _nom ;
}
}
...
Le constructeur par dfaut initialise 0 les champs de type valeur et nul les champs de
type rfrence.

Une structure peut avoir des mthodes autres que les constructeurs :
...
struct Employee {
int salaire ;

366

Chapitre 10 : Le systme de types


public string nom ;
public int GetSalaire(){return salaire;} // Cette m
ethode est publique.
}
...

Cependant les structures et les classes prsentent des dirences importantes :

Une structure est un type valeur alors quune classe est un type rfrence, avec toutes les
dirences que cela induit.

Les structures ne peuvent driver daucune classe ou structure, et ne peuvent servir de base
pour aucune classe ou structure drive, bien que toutes les structures drivent implicitement de la classe Object.

Contrairement aux champs dune classe, les champs dune structure ne peuvent tre explicitement initialiss dans la dclaration mme du champ.

Le dveloppeur ne peut surcharger le constructeur par dfaut (i.e sans argument).

Les instances des structures tant souvent stockes sur la pile, il vaut mieux quelles ne soient
pas trop volumineuses. Dans le cas de trs grosses structures, il vaut mieux opter pour des
classes.

Les numrations
C++ C  Les numrations du C  restent assez proches de celles du C++. Cependant
quelques dirences sont remarquer.
La classe System.Enum prsente des fonctions daide la manipulation des numrations.
Les numrations en C  sont idales pour remplacer le mcanisme de drapeaux binaires en C++.
Rappelons quen C++ le mcanisme de drapeaux binaires utilise les macros. Une telle approche
empche le bnfice de la vrification du type par le compilateur.
C  Un type numration dfinit un ensemble de valeurs. En C  , un tel type est dfini avec
le mot-cl enum. Les variables dun type numration prennent leurs valeurs dans cet ensemble.
Par exemple :
Exemple 10-22 :
class Program {
enum Marque { Renault, Peugeot, Lancia }
static void Main() {
Marque marque = Marque.Renault ;
switch (marque){
case Marque.Renault: System.Console.WriteLine("Renault") ; break ;
case Marque.Peugeot: System.Console.WriteLine("Peugeot") ; break ;
case Marque.Lancia: System.Console.WriteLine("Lancia") ; break ;
}
}
}
Une numration peut tre dfinie :

Les numrations

367

lintrieur dune structure ou dune classe. Elle peut cependant tre instancie hors de la
structure ou de la classe encapsulante, si son niveau de visibilit le permet.

lextrieur de toute classe et de toute structure. On peut donc instancier cette numration partout dans lespace de noms courant, et dans les zones du code utilisant cet espace
de noms.

Une numration ne peut tre dfinie lintrieur dune mthode.

Les numrations et les types entiers


Le compilateur considre que la valeur dune variable numration est un entier de type int.
Par consquent chaque valeur possible de lensemble des valeurs de lnumration est associe
une valeur entire. On peut alors :

Transtyper (caster) explicitement une variable dun type numration en un entier :


...
int i = (int) marque ;
...

Incrmenter ou dcrmenter une variable dun type numration :


...
Marque marque = Marque.Renault ;
Marque marque2 = marque++ ;
marque2
+= 2 ;
...

Associer nos propres valeurs entires dans lensemble des valeurs de lnumration :
...
enum Marque{ Renault = 100 , Peugeot= Renault+1 , Lancia = 200 }
...
Par dfaut la premire valeur est 0, puis il y a un incrment de 1 pour les suivantes. Il vaut
mieux dfinir la valeur 0 comme la plus courante, car les constructeurs initialisent 0 par
dfaut les champs de type numration.

Choisir nimporte quel autre type entier que int pour dfinir les valeurs du type numration :
...
enum MarqueFR : byte { Renault , Peugeot } ;
enum MarqueIT : long { Fiat , Ferrari } ;
...

Toute numration drive de la classe Object. Par consquent les mthodes de cette classe sont
accessibles sur une numration. Notons que la mthode Object.ToString() est redfinie pour
chaque numration, de faon que la chane de caractres retourne corresponde la chane de
caractres dsignant ltat courant dans le code. Par exemple :

368

Chapitre 10 : Le systme de types

Exemple 10-23 :
class Program {
enum Marque { Renault, Peugeot, Lancia }
static void Main() {
Marque marque = Marque.Renault ;
string s = marque.ToString();
System.Console.WriteLine(s) ;
}
}
Ce programme ache :
Renault

La classe System.Enum
La classe System.Enum drive de la classe System.ValueType. Elle prsente des mthodes statiques daide la manipulation dun type numration. Parmi ces mthodes remarquons :

static string Format(Type type, object value, string format)


Cette mthode permet de rcuprer une chane de caractres correspondant la valeur de
lnumration : largument format doit tre gal "G" ou "g" pour renvoyer le nom (par
exemple "Renault"). Il doit tre "D" ou "d" pour renvoyer la valeur correspondante (par
exemple "100") .
Exemple 10-24 :
...
Marque marque = Marque.Renault ;
string s = System.Enum.Format( typeof( Marque ) , marque , "G" ) ;
// Ici la chane de caract`
eres s contient "Renault".
...

static object Parse(Type type, string value, bool ingnoreCase)


Convertit une chane de caractres en une des valeurs de lnumration. Si largument
ignoreCase vaut true, cette mthode ne tient pas compte des majuscules/minuscules :
Exemple 10-25 :
...
Marque marque = (Marque)
System.Enum.Parse(typeof(Marque),"ReNaUlt",true) ;
// Ici marque prend la valeur Marque.Renault.
...
Si la chane de caractres ne correspond aucune valeur, lexception ArgumentException
est gnre.

string [] GetNames(Type type)


Renvoie les dirents noms de lnumration dans un tableau de chane de caractres. Par
exemple :

Les numrations

369

Exemple 10-26 :
...
foreach( string s in System.Enum.GetNames( typeof(Marque) ) )
System.Console.WriteLine(s) ;
...

object[] GetValues(Type type)


Renvoie les direntes valeurs de lnumration dans un tableau dobjets. Par exemple
(soyez conscient quici la rfrence o rfrence tour tour chaque valeur de lnumration
boxe) :
Exemple 10-27 :
...
foreach( object o in System.Enum.GetValues( typeof(Marque) ) )
System.Console.WriteLine( o.ToString() ) ;
...

Indicateurs binaires
On peut faire en sorte quune instance dune numration puisse contenir plusieurs valeurs
de lensemble des valeurs de lnumration. Cette notion est connue sous le nom dindicateurs
binaires. Les indicateurs binaires sont notamment utiliss pour manipuler les drapeaux (flag en
anglais) pour stocker plusieurs informations binaires dans une mme variable. Par exemple :
Exemple 10-28 :
// Dans cet exemple, pas besoin de plus quun octet.
[System.Flags()]
enum Drapeaux : byte {
Drapeau1 = 0x01, // Le bit 1 est `
a 1, les autres `
a 0.
Drapeau2 = 0x04, // Le bit 3 est `
a 1, les autres `
a 0.
Drapeau3 = 0x10 // Le bit 5 est `
a 1, les autres `
a 0.
}
class Program {
public static void Main() {
// En binaire, Drap vaut 10001000.
Drapeaux drapeau = Drapeaux.Drapeau1 | Drapeaux.Drapeau3 ;
// (Si le bit 1 est positionn
e)
equivalent `
a
// (Si Drapeau1 positionne).
if ((drapeau & Drapeaux.Drapeau1) > 0) {/* */}
// (Si les bits 3 et 5 sont positionn
es)
equivalent `
a
// (Si Drapeau2 et Drapeau3 positionn
es).
if ( (drapeau & Drapeaux.Drapeau2) > 0 &&
(drapeau & Drapeaux.Drapeau3) > 0 )
{ /* */ }
System.Console.WriteLine( drapeau.ToString() ) ;
}
}

370

Chapitre 10 : Le systme de types

Remarquez que les indicateurs binaires doivent tre marqus avec lattribut System.Flags. Cet
attribut permet de prciser au CLR et aux clients quil sagit dun type dindicateur binaire et
non pas dune numration classique. Notamment, la prsence de cet attribut aecte le rsultat
de la mthode ToString() et cet exemple ache ceci :
Drapeau1, Drapeau3
Il acherait ceci si lattribut Flags tait absent :
17
Un exemple dindicateur binaire prsent par le framework .NET est lnumration System.
Threading.ThreadState dcrite page 143.

Les chanes de caractres


La classe System.String
C++ C  Le framework .NET introduit la classe System.String pour stocker des chanes de
caractres et cela rompt compltement avec les traditionnels (et dangereux) char* et char[]
(adieu les strcpy() et autres fonctions dangereuses). La philosophie dutilisation de la classe
String est proche de celle du type string fourni par la STL du C++.
Une grosse dirence avec le type de la STL est quen C  , une instance de la classe String est immuable, elle ne peut tre modifie. Elle garde donc constamment la valeur qui lui a t donne
par son constructeur.
Une autre grosse dirence est que la convention C/C++ de terminer une chane de caractres
par le caractre nul nexiste plus en C  .
C  Le mot-cl C  string est un alias vers la classe System.String. Une instance de cette classe
reprsente une chane de caractres au format UNICODE.
La classe String est dclare comme sealed, cest--dire quaucune classe ne peut en driver. De
plus les instances de la classe String se comparent selon lquivalence.
Les instances de la classe String sont immuables (immutable en anglais). Cest--dire quune
chane de caractres est initialise par un constructeur, mais elle nest jamais modifie. Les
nombreuses mthodes de modification de chanes de caractres retournent toujours une
nouvelle instance de la classe String contenant la chane modifie. Ce comportement nuit
aux performances, aussi vous pouvez utiliser la classe System.Text.StringBuilder qui permet
de manipuler directement des chanes de caractres. Cette classe est prsente un peu plus loin.
Les trois caractristiques prcdentes (sealed, comparaison selon lquivalence et immuabilit des instances) font quen pratique les chanes de caractres se manipulent quasiment
comme des instances de type valeur. Cependant gardez bien lesprit que le type String
est une classe et est donc de type rfrence. Nous vous conseillons de revenir au dbut du
prsent chapitre pour tre bien conscient de tout ce que cela implique (manipulation seulement
par des rfrences, stockage des instances de String sur le tas, prise en compte par le ramassemiettes etc).

Les chanes de caractres

371

Chanes de caractres constantes rgulires


C++ C 

Contrairement C++, C  propose deux sortes de chanes de caractres constantes :

Les chanes de caractres constantes rgulires, similaires aux chanes de caractres constantes
de C++.

Les chanes de caractres constantes verbatim, dcrites dans la section suivante.

C

Voici un exemple de constantes de type string :


string

s = "ABCDEFG" ;

On peut introduire des caractres spciaux dans une constante chane de caractres. Comprenez
bien que ces caractres sont interprts par le compilateur C  . En voici la liste :
Terme franais

Terme anglais

Reprsentation

Valeur

Nouvelle ligne

New line

\n

0x000A

Tabulation horizontale

Horizontal tab

\t

0x0009

Tabulation verticale

Vertical tab

\v

0x000B

Retour arrire

Backspace

\b

0x0008

Retour chariot

Carriage return

\r

0x000D

Saut de page

Form feed

\f

0x000C

Antislash

Backslash

\\

0x005C

Apostrophe simple

Single quote

0x0027

Apostrophe doubles

Double quote

\"

0x0022

Caractre nul

Null

\0

0x0000

Par exemple le programme suivant ache "hel\nlo" :


Exemple 10-29 :
class Program {
public static void Main() {
string s = "hel\\nlo" ;
System.Console.WriteLine(s) ;
}
}

372

Chapitre 10 : Le systme de types

Chanes de caractres constantes verbatim


Le langage C  autorise la dfinition dune chane de caractres constante verbatim en ajoutant le
caractre @ juste devant la double apostrophe marquant le dpart de la chane. Une chane
de caractres constante verbatim a les particularits suivantes :

Elle accepte tous les caractres tels quils sont, y compris le caractre antislash \ mais mis
part le caractre double apostrophes " . En eet, ce dernier dfinit la fin de la chane.

Elle accepte et prend en compte le passage la ligne, dans lcriture de la chane de caractres. Cette fonctionnalit est particulirement utile lorsque lon gnre du code.

Voici un exemple :
Exemple 10-30 :
class Program {
public static void Main() {
string sReguliere ="\\bonjour\n\\comment ca va\n\\ce matin ?" ;
string sVerbatim = @"\bonjour
\comment ca va
\ce matin ?" ;
System.Console.WriteLine("Chane de caract`
eres r
eguli`
ere :") ;
System.Console.WriteLine(sReguliere) ;
System.Console.WriteLine("Chane de caract`
eres verbatim :") ;
System.Console.WriteLine(sVerbatim) ;
}
}
Voici lachage obtenu :
Chane de caract`eres reguli`ere :
\bonjour
\comment ca va
\ce matin ?
Chane de caract`eres verbatim :
\bonjour
\comment ca va
\ce matin ?

Manipulation de chanes de caractres


La classe String prsente plusieurs mthodes pour la manipulation de chanes de caractres.
Par exemple on peut obtenir la taille dune chane, concatner deux chanes, rechercher une
chane lintrieur dune autre chane etc.

Les chanes de caractres

373

Les mthodes (toutes


publiques)

Description

int Length()

Renvoie le nombre de caractres.

static int Compare( string


s1,string s2)

Renvoie 0 si s1 est gal, caractre par caractre s2. Sinon


calcule un poids sur chaque chane et renvoie la dirence
Poid(s2)-Poid(s1). Le poids est calcul selon lordre des
caractres dans la norme UNICODE.

int CompareTo(string s)

Semblable Compare() mais cette mthode est non statique. Retourne Poids(de linstance)-Poids(de largument).

static string Concat( string


s1,string s2)

Retourne une nouvelle chane qui est la concatnation de


s1 puis s2. Notez que la notation s1 + s2 est quivalente.

static string Copy(string s)

Retourne une nouvelle chane qui est gale s.

bool EndWith(string s)

Retourne true si la chane reprsente par linstance courante se termine par la chane s.

static string Format( string


s, de un `
a quatre arguments)

Met en forme une chane de caractres partir dau plus


quatre arguments. Voir la section suivante pour la mise en
forme.

int IndexOf( char c )

Renvoie lindex du caractre c dans la chane reprsente


par linstance courante. Si le caractre est en premire position lindex vaut 0. Si le caractre est absent de la chane
cette mthode retourne -1. La mthode LastIndexOf() est
quivalente, mis part quelle balaye la chane de droite
gauche.

int IndexOf( char c ,int pos


)

Idem, mais la recherche commence partir du pos-ime


caractre.

int IndexOf( string s )

Idem, mais on recherche une autre chane au lieu dun caractre.

int IndexOf( string s , int


pos )

Idem, mais la recherche commence partir du pos-ime


caractre.

string Insert( int pos,


string s )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a insr la
chane s partir du pos-ime caractre.

string PadLeft( int n )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a insr n espaces au dbut.

374

Chapitre 10 : Le systme de types

string PadLeft( int n , char


c )

Idem, mais le caractre de remplissage est c.

string PadRight( int n )

Idem, mais le remplissage se fait droite, avec des espaces.

string PadRight( int n ,


char c )

Mme chose mais le remplissage se fait droite, avec le caractre c.

string Remove( int pos, int


n )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a enlev n caractres partir du pos-ime caractre.

string Replace(string
oldstr, string newstr)

Retourne une nouvelle chane qui est la chane reprsente par linstance courante sur laquelle on a remplac chacune des sous chanes gale oldstring par newstring.

string Replace( char oldchar, char newchar )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante dans laquelle on a remplac
chaque caractre oldchar par newchar.

string Split( char[] )

Retourne un tableau de nouvelles chanes obtenues partir de la chane reprsente par linstance courante. Un tableau de caractres sparateurs est pass en argument.

bool StartWith( string s )

Retourne true si la chane reprsente par linstance courante commence par la chane s.

string SubString(int pos,


int n)

Retourne une nouvelle chane gale lextraction de la


sous chane la pos-ime position de longueur n caractres.

string ToLower()

Retourne une nouvelle chane qui est la chane reprsente par linstance courante avec toutes les majuscules transformes en minuscules.

string ToUpper()

Retourne une nouvelle chane qui est la chane reprsente par linstance courante avec toutes les minuscules transformes en majuscules.

string Trim()

Retourne une nouvelle chane qui est la chane reprsente par linstance courante avec les espaces, en dbut et
en fin de chane, supprims. TrimStart() (respectivement
TrimEnd()) est identique mais ne traite que le dbut (respectivement la fin).

string Trim( char[] )

Retourne une nouvelle chane qui est la chane reprsente par linstance courante, avec les caractres spcifis dans le tableau en argument en dbut et en fin
de chane supprime. TrimStart(char[]) (respectivement
TrimEnd(char[])) est identique mais ne traite que le dbut
(respectivement la fin).

Les chanes de caractres

375

Mise en forme dune chane de caractres


C++ C  En C/C++ la mise en forme des chanes de caractres utilise des indicateurs
(comme "%d") au sein de la chane de caractres fournie printf(). Ces indicateurs contiennent
une information sur le type de la variable acher.
En C  la mise en forme passe toujours par des indicateurs, mais ils sont dirents et ne
contiennent plus aucune information de type. En revanche ils contiennent linformation de
position dans la liste des arguments de la mthode Format().
C

Cette section dtaille laction de la mthode statique Format() de la classe string :


static string Format(string s, de un `
a quatre arguments) ;

En eet cette mthode est trs utilise et prsente un trs grand nombre doptions. Le tableau
ci-dessous illustre plusieurs exemples de mise en forme avec les variables suivantes :
int i = 123 ;
double d = 3.1415 ;
string sOut = System.String.Format( Signature de Format() ) ;
Signature de
Format()

Valeur de sOut

Remarque

"abcd"

"abcd"

"ab{0}cd",i

"ab123cd"

"ab{0}cd",d

"ab3,1415cd"

"ab{0}cd",i,d

"ab123cd"

Largument d nest pas utilis, mais aucune erreur nest dclare ni provoque.

"ab{0}cd{1}ef",i,d

"ab123cd3.1415ef"

Utilisation de deux arguments.

"ab{1}cd{0}ef",i,d

"ab3.1415cd123ef"

Autre utilisation de deux arguments.

"ab{0}{0}cd ",i

"ab123123cd"

Double utilisation du mme argument

"ab{0}cd{1}ef",i

ERREUR `
a lex
ecution

Un deuxime argument est requis.

"ab{0,6}cd",i

"ab 123cd"

i est reprsent par six caractres avec cadrage droite. Trois caractres despace sont
ajouts. Si le nombre de caractres tait infrieur au nombre de caractres requis pour
acher i, i aurait t ach dans sa totalit
quand mme.

"ab{0,-6}cd",i

"ab123 cd"

Mme chose mais le cadrage se fait gauche.

"ab{0:0000}cd",i

"ab0123cd"

Achage dau moins quatre chires, quitte


mettre des zros en tte.

376

Chapitre 10 : Le systme de types

"ab{0,6:0000}cd",i

"ab 0123cd"

Achage dau moins quatre chires quitte


mettre des zros en tte, reprsent sur 6 caractres, avec cadrage droite.

"ab{0:####}cd",i

"ab123cd"

Un caractre # remplacer nest pas utilis.

"ab{0:##}cd",i

"ab123cd"

Le format nest pas respect.

"ab{0:##.##}cd",d

"ab3,14cd"

Au plus deux dcimales sont aches.

"ab{0:##.#}cd",3.14

"ab3,1cd"

Arrondi infrieur.

"ab{0:##.#}cd",3.18

"ab3,2cd"

Arrondi suprieur.

"ab{0:##%}cd",0.143

"ab14%cd"

0.143 est multipli par 10 puis arrondi infrieur. 0.147 aurait produit 15%.

"ab{0:E}cd",d

"ab3,141500E+000cd"

Reprsentation scientifique.

"ab{0:X}cd",i

"ab7Bcd"

Reprsentation hexadcimale avec des lettres


majuscules.

"ab{0:x}cd",I

"ab7bcd"

Reprsentation hexadcimale avec des lettres


minuscules.

La classe System.Text.StringBuilder
Comme nous lavons vu au dbut de la prsente section, les instances de la classe String sont
immuables. Ce fait nuit aux performances, car chaque modification dune chane de caractres
il faut allouer une nouvelle instance de String. Ceci est dautant plus coteux si vous manipulez
des chanes de caractres longues.
Si une de vos applications modifie souvent des chanes de caractres ou manipule des chanes
de caractres volumineuses, il est plus ecace dutiliser la classe System.Text.StringBuilder.
En gnral une instance de StringBuilder se construit partir dune instance de la classe String
avec le constructeur StringBuilder(string).
Pour bien comprendre la classe StringBuilder, il faut assimiler la notion de capacit dune instance de StringBuilder. Pour cela nous prsentons les proprits suivantes de StringBuilder
(qui sont toutes publiques et non statiques) :

int Length{get;}
Cette proprit donne le nombre de caractres de la chane.

int Capacity{get;set;}
Cette proprit reprsente le nombre de caractres allous physiquement pour stocker la
chane de caractres. Ce nombre est la capacit. La valeur de ce champ est toujours suprieure ou gale la valeur du champ Length. Il est particulirement utile de positionner
ce champ une valeur suprieure la taille relle de la chane. Vous pouvez ainsi prvenir
des oprations dallocation mmoire du ramasse-miettes (parfois coteuses), en vous crant
une marge pour manipuler la chane.

Les chanes de caractres

377

Notez que la valeur de ce champ peut tre automatiquement incrmente lorsquune manipulation sur la chane de caractres produit un rsultat dune taille plus grande que la capacit. La valeur de cette incrmentation dpend de limplmentation du framework. Dans
cette situation, limplmentation courante de Microsoft double la capacit.

Si vous positionnez ce champ une valeur infrieure la valeur du champ Length lexception ArgumentOutOfRangeException est lance.

int MaxCapacity{get ;}
Cette proprit donne la capacit maximale que peut avoir la chane de caractres. Dans
limplmentation courante de Microsoft ce champ vaut Int32.MaxValue soit 2.147.483.647
caractres.

La classe StringBuilder prsente les mthodes suivantes :

int EnsureCapacity(int capacity)


Assure que la capacit est au moins gale la valeur de capacity. Si ce nest pas le cas, la
capacit est augmente. La nouvelle capacit est retourne.

StringBuilder Append(...)
Ajoute des caractres la fin de la chane de caractres. Cette mthode existe en de nombreuses versions surcharges.

StringBuilder Insert(int index,...)


Insre des caractres la position spcifie par index. La position 0 signifie le premier caractre de la chane. Cette mthode existe en de nombreuses versions surcharges.

StringBuilder Remove(int startIndex,int length)


Supprime les caractres compris entre la position startIndex et la position startIndex+length. Lexception ArgumentOutOfRangeException est lance si une de ces positions
est ngative ou excde la taille de la chane de caractres.

StringBuilder Replace(...old,...new,...)
Remplace le ou les caractres spcifis par old, par le ou les caractres spcifis par new. La
recherche de old peut ventuellement se faire sur une sous chane de caractres en utilisant
dautres versions surcharges de cette mthode.

Voici un petit programme qui illustre tout ceci (lachage de chaque appel Program.Display()
est insr en commentaire en blanc sur fond noir) :
Exemple 10-31 :
class Program{
static void Display(System.Text.StringBuilder s) {
System.Console.WriteLine("La chane : \"{0}\"",s) ;
System.Console.WriteLine(" Length
: {0}", s.Length) ;
System.Console.WriteLine(" Capacity : {0}", s.Capacity) ;
}
public static void Main(){
System.Text.StringBuilder s = new

378

Chapitre 10 : Le systme de types


System.Text.StringBuilder("hello") ;
Display(s) ;
//La chane : "hello"
// Length
: 5
// Capacity : 16
s.Insert( 4 ,
Display(s) ;
//La chane :
// Length
:
// Capacity :

"--salut--" ) ;
"hell--salut--o"
14
16

s.Capacity = 18 ;
Display(s) ;
//La chane : "hell--salut--o"
// Length
: 14
// Capacity : 18
s.Replace("salut","SALUT `
A TOUS") ;
Display(s) ;
//La chane : "hell--SALUT `
A TOUS--o"
// Length
: 21
// Capacity : 36
s.EnsureCapacity(42) ;
Display(s) ;
//La chane : "hell--SALUT `
A TOUS--o"
// Length
: 21
// Capacity : 72
}
}
Avant davoir recours aux services de la classe StringBuilder, vous devez tre conscient quil
est possible de violer la condition dimmutabilit des instances de la classe String si vous vous
autorisez utiliser du code non vrifiable. Cette possibilit est expose en page 508.

Les dlgations et les dlgus


Introduction
C++ C 

C  dfinit la notion de dlgus qui est absente en C++.

Cependant cette notion est conceptuellement proche de celle des pointeurs sur fonction et des
pointeurs sur mthode du C++. Au mme titre que ces derniers, un dlgu est une rfrence
vers une ou plusieurs mthodes (statiques ou non).
Les dlgus sont nanmoins plus puissants que les pointeurs sur fonctions, puisquun mme
dlgu peut rfrencer plusieurs mthodes. En outre la syntaxe est beaucoup plus claire et
conviviale.

Les dlgations et les dlgus

379

C  C  permet la cration de classes particulires avec le mot-cl delegate en post-fixe. On


appelle ces classes dlgations. Les instances des dlgations sont appeles dlgus.
Conceptuellement, un dlgu est une rfrence vers une ou plusieurs mthodes (statiques ou
non). On peut donc appeler/invoquer un dlgu avec la mme syntaxe que lappel dune
mthode. Ceci provoque lappel des mthodes rfrences. Notez que lappel de ces mthodes
est eectu par le mme thread qui ralise lappel au dlgu. On parle donc dappel synchrone.
Il existe de nombreuses dlgations de base, dfinies dans le framework .NET. Vous pouvez trouver la liste de ces dlgations dans larticle MulticastDelegate Hierarchy des MSDN. Comme
lindique cet article, chaque dlgation, quelle soit propritaire ou standard, est une classe drive de la classe System.MulticastDelegate. Cette classe est trs spciale, on ne peut en driver
explicitement. Seuls les compilateurs des langages .NET peuvent construire des classes qui drivent de MulticastDelegate. Grce certaines mthodes de la classe MulticastDelegate, un
dlgu peut aussi servir eectuer des appels asynchrones sur une mthode. Cette notion de
dlgus asynchrones fait lobjet dune autre section, page 171.

Certains ouvrages utilisent les termes classe dlgue ou type dlgu pour nommer
les dlgations. Dautres publications utilisent le terme dlgu pour nommer les dlgations. Dans ce cas il faut utiliser les termes instance de dlgu ou objet dlgu
pour nommer ce que nous appelons ici dlgu . Soyez vigilants, et reprez chaque
fois la dirence entre les types et les instances.

Utilisation de dlgus avec des mthodes statiques


Voici un exemple utilisant deux dlgations :
Exemple 10-32 :
public class Program {
// Definition des delegations Deleg1 et Deleg2.
delegate void Deleg1();
delegate string Deleg2(string s);
static void f1() {
System.Console.WriteLine("Appel de f1.") ;
}
static string f2(string s) {
string _s=string.Format("Appel de f2 avec largument \"{0}\"",s) ;
System.Console.WriteLine(_s) ;
return _s ;
}
public static void Main() {
// Cree un delegue instance de Deleg1 r
ef
eren
cant la m
ethode f1().
Deleg1 d1 = new Deleg1(f1);
// Appel de f1() avec le del
egu
e d1.
d1();
// Cree un delegue instance de Deleg2 r
ef
eren
cant la m
ethode f2().
Deleg2 d2 = new Deleg2(f2);

380

Chapitre 10 : Le systme de types


// Appel de f2("hello") avec le d
el
egu
e d2.
string s = d2("hello");
}
}

Ce programme ache :
Appel de f1.
Appel de f2 avec largument "hello"
Nous utilisons deux dlgations, pour bien souligner le fait quun dlgu ne peut rfrencer
seulement une mthode dont la signature (mme ordre, nombre et type des arguments et mme
type de retour) correspond celle fournie lors de la dclaration de la dlgation. Par exemple
la ligne suivante aurait provoqu une erreur de compilation car la mthode, f1() na pas la
signature adquate :
Deleg2 d2

= new Deleg2(f1) ;

Infrence de la dlgation par le compilateur C  2


Le compilateur C  2 introduit une facilit avec la possibilit dinfrer le type dun dlgu que
lon cre. Vous pouvez ainsi assigner directement une mthode un dlgu cr implicitement. Le programme prcdent peut ainsi tre rcrit comme suit :
Exemple 10-33 :
public class Program {
delegate void Deleg1() ;
delegate string Deleg2(string s) ;
static void f1() {
System.Console.WriteLine("Appel de f1.") ;
}
static string f2(string s) {
string _s = string.Format(
"Appel de f2 avec largument \"{0}\"", s) ;
System.Console.WriteLine(_s) ;
return _s ;
}
public static void Main() {
Deleg1 d1 = f1; // Au lieu de
...new Deleg1(f1)...
d1() ;
Deleg2 d2 = f2; // Au lieu de
...new Deleg2(f1)...
string s = d2("hello") ;
}
}
Ne vous y trompez pas : ceci est juste une facilit dcriture. Si vous analysez le code IL produit
par cet exemple vous pourrez visualisez quil y a bien appel aux constructeurs des dlgations
Deleg1 et Deleg2.

Les dlgations et les dlgus

381

Utilisation de dlgus avec des mthodes non statiques


On peut obtenir le mme comportement avec des mthodes non statiques. Naturellement
lorsque lon fournit la mthode rfrencer, il faut la lier avec lobjet sur lequel elle doit tre
appele. Par exemple :
Exemple 10-34 :
using System ;
public class Article {
private int m_Prix = 0 ;
public Article(int Prix) { m_Prix = Prix ; }
public int IncPrix(int i) {
m_Prix += i ;
return m_Prix ;
}
}
public class Program {
public delegate int Deleg(int i) ;
public static void Main() {
// Cree un article de prix 100.
Article article = new Article(100);
// Cree un delegue referen
cant IncPrix() sur lobjet article.
Deleg deleg = article.IncPrix;
int p1 = deleg(20) ;
Console.WriteLine(
"Prix de article apr`es incr
ement de 20 : {0}", p1) ;
int p2 = deleg(-10) ;
Console.WriteLine(
"Prix de article apr`es un d
ecr
ement de 10 : {0}", p2) ;
}
}
Ce programme ache ceci :
Prix de article apr`es increment de 20 : 120
Prix de article apr`es un decrement de 10 : 110

Utilisation de dlgus avec plusieurs mthodes


On peut rfrencer plusieurs mthodes (statiques ou non) de mmes signatures avec le mme
dlgu. Dans ce cas lappel au dlgu entrane lappel successif de ces mthodes par le mme
thread qui a provoqu lappel. Ces mthodes sont appeles dans lordre dans lequel elles ont t
ajoutes dans le dlgu, chacune avec les mmes arguments. Par exemple:
Exemple 10-35 :
using System ;
public class Article {
public int m_Prix = 0 ;
public Article(int Prix) { m_Prix = Prix ; }

382

Chapitre 10 : Le systme de types


public int IncPrix(int i) {
m_Prix += i ;
return m_Prix ;
}
}
public class Program {
public delegate int Deleg(int i) ;
public static void Main() {
// Cree trois articles de prix diff
erents.
Article a = new Article(100) ;
Article b = new Article(103) ;
Article c = new Article(107) ;
// Assigne 3 methodes `a un m
eme d
el
egu
e.
Deleg deleg = a.IncPrix;
deleg += b.IncPrix;
deleg += c.IncPrix;
// Incremente les trois prix de 20 en un seul appel `
a deleg.
int p1 = deleg(20);
// Ici p1 vaut 127, le prix du dernier article.
Console.WriteLine(
"Prix apr`es increment de 20 : a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix) ;
// Decremente les trois prix de 10 en un seul appel `
a deleg.
int p2 = deleg(-10);
// Ici p2 vaut 117, le prix du dernier article.
Console.WriteLine(
"Prix apr`es un decrement de 10 : a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix ) ;
}
}

Cet exemple ache :


Prix apr`es increment de 20 : a:120 b:123 c:127
Prix apr`es un decrement de 10 : a:110 b:113 c:117
Si la signature retourne une valeur dans le cas o il y a plusieurs mthodes rfrences cest la
valeur retourne par le dernier appel qui est renvoye par le dlgu.
Bien que cet exemple ne le montre pas, le mme dlgu peut la fois rfrencer des mthodes
statiques et des mthodes non statiques, de la mme classe ou de classes direntes. Il sut juste
que ces mthodes aient la mme signature et le mme type de valeur de retour.

La classe System.Delegate
Il faut savoir quen interne, lorsque vous placez plusieurs mthodes dans le mme dlgu,
une instance de la classe System.Deletage est cre pour chaque mthode. En fait, la classe
MulticastDelegate est une liste dinstances de la classe System.Delegate. Lexemple suivant
montre comment utiliser la classe Delegate pour invoquer une une les mthodes contenues
dans un dlgu. Notez le recours la mthode GetInvocationList() pour obtenir la liste des
dlgus.

Les dlgations et les dlgus

383

Exemple 10-36 :
using System ;
public class Article {
public int m_Prix = 0 ;
public Article(int Prix) { m_Prix = Prix ; }
public int IncPrix(int i) {
m_Prix += i ;
return m_Prix ;
}
}
public class Program {
public delegate int Deleg(int i) ;
public static void Main() {
Article a = new Article(100) ;
Article b = new Article(103) ;
Article c = new Article(107) ;
Deleg delegs = a.IncPrix ;
delegs += b.IncPrix ;
delegs += c.IncPrix ;
int somme = 0 ;
// Obtient la liste des del
egu
es.
Delegate[] delegArr = delegs.GetInvocationList();
// Invoque une `a une chaque m
ethode r
ef
erenc
ee.
foreach (Deleg deleg in delegArr)
somme += deleg(20);
Console.WriteLine(
"Prix apr`es increment de 20 : a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix) ;
Console.WriteLine("Somme des prix:{0}", somme ) ;
}
}
Cet exemple ache :
Prix apr`es increment de 20 : a:120 b:123 c:127
Somme des prix:370

Subtilit dans la manipulation des dlgus


Lexemple prcdent montre que lon peut ajouter (respectivement supprimer) un dlgu
un autre dlgu de mme classe avec loprateur += (respectivement -= ). Si le dlgu
ajout contient plusieurs rfrences de mthodes, ce sont toutes ces rfrences qui sont ajoutes
la fin de la liste des rfrences du dlgu cible. En revanche, dans le cas de la suppression,
il se peut que lopration ne puisse se faire si la sous liste de rfrences du dlgu supprim
napparat pas dans la liste de rfrence du dlgu cible. Lexemple suivant illustre ceci :
Exemple 10-37 :
using System ;
public class Article {

384

Chapitre 10 : Le systme de types


public int m_Prix = 0 ;
public Article(int Prix) { m_Prix = Prix ; }
public int IncPrix(int i) {
m_Prix += i ;
return m_Prix ;
}
}
public class Program {
public delegate int Deleg(int i) ;
public static void Main() {
Article a = new Article(100) ;
Article b = new Article(103) ;
Article c = new Article(107) ;
// Construction du delegu
e ( a.IncPrix , b.IncPrix , c.IncPrix ).
Deleg deleg = a.IncPrix ;
Deleg deleg1 = b.IncPrix ;
deleg1 += c.IncPrix ;
deleg += deleg1 ;
deleg(10) ;
Console.WriteLine("a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix ) ;
// Essai de suppression du d
el
egu
e ( a.IncPrix , c.IncPrix )
// non present dans ( a.IncPrix , b.IncPrix , c.IncPrix ).
Deleg deleg2 = a.IncPrix ;
deleg2 += c.IncPrix;
deleg -= deleg2 ;
deleg(10) ;
Console.WriteLine("a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix ) ;
// Essai de suppression du d
el
egu
e ( a.IncPrix , b.IncPrix )
// present dans ( a.IncPrix , b.IncPrix , c.IncPrix ).
Deleg deleg3 = a.IncPrix ;
deleg3 += b.IncPrix;
deleg -= deleg3 ;
deleg(10) ;
Console.WriteLine("a:{0} b:{1} c:{2}",
a.m_Prix , b.m_Prix , c.m_Prix) ;
}
}

Cet exemple ache :


a:110 b:113 c:117
a:120 b:123 c:127
a:120 b:123 c:137

Les types nullables

385

Dans le premier essai de suppression, le dlgu deleg nest pas modifi. Pour infirmer ou
confirmer le bon droulement dune suppression, vous pouvez inspecter la taille de la liste des
instances de la classe Delegate obtenue avec la mthode GetInvocationList().

Les types nullables


Les concepteurs de C  2 ont ajout la notion de type nullable pour pallier une faiblesse des
types valeurs par rapport aux types rfrences. Il est donc essentiel davoir bien assimil ces deux
notions prsentes en dbut de chapitre avant de pouvoir aborder cette section.

La problmatique dune valeur nulle pour les types valeurs


Une rfrence de type un type rfrence est nulle lorsquelle ne rfrence aucun objet. Cest la
valeur prise par dfaut par toute rfrence. Il sut danalyser le code de nimporte quelle application pour sapercevoir que les dveloppeurs exploitent intensivement les rfrences nulles.
En gnral, lutilisation dune rfrence nulle permet de communiquer une information :

Une mthode qui doit retourner une rfrence vers un objet retourne une rfrence nulle
pour signifier que lobjet demand ne peut tre fabriqu ou trouv. Cela vite dimplmenter un code derreur de retour binaire.

Lorsque vous rencontrez une mthode qui accepte un argument de type rfrence qui peut
tre nulle, cela signifie en gnral que largument est optionnel.

Un champ de type rfrence nulle peut signifier que lobjet qui le prsente est en cours
dinitialisation, de mise jour ou de destruction et na donc pas un tat valide.

La notion de nullit est aussi largement exploite dans les bases de donnes relationnelles pour
signifier quune valeur dans un enregistrement na pas t assigne. La notion de nullit peut aussi
servir pour dsigner un attribut optionnel dans un lment XML.
Parmi les nombreuses dirences entre les types valeurs et les types rfrences, nous pouvons
nous pencher sur le fait quune instance dun type valeur ne peut avoir de valeur nulle. Cela
pose en gnral de nombreux problmes. Par exemple, comment interprter une valeur entire
nulle (non encore assigne) rcupre dune base de donne ? De multiples solutions existent
mais aucune dentre elles nest pleinement satisfaisante :

Si tout lintervalle de valeurs entires nest pas utilisable, on cre une convention. Par
exemple, une valeur entire nulle est reprsente par un entier gal 0 ou -1. Les nombreux
dsavantages de cette solution sont vidents : contrainte maintenir partout dans le code,
possibilit dvolution de lintervalle de valeurs pris par ce champ particulier etc.

On cre une structure wrapper contenant deux champs, un entier et un boolen qui, si
positionn false, signifie que nous avons une valeur nulle. Ici, on doit grer une structure
en plus pour chaque type valeur, et un tat en plus pour chaque valeur.

On cre une classe wrapper contenant un champ entier. Ici, le dsavantage est quen plus du
maintient dune nouvelle classe, on surcharge le ramasse-miettes en crant de nombreux
objet sur le tas.

On utilise le boxing, par exemple en transtypant notre valeur entire en une rfrence de
type object. En plus de ne pas tre type-safe, cette solution a aussi le dsavantage de surcharger le ramasse-miettes.

386

Chapitre 10 : Le systme de types

Pour pallier ce problme rcurrent, les concepteurs de C  2 ont dcid dajouter au langage la
notion de types nullables.

La structure System.Nullable<T>
Le framework .NET 2005 prsente la structure gnrique System.Nullable<T> dfinie comme
ceci :
public struct System.Nullable<T> {
public Nullable(T value) ;
public static explicit operator T(T? value) ;
public static implicit operator T?(T value) ;
public bool HasValue { get ; }
public T Value { get ; }
public
public
public
public
public

override bool Equals(object other) ;


override int GetHashCode() ;
T GetValueOrDefault() ;
T GetValueOrDefault(T defaultValue) ;
override string ToString() ;

}
Cette structure rpond bien la problmatique des valeurs nulles lorsque le type paramtre T
prend la forme dun type valeur tel que int. Voici un petit exemple qui illustre lutilisation de
cette structure. La premire version de Fct() exploite la nullit dune rfrence de type string
tandis que la seconde version exploite la nullit dune instance de la structure Nullable<int> :
Exemple 10-38 :
class Foo {
static string Fct(string s) {
if (s == null)
return null ;
return s + s ;
}
static System.Nullable<int> Fct(System.Nullable<int> ni){
if (!ni.HasValue)
return ni ;
return (System.Nullable<int>) (ni.Value + ni.Value) ;
}
}

volution de la syntaxe C  : Nullable<T> et le mot cl null


La syntaxe C  vous permet dassigner et de comparer le mot cl null une instance de System.Nullable<T> :
Nullable<int> ni = null;
System.Diagnostics.Debug.Assert( ni == null ) ;

Les types nullables

387

Ces deux lignes de code sont quivalentes :


// Appel du constructeur par d
efaut qui positionne HasValue `
a false.
Nullable<int> ni = new Nullable<int>() ;
System.Diagnostics.Debug.Assert( !nullable1.HasValue ) ;
Lutilisation de la structure System.Nullable<T> est intuitive, mais peut rapidement amener
se poser des questions. Il nest pas vident que ces deux programmes sont quivalents (les deux
codes IL gnrs sont quivalents) :
Exemple 10-39 :
class Program{
static void Main(){
System.Nullable<int> ni1 = 3 ;
System.Nullable<int> ni2 = 3 ;
bool b = (ni1 == ni2) ;
System.Diagnostics.Debug.Assert(b) ;
System.Nullable<int> ni3 = ni1 + ni2 ;
ni1++;
}
}
Exemple 10-40 :
using System ;
class Program {
static void Main() {
Nullable<int> ni1 = new Nullable<int>(3) ;
Nullable<int> ni2 = new Nullable<int>(3) ;
bool b = (ni1.GetValueOrDefault() == ni2.GetValueOrDefault()) &&
(ni1.HasValue == ni2.HasValue);
System.Diagnostics.Debug.Assert( b ) ;
Nullable<int> ni3 = new Nullable<int>() ;
if (ni1.HasValue && ni2.HasValue)
ni3 = new Nullable<int>( ni1.GetValueOrDefault() +
ni2.GetValueOrDefault() ) ;
if (ni1.HasValue)
ni1 = new Nullable<int>( ni1.GetValueOrDefault() + 1 ) ;
}
}
En outre, cela peut sembler trange que la linstruction ni++ appele lorsque la variable ni est
sense tre null ne provoque pas une exception de type NullReferenceException.

volution de la syntaxe C  : quivalence entre Nullable<T> et T ?


En C  2, vous pouvez faire suivre le nom dun type valeur non nullable T par un point dinterrogation. Dans ce cas, le compilateur C  2 remplacera toute expression T? par Nullable<T>. Pour
simplifier, vous pouvez imaginer que ceci est un prtraitement eectu directement sur le code
source, un peu comme un prcompilateur. Ainsi, la ligne suivante...

388

Chapitre 10 : Le systme de types


int? i = null ;

...est quivalente :
Nullable<int> i = null ;
Par exemple, les deux mthodes suivantes sont rigoureusement quivalentes :
Exemple 10-41 :
class Foo {
static System.Nullable<int> Fct1( System.Nullable<int> ni ) {
if ( !ni.HasValue )
return ni ;
return (System.Nullable<int>) ( ni.Value + ni.Value ) ;
}
static int? Fct2(int? ni){
if (ni == null)
return ni;
return ni + ni ;
}
}
En gnral, les instances de types nullables quivalentes se mlangent bien :
Exemple 10-42 :
class Program{
static void
int? ni1
int? ni2
int? ni3
int? ni4
int? ni5
ni1++ ;
ni2++ ;
}
}

Main(){
= null ;
= 9 ;
= ni1 + ni2 ; // OK,
= ni1 + 3 ; // OK,
= ni2 + 3 ; // OK,
// OK,
// OK,

ni3
ni4
ni5
ni1
ni2

vaut null.
vaut null.
vaut 12.
reste `
a null.
passe `
a 1.

En revanche, le compilateur vous empche de convertir implicitement un objet dun type


nullable dans le type sous-jacent. De plus, il est dangereux de raliser une telle conversion explicitement sans tests pralables puisque vous risquer de lever une exception de type
InvalidOperationException.
Exemple 10-43 :
class Program{
static void
int? ni1
int? ni2
int i1 =

Main(){
= null ;
= 9 ;
ni1 ;

int i2 = ni2 ;

// KO: Cannot implicitly convert


//
type int? to int.
// KO: Cannot implicitly convert

Les types nullables

389

//
type int? to int.
// KO: Cannot implicitly convert
//
type int? to int.
int i4 = ni1 + 6 ;
// KO: Cannot implicitly convert
//
type int? to int.
// OK `a la compilation mais une exception de type
// InvalidCastException est lanc
ee `
a lex
ecution,
// car ni1 est toujours nulle.
int i5 = (int)ni1 ;
int i3 = ni1 + ni2 ;

}
}

Pas de traitement spcial de bool? en C  2.0


Contrairement ce que vous avez peut tre vu dans les versions btas de C  2.0, en version finale
il ny a plus de traitement spcial du type bool? par les mots-cls if, while et for. Ainsi cet
exemple ne compile pas :
Exemple 10-44 :
class Program{
static void Main(){
bool? b = null ;
// Cannot implicitly convert type bool? to bool.
if ( b ) { /*...*/ }
}
}

Les types nullables et les oprations de boxing et de unboxing


Lors de la conception de .NET 2.0, jusquau dernier moment les types nullables reprsentaient
un artefact qui nimpactait pas le CLR. Ils taient implments seulement laide du compilateur C  2.0 et de la structure System.Nullable<T>. Cela posait un problme puisque lorsquune
instance dun type nullable tait boxe elle ne pouvait tre en aucun cas nulle. Il y avait bien
une incohrence avec la manipulation des types rfrences :
string s = null ;
object os = s ;
int?
i = null ;
object oi = i ;

// os est une r
ef
erence nulle
// oi n
etait pas une r
ef
erence nulle

Sous la pression de la communaut, les ingnieurs de Microsoft ont dcid de remdier ceci.
Ainsi, lassertion du programme suivant est vrifie :
Exemple 10-45 :
class Program{
static void Main(){
int? ni = null ;
object o = ni ;
// boxing
System.Diagnostics.Debug.Assert( o == null ) ;

390

Chapitre 10 : Le systme de types


}
}

Du point de vue du CLR, une instance dun type valeur T boxe peut tre nulle. Si elle nest pas
nulle, le CLR ne stocke pas dinformation quant savoir si elle tait originalement issue du type
T ou Nullable<T>. Vous pouvez ainsi unboxer un tel objet dans lun de ces deux types comme
le montre lexemple suivant. Soyez nanmoins conscient que vous ne pouvez pas unboxer une
valeur nulle :
Exemple 10-46 :
class Program {
static void Main() {
int i1 = 76 ;
object o1 = i1 ;
// boxing dun int
int? ni1 = (int?)o1 ; // unboxing en un int?
System.Diagnostics.Debug.Assert( ni1 == 76 ) ;
int? ni2 = 98 ;
object o2 = ni2 ;
// boxing dun int?
int i2 = (int)o2 ;
// unboxing en un int
System.Diagnostics.Debug.Assert( i2 == 98 ) ;
int? ni3 = null ;
object o3 = ni3 ;
int i3 = (int)o3 ;

// boxing dun nullable nul


// unboxing -> NullReferenceException lev
ee !

}
}

Les structures et les numrations nullables


La notion de type nullable peut sutiliser aussi sur vos propres structures et numrations. Cela
peut mener des erreurs de compilations rdhibitoires illustres par lexemple suivant o la
structure Nullable<MyStruct> ne supporte pas les membres de MyStruct.
Exemple 10-47 :
struct Struct {
public Struct(int i) { m_i = i ; }
public int m_i ;
public void Fct(){}
}
class Program {
static void Main(){
Struct? ns1 = null ; // OK
Struct? ns2 = new Struct?(3) ; //
//
Struct? ns3 = new Struct?() ; //
//
Struct? ns4 = new Struct(3) ; //

KO: Cannot implicitly convert


type int to Struct.
OK Struct.ctor() par d
efaut
est appel
e.
OK

Les types nullables

391

Struct? ns5 = new Struct() ;


ns4.m_i = 8 ; //
//
ns4.Fct() ; //
//

// OK Struct.ctor() par d
efaut
// est appel
e.
KO: System.Nullable<Struct> does not
contain a definition for m_i.
KO: System.Nullable<Struct> does not
contain a definition for Fct.

}
}
En revanche, si besoin est, le compilateur saura utiliser vos redfinitions doprateurs :
Exemple 10-48 :
struct Struct {
public Struct(int i) { m_i = i ; }
public int m_i ;
public static Struct operator +( Struct a, Struct b) {
return new Struct( a.m_i + b.m_i ) ; }
}
class Program {
static void Main() {
Struct? ns1 = new Struct(3) ;
Struct? ns2 = new Struct(2) ;
Struct? ns3 = null ;
Struct? ns4 = ns1 + ns2 ; // OK, ns4.m_i vaut 5.
Struct? ns5 = ns1 + ns3 ; // OK, ns5 vaut null.
}
}
En ce qui concerne une instance dune numration nullable, ayez conscience que vous devez
toujours obtenir la valeur sous-jacente pour lutiliser. Par exemple :
Exemple 10-49 :
class Program{
enum MyEnum { VAL1, VAL2 }
static void Main(){
MyEnum? e = null;
if( e == null )
System.Console.WriteLine("e est null") ;
else
switch(e.Value){ // Ici on est sur que e nest pas null.
case MyEnum.VAL1: System.Console.WriteLine("e vaut VAL1") ;
break ;
case MyEnum.VAL2: System.Console.WriteLine("e vaut VAL2") ;
break ;
}
}
}

392

Chapitre 10 : Le systme de types

Dfinir un type sur plusieurs fichiers sources


C  2 prsente la possibilit dtaler la dclaration dune classe, dune structure ou dune interface
sur plusieurs fichiers sources. Les documentations anglaises ont recours lexpression partial
type pour nommer cette possibilit. Nous aurons recours dans la suite aux expressions type dfini
sur plusieurs fichiers sources et dfinition partielle dun type qui sont plus parlantes que type partiel.
Notez que vous ne pouvez dclarer une dlgation ou une numration sur plusieurs fichiers
sources.
Pour dclarer un type sur plusieurs fichiers sources, il faut faire prcder un des mots cls class,
struct ou interface par le mot cl partial dans chaque dclaration partielle. Les dfinitions partielles doivent imprativement appartenir au mme espace de nom. De plus, le compilateur
autorise quun type nadmettre quune seule dfinition partielle prcde du mot cl partial.
Les dirents fichiers sources contenant les direntes dfinitions partielles dun mme type
doivent tous tre compils en une seule fois. Une consquence est que cette possibilit ne peut
tre utilise pour taler la dfinition dun mme type sur plusieurs modules dun mme assemblage ou sur plusieurs assemblages. Cette limitation est une consquence du fait que la
possibilit de dfinir un type sur plusieurs fichiers sources nest que du sucre syntaxique. Elle ne
concerne que le compilateur C  2 csc.exe et na aucune incidence ni sur la faon dont les types
sont contenus dans les assemblages, ni par leurs traitements par le CLR lexcution.
Le fait de dfinir un type sur plusieurs fichiers sources a tendance complexifier la lecture du
code. Aussi, nous vous conseillons davoir recours cette possibilit que lorsque vous souhaitez
gnrer du code. Dans ce cas, il est apprciable de ne gnrer que partiellement le code dun type
afin de faire interagir aisment le code gnr avec le code fait la main . Cela vite davoir
recours des artifices pour rendre possible cette interaction. Visual Studio 2005 inclut plusieurs
gnrateurs de code qui exploite cette notion de dfinition partielle : gnrateur de formulaires
Windows Forms (page 669), le gnrateur de DataSet typs (page 731), le gnrateur de pages
web (page 869) etc.
Enfin, prcisons quun type encapsul dans un autre type peut tre dfini sur plusieurs fichiers
sources. Dans ce cas chaque dfinition partielle dun type encapsul doit imprativement tre
rdige dans une des dfinitions partielles du type encapsulant.

Les modificateurs qui doivent tre rpts


Les direntes dfinitions partielles dun mme type doivent toutes tre prcdes du mot cl
partial. En outre, si le type est gnrique, la dfinition des types paramtres doit tre rptes
pour chaque dfinition partielle. Les noms des types paramtres ainsi que leurs positions dans
la liste doivent tre identiques pour chaque dfinition partielle.

Les modificateurs qui peuvent tre rpts sans rpercussion sur


leurs eets
Les modificateurs de type suivants peuvent tre rpts ou non dans les direntes dfinitions
partielles dun type. Il sut quils soient prsents sur une seule dfinition partielle pour que
leurs eets se rpercutent sur le type entier. Bien entendu, le compilateur C  2 dtecte et sanctionne dune erreur toutes les incohrences telle quune dclaration partielle ayant une visibilit
publique et une autre ayant une visibilit interne.

Dfinir un type sur plusieurs fichiers sources

393

Les mots-cls abstract et sealed. Prcisons quun mme type ne peut tre la fois abstract
et sealed .

La visibilit dun type.

La classe de base dun type.

Lensemble des contraintes pour un type paramtre.

Les modificateurs dont les eets se cumulent


Les modificateurs de type suivant peuvent tre rpts dans les direntes dfinitions partielles
dun type. Dans ce cas leurs eets se cumulent. Ici aussi le compilateur C  dtecte et sanctionne
dune erreur toutes les incohrences telle que la dfinition rpte dune mme mthode dans
plusieurs dclarations partielles dun mme type.

Les membres. Lensemble des membres dun type dfini sur plusieurs fichiers sources est
lunion des membres de chaque dfinition partielle.

Les attributs. Lensemble des attributs sappliquant sur un type dfini sur plusieurs fichiers
sources est lunion des attributs appliqus sur chaque dfinition partielle. En particulier, le
type concern sera marqu plusieurs fois par un mme attribut si celui-ci marque plusieurs
dfinitions partielles.

Les interfaces implmentes. Lensemble des interfaces implmentes par un type dfini sur
plusieurs fichiers sources est lunion des interfaces dclares dans chaque dfinition partielle.

Les modificateurs dont les eets sont locaux


Seul le mot cl unsafe rentre dans cette catgorie. Il peut tre appliqu sur une ou plusieurs dfinitions partielles dun mme type. Dans ce cas, son eet nest limit quaux membres dclars
dans les dfinitions partielles concernes.

11
Notions de classe et dobjet

Remarques sur la programmation objet


C++ C  C  peut tre considr plus orient objet que C++. En eet la notion de fonction
globale ou de variable globale nexiste pas en C  . Seules les mthodes (statiques ou non) de
classes existent.
C  Le concept de la programmation procdurale est construit autour de la notion de fonction.
Tout programme est un ensemble de fonctions sappelant entre elles.
Le concept de la programmation objet est construit autour des notions dobjet et de classe. Une
classe est limplmentation dun type de donnes (au mme titre que int ou une structure).
Un objet est une instance dune classe.
Dans la programmation objet, tout programme peut tre vu comme un ensemble dobjets qui
interagissent entre eux. Nous allons dtailler comment C  traite la programmation oriente
objet (POO). Cependant cet ouvrage nest pas un ouvrage sur la POO. En eet, la plupart des
concepts sont abords mais cela ne sut pas pour savoir les utiliser bon escient.

Notions et vocabulaire
C++ C  En C  , en franais, le mot attribut est rserv pour dautres notions que celle de
champ dune classe. Cette notion dattribut est prsente page 248. De plus on verra que C 
distingue la notion de champs et de proprits.
Enfin puisque la plateforme .NET dispose dun ramasse-miettes, un destructeur C  est trs diffrent dun destructeur C++.
C  Une classe est un type de donnes. Les notions de variable dun type et dinstance dune
classe sont similaires, dans le sens o les traitements sorganisent autour des donnes. Un objet
est une instance dune classe. La notion de classe dfinit un concept alors que la notion dobjet

396

Chapitre 11 : Notions de classe et dobjet

dfinit linstance dun concept. La notion de classe est au concept de voiture ce que la notion
dobjet est une voiture particulire. Une voiture dans la rue est une instance du concept de
voiture.
Il est intressant de souligner un paradoxe de la programmation par objets. Les objets nexistent
qu lexcution et sont cres, grs et dtruits par lenvironnement dexcution. Les dveloppeurs ne font qucrire des classes qui lors de lexcution, entraneront la cration dobjets. Aussi,
certains prfreraient utiliser la terminologie programmation par classes.
Dans le contexte dun langage objet tel que C  , le terme variable est utilis pour dsigner un
objet instance dun type valeur, allou dans le corps dune mthode et dont la dure de vie ne
dpasse pas la porte de cette mthode.
Une classe est constitue de plusieurs entits de direntes natures : les champs, les proprits,
les mthodes, les vnements, les indexeurs et les types encapsuls. On appelle ces entits les
membres de la classe. Nous allons avoir loccasion dans le prsent chapitre de dtailler chacune
de ces entits.
Chaque objet renferme une quantit dinformation sous forme de valeurs stockes dans ses
champs. Lensemble de ces valeurs est appel ltat de lobjet.
La notion de mthode se rapproche de celle de fonction, ceci prs quune mthode est appele
sur une instance de la classe. Une mthode agit sur lobjet sur lequel elle est appele. Elle peut
aussi bien lire ou crire les champs de lobjet, queectuer une action sur lobjet.
Un objet a une dure de vie qui est un sous intervalle de la dure dexcution du programme
qui lhberge. Un objet est ncessairement construit un moment donn et dtruit plus tard
un autre moment. Une mthode est automatiquement appele lors de la construction. Elle
est nomme constructeur. Elle permet par exemple dinitialiser lobjet ou dallouer des ressources
consommes par lobjet. Il peut y avoir plusieurs constructeurs pour une mme classe car il peut
y avoir plusieurs faons de construire un objet.
Une mthode est automatiquement appele lors de la destruction dun objet par le ramassemiettes de la plateforme .NET. La destruction des objets est un sujet sensible en .NET/C  qui
doit absolument tre bien compris.

Dfinition dune classe


C++ C  C  ne connat pas loprateur de rsolution de porte :: tel quil est utilis en
C++ lors de la dfinition des classes (cet oprateur a t introduit avec C  2 nanmoins, mais
pour dautres raisons).
Par consquent, la dfinition dune classe (le corps des mthodes ainsi que linitialisation des
champs statiques compris) doit se faire absolument lintrieur de la paire daccolade aprs le
mot-cl class.
Le concept de classes et mthodes amies dune classe, prsent dans le C++, a totalement disparu
en C  . La raison est que ce concept reprsente une violation du concept dencapsulation qui est
plus prpondrant en POO. Signalons cependant le nouveau concept .NET 2.0 dassemblages
amis qui se rapproche de ceci (prsent en page 27).
Enfin le caractre ; nest pas obligatoire la fin de la dfinition dune classe.
C

La dfinition dune classe se fait avec le mot-cl class. Voici un exemple :

Les champs

397

Exemple 11-1 :
class Program{
// Ici sont places les membres de la classe Program.
static void Main() {
// Ici sont placees les instructions de la m
ethode Main().
}
// Ici aussi, sont places les membres de la classe Program.
}
Les membres dune classe sont des entits dclares dans la classe. Il y a six sortes de membres :

Les champs

Les proprits

Les indexeurs

Les mthodes

Les vnements

Les types encapsuls dans la classe

part quelques dtails que nous soulignerons, tout ce qui va tre dit ici propos des membres
dune classe est aussi valable pour les membres dune structure.

Accs aux membres


C++ C  Laccs aux membres est plus simple en C  quen C++. Puisque le mode dallocation (statique ou dynamique) dun objet nest plus du ressort du programmeur, il ny a plus
lieu de faire la dirence entre les objets de type valeur et les objets de type rfrence. Ainsi
loprateur flche -> disparat et seul reste loprateur point . .
C  Lorsquune zone de code a accs un membre (non statique) dune classe (respectivement dune structure), on peut utiliser loprateur point . partir dune instance de cette
classe (respectivement de cette structure) pour accder au membre. Ceci est valable que ...

...le membre soit un champ une proprit une mthode un vnement ou un type.

...lobjet soit de type valeur (cas dune structure) ou de type rfrence (cas dune classe).

Les champs
C++ C 

Quelques petites dirences entre C  et C++.

Tout dabord, les champs (statiques ou non) peuvent tre initialiss directement dans leurs dclarations au sein de la classe. La valeur dun champ non statique dun objet sera aecte avant
lappel au constructeur. La valeur dun champ statique sera aecte avant la cration de toutes
les instances de la classe.
En revanche, on perd la syntaxe C++ dinitialisation de champs directement aprs le prototype
du constructeur :
ctor(int champ) :m_Champ(champ) {}

398

Chapitre 11 : Notions de classe et dobjet

Les spcifications du compilateur C  sont moins permissives que celles du C++. Elles obligent
que tous les champs non statiques de type valeur soient initialiss avant la fin de lappel dun
constructeur de la classe. Les champs de type rfrence non initialiss sont automatiquement
positionns null.
Enfin un champ peut tre const, cest--dire initialis directement au sein de la classe ou
readonly, cest--dire initialis au sein de la classe ou dans les constructeurs. Dans les deux
cas le champ nest plus modifiable aprs son initialisation.
C  La notion de champ dune classe est la mme que la notion de champ dune structure.
Lensemble des tats des champs non statiques dune instance dune classe reprsente ltat de
cette instance. Un champ peut tre de nimporte quel type (primitif, numration, structure,
classe, interface...).
Lorsquun objet A a pour champ un autre objet B, on dit quil y a une agrgation de A sur B si B
est de type valeur. Si B est de type rfrence, on dit que A a une rfrence sur B. Cette distinction
est importante pour une approche de type UML ou OMT.

Initialisation dun champ


Tous les champs de type valeur doivent tre initialiss avant que lobjet puisse tre utilis et
donc, avant la fin de la construction de lobjet. Les champs peuvent tre initialiss de deux faons :

Directement lors de leur dclaration au sein de la classe (Champs1 et Champs2 dans lexemple).

Dans tous les constructeurs de la classe (Champs1 et Champs3 dans lexemple).

Exemple 11-2 :
class Foo {
int Champ1 = 4 ;
int Champ2 = 3 ;
int Champ3 ;
public Foo() { // Un constructeur public de la clase Foo.
Champ1 = 7 ;
Champ3 = 6 ;
}
}
On voit bien avec Champ1 que les deux faons ne sont pas incompatibles, cependant linitialisation de Champ1 dans la classe est inutile ici, puisque cest son initialisation dans le constructeur
qui sera excute en dernier.

Champs constants
On peut rendre un champ constant, cest--dire quil prend sa valeur linitialisation (au sein
de la classe ou dans le constructeur) puis ne peut plus tre modifi. C  propose deux faons de
rendre un champ constant :

On le dclare avec le mot-cl const.

On le dclare avec le mot-cl readonly.

Les champs

399

readonly autorise linitialisation du champ au sein de la classe ou dans le constructeur, alors


que const nautorise que linitialisation du champ au sein de la classe.
Exemple 11-3 :
class Foo {
const
int Champ1 = 4 ;
readonly
int Champ2 = 3 ;
// Un constructeur public de la classe Foo.
public Foo() {
// Champ1 ne peut etre initialis
e ici, sous peine dune
// erreur de compilation.
Champ2 = 7 ;
}
}
Ceci implique quun champ de type rfrence dclar avec const doit imprativement tre initialis lors de sa dclaration au sein de la classe. Dans le cas contraire le compilateur dtecte une
erreur.
Une autre implication est quun champ de type rfrence dclar avec readonly doit imprativement tre initialis avec un objet allou avant la fin dau moins un constructeur. Dans le cas
contraire le compilateur ne dit rien, mais ce champ ne sert alors absolument rien puisquil ne
rfrence aucun objet (i.e il est positionn null) et ne peut tre modifi.

Un problme potentiel linitialisation des champs


Le compilateur C  est incapable de dtecter une boucle dans le graphe des agrgations/rfrences
dobjets. Cest--dire quil ne voit pas que le programme suivant nest pas valide. En eet B
instancie un objet de la classe A, qui instancie un objet de la classe B, qui instancie un objet
de la classe A etc ... La terminaison du programme est provoque soit par le dpassement de
limite de la taille de la pile ( cause des nombreux appels de mthodes, imbriqus) soit par le
dpassement de limite de la taille du tas.
Exemple 11-4 :
class Program {
static void Main() {
B b = new B () ;
}
}
class A {
B m_Champ = new B () ;
}
class B {
A m_Champ = new A () ;
}

400

Chapitre 11 : Notions de classe et dobjet

Les mthodes
C++ C  De nombreuses dirences existent entre C  et C++ en ce qui concerne les mthodes. Notez que la possibilit davoir des mthodes constantes (i.e qui ne changent pas les
champs de lobjet) du C++, nexiste pas en C  .
En C++ le passage dargument une mthode ou une fonction peut se faire soit par pointeur,
soit par valeur soit par rfrence.
C  permet pour toute mthode, le passage dargument par valeur et par rfrence. Sous certaines conditions C  permet aussi le passage par pointeur.
Par dfaut, C  fait passer les arguments de type valeur par valeur, et les arguments de type rfrence par rfrence.
La syntaxe du passage par rfrence des arguments de type valeur a chang.
C  introduit la possibilit quun argument ne soit que out, i.e on ne sintresse qu sa valeur
de sortie.
Enfin les arguments par dfaut du C++ ont disparu. Ils sont remplacs par un concept beaucoup
plus puissant, proche du passage dargument en nombre indtermin du C++ (utilis notamment par la fonction printf).
C  Une mthode dinstance dune classe est une fonction qui porte sur les objets de la classe.
Une mthode peut manipuler les champs et proprits de la classe afin deectuer un calcul ou
afin de modifier ltat de lobjet.
Exemple 11-5 :
public class Article {
public decimal PrixHT = 10 ;
public decimal TVA = 19.6M ;
public decimal GetPrixTTC() { // Une m
ethode de la classe Article.
return PrixHT + PrixHT * (TVA / 100);
}
}
Les mthodes font partie de la classe au mme titre que les champs et les proprits. Les mthodes jouent un rle essentiel en C  et rassemblent une grande partie des points matriser
de ce langage.
Pour une bonne comprhension de ce qui suit, il est prfrable de bien avoir assimil les notions
de type valeur et de type rfrence, dcrites page 339.

Passage darguments par valeur et par rfrence


En thorie de la programmation il na que deux faons bien distinctes de faire passer une variable (un objet) sous forme dun argument, une mthode :

Le passage dargument par rfrence :


Une rfrence sur lobjet est passe. Ceci implique que la mthode appele et la mthode
appelante utilisent le mme objet. La consquence majeure est que si la mthode appele
modifie ltat de lobjet, la mthode appelante utilisera lobjet avec ces modifications.

Les mthodes

401

Le passage dargument par valeur :


La valeur (ltat) de lobjet est passe. Ceci implique que la mthode appele et la mthode
appelante utilisent chacune un objet dirent. Le compilateur soccupe de crer une copie (un clone) de lobjet pass sur la pile de lunit dexcution courante. Cette copie nest
utilisable que par la fonction appele et est dtruite lorsque lunit dexcution sort de la
fonction appele et retourne la fonction appelante. La consquence majeure est que si la
fonction appele modifie ltat de lobjet (du clone), la fonction appelante ne verra jamais
ces modifications.

Les rgles que C  applique par dfaut


Le fait quil existe un passage dargument par valeur et un passage dargument par rfrence
est mettre en relation avec le fait quen C  chaque type est soit un type valeur, soit un type
rfrence. Trs logiquement, par dfaut C  applique cette rgle :

Une variable (un objet) de type valeur est passe par valeur une mthode.

Un objet de type rfrence est pass par rfrence une mthode.

Par exemple :
Exemple 11-6 :
public class Article { public int Prix = 0 ; }
class Program {
static void Main() {
int i = 10 ; // int est un type valeur.
Article article = new Article() ; // Article est un type r
ef
erence.
article.Prix = 10 ;
fct(i, article);
// Ici i vaut 10 et article.Prix vaut 100.
}
static void fct(int i, Article article) {
// Lentier i nest pas le m
eme que lentier i de Main().
// Linstance de la classe Article pass
ee est bien la m
eme que
// linstance de la classe Article de la m
ethode Main.
i = 100;
article.Prix = 100;
}
}

La possibilit de forcer le passage dargument par rfrence


C++ C  En C  , on peut forcer le passage dargument par rfrence, de mme quen C++.
En C  la syntaxe a chang et corrige le fait quen C++, lutilisateur na pas toujours conscience
de passer par rfrence un argument. En eet, en C++, la syntaxe ne change pas lappel de la
fonction mais seulement dans le prototype de la fonction (avec lutilisation de & ).
En C  ce problme nexiste plus. Lorsquun argument est pass par rfrence il faut utiliser le
mot-cl ref , la fois lors de la dclaration de la mthode, et lors de lappel.

402

Chapitre 11 : Notions de classe et dobjet

C  C  ore la possibilit de forcer le passage par rfrence dun argument. Cette technique
est utilisable pour les types valeur et rfrence. Cette technique permet de passer par rfrence
des arguments de type valeur. Nous allons voir juste aprs que cette technique impacte aussi le
passage des objets de type rfrence. Cette technique utilise le mot-cl ref, la fois lors de la
dclaration de la mthode, et lors de lappel, par exemple :
Exemple 11-7 :
class Program {
static void Main() {
int i = 10 ; // int est un type valeur.
fct(ref i);
// Ici i vaut 100.
}
static void fct( ref int i ) {
// Lentier i est le meme que lentier i de Main.
i = 100 ;
}
}

Passer par rfrence un argument de type rfrence


C++ C  Nous prsentons ici une technique de passage dargument en C  , conceptuellement proche de lutilisation dun double pointeur en C++.
C  . Passer un argument de type rfrence par rfrence permet la mthode appele dagir
directement sur la rfrence. Concrtement, la mthode appele peut modifier lobjet qui est
rfrenc par la rfrence passe. Ceci est illustr dans lexemple suivant :
Exemple 11-8 :
public class Article { public int Prix = 0 ; }
class Program {
static void Main() {
Article articleA = null ;
Article articleB = null ;
// Article est un type ref
erence.
fct(articleA, ref articleB) ;
// Ici, articleA ne refer
ence aucun objet,
// cest toujours une ref
erence nulle.
// articleB reference le second objet allou
e dans fct().
}
static void fct(Article articleA, ref Article articleB) {
if (articleA == null)
articleA = new Article();
if (articleB == null)
articleB = new Article();
}
}

Les mthodes

403

linstar de cet exemple, on utilise souvent cette technique pour dlguer lallocation dun ou
plusieurs objets, dans une mthode. Dans ce cas, il est prfrable dutiliser la technique dargument out, expose un peu plus loin.

Initialisation des arguments


C++ C  Le compilateur C++ ne dtecte pas lutilisation de variables ou dobjets non initialiss explicitement. Ceci est un norme problme puisque C++ ninitialise pas les variables et
les objets implicitement. En C++, de nombreux bugs sont dus une variable non initialise.
De plus, certains compilateurs ne respectent pas la norme C++ (discutable il est vrai) et initialisent zro la mmoire alloue aux variables. Le code est ainsi peu portable puisque des
compilateurs dirents gnrent des comportements dirents.
Le compilateur C  corrige ces problmes en imposant que les arguments passs une mthode
soient initialiss.
C  C  oblige le dveloppeur initialiser ses objets de type valeur avant de les passer une
mthode. Cette rgle sapplique, que largument soit pass par valeur ou par rfrence. Dans le
cas dun argument de type rfrence, soit la rfrence est sur un objet dj construit, auquel cas
C  a dj forc son initialisation, soit la rfrence nest sur aucun objet, auquel cas elle doit avoir
t initialise avec le mot-cl null. Voici un exemple important, bien assimiler :
Exemple 11-9 :
public class Article { public int Prix = 0 ; }
class Program {
static void Main() {
// Cette variable de type valeur doit etre initialis
ee
// avant detre passee `a la m
ethode fct().
int i = 10 ;
// Ces deux references doivent
etre initialis
ees
// avant detre passees `a la methode fct().
Article articleA = null ;
Article articleB = new Article() ;
articleB.Prix = 100 ;
fct(i, articleA, articleB) ;
// Ici i vaut 10.
// articleA ne reference aucun objet.
// articleB.Prix vaut 10.
}
static void fct(int i, Article articleA, Article articleB) {
if (articleA == null)
articleA = new Article() ;
articleA.Prix = i ;
articleB.Prix = i ;
}
}

404

Chapitre 11 : Notions de classe et dobjet

Rcupration dinformation partir dune mthode


(les paramtres out)
C++ C  En C++, pour rcuprer le rsultat dune mthode il faut soit utiliser la valeur
de retour, soit passer des arguments par rfrence ou par pointeur. Dans le deuxime cas, lintention du dveloppeur dutiliser un argument pour rcuprer un rsultat, nest pas vidente,
puisque la mme syntaxe permet aussi de passer des informations la fonction. C  ore une
syntaxe lgante pour pallier ce problme.
C  C  permet une mthode de retourner des informations (par exemple les rsultats dun
calcul) de deux faons direntes :

Directement par le paramtre de retour de la mthode. Le problme de cette technique


largement utilise dans beaucoup de langages, est lunicit du paramtre de retour. Le motcl return est utilis dans le corps de la mthode pour dfinir le contenu du paramtre
de retour. Notez quil peut y avoir plusieurs utilisations du mot-cl return au sein dune
mthode. Si la mthode na rien retourner, elle doit tre dclare avec le type void comme
type de retour. Dans ce cas il nest pas obligatoire dutiliser le mot-cl return dans le corps
de la mthode.
C  permet un ou plusieurs paramtres de la mthode, dtre des paramtres de retour.
Dans ce cas les paramtres sont signals avec le mot-cl out dans le prototype de la mthode
et lors des appels de la mthode. De plus les arguments nont pas besoin dtre initialiss.
La mthode est oblige dinitialiser ces paramtres. Elle na pas le droit de se servir de ces
paramtres tant quelle ne les a pas explicitement initialiss.

Exemple 11-10 :
public class Article { public int Prix = 0 ; }
class Program {
static void Main() {
int i ; // i nest pas initialis
e.
Article articleA ; // articleA nest pas initialis
e.
Article articleB = new Article() ; // articleB r
ef
erence un objet.
articleB.Prix = 100 ;
fct( out i, out articleA, out articleB ) ;
// Ici i vaut 10. articleA.Prix vaut 10 et articleB r
ef
erence
// lobjet cree dans fct(). articleB.Prix vaut 10 mais articleB
// ne reference plus le m
eme objet quavant lappel de fct().
}
static void fct(out int i, out Article a, out Article b) {
i = 10 ;
a = new Article() ;
b = new Article() ;
a.Prix = i ;
b.Prix = i ;
}
}

Les mthodes

405

La possibilit davoir des arguments variables en nombre et en type


C++ C  C++ permet une fonction davoir des arguments par dfaut. C++ permet aussi
une fonction davoir un nombre darguments variable. Cette seconde possibilit est par exemple
utilise dans la fonction printf().
C  ne permet pas davoir des arguments par dfaut. Cependant C  permet une fonction davoir
un nombre darguments variable. Pour remplacer les arguments par dfaut du C++ on peut aussi
surcharger la mthode, mais cette technique est assez lourde.
C  C  permet une mthode davoir un nombre quelconque darguments de mme type ou
non. La syntaxe de cette possibilit utilise le mot-cl params. Largument qui utilise le mot-cl
params doit tre le dernier argument dans la liste des arguments dune mthode. Notez que C 
compile une telle mthode avec lattribut System.ParamArrayAttribute. lexcution, le CLR
comprend alors que la mthode prsente cette possibilit. Voici un exemple dutilisation :
Exemple 11-11 :
using System ;
class Program {
static void Main() {
fct("Appel1") ;
fct("Appel2", 67, 3.1415, "hello", 8) ;
fct("Appel3", "bonjour", 2.7, 1729, 691, "au revoir") ;
}
static void fct(string str, params object[] args) {
Console.WriteLine(str) ;
foreach (object obj in args) {
if (obj is int) Console.WriteLine(" int:" + obj) ;
else if (obj is double) Console.WriteLine(" double:" + obj) ;
else if (obj is string) Console.WriteLine(" string:" + obj) ;
else Console.WriteLine(" autre type:" + obj) ;
}
}
}
Cet exemple ache :
Appel1
Appel2
int:67
double:3,1415
string:hello
int:8
Appel3
string:bonjour
double:2,7
int:1729
int:691
string:au revoir

406

Chapitre 11 : Notions de classe et dobjet

Cet exemple utilise loprateur is expliqu dans le prochain chapitre. Comme vous le voyez,
cet oprateur permet de tester le type dun objet. De plus nous spcifions que les arguments
correspondant largument qui utilise le mot-cl params, doivent tre de type object. Cest grce
cette astuce que nous pouvons faire varier le type des arguments, car tous les types hritent du
type object.

Surcharge de mthodes
C++ C 

La surcharge de mthodes existe en C  , tout comme en C++.

C  C  permet plusieurs mthodes dune mme classe davoir le mme nom. Cette possibilit est appele surcharge (overloading en anglais) du nom dune mthode. Dans ce cas, les
listes des arguments de ces mthodes doivent tre direntes deux deux. En eet, dans le cas
contraire il y aurait ambigut lors de lappel. Largument de retour nest pas pris en compte
par le compilateur pour cette direnciation. Cest--dire que ce dernier gnre une erreur si
deux mthodes ont le mme nom, la mme liste de paramtres, mais un type de paramtre
de retour dirent. En revanche, si deux mthodes dun mme type ont le mme nom et des
listes de paramtres direntes, elles peuvent avoir un type de retour dirent. Il est nanmoins
fortement dconseill que les direntes surcharges aient des types de retour dirents car ce
dernier est en gnral fortement li la smantique associe au nom commun des surcharges.
Dautres ambiguts peuvent apparatre si certaines de ces mthodes acceptent des arguments
variables en type et en nombre. Dans ce cas le compilateur C  lve lambigut en prfrant les
mthodes sans arguments variables en type et en nombre. Cependant ce type dambigut est
viter afin de garder un code lisible. Par exemple :
Exemple 11-12 :
using System ;
class Program {
static void Main() {
fct("Appel1", "bonjour") ; // Appel de la surcharge
fct("Appel2") ;
// Appel de la surcharge
fct("Appel3", 10) ;
// Appel de la surcharge
fct("Appel4", 10, 11) ;
// Appel de la surcharge
fct("Appel5", 10.1) ;
// Appel de la surcharge
}
// Surcharge 1 :
static void fct(string str) {
System.Console.WriteLine("surcharge 1") ;
}
// Surcharge 2 :
static void fct(string str, int i) {
System.Console.WriteLine("surcharge 2") ;
}
// Surcharge 3 :
static void fct(string str, params object[] Args) {
System.Console.WriteLine("surcharge 3") ;
}
// Surcharge 4 :

3.
1.
2.
4.
3.

Les proprits

407

static void fct(string str, params int[] Args) {


System.Console.WriteLine("surcharge 4") ;
}
}
En page 477 nous expliquons le type dambigut qui peuvent apparatre cause des paramtres
gnriques et comment le compilateur C  2 les rsout.

Les proprits
C++ C 

Le concept de proprit nexiste pas en C++.

C  C  permet aux classes et aux structures davoir des proprits. Les proprits permettent
daccder ltat dune instance avec la mme syntaxe que laccs un champ, mais sans passer
directement par un champ. Laccs se fait par des mthodes spciales appeles les accesseurs de
la proprit. Lavantage est double :

Lutilisateur de la classe veut un accs ltat de lobjet avec la mme syntaxe que laccs
un champ. Il na pas appeler explicitement une mthode.

Le dveloppeur de la classe veut intercepter tous les accs ltat de lobjet. Pour cela il
utilise les accesseurs.

Il y a deux types daccs ltat dun objet. Aussi, il y a deux accesseurs possibles pour chaque
proprit :

chaque accs en lecture dune proprit laccesseur get de la proprit est appel.

chaque accs en criture dune proprit laccesseur set de la proprit est appel.

Par exemple :
Exemple 11-13 :
public class Foo {
private int m_ChampPrivate = 10 ;
public int Prop { // Une propri
et
e de type int.
get{ return m_ChampPrivate;}
set{ m_ChampPrivate = value;}
}
}
public class Program {
static void Main() {
Foo foo = new Foo() ;
foo.Prop = 56 ;
// Laccesseur set est appel
e.
int i = foo.Prop ; // Laccesseur get est appel
e.
}
}
Les accesseurs se codent comme des mthodes mis part que :

Ils ne peuvent tre appels explicitement comme des mthodes.

408

Chapitre 11 : Notions de classe et dobjet


Dans leurs dclarations, ils nont pas darguments ni dentre ni de retour.

Une proprit peut tre de nimporte quel type (valeur ou rfrence).


linstar de cet exemple, il y a en gnral pour chaque proprit publique un champ priv qui
lui correspond et vice versa.

Accesseur get
Laccesseur get dune proprit a pour contrainte de retourner un objet de mme type que sa
proprit (ou une rfrence vers un objet si cest un type rfrence).
Une proprit nest pas oblige dimplmenter laccesseur set. Dans ce cas on dit quelle est accessible en lecture seule (read only en anglais, bien que lon ne parle pas ici du mot-cl readonly).
Une proprit accessible en lecture seule ne peut pas tre initialise. En revanche, on peut retourner un objet dont ltat est calcul dans laccesseur get.
Exemple 11-14 :
public class Foo {
private int m_ChampPrivate = 10 ;
public bool Prop { // Une propri
et
e de type bool en lecture seule.
get{ return (m_ChampPrivate >100);}
}
}
public class Program {
static void Main() {
Foo foo = new Foo() ;
bool b = foo.Prop ; // Laccesseur get est appel
e.
}
}

Accesseur set
Dans le corps de laccesseur set, le mot-cl value est utilisable. Il reprsente un objet du mme
type que la proprit (ou une rfrence si cest un type rfrence). Cest lobjet (ou la rfrence)
fourni lors de lassignation de la proprit.
Une proprit nest pas oblige dimplmenter laccesseur get. Dans ce cas on dit quelle est
accessible en criture seule. Une proprit accessible en criture seule est inaccessible en lecture.
En revanche, on peut calculer un nouvel tat pour lobjet qui prsente la proprit, en tenant
compte de la valeur passe.
Exemple 11-15 :
public class Foo {
private int m_ChampPrivate = 10 ;
public int Prop { // Une propri
et
e de type int en
ecriture seule.
set{ m_ChampPrivate = value*2;}
}
}
public class Program {

Les indexeurs

409

static void Main() {


Foo foo = new Foo() ;
foo.Prop = 56 ; // Laccesseur set est appel
e.
}
}

Remarques
Une proprit est oblige davoir au moins un accesseur. Elle a soit un accesseur get, soit un
accesseur set, soit les deux.
Il est conseill de ne pas modifier ltat dun objet dans les accesseurs get. Il faut considrer les
accesseurs get comme un moyen dintercepter et de contrler un accs ltat de lobjet. On
peut par exemple en profiter pour loguer ces accs.
Il est conseill de ne pas lancer dexception partir dun accesseur, moins quelle soit rattrape
dans laccesseur mme. En eet, syntaxiquement le client na pas ncessairement conscience
dappeler un accesseur lors de laccs une proprit. Le client peut donc tre surpris par une
exception.
En page 419, nous expliquons que les accesseurs dune mme proprit peuvent avoir des niveaux de visibilit dirents.

Les indexeurs
C++ C  Comme le langage C++, le langage C  permet de dfinir loprateur daccs un
tableau [] dans une classe. Cependant C  permet une dfinition beaucoup plus complte de
cet oprateur. En eet, il peut y avoir plusieurs index, on peut utiliser nimporte quel type pour
les index (y compris vos propres classes) et il peut y avoir plusieurs dfinitions de cet oprateur
pour une mme classe.
C  C  permet de considrer un objet comme un tableau une ou plusieurs dimensions. En
eet, C  autorise lutilisation de loprateur [] directement aprs le nom dun objet. Cet oprateur accepte une liste de un ou plusieurs paramtres, de nimporte quel type (entier, chane de
caractres, objet de toutes classes etc). Les paramtres doivent tre spars par des virgules.
Bien videmment, la classe de lobjet doit prvoir le fait que loprateur [] puisse tre utilis
directement aprs le nom dun objet. Pour cela elle dclare un ou plusieurs indexeurs. Les indexeurs des classes peuvent tre considrs comme des proprits particulires, aux dirences
suivantes prs :
Proprit

Indexeur

Identifie par son nom.

Identifi par sa signature.

Accde partir de son nom, comme un


champ.

Accd avec loprateur daccs aux lments


[].

Peut tre statique ou non.

Ne peut pas tre statique.

Laccesseur get na pas de paramtres.

Laccesseur get a la liste des paramtres de lindexeur.

410

Chapitre 11 : Notions de classe et dobjet

Laccesseur set peut utiliser le paramtre


value implicite.

Laccesseur set a la liste des paramtres de lindexeur en plus du paramtre value implicite.

Comme pour les proprits, les accesseurs des indexeurs sont facultatifs, bien quil en faille au
moins un par indexeur. La dfinition des accesseurs autorise un accs en lecture seule, criture
seule ou lecture/criture. Les paramtres des indexeurs ne peuvent utiliser les mots-cls ref et
out.
Lexemple suivant expose la syntaxe des indexeurs. Nous prsentons laccs un tableau de personnes. Les personnes peuvent tre accdes par leurs noms (une chane de caractres) en lecture
seulement. Les personnes peuvent aussi tre accdes par leurs index dans le tableau interne
lobjet qui les contient, en lecture/criture.
Exemple 11-16 :
using System ;
public class Personnes {
// Tableau prive interne qui contient les noms des personnes.
string [] m_Noms ;
// Le constructeur qui initialise le tableau.
public Personnes(params string [] noms){
m_Noms = new string[noms.Length] ;
// Copie le tableau.
noms.CopyTo(m_Noms,0) ;
}
// Lindexeur qui retourne lindex `
a partir du nom.
public int this[string nom]{
get{ return Array.IndexOf(m_Noms,nom);}
}
// Lindexeur qui retourne le nom `
a partir de lindex.
public string this[int index]{
get{ return m_Noms[index];}
set{ m_Noms[index] = value;}
}
}
class Program {
static void Main() {
Personnes tableau = new Personnes (
"Anna" , "Ingrid" , "Maria" , "Ulrika" ) ;
Console.WriteLine(tableau [1]) ; // Affiche "Ingrid"
int index = tableau["Maria"] ;
tableau[index] = "Marie" ;
Console.WriteLine(tableau[index]) ; // Affiche "Marie"
}
}
En gnral les classes qui ont des indexeurs doivent prsenter la possibilit dtre utilises avec
la syntaxe avec la syntaxe des mots-cls foreach et in.

Les vnements

411

Les vnements
Introduction
C++ C  C  dfinit la notion dvnement qui est compltement absente en C++. En C++ il
faut implmenter soi-mme la plomberie du mcanisme dvnement, par exemple au moyen
du design pattern (Gof) observateur .
C

C  permet a un objet de prsenter des vnements. Le concept dvnement rassemble :

Des abonns lvnement. Ces derniers sont prvenus chaque fois que lvnement est
dclench. Ils ont la possibilit de sabonner et de se dsabonner lvnement dynamiquement (i.e durant lexcution du programme). En C  , un abonn est reprsent par une
mthode.

Lvnement lui-mme, qui peut tout moment tre dclench par lobjet. En interne, lvnement a la connaissance de ses abonns. La responsabilit de lvnement est de prvenir
ses abonns lorsquil est dclench. Lvnement a la possibilit de fournir des informations
ses abonns, lorsquil est dclench avec lobjet argument de lvnement. En C  , un vnement est semblable un dlgu membre dune classe ou dune structure, mis part quil
est dclar avec le mot cl event.

Le concept vnement/abonns nest pas nouveau et est connu sous le nom de publisher/subscriber
(diteur/souscripteur) en Java. Le mme comportement est obtenu avec lutilisation du design
pattern (Gof) observateur (sujet/observateur).
Les vnements sont trs utiliss dans les applications avec une interface graphique (Windows
Forms et ASP.NET). En eet, chaque action possible sur linterface (clic sur un bouton, texte
tap, ComboBox droule, etc) dclenche lappel la mthode adquate. Lors dun clic souris,
largument de lvnement peut tre par exemple la position de la souris sur lcran. Dans la
plateforme .NET, lhritage combin avec les vnements sont la base de tous les contrles
graphiques.

La syntaxe C 
Supposons que nous ayons cr dans une classe un vnement qui sappelle EventName. Les
noms des entits concernes par lvnement sont fonction du nom EventName. Un argument
dvnement est un objet dune classe drive de la classe System.EventArgs. Vous avez donc
la possibilit de crer vos propres types darguments dvnement en crant vos propres classes
drives de System.EventArgs.
Pour amliorer la lisibilit du code, il est prfrable que la classe dargument de lvnement
EventName sappelle EventNameEventArgs.
Les abonns sont matrialiss par des mthodes (statiques ou non). Le fait de prvenir les abonns se traduit par un appel chacune de ces mthodes. Ces mthodes ont en gnral la signature
suivante :

Retour du type void.

Premier argument de type objet. Lorsque la mthode est appele, cet argument rfrence
lobjet qui contient lvnement.

412

Chapitre 11 : Notions de classe et dobjet


Deuxime argument de type System.EventArgs. Lorsque la mthode est appele cet argument rfrence largument de lvnement.

Un vnement est un membre dune classe, dclare avec le mot-cl event. Un vnement est
une instance de la dlgation EventNameEventHandler. Un vnement a la possibilit dtre statique ou non. Les niveaux de visibilit sappliquent aux vnements. Comme un vnement est
un membre dune classe, la dclaration dun vnement peut tre prcde par un ou plusieurs
des mots-cls suivants :
new
virtual

public
sealed

protected
override

internal
abstract

private
extern

static

Un vnement contient :

Un dlgu qui renferme les rfrences vers les mthodes abonnes. La dlgation a la
mme signature que les mthodes abonnes et doit sappeler EventNameEventHandler.

Un accesseur add qui est invoqu lorsquune mthode est abonne lvnement.

Un accesseur remove qui est invoqu lorsquune mthode est dsabonne lvnement.

Ces trois entits sont dfinies automatiquement par le compilateur C  ds que lvnement est
dclar. Vous pouvez nanmoins dfinir vos propres corps daccesseurs. Si vous changez le corps
dun accesseur, vous devez aussi coder le corps de lautre accesseur. Dans le corps dun accesseur,
le mot-cl value dsigne le dlgu ajouter ou supprimer. Par exemple les deux dfinitions
suivantes dvnement sont acceptables :
// sans accesseurs
public event ClickButtonEventHandler ClickButton ;
// avec accesseurs
private event ClickButtonEventHandler m_ClickButton ;
public event ClickButtonEventHandler ClickButton{
add
{ m_ClickButton += value ; }
remove
{ m_ClickButton -= value ; }
}
Notez quil est trs rare davoir modifier les accesseurs add et remove. Cependant, cette facilit
peut tre utile afin de restreindre le niveau de visibilit de lvnement. Comprenez que les
possibilits dexploiter le polymorphisme sur un vnement (i.e le fait de pouvoir utiliser les
mots-cls virtual, new, override et abstract dans la dclaration dun vnement) ne sapplique
qu ces deux accesseurs add et remove. Clairement, cette facilit et sduisante pour le puriste
mais extrmement peu usite.
En C  lvnement est dclench par lappel son dlgu. Ce nest pas le cas dautres langages
.NET. Par exemple VB.NET dclenche un vnement au moyen du mot-cl RaiseEvent qui
nexiste pas en C  . Le compilateur impose que lappel au dlgu doit tre ralis au sein de
la classe qui dclare lvnement. Aussi, cette classe a la possibilit de prsenter une mthode
publique OnEventName() qui permet de dclencher lvnement hors de la classe.
...
public void OnClickButton(System.EventArgs Arg){
if(ClickButton != null )
// Declenche levenement.

Les vnements

413

ClickButton(this,Arg);
}
...

Un exemple
Voici un exemple o un objet de la classe AgenceDePresse publie des bulletins dinformation
sur la France et le monde. La publication se fait laide des deux vnements InfoMonde
et InfoFrance. Largument dun vnement bulletin dinformation (instance de la classe
InfoEventArgs) contient la description du bulletin dinformation. Enfin, les instances de la
classe Abonne ont la possibilit de recevoir un bulletin dinformation en abonnant leur mthode
ReceptionInfo().
Exemple 11-17 :
using System ;
class Abonne{
private string m_Nom ;
public Abonne( string Nom ) { m_Nom = Nom ; }
// Methode `a appeler lorsquun
ev
enement est d
eclench
e
// (i.e lorsquun bulletin dinformation est publi
e).
public void ReceptionInfo(object sender, EventArgs e){
InfoEventArgs info = e as InfoEventArgs ;
if( info != null ){
Console.WriteLine( m_Nom + " re
coit linfo : " +
((InfoEventArgs)e).GetBulletinInfo() ) ;
}
}
}
//La classe dargument de levenement bulletin dinformation.
class InfoEventArgs: EventArgs{
private string m_Description ;
public string GetBulletinInfo() { return m_Description ; }
public InfoEventArgs (string Description) {
m_Description = Description ;
}
}
// Definition du type delegue handler dun bulletin dinformation.
public delegate void InfoEventHandler(object sender, EventArgs e) ;
// Classe contenant les evenements bulletin dinformation.
class AgenceDePresse {
// Definition des evenements bulletins dinformation.
public event InfoEventHandler InfoFrance;
public event InfoEventHandler InfoMonde;
// Methodes de declenchement des
ev
enements

414

Chapitre 11 : Notions de classe et dobjet


// (i.e de publication de bulletin dinformation).
public void OnInfoFrance(InfoEventArgs bulletinInfo) {
if( InfoFrance != null )
InfoFrance(this,bulletinInfo) ;
}
public void OnInfoMonde(InfoEventArgs bulletinInfo) {
if( InfoMonde != null )
InfoMonde(this,bulletinInfo) ;
}
}
class Program {
public static void Main(){
// Creation de lagence de presse.
AgenceDePresse afp = new AgenceDePresse () ;
// Creation des abonnes.
Abonne raymond = new Abonne("Raymond") ;
Abonne olivier = new Abonne("Olivier") ;
Abonne mathieu = new Abonne("Mathieu") ;
// Creation des abonnements aux
ev
enements bulletins dinfo.
afp.InfoFrance += raymond.ReceptionInfo ;
afp.InfoFrance += olivier.ReceptionInfo ;
afp.InfoMonde += olivier.ReceptionInfo ;
afp.InfoMonde += mathieu.ReceptionInfo ;
// Publication de bulletins dinformation
// (declenchement des evenement).
afp.OnInfoFrance(new InfoEventArgs("Hausse du prix du tabac.")) ;
afp.OnInfoMonde(new InfoEventArgs(
"Nouvelle election au
Etats-Unis.")) ;
// Resilation dabonnement.
afp.InfoFrance -= new InfoEventHandler(olivier.ReceptionInfo ) ;
// Publication de bulletins dinformation.
afp.OnInfoFrance(new InfoEventArgs("Baisse des imp
ots.")) ;
}
}

Le programme ache :
Raymond
Olivier
Olivier
Mathieu
Raymond

recoit
recoit
recoit
recoit
recoit

linfo
linfo
linfo
linfo
linfo

:
:
:
:
:

Hausse du prix du tabac.


Hausse du prix du tabac.
Nouvelle
election au Etats-Unis.
Nouvelle
election au Etats-Unis.
Baisse des imp
ots.

Dans cet exemple, rien ne change si vous enlevez les deux occurrences du mot-cl event (i.e rien
ne change si nous utilisons deux dlgus au lieu dutiliser deux vnements). Il y a deux raisons
cela. La premire est que nous nutilisons pas daccesseurs dvnements. La seconde est que
nous ne dclenchons pas un de ces vnements hors de la classe AgenceDePresse. En eet, une
utilit du mot-cl event est quil empche lvnement dtre dclench lextrieur de sa classe.
Le dclenchement dun vnement est aussi interdit partir des mthodes des classes drives

Les vnements

415

de la classe dfinissant lvnement, y compris si lvnement est virtuel. Ainsi le compilateur C 


produirait une erreur si nous avions crit la ligne suivante dans le corps de la mthode main()
de lExemple 11-17 :
Exemple 11-18 :
public static void Main(){
...
afp.InfoFrance(
afp, new InfoEventArgs("Hausse du prix du tabac.")) ;
...
}
En C  la notion dvnement est donc relativement proche de celle de dlgu. Le mcanisme
permet surtout de fournir un cadre standard la gestion des vnements. Notez que le langage VB.NET prsente plusieurs mots-cls spcialement conus pour la gestion des vnements
(RaiseEvent, WithEvents etc).

vnements asynchrones
Lorsquun vnement est dclench, il faut bien tre conscient que cest le mme thread qui
excute linstruction de dclenchement et les mthodes abonnes lvnement.
Un problme potentiel est que certaines mthodes abonnes sexcutent en un temps inacceptable. Un autre problme potentiel est quune mthode abonne peut lever une exception. Cette
dernire empchera alors les excutions des autres mthodes abonnes non encore excutes
et remontera jusqu la mthode qui a dclench lvnement. Il est clair que cela constitue un
problme puisque la mthode qui dclenche un vnement doit absolument tre totalement
dcouple des mthodes abonnes.
Pour pallier ces problmes, il serait bien pratique que les mthodes abonnes soient excutes
dune manire asynchrone (la notion dappel de mthode asynchrone est dcrite page 171). En
outre, il serait aussi intressant que dirents threads excutent les direntes mthodes abonnes. Ainsi, une mthode abonne qui sexcute en un temps inacceptable ou qui lance une
exception ne gne ni lexcution du dclencheur de lvnement, ni les excutions des autres
mthodes abonnes. Le pool de threads du CLR qui est dcrit en page 167 est particulirement
adapt ces deux contraintes.
Ceux qui ont compris la syntaxe dun appel asynchrone de mthode risquent dtre tents dutiliser directement la fonction BeginInvoke() sur lvnement lui-mme. Le problme de cette
solution est que seule une mthode abonne sera excute. La bonne solution, illustre par
lexemple suivant qui est base sur lExemple 11-17 ncessite le parcours explicite de la liste des
mthodes abonnes :
Exemple 11-19 :
...
v
// Methodes de declenchement des e
enements
// (i.e de publication de bulletin dinformation).
public void OnInfoFrance(InfoEventArgs BulletinInfo){
if( InfoFrance != null ){
Delegate[] abonnes = InfoFrance.GetInvocationList() ;

416

Chapitre 11 : Notions de classe et dobjet


foreach(Delegate _abonne in abonnes ){
InfoEventHandler abonne = (InfoEventHandler) _abonne ;
abonne.BeginInvoke(this,BulletinInfo,null,null) ;
}
// Attend un peu pour permettre les ex
ecutions asynchrones
// celles ci se faisant sur des threads du pool,
// qui sont de threads background. Sans cet artifice,
// les threads du pool nauraient pas le temps de commencer
// leur travail que le programme serait d
ej`
a fini.
System.Threading.Thread.Sleep(100) ;
}
}
...

Notez enfin quil ny a pas lieu dappeler la mthode EndInvoke() puisque les mthodes abonnes ne sont pas senses retourner de rsultat la mthode qui a dclench lvnement.

Se protger des exceptions lances par les mthodes abonnes dans


le cas synchrone
Invoquer les mthodes abonnes un vnement dune manire asynchrone est une technique
ecace pour protger le code qui dclenche un vnement des exceptions remontes par les mthodes abonnes. Il est cependant possible davoir le mme niveau de protection en invoquant
les mthodes dune manire synchrone. Pour cela, il faut invoquer les direntes mthodes
abonnes une une, comme le montre lexemple suivant :
Exemple 11-20 :
...
v
// Methodes de declenchement des e
enements
// (i.e de publication de bulletin dinformation).
public void OnInfoFrance(InfoEventArgs BulletinInfo){
if( InfoFrance != null ){
Delegate[] abonnes = InfoFrance.GetInvocationList() ;
foreach(Delegate _abonne in abonnes ){
InfoEventHandler abonne = (InfoEventHandler) _abonne ;
try{
abonne(this,BulletinInfo) ;
}
catch(Exception){ /*traitement dexception*/ }
}
}
}
...

Les types encapsuls


On peut dfinir un type lintrieur dun autre type. Dans ce cas le type dfini lintrieur est
appel type encapsul (nested type en anglais). Il y a au moins deux avantages dfinir un type
dans un autre :

Encapsulation et niveaux de visibilit

417

On peut restreindre la visibilit du type encapsul. Cest--dire quun type dfini dans une
classe ne pourra tre utilis partout dans le code, mais seulement l o vous lavez dcid
(en gnral, seulement lintrieur de la classe encapsulante).

On peut accder tous les membres de la classe encapsulante, partir des mthodes du
type encapsul. Voici un exemple pour illustrer cette possibilit qui nest pas triviale.
Les instances de TypeEncapsule ont un libre accs au champ priv m_i dune instance
de TypeEncapsulant :

Exemple 11-21 :
public class TypeEncapsulant {
private int m_i = 10 ;
public class TypeEncapsule {
public void Add(TypeEncapsulant Foo, int i) {
Foo.m_i += i ; // Acc`
es au champ priv
e TypeEncapsulant.m_i.
}
}
}
public class Program {
static void Main() {
TypeEncapsulant foo1 = new TypeEncapsulant() ;
TypeEncapsulant.TypeEncapsule foo2 = new
TypeEncapsulant.TypeEncapsule() ;
foo2.Add(foo1, 3) ;
// Ici foo1.m_i vaut 13.
}
}

Notez qu lextrieur de la dfinition de la classe TypeEncapsulant, la classe


TypeEncapsule sappelle TypeEncapsulant.TypeEncapsule.

Encapsulation et niveaux de visibilit


Les niveaux de visibilit des membres
C++ C  Deux nouveaux niveaux de visibilit sont introduits avec C  : interne (internal)
et interne protg (internal protected). Ces nouveaux niveaux de visibilit ne concernent que
les membres des classes et pas les membres des structures. Leur signification est lie la notion
dassemblage.
Une autre dirence avec le C++ est au niveau syntaxique. Les mots-cls de visibilit (private,
protected public ou internal) dun membre doivent tre crits devant chaque membre.
Si aucun de ces mots-cls nest prcis, le membre est dclar private par dfaut, dans une classe
mais aussi dans une structure.
La possibilit du C++ de driver dune classe de base avec les mots-cls private protected ou
public afin de conserver ou de restreindre les niveaux de visibilit des membres de la classe

418

Chapitre 11 : Notions de classe et dobjet

de base dans la classe drive, nexiste pas en C  . En C  on ne peut que conserver le niveau de
visibilit des membres de la classe de base.
C  Lencapsulation est un puissant concept de la programmation par objets qui permet de
matriser la visibilit que lon a des membres dune classe ou dune structure vus de lextrieur
de celle-ci.
Lutilisateur dune classe (cest--dire un dveloppeur qui crit du code qui instancie ou rfrence une instance de la classe), na donc accs qu certains membres. Cela rduit dautant la
complexit dutilisation des objets puisque le nombre de points daccs est rduit.
Pour bien comprendre les dirents niveaux de visibilit il faut avoir compris la notion dassemblage qui fait lobjet du chapitre 2 et la notion de classe drive, qui fait lobjet du chapitre 12.
Il y a cinq niveaux de visibilit dirents. Chaque niveau de visibilit porte sa restriction une
partie du code source extrieur de la classe. Seul le niveau de visibilit public ninduit aucune
restriction. Chaque membre dune classe a un niveau de visibilit (et un seul) parmi :

Le niveau de visibilit public : mot-cl public.


Un membre qui a le niveau de visibilit public est accessible partout dans le code de lassemblage courant et dans le code des assemblages rfrenant lassemblage courant.

Le niveau de visibilit priv : mot-cl private.


Un membre qui a le niveau de visibilit priv et qui est dfini dans un type T est accessible
seulement partir du code contenu dans T ainsi qu partir du code des types encapsuls de
T.

Le niveau de visibilit protg : mot-cl protected.


Le membre peut tre accd seulement dans le code des mthodes de la classe et des mthodes des classes drives. Ceci reste valable pour le code des mthodes des classes drives
dfinies dans dautres assemblages.

Le niveau de visibilit interne : mot-cl internal.


Le membre peut tre accd partout dans le code. Cependant, si la classe est utilise dans
dautres assemblages le membre qualifi dinterne est invisible dans ces autres assemblages.

Le niveau de visibilit interne protg : mot-cl internal protected.


Le membre peut tre accd partout dans le code de lassemblage courant, partir dune
instance de la classe. Cependant, si la classe est utilise dans dautres assemblages le membre
qualifi dinterne protg nest visible que dans le code des mthodes des classes drivant de
la classe concerne. Notez que cest le seul cas o deux mots-cls peuvent tre utiliss pour
dclarer un niveau de visibilit.

Rsumons tout ceci dans un tableau. En colonne les niveaux de visibilit dun membre dune
classe A. En ligne les zones de code. Un OK signifie que la zone de code concerne a accs ce
membre.

Encapsulation et niveaux de visibilit

419

public

internal
protected

internal

protected

private

Mthodes de la classe A.

OK

OK

OK

OK

OK

Mthodes dune classe drive de


A, dans le mme assemblage.

OK

OK

OK

OK

Mthodes dune autre classe dans


le mme assemblage.

OK

OK

OK

Mthodes dune classe drive de


A dans un assemblage dirent.

OK

OK

Mthodes dune autre classe dans


un assemblage dirent.

OK

OK

Les niveaux de visibilit interne, et interne protg, ne peuvent sappliquer aux membres dune
structure.
Un vnement ne peut tre dclench hors de son type, quelque soit son niveau de visibilit.
Mme le code dun type encapsul de son propre type ne peut dclencher un vnement. Seuls
les accesseurs de lvnement sont impacts par son niveau de visibilit.
Par dfaut (i.e si on ne spcifie pas explicitement un niveau de visibilit) les membres dune
classe et dune structure sont privs.

Visibilit des types


Un type qui nest pas encapsul dans un autre type ne peut avoir que les niveaux de visibilit
interne et publique (ATTENTION, on parle du niveau de visibilit du type et pas du niveau
de visibilit des membres du type). Si aucun niveau de visibilit nest aect explicitement un
type, le niveau interne est choisi par dfaut. En page 27 nous introduisons la notion dassemblage
amis. Les assemblages amis dun assemblage ont la possibilit daccder ses types non publics.

Visibilit des accesseurs dune proprit ou dun indexeur


C  2 permet aux accesseurs dune proprit ou dun indexeur davoir un niveau de visibilit
dirent. Pour exploiter cette possibilit il sut dassigner un niveau de visibilit un des deux
accesseurs. Ce niveau de visibilit doit tre dirent et plus restrictif que celui de la proprit
ou de lindexeur qui dfinit laccesseur. Lautre accesseur aura automatiquement le niveau de
visibilit de la proprit ou de lindexeur qui le dfini. En gnral, cette possibilit est utilise
pour faire en sorte que le niveau de visibilit de laccesseur set soit plus restrictif que celui de
laccesseur get :
Exemple 11-22 :
class Foo {
public int Prop {
get { return 5 ; }

420

Chapitre 11 : Notions de classe et dobjet


private set { }
}
protected string this[int index] {
private get { return "hello" ; }
set { }
}
}

Cette possibilit nest pas exploitable sur les accesseurs add et remove dun vnement.

Remarque sur les membres privs


Puisque les membres privs dune classe A ne sont accessibles quau sein de la classe, cela signifie
quune instance I1 de la classe A peut accder aux membres privs dune instance I2 de la classe
A. Soyez conscient de cette possibilit. En eet ceci na rien de logique et dcoule dun choix
arbitraire fait lors de la spcification du langage C  . Ce choix nest pas forcment le mme dans
dautres langages orients objet tel que Smalltalk par exemple.

Remarque sur les niveaux de visibilit du langage IL


Tout programme crit en langage C  se compile en un programme crit en langage IL. Il se
trouve que le langage IL prsente un sixime niveau de visibilit qui nest pas support par C  .
Ce niveau de visibilit est interne ET protg. Pour saisir son eet, imaginez que le mot cl C 
internal protected se compile en un niveau de visibilit interne OU protg. Si nous lavions
reprsent dans le tableau prcdent, seul les deux premires lignes auraient t coches pour
la colonne de ce sixime niveau de visibilit.

Le mot-cl this
C++ C  Comme en C++, le mot-cl this existe en C  . En C  , ce mot-cl, utilisable dans
les mthodes non statiques, est une rfrence (et non un pointeur comme en C++) vers lobjet
courant.
C  Dans toutes les mthodes non statiques, de toutes les classes, C  permet dutiliser le motcl this. Ce mot-cl est une rfrence vers lobjet sur lequel opre la mthode courante. Le motcl this ne peut donc tre utilis dans les mthodes statiques.
En C  , le mot-cl this permet des arguments dune mthode dinstance et des champs (ou
proprits) dinstances de la classe davoir des noms similaires. Ainsi, dans le corps de la mthode, il faut faire prcder le nom en question par this pour dsigner la variable globale au
lieu de la variable locale (i.e le champ au lieu de largument). Par exemple :
Exemple 11-23 :
class Foo {
string str ; // Champ non statique de la classe.
void fct( string str ){ // M
ethode non statique.
this.str = str ; // Le champ str est
egale `
a largument str.
}
}

Construction des objets

421

Une autre utilit du mot-cl this est de communiquer une rfrence de lobjet courant
dautres mthodes, ventuellement dautres classes. Ce type dutilisation tmoigne en gnral
dune architecture objet volue. Par exemple, le design pattern (Gof) nomm communment
visiteur utilise cette facilit :
Exemple 11-24 :
class Foo {
void fct(){ // Methode non statique.
fct2(this);
}
static void fct2( Foo f ){
// Travaille avec lobjet r
ef
erenc
e par f...
}
}
En outre, le mot-cl this est utilis par la syntaxe des indexeurs dcrite page 409. Le mot-cl this
est aussi utilisable au sein des mthodes non statiques des structures. Bien quune structure soit
un type valeur, le mot-cl this reprsente bien linstance courante. Ceci est bien une facilit C 
car il ny a pas dopration de boxing ralise et donc pas de rfrence.
Pour ceux qui sont intresss par le langage IL, sachez quen IL, la rfrence this est passe
comme largument zro une mthode dinstance, dcalant ainsi dune position dans la liste
les autres arguments.

Construction des objets


Dclaration des constructeurs
C++ C  Le langage C  na pas la syntaxe C++ dinitialisation de champs, qui permet daccomplir cette tche immdiatement aprs le prototype du constructeur (ctor(int Champs) :
m_Champs(Champs) {}).
Le compilateur C  est moins permissif et impose que tous les champs de type valeur soient
initialiss avant la fin de lexcution dun constructeur.
Nous verrons que la syntaxe de passage darguments au constructeur dune classe de base,
partir dun constructeur dune classe drive, est dirente par rapport celle du C++.
Nous verrons quil peut exister en C  un constructeur de classes, appel lors du chargement de
la classe dans un domaine dapplication. Cest une possibilit qui nexiste pas en C++.
La facilit de constructeur de transtypage du C++ nexiste pas en C  . On peut videmment
construire un objet de classe A partir dun autre objet de classe B (en dfinissant un constructeur de la classe A qui prend un objet de B en paramtre) mais la syntaxe de la cration de A
par constructeur de transtypage :
explicite A objA = (A)objB;
ou implicite A objA = objB;
de C++ nexiste pas en C  . Il faut crire :
A objA=new A(objB).

422

Chapitre 11 : Notions de classe et dobjet

Toutefois la surcharge des oprateurs de transtypage est possible en C  , ce qui rend quand mme
les exemples possibles.
C  Une mthode est automatiquement appele lorsque lobjet est construit. On appelle
ce type de mthode un constructeur (ctor en abrg). En C  , syntaxiquement, une mthode
constructeur porte le nom de la classe, et ne retourne rien (mme pas le type void). Il peut y
avoir :

Aucun constructeur : dans ce cas le compilateur fournit automatiquement un constructeur


par dfaut, qui naccepte aucun argument. Un tel constructeur est public.

Un seul constructeur : dans ce cas, ce sera toujours lui qui sera appel. Le compilateur ne
fournit pas de constructeur par dfaut.

Plusieurs constructeurs : dans ce cas ils dirent selon leurs signatures, le constructeur est
alors une mthode surcharge. Le compilateur ne fournit pas de constructeur par dfaut.

Pour rsumer, partir du moment o il y a au moins un constructeur, le compilateur ne fournit


plus de constructeur par dfaut dans le cas dune classe. Ceci est illustr dans les exemples de la
prochaine section.
Dans le cas dune structure cest dirent puisquil est interdit de dfinir un constructeur sans
argument. Le constructeur par dfaut du compilateur est donc toujours accessible. Ceci est illustr dans les exemples de la prochaine section.
Lorsquun constructeur retourne, il est impratif que tous les champs non statiques de type
valeur de la classe aient t initialiss dune manire ou dune autre.
Les constructeurs se plient aux mmes rgles de niveau de visibilit que les autres mthodes. Il
est lgitime de se demander quoi sert un constructeur priv ou protg, i.e un constructeur
inaccessible hors de sa classe. Ce type dutilisation tmoigne en gnral dune architecture objet
volue. Par exemple, le design pattern (Gof) nomm communment singleton utilise cette facilit. Une autre utilisation dun constructeur priv se fait dans une classe qui na que des membres
statiques. Il ny a alors pas lieu dinstancier une telle classe. Sachez cependant que C  2 introduit
la possibilit de dclarer une classe comme statique, ce qui annule lintrt dune telle pratique.
La dclaration dun constructeur dune classe drive peut appeler un constructeur dune classe
de base.

Accs un constructeur lors de la construction dun objet


C++ C  Contrairement au C++, en C  lors de la construction dun objet avec loprateur
new, il est impratif de mettre une paire de parenthses pour signaler explicitement que lon
veut appeler le constructeur sans argument. Nous prcisons que C++ ne fait pas la dirence
entre ces deux syntaxes.
C  Un constructeur est appel la cration de chaque objet. Voici un exemple dans le cas
dune structure :
Exemple 11-25 :
struct Article {
public int Prix ;
public Article(int Prix) { this.Prix = Prix ; }

// ctor 1

Destruction des objets

423

public Article(double Prix) { this.Prix = (int)Prix ; }// ctor 2


}
class Program {
static void Main() {
Article a ; // Appel au ctor par d
efaut de la structure.
Article b = new Article(6) ; // Appel au ctor 1.
Article c = new Article(6.3) ; // Appel au ctor 2.
}
}
Voici un exemple dans le cas dune classe :
Exemple 11-26 :
class Article {
public int Prix ;
public Article(int Prix) { this.Prix = Prix ; }
// ctor 1
public Article(double Prix) { this.Prix = (int)Prix ; } // ctor 2
public Article() { Prix = 0 ; }
// ctor 3
}
class Program {
static void Main() {
Article a = new Article() ; // Appel au ctor 3.
Article b = new Article(6) ; // Appel au ctor 1.
Article c = new Article(6.3); // Appel au ctor 2.
}
}
Le mot-cl new est requis pour appeler un constructeur dun type rfrence. Pour un type valeur,
le mot-cl new nest requis que pour appeler un constructeur avec arguments. Si vous nutilisez
pas le mot-cl new, le constructeur par dfaut est automatiquement appel.

Destruction des objets


Destructeur, finaliseur et la mthode Finalize()
C++ C  La notion de ramasse-miettes est trangre la norme C/C++. Il existe bien
quelques implmentations de ce type dalgorithme pour C++ mais dans le monde Microsoft,
elles sont peu utilises.
Les destructeurs, si utiliss et si sujets controverses en C++ (exceptions lances dans un destructeur, destructeur virtuel...) existent toujours en C  . Cependant le dveloppeur ne matrise
absolument pas le moment de lappel dun destructeur.

C  Pour une bonne comprhension de la prsente section, il faut avoir assimil la section concernant le ramasse-miettes en page 116. Par rapport C++ nous parlerons de finaliseur plutt que de destructeur.
Dans la section concernant le ramasse-miettes, nous expliquons quil existe un thread allou et
gr par le CLR qui est ddi lappel des finaliseurs des objets. Le finaliseur dune classe est

424

Chapitre 11 : Notions de classe et dobjet

la rcriture de la mthode Finalize() de la classe Object. Les structures nadmettent pas de


finaliseurs. Le CLR considre quune classe qui ne rcrit pas cette mthode na pas de finaliseur. Nous avons vu que le finaliseur dun objet est excut aprs sa collecte par le ramassemiettes. La mmoire dtenue pour stocker ltat de lobjet sera libre la prochaine collecte.
En consquence, les objets dont la classe admet un finaliseur survivent une collecte de plus
que les autres. En outre vous ne matrisez ni lidentit du thread qui invoque les finaliseurs, ni
le moment durant lequel ils sont invoqus. Les objets avec finaliseurs sont donc globalement
plus coteux et moins pratiques utiliser.
Le compilateur C  interdit la rcriture directe de la mthode Finalize() de la classe Object.
Vous tes contraint dutiliser la syntaxe du destructeur qui reprend le nom de la classe prfixe par le caractre tilde . Le programme suivant illustre cette syntaxe. Ce programme cre
une instance de la classe Article, puis dtruit la seule rfrence vers cet objet puis contraint le
ramasse-miettes eectuer une collecte :
Exemple 11-27 :
using System.Threading ;
public class Article {
public int m_Prix ;
public Article(int prix) { this.m_Prix = prix ; }
// Constructeur.
~Article() { // Finaliseur d
efini avec la syntaxe du destructeur.
System.Console.WriteLine( "Thread#{0} Thread finaliseur.",
Thread.CurrentThread.ManagedThreadId );
}
}
class Program {
static void Main() {
System.Console.WriteLine( "Thread#{0} Thread principal.",
Thread.CurrentThread.ManagedThreadId ) ;
Article a = new Article(300) ;
a = null; // Le nouvel objet Article nest plus r
ef
erenc
e.
System.GC.Collect(); // Force une collecte du ramasse-miettes.
System.GC.WaitForPendingFinalizers();
}
}
Ce programme ache :
Thread#1 Thread principal.
Thread#2 Thread finaliseur.
Nous verrons un peu plus loin que le code dun finaliseur ne devrait contenir que des instructions pour librer des ressources non gres. Il est notamment dangereux daccder un objet
partir du finaliseur parce quil se peut que cet objet ait dj t finalis. En eet, le thread ddi
du ramasse-miettes invoque les finaliseurs dans un ordre imprvisible.
Enfin, en page 129 nous dcrivons la notion de finaliseur critique qui permet dajouter des garanties quant la fiabilit de la libration des ressource.

Destruction des objets

425

Linterface IDisposable et sa mthode Dispose()


C++ C  Cette technique vise minimiser les problmes dus au non dterminisme du
ramasse-miettes de C  et na donc aucun quivalent en C++.
C  Il existe une technique pour pouvoir appeler automatiquement une certaine mthode
sur un objet, quand on dcide que lon naura plus besoin de ce dernier. Comprenez bien que
cette technique nest quune aide aux dveloppeurs et ne modifie en aucun cas le comportement du ramasse-miettes. Concrtement il faut que la classe implmente linterface System.
IDisposable qui na quun seul membre, la mthode Dispose() :
Exemple 11-28 :
using System.Threading ;
public class Article : System.IDisposable {
public int m_Prix ;
public Article(int prix) { this.m_Prix = prix ; } // constructeur
public void Dispose() {
System.Console.WriteLine("Thread#{0} dispose.",
Thread.CurrentThread.ManagedThreadId ) ;
}
}
class Program {
static void Main() {
System.Console.WriteLine("Thread#{0} Thread principal.",
Thread.CurrentThread.ManagedThreadId ) ;
Article a = new Article(300) ;
try {
// Ici, vous pouvez utiliser lobjet a
// cest sa zone de validit
e.
}
finally{
a.Dispose() ;
}
}
}
Ce programme ache :
Thread#1 Thread principal.
Thread#1 dispose.
Notez bien que le client doit appeler la mthode Dispose() partir dun block finally pour
viter quune exception compromette lappel Dispose().
Le langage C  fournit une syntaxe avec le mot cl using pour invoquer implicitement la mthode Dispose() au sein dun block try/finally. Ainsi, la mthode Main() du programme
prcdent peut tre rcrite comme suit :

426

Chapitre 11 : Notions de classe et dobjet

Exemple 11-29 :
...
static void Main() {
System.Console.WriteLine("Thread#{0} Thread principal.",
Thread.CurrentThread.ManagedThreadId ) ;
Article a = new Article(300) ;
using(a){
// Ici, vous pouvez utiliser lobjet a
// cest sa zone de validit
e.
}// Cette accolade provoque lappel automatique de a.Dispose().
// Elle signe la fin de la zone de validit
e de a.
}
...
ATTENTION : lutilisation du mot-cl using prsente ci-dessous na rien voir avec les espaces
de noms et les alias.
Notez quune zone de validit peut tre commune plusieurs objets, de mmes classes ou de
classes direntes :
Exemple 11-30 :
...
static void Main() {
System.Console.WriteLine("Thread#{0} Thread principal.",
Thread.CurrentThread.ManagedThreadId ) ;
Article a = new Article(300) ;
Article b = new Article(400) ;
using(a) using(b){
// Ici, eventuellement utilisation des objets a et b.
}// Fin de la zone de validit
e des objets a et de b.
}
...
Les mthodes Dispose() seront appeles dans lordre inverse de la dclaration des using laccolade de la fin de la zone de validit (ici b.Dipose() puis a.Dispose() ).
Pour des raisons de smantique de nom de mthode, vous pouvez tre tent dutiliser une
mthode par exemple nomme Close() au lieu dimplmenter linterface IDisposable et sa
mthode Dispose(). Il vaut mieux viter cette pratique car le simple fait dimplmenter linterface IDispose est un moyen standard dexposer vos clients quils doivent utiliser la mthode
Dispose(). Il existe mme des analyseurs de code capables de dtecter que Dispose() nest pas
appele sur un objet dont la classe implmente IDisposable.
Si vous optez cependant pour lexistence dune mthode Close() il faut encapsuler lappel
cette mthode dans la mthode Dispose(), certaines classes .NET le font. Il est alors important
de bien documenter cela afin dviter de voir appeler successivement Close() et Dispose().
Parfois une telle pratique peut provoquer la leve dexceptions.
Lorsquune classe implmente linterface IDisposable il est conseill dinterdire lappel toutes
les autres mthodes sur un objet qui a t dispos en lanant une exception de type System.
ObjectDisposedException. Pour laisser une plus grande marge de manuvre aux clients de

Destruction des objets

427

votre classe, il est aussi conseill de permettre dappeler plusieurs fois la mthode Dispose()
sur un mme objet. Bien entendu seul le premier appel aura un eet. Avec ces rgles, la classe
Article peut tre rcrite comme ceci (notez le recours un champ priv boolen) :
Exemple 11-31 :
public class Article : System.IDisposable {
public int m_Prix ;
public Article(int prix) { this.m_Prix = prix ; }
private bool m_bDisposed = false;
public void Fct() {
if (m_bDisposed)
throw new System.ObjectDisposedException("Nom de lobjet");
// Ici le corps de Fct().
}
public void Dispose() {
if (!m_bDisposed){
m_bDisposed = true;
// Ici liberation des ressources.
}
}
}
Limplmentation prcdente de Article a une faiblesse. Dans une application multi-threaded,
il se peut que le test sur m_bDisposed dans une mthode ait lieu entre le test dans la mthode
Dispose() et laectation true dans la mthode Dispose(). Pour la mme raison, il se peut
aussi que le code de libration des ressources de la mthode Dispose() soit appele deux fois
par deux thread dirents. Pour pallier ce problme sans rendre la classe toute entire threadsafe vous pouvez par exemple synchroniser laccs m_bDisposed comme ceci :
Exemple 11-32 :
public class Article : System.IDisposable {
public int m_Prix ;
public Article(int prix) { this.m_Prix = prix ; }
private object m_SyncRootDisposed = new object();
private bool m_bDisposed = false ;
public void Fct() {
lock ( m_SyncRootDisposed )
if ( m_bDisposed )
throw new System.ObjectDisposedException("Nom de lobjet") ;
// Ici le corps de Fct().
}
public void Dispose() {
bool bOldDisposedState = true;
lock ( m_SyncRootDisposed )
if ( !m_bDisposed ) {
bOldDisposedState = false;
m_bDisposed = true ;

428

Chapitre 11 : Notions de classe et dobjet


}
if ( !bOldDisposedState ) {
// Ici liberation des ressources.
}
}
}

Conseils dutilisation des finaliseurs et de IDisposable


Il est lgitime de se demander pourquoi et quand vous devez aubler vos classes avec un finaliseur et/ou avec une mthode Dispose(). La rponse en ce qui concerne le finaliseur est simple :

Toutes les classes qui maintiennent une ou plusieurs ressources non gres (telle quun
handle vers un objet Windows par exemple) doivent avoir un finaliseur qui libre ces ressources. On peut ajouter quil est essentiel quune telle classe doit toujours garder ses ressources non gres prives. Ces deux conditions permettent de garantir que les ressources
non gres seront libres. En outre, seules les classes doivent tre habilites maintenir
des ressources non gres puisque les structures nont pas de finaliseur.
La remarque prcdente implique quune classe qui ne maintient que des ressources gres na
pas besoin de finaliseur. En eet, il ne servirait alors rien dassigner les champs de types rfrence nul car un finaliseur est excut strictement entre deux collectes du ramasse-miettes et
son excution implique la perte des rfrences contenues dans les champs de lobjet concern.
On peut tre tent dimplmenter un finaliseur dans une classe qui maintient des rfrences vers
des objets gres qui ont une mthode Close() ou Dispose() (comme une connexion une base
de donnes par exemple). En fait, ce genre de classe maintient directement ou indirectement
par lintermdiaire dautres classes des ressources non gres. Lappel la mthode Close() ou
Dispose() ne permet donc que danticiper le moment o ces ressources non gres seront libres. Il ne sert donc rien dinvoquer ces mthodes dans un finaliseur. Il faut les invoquer
avant, partir dune mthode Dispose(). Et ceci soulve une autre question : quand doit-on
implmenter linterface IDisposable ?
Une partie de la rponse est simple : si votre classe maintient des rfrences vers des objets dont
la dure de vie est la mme que celle de ses instances et dont limplmentation prsente une
mthode telle que Dispose() ou Close(), il faut quelle implmente une mthode Dispose()
qui appelle les mthodes Dispose() et Close() de ces objets. Si votre classe ne rpond pas au critre cit, la dcision dimplmenter ou non linterface IDisposable est plus complique puisque
vous tes face un dilemme. Dun cot il est bon davoir une mthode Dispose() pour mettre
nul les rfrences que vous maintenez vers dautres objets grs de faon augmenter leurs
chances dtre librs lors de la prochaine collecte. Dun autre cot implmenter linterface
IDisposable complexifie votre code ainsi que celui de vos clients. Le choix doit donc se faire en
fonction des besoins en mmoire de vos applications et de la taille des objets grs rfrencs.
Lorsquune de vos classes implmente un finaliseur, vous pouvez tre tent dimplmenter aussi
linterface IDisposable pour permettre au client de librer les ressources (gres et non gres)
au plus tt. Dans ce cas nous vous conseillons dappliquer les rgles suivantes :

Appliquer les rgles nonces pour implmenter IDisposable.

Destruction des objets

429

Implmentez une mthode protected virtual void Dispose(bool bDisposeManagedRes). Appelez cette mthode partir de la mthode Dispose() avec le paramtre bDisposeManagedRes gal true. Appelez cette mthode partir du finaliseur avec le paramtre
bDisposeManagedRes gal false.

Dans la mthode Dispose(bool bDisposeManagedRes), librez les ressources non gres et


ne librez les ressources gres que si bDisposeManagedRes est gal true.

Dans la mthode Dispose(), aprs avoir appel la surcharge Dispose(bool bDisposeManagedRes) appelez GC.SuppressFinalize(this). Vous indiquez ainsi au ramasse-miettes quil
naura pas besoin dappeler le finaliseur de cet objet.

Si vous appliquez ces conseils, vous soulagez ainsi le thread finaliseur de la libration inutile des
ressources gres, vous vous protgez des oublis de lappel `
a Dispose() de la part de vos clients
et vous optimisez lexcution des clients qui noublient pas dappeler Dispose(). Voici une classe
Foo qui suit ces conseils (sans la synchronisation des accs m_bDisposed) :
Exemple 11-33 :
public class Foo : System.IDisposable {
public Foo() {
// Ici eventuellement allocation des ressources.
}
private bool m_bDisposed = false;
public void Fct() {
if ( m_bDisposed )
throw new System.ObjectDisposedException("Nom de lobjet");
// Ici le corps de Fct().
}
public void Dispose() {
Dispose(true);
System.GC.SuppressFinalize(this);
}
~Foo() { Dispose(false) ; }
protected virtual void Dispose(bool bDisposeManagedRes) {
if ( !m_bDisposed ) {
m_bDisposed = true;
// Ici liberer les ressources non-g
er
ees.
if ( bDisposeManagedRes ) {
// Ici liberer les ressources g
er
ees.
}
}
}
}
Dans le cas o Foo est une classe de base qui admet des classes drives qui maintiennent des
ressources, il est judicieux de rcrire la mthode Dispose(bool bDisposeManagedRes) puisque
celle-ci est virtuelle et protge. Il est intressant de remarquer que le design des classes du framework suit les rgles nonces.

430

Chapitre 11 : Notions de classe et dobjet

Les membres statiques


C++ C  En C  , il existe plusieurs petites dirences de syntaxe par rapport aux membres
statiques de C++. En C  :

Nous accdons aux membres statiques avec cette syntaxe NomDeLaClasse.NomDuMembreStatic. Nous navons pas besoin de loprateur de rsolution de porte comme en C++.

Nous ne pouvons pas accder un membre statique au moyen dune instance de la classe.

Nous pouvons avoir des classes statiques.

Au niveau conceptuel rien ne change mis part quil peut exister un constructeur statique et
des classes purement statiques.
C  Les champs, les proprits, les mthodes et les vnements dune classe ont la possibilit
dtre dclars avec le mot-cl static. Ce mot-cl signifie que le membre appartient la classe,
et non aux objets, comme cest le cas des membres non statiques. Les membres statiques sont
parfois appels membres partags, car ils sont partags par les instances de la classe. Dailleurs le
langage VB.NET utilise le mot-cl Shared qui veut dire partag en anglais la place du mot cl
C  static. Les membres non statiques sont appels membres dinstances.
Les membres dclars comme statiques subissent les mmes rgles de niveau de visibilit que
les autres membres. De plus, parmi les direntes sortes de membres, seuls les types encapsuls
et les indexeurs ne peuvent tre dfinis avec le mot-cl static.

Champs proprits et vnements statiques


Les champs, proprits ou vnements statiques dune classe, se distinguent par les caractristiques suivantes :

Ils existent indpendamment des instances de la classe, y compris si aucune instance de la


classe na encore t cr.

Ils sont partags par toutes les instances de la classe.

Ils sont accessibles dans toutes les mthodes de la classe par leurs noms, et lextrieur de la
classe (selon leurs niveaux de visibilit) par la syntaxe : NomDeLaClasse.NomDuChampStatic

Les champs peuvent tre initialiss directement durant leurs dclarations au sein de la classe.
Comme pour linitialisation des champs dinstances, linitialisation des champs statiques est un
sujet subtil. En eet, lorsque la classe est cre, les champs statiques sont dabord initialiss
leurs valeurs par dfaut. Puis ils sont initialiss avec leur valeur dinitialisation (pour ceux qui
en ont une), dans leur ordre dapparition dans la classe. Enfin le constructeur statique (voir plus
loin) est appel. Tout ceci permet de faire des rfrences croises comme ci-dessous, bien que ce
soit fortement dconseill :

Les membres statiques

431

Exemple 11-34 :
public class Program {
static int a = b + 2 ; // a r
ef
erence
static int b = a + 3 ; // b r
ef
erence
public static void Main() {
System.Console.WriteLine("a = {0}",
System.Console.WriteLine("b = {0}",
}
}

b pour son initialisation.


a pour son initialisation.
a) ;
b) ;

// a = 2
// b = 5

Le cas de figure suivant porte aussi confusion, dailleurs le rsultat est dirent du prcdent.
En eet, la construction de la classe CB se fait au milieu de la construction de la classe CA, puisque
CA est appele en premier mais CA utilise CB !
Exemple 11-35 :
public class CA {
static public int a = CB.b + 2 ;
}
public class CB {
static public int b = CA.a + 3 ;
}
public class Program {
public static void Main() {
System.Console.WriteLine("CA.a = {0}", CA.a) ;
System.Console.WriteLine("CB.b = {0}", CB.b) ;
}
}

// CA.a = 5
// CB.b = 3

Mthodes statiques
Les mthodes statiques se distinguent par les caractristiques suivantes :

Une mthode statique na pas accs aux mthodes, champs, proprits et vnements non
statiques de la classe.

Une mthode statique nest pas accessible partir dune rfrence vers une mthode de la
classe,

Une mthode statique est accessible dans toutes les mthodes (statiques ou non) de la classe
par son nom, et lextrieur de la classe (si son niveau de visibilit le permet) par la syntaxe :
NomDeLaClasse.NomDeLaMethodeStatique()

Une mthode statique ne peut utiliser le mot-cl this expliqu dans la section prcdente.

Constructeur statique (ou constructeur de classe)


C  autorise la dfinition dun constructeur statique parfois nomm aussi constructeur de classe
ou cctor (pour class constructor). Comme toutes les mthodes statiques, cette mthode na accs
quaux membres statiques et peut servir, par exemple, les initialiser. La syntaxe est la mme
que celle dun constructeur sans argument, avec le mot-cl static au dbut. Un constructeur
statique na pas de niveau de visibilit. En outre, ni le moment exact de lappel ce constructeur

432

Chapitre 11 : Notions de classe et dobjet

ni quel thread ralise cet appel ne sont fixs par la spcification du langage C  . Cependant, les
points suivant de la spcification C  peuvent vous aider valuer ce moment :

Le constructeur statique est appel par le CLR lorsque celui-ci charge les mtadonnes de
types de la classe concerne, dans le domaine dapplication courant.

Le constructeur statique doit tre appel avant toute cration dinstance de la classe.

Le constructeur statique doit tre appel avant quun membre statique de la classe soit accd.

Le constructeur statique doit tre appel aprs que les champs statiques initialiss explicitement dans le code, soient initialiss.

Le constructeur statique dune classe charge par le CLR doit tre appel au moins une fois
durant lexcution du programme.

Vous avez la possibilit de forcer lappel au constructeur dune classe avec la mthode static
void RunClassConstructor(type t) de la classe System.Runtime.CompilerServices.RuntimeHelpers. Naturellement, lexcution na lieu que si le constructeur de classe concern na pas
dj t appel.
Voici une classe qui compte le nombre dinstances couramment valides, ce nombre tant initialis dans son constructeur statique :
Exemple 11-36 :
public class Article {
static int NbArticles ;
// Un champ statique.
static Article() {
// Le constructeur statique.
NbArticles = 0;
}
int Prix = 0 ;
// Un champ priv
e non statique.
public Article(int Prix) { // ctor
this.Prix = Prix ;
NbArticles++ ;
}
~Article() { NbArticles-- ; }// finaliseur
}

Classes statiques
C  permet la dfinition de classes statiques. Une classe statique ne peut avoir que des membres
statiques. Nayant pas de constructeur dinstance, une classe statique ne peut tre instancie. En
outre on ne peut driver dune classe statique. Notez quune structure ne peut tre statique.
Pour dfinir une classe statique il sut dutiliser le mot-cl static dans sa dfinition :
Exemple 11-37 :
static class Program {
static void Main() { }
}

Surcharge des oprateurs

433

Surcharge des oprateurs


C++ C  En C  la surcharge des oprateurs arithmtiques prsente beaucoup de dirences avec le C++. On peut dj dire que la surcharge doprateurs est plus simple, moins
permissive mais moins puissante en C  quen C++.
Tous dabord, de la quarantaine des oprateurs surchargeables en C++, il nen reste quune vingtaine en C  . Ceux qui nexistent pas en langage C  sont :

les oprateurs += %= etc ;

les oprateurs && || ;

loprateur daectation = ;

loprateur modulo % ;

les oprateurs dallocation/dsallocation new delete[] etc ;

les oprateurs trs proches du langage -> ->* , ;

loprateur dappel dune fonction () qui permettait les foncteurs (fonctions objets) sur
laquelle repose une grande partie de la STL. Cette notion de foncteur reste cependant implmentable dun autre faon comme nous lexpliquons en page 591.

Loprateur dindexation [] a donn naissance en C  aux indexeurs prsents un peu plus


haut. Les oprateurs de transtypages existent en C  et sont dcrits dans la prsente section.
La syntaxe de dclaration des oprateurs a aussi chang. En eet en C++ une surcharge peut tre
interne la classe (la mthode de surcharge de loprateur est non statique et dans la classe) ou
externe (la mthode de surcharge de loprateur est une fonction amie de la classe). En C  la
mthode de surcharge de loprateur est une fonction statique de la classe obissant certaines
rgles nonces ci-dessous. Notez quen C  la notion damiti du C++ nexiste pas.
C  Pour prsenter les oprateurs surchargeables en C  , nous allons les classer en trois catgories :

Les oprateurs arithmtiques + - etc.

Les oprateurs de conversion de type, aussi appels oprateurs de transtypage.

Les oprateurs de comparaison.

Surcharge des oprations arithmtiques


Le langage C  permet une classe de surcharger des oprateurs arithmtiques tels que loprateur plus + ou loprateur de ngation ! , avec le mot-cl operator. Concrtement lorsque
le compilateur C  rencontre des objets de cette classe manipuls avec un oprateur surcharg
par la classe, il appelle la mthode adquate, qui correspond loprateur.
Une mthode qui surcharge un oprateur est statique. linstar de toutes les mthodes statiques, elle est sujette aux niveaux de visibilit. Les oprateurs arithmtiques que lon peut redfinir se classent en deux catgories :

Les oprateurs unaires, qui nagissent que sur un seul oprande. La mthode statique surchargeant un oprateur unaire naccepte quun argument du type de sa classe. De plus elle
renvoie aussi une valeur du type de sa classe.

434

Chapitre 11 : Notions de classe et dobjet


Les oprateurs binaires qui agissent sur deux oprandes. La mthode statique surchargeant
un oprateur binaire a deux arguments : le premier du type de la classe et le deuxime dun
type quelconque. Elle renvoie une valeur de type quelconque.

Tous les oprateurs ne sont pas surchargeables, et cest heureux. En eet, le code devient vite plus
complexe lorsque des oprateurs sont surchargs, moins que la signification de loprateur soit
vraiment logique dans le contexte de la classe. En eet, chaque oprateur surchargeable a une signification bien connue des dveloppeurs. Ainsi loprateur + est utilis pour laddition ou la
concatnation. Il est logique de lutiliser dans une classe reprsentant des nombres (par exemple
des nombres fractionnaires, complexes ou les quaternions etc) dautres objets mathmatiques
contenus dans un ensemble avec une structure de groupe additive (matrice, vecteur, fonction,
forme etc) ou des chanes de caractres. Mais, que penser dune utilisation de cet oprateur dans
une classe reprsentant des personnes ? Cest pour cette raison que nous vous conseillons de ne
surcharger les oprateurs que lorsque cela est logique dans le contexte de la classe.
Voici la liste des oprateurs surchargeables :
Oprateurs
geables

unaires

surchar-

Oprateurs binaires surchargeables

+ -!

++ --

+ - * / % & | << >>

Certains oprateurs sont la fois binaires et unaires. Par exemple loprateur - peut la fois
rendre un oprande ngatif, ou soustraire deux oprandes.
Les oprateurs unaires dincrmentation ++ et de dcrmentation -- surchargs, provoquent lappel vers la mme mthode, quils soient postfixs ou prfixs. Rappelons que
lorsque lon utilise ces oprateurs, lordre dvaluation des oprateurs est dirent, selon leur
position par rapport loprande.
Voici un exemple dutilisation des oprateurs. Nous crons une classe Distance et une classe
Surface. En interne les valeurs sont sauves dans des doubles ou une unit reprsente un mtre
(respectivement un mtre carr). Nous surchargeons les oprateurs plus + et toile * entre
deux distances, loprateur dincrmentation ++ dun mtre sur une distance, et loprateur
de division / par une distance ou par une surface pour une surface :
Exemple 11-38 :
public class Distance {
double m_Mesure = 0.0 ;
public double Mesure { get { return m_Mesure ; }
set { m_Mesure = value ; } }
public Distance(double d) { m_Mesure = d ; }
public static Distance operator +(Distance d1, Distance d2) {
return new Distance(d1.m_Mesure + d2.m_Mesure) ;
}
public static Surface operator *(Distance d1, Distance d2) {
return new Surface(d1.m_Mesure * d2.m_Mesure) ;
}
public static Distance operator ++(Distance d) {

Surcharge des oprateurs

435

return new Distance(d.m_Mesure++) ;


}
}
public class Surface {
double m_Mesure = 0.0 ;
public double Mesure {
get { return m_Mesure ; }
set { m_Mesure = value ; }
}
public Surface(double d) { m_Mesure = d ; }
public static Distance operator /(Surface s, Distance d) {
return new Distance(s.m_Mesure / d.Mesure) ;
}
public static double operator /(Surface s1, Surface s2) {
return s1.m_Mesure / s2.m_Mesure ;
}
}
class Program {
static void Main() {
Distance d1 = new Distance(5.3) ;
Distance d2 = new Distance(2.4) ;
Distance d3 = d1 + d2 ;
Surface s1 = d1 * d2 ;
Surface s2 = d3 * d2 ;
Distance d4 = s1 / d3 ;
double dRapport = s1 / s2 ;
Distance d5 = d1++ ; // Apr
es ceci d5 mesure 6.3, d1 mesure 5.3.
Distance d6 = ++d1 ; // Apr
es ceci d6 mesure 5.3, d1 mesure 5.3.
}
}
Soyez conscient que dans ce code, nous ne testons pas la nullit potentielle des rfrences en entre des oprateurs. Nous acceptons donc le fait quune exception de type NullReferenceException
sera leve automatiquement par le CLR le cas chant.
La faon dont nous avons cod loprateur ++ dans Distance provoque un rsultat inattendu.
En eet lappel de cet oprateur retourne un nouvel objet Distance dont la mesure est celle de la
distance source incrment de un. Ce comportement est particulirement dangereux puisquen
gnral, loprateur ++ modifie lobjet sur lequel il est appel. Une faon plus logique de
coder cet oprateur est :
Exemple 11-39 :
...
public static Distance operator ++(Distance d) {
d.m_Mesure++ ; return d ;
}
...
Distance d5 = d1++ ; // Apr`
es ceci d5 mesure 6.3, d1 mesure 6.3.
Distance d6 = ++d1 ; // Apr`
es ceci d6 mesure 7.3, d1 mesure 7.3.
...

436

Chapitre 11 : Notions de classe et dobjet

Cet exemple montre bien la ncessit de coder les oprateurs dans la logique de leur utilisation
habituelle en C  . Sinon le code devient vite compltement illisible.

Oprateurs de conversion de type (de transtypage)


C++ C  Comme C++, C  autorise la dfinition doprateurs de transtypage explicites ou
implicites. La syntaxe du langage C  oblige ces oprateurs tre statiques.
En C  , les constructeurs de transtypages nexistent pas. Rappelons quen C++ les constructeurs
de transtypage et les oprateurs de transtypages ont la mme fonctionnalit. La dirence vient
du fait que les constructeurs de transtypages sont dans la classe destination et les oprateurs de
transtypages dans la classe source. Ceci peut mener une ambigut lorsque les deux formes
coexistent. Ce problme nexiste donc pas en C  .
C  Lopration de transtypage reprsente la construction dun objet instance dune classe
destination, partir dun objet instance dune classe source. C  permet de crer vos propres
oprateurs de transtypage dans la classe source, cest--dire des mthodes automatiquement appeles lorsque vous crivez :
CDest objDest = objSrc ;
Le code de ces mthodes contient principalement des recopies de champs et de proprits. Il
existe deux types doprateurs de transtypage.

Les oprateurs de transtypage implicite. Lcriture suivante sut provoquer lappel loprateur de transtypage implicite de CSrc vers CDest, dclar dans CSrc.
CDest objDest = objSrc ;

Les oprateurs de transtypage explicite. Lcriture prcdente ne sut pas provoquer lappel
loprateur de transtypage explicite de CSrc vers CDest, dclar dans CSrc. Dans ce cas,
le compilateur gnre une erreur et le dveloppeur doit alors explicitement exprimer le
transtypage comme ceci :
CDest objDest = (CDest) objSrc ;

Comme tous les oprateurs, les oprateurs de transtypage sont des mthodes statiques. Ils sont
dclars dans la classe source et ils portent le nom de la classe destination. Lutilisation de lun
des mots-cls explicit ou implicit est obligatoire.
Voici un exemple de code o un objet de classe Distance peut tre transtyp implicitement en
une variable double et explicitement en objet de classe DistanceEnti`
ere. Notez la ncessit de
tenir compte de la nullit potentielle de la rfrence en entre des oprateurs :
Exemple 11-40 :
public class Distance {
public double m_Mesure = 0.0 ;
public Distance(double d) { m_Mesure = d ; }
public static implicit operator double(Distance d) {
// Doit tenir compte du cas ou d est nulle.
if (object.ReferenceEquals(d, null))
return 0.0 ;

Surcharge des oprateurs

437

return d.m_Mesure ;
}
public static explicit operator DistanceEntiere(Distance d) {
if (object.ReferenceEquals(d, null))
// Vous pouvez aussi pr
ef
erer retourner la r
ef
erence
// nulle dans ce cas.
return new DistanceEntiere(0) ;
// Notez la necessite de construire un nouvel objet pour un
// type destination reference.
return new DistanceEntiere((int)d.m_Mesure) ;
}
}
// Distance enti`ere signifie que la distance est cod
ee sur un entier.
public class DistanceEntiere {
public int m_Mesure = 0 ;
public DistanceEntiere(int i) { m_Mesure = i ; }
}
class Program {
static void Main() {
Distance d1 = new Distance(5.3) ;
// OK le transtypage est implicite.
double dbl1 = d1 ;
// La forme explicite est aussi accept
ee.
double dbl2 = (double)d1 ;
// Erreur de compilation !! : le transtypage doit
etre explicite.
DistanceEntiere de1 = d1 ;
// OK le transtypage est explicite.
DistanceEntiere de2 = (DistanceEntiere)d1 ;
// Teste le cas o`u la ref
erence source est nulle.
Distance d2 = null ;
double dbl3 = (double)d2 ;
DistanceEntiere de3 = (DistanceEntiere)d2 ;
}
}
Il est toujours prfrable de dclarer ses oprateurs de transtypage comme explicites. Dans le cas
contraire votre code risque dtre permissif, cest--dire que le compilateur aura faire des choix.
Au mieux il dclarera une erreur car il ne saura pas quel choix faire. Au pire, il ne donnera pas
davertissements et fera le choix contraire votre attente. Par exemple imaginons quune instance de la classe Distance puisse tre convertie implicitement en une instance du type double
ou une instance de la classe string. Quel choix de transtypage le compilateur doit-il faire lorsque
lon veut acher cet objet avec la mthode Console.WriteLine(object) ?
Exemple 11-41 :
public class Distance {
public double m_Mesure = 0.0 ;
public Distance(double d) { m_Mesure = d ; }
public static implicit operator double(Distance d) {
if (object.ReferenceEquals(d, null))

438

Chapitre 11 : Notions de classe et dobjet


return 0.0 ;
return d.m_Mesure ;
}
public static implicit operator string(Distance d) {
if (object.ReferenceEquals(d, null))
return null ;
return string.Format("Distance:{0:##.##} m`
etres", d.m_Mesure) ;
}
}
class Program {
static void Main() {
Distance d1 = new Distance(5.3) ;
// Erreur de compilation : Op
erateur de transtypage ambigu
e.
// Doit-on transtyper d1 en un double ou une string ?
System.Console.WriteLine(d1);
// OK pas dambigute, mais il vaut mieux d
eclarer les op
erateurs
// de transtypage comme explicite.
System.Console.WriteLine( (string) d1);
}
}

Oprateurs de comparaison
C++ C  Comme C++, C  permet de redfinir le comportement des oprateurs de comparaison == et != .
Cependant les motivations sont direntes en C  . Par dfaut le comportement de ces oprateurs
sur des objets de type rfrence est de comparer si les rfrences rfrencent le mme objet. Or,
assez souvent, on souhaite comparer le contenu.
De plus en C  les oprateurs de comparaison ne sont pas dfinis par dfaut sur les structures,
contrairement C++.
C  Les oprateurs de comparaison sont les suivants :
Oprateur

Nom

Commentaires

==

Oprateur dgalit.

Lorsque cet oprateur est dfini, le compilateur


oblige le dveloppeur dfinir aussi loprateur
dingalit.

!=

Oprateur dingalit.

Lorsque cet oprateur est dfini, le compilateur


oblige le dveloppeur dfinir aussi loprateur
dgalit.

<=

Oprateur infrieur ou
gal.

Lorsque cet oprateur est dfini, le compilateur


oblige le dveloppeur dfinir aussi loprateur
suprieur ou gal.

Surcharge des oprateurs

439

>=

Oprateur suprieur ou
gal.

Lorsque cet oprateur est dfini, le compilateur


oblige le dveloppeur dfinir aussi loprateur
infrieur ou gal.

<

Oprateur
strictement.

infrieur

Lorsque cet oprateur est dfini, le compilateur


oblige le dveloppeur dfinir aussi loprateur
suprieur strictement.

>

Oprateur
suprieur
strictement.

Lorsque cet oprateur est dfini, le compilateur


oblige le dveloppeur dfinir aussi loprateur
strictement.

Par dfaut, si ces oprateurs ne sont pas redfinis, les rgles suivantes sont appliques selon la
nature des types des oprandes :

Si les deux oprandes sont de types primitifs ou numration, le compilateur va dabord


essayer de transtyper un des deux oprandes pour quil soit du type de lautre. Sil y parvient,
les oprateurs de comparaison vont comparer le contenu des objets.

Si les deux oprandes sont de mme type structure, le compilateur produit une erreur. La
comparaison par dfaut sur des structures nexiste pas en langage C  .

Si les deux oprandes sont de type rfrence et que loprateur de comparaison est lgalit
ou lingalit, le compilateur va dabord vrifier que les classes des deux objets rfrencs
sont les mmes. Cette condition peut tre remplie mme si les types des rfrences sont
dirents. En eet, lhritage, qui fait lobjet du prochain chapitre, permet un objet et
une rfrence vers cet objet dtre de types dirents. Si les deux objets rfrencs ont la
mme implmentation, les oprateurs de comparaison vont vrifier si cest le mme objet
qui est rfrenc ou non.

Si les deux oprandes sont de type rfrence et que loprateur de comparaison nest pas
lgalit ou lingalit, une erreur est produite par le compilateur. La comparaison par dfaut autre que lgalit/ingalit, sur des types rfrence nexiste pas en langage C  .

Si un oprande est de type valeur et lautre de type rfrence, le compilateur va produire


une erreur.

Lorsque vous dcidez de redfinir les oprateurs dgalit et dingalit, il est conseill que vous
redfinissiez aussi la mthode Equals() de la classe Object. Si vous suivez ce conseil, il faut que
les deux surcharges appellent la mthode Equals(), comme dans lexemple suivant : (plus dinformation concernant la mthode Object.Equals() sont disponibles en page 345, notamment
on y explique pourquoi la compilation du programme suivant met un avertissement quant
la mthode Object.GetHashCode()).
Exemple 11-42 :
public class Distance {
private double m_Mesure = 0.0 ;
public Distance(double d) { m_Mesure = d ; }
public override bool Equals(object obj) {
Distance d = obj as Distance;
// Verifie que lobjet auquel on se compare est bien une distance.

440

Chapitre 11 : Notions de classe et dobjet


// Par la meme occasion traite le cas o`u obj est nulle.
if( !Distance.ReferenceEquals( d , null ) )
// Comparaison du contenu.
return m_Mesure == d.m_Mesure;
return false;
}
public static bool operator ==(Distance d1, object
// Traite les cas o`u une ou les deux r
ef
erences
if( Distance.ReferenceEquals( d1 , null ) ) {
return Distance.ReferenceEquals( d2 , null )
}
return d1.Equals(d2) ;
}
public static bool operator !=(Distance d1, object
return ! (d1 == d2) ;
}

d2) {
sont nulles.
;

d2) {

}
class Program {
static void Main() {
Distance d1 = new Distance(5.2) ;
Distance d2 = new Distance(5.2) ;
Distance d3 = new Distance(7.3) ;
Distance d4 = null ;
// Toutes ces assertions sont vrais.
System.Diagnostics.Debug.Assert( d1 == d2 ) ;
System.Diagnostics.Debug.Assert( d1 != d3 ) ;
System.Diagnostics.Debug.Assert( d1 != null ) ;
System.Diagnostics.Debug.Assert( null != d1 ) ;
System.Diagnostics.Debug.Assert( !(d1 == null) ) ;
System.Diagnostics.Debug.Assert( !(null == d1) ) ;
System.Diagnostics.Debug.Assert( d4 == null ) ;
}
}
Comme vous pouvez le constater en analysant cet exemple, surcharger les oprateurs == et =!
nest pas une manipulation aise :

Il faut dabord envisager les cas o une voire les deux rfrences sont nulles. Par convention,
deux rfrences nulles doivent tre considres comme gales.

Il faut aussi envisager le cas o lautre rfrence laquelle on se compare ne rfrence pas
un objet de type Distance.

Enfin, en interne, il faut penser viter lutilisation des oprateurs == et != au risque de


provoquer une boucle infinie. Cest pour cela que nous faisons appel la mthode Object.ReferenceEquals().

La surcharge des oprateurs et le CLS


La surcharge des oprateurs est prsente en C  , principalement pour amliorer la lisibilit du
code source C  . La surcharge des oprateurs nest pas CLS Compliant. Cela veut dire que dautres

Surcharge des oprateurs

441

langages .NET ne la supportent pas et ne peuvent pas lutiliser. Aussi, si votre classe est destine tre utilise par dautres langages .NET, nous vous conseillons de prvoir des mthodes
publiques avec les mmes fonctionnalits que vos oprateurs redfinis. Par exemple si vous
surchargiez loprateur + dans une classe, nous vous conseillons de prvoir une mthode
statique Add() remplissant la mme fonctionnalit. Rappelons que si vous avez un doute quant
aux respect de votre code par rapport au CLS, vous pouvez utilisez lattribut CLSCompliant que
nous dcrivons en page 131.
Voici la liste des noms de substitution donns aux mthodes qui ont pour but dorir la mme
fonctionnalit quun oprateur redfini. Ces noms sont conseills, et le compilateur vous autorise utiliser vos propres identificateurs.
Oprateur

Nom de substitution conseill.

Oprateurs de transtypage
Transtypage implicite vers le type Xxx

ToXxx FromXxx

Transtypage explicite vers le type Xxx

ToXxx FromXxx

Oprateurs arithmtiques binaires


+

Add

Subtract

Multiply

Divide

Mod

Xor

&

BitwiseAnd

BitwiseOr

<<

LeftShift

>>

RightShift

Oprateurs arithmtiques unaires


+

Plus

Negate

++

Increment

--

Decrement
OnesComplement

442

Chapitre 11 : Notions de classe et dobjet

Not

Oprateurs de comparaison
==

Equals

!=

Compare

<=

Compare

>=

Compare

<

Compare

>

Compare

12
Hritage/drivation
polymorphisme et abstraction

Objectif : rutilisation de code


La problmatique
La loi suivante est fondamentale en programmation :

La complexit dun programme en fonction de sa taille, grandit plus vite quune fonction
linaire de sa taille.

Complexit dun programme

Cette loi est illustre par la Figure 12-1 :


Complexit dun
programme
en fonction
de sa taille

Reprsentation
de la fonction
linaire

Taille du programme

Figure 12 -1 : Complexit dun programme en fonction de sa taille

444

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

Un programme deux fois plus gros quun autre est donc plus que deux fois plus long crire,
tester et maintenir.
Cette loi est empirique. Le travail de du concepteur dun programme consiste ce que la complexit reste aussi proche que possible de la fonction linaire. Le mcanisme de drivation ou
dhritage est un point important pour laider dans cette tche mais nous verrons en fin du
prsent chapitre que ce nest pas le seul.
Concrtement, on part du constat que dans un programme, direntes classes ont des fonctionnalits identiques :

Dans un programme de gestion de personnel dune entreprise, il peut y avoir une classe
pour les secrtaires, une classe pour les techniciens, une classe pour les cadres. Toutes ces
classes ont ceci de commun quune instance reprsente un employ, avec les attributs Nom,
Age, Adresse, Salaire et les mthodes Evalue(), ModifieLesHoraires(), Augmente().

Dans un programme de dessin, il peut y avoir une classe pour les cercles, une classe pour
les rectangles, une classe pour les triangles. Toutes ces classes ont ceci de commun quune
instance a les attributs CouleurDuTrait, TailleDuTrait et les mthodes Dessine(), Translation(), Rotation(), Grossi().

Dans un programme qui communique avec dirents protocoles de communication,


chaque point de communication a, indpendamment du protocole sous-jacent, les attributs
NbOctetsEnvoyes/Recus, DateDeCreation et la mthode EnvoieUnStream().

Une solution : lhritage


Lide de la rutilisation est de cerner ces similitudes, et de les runir dans une classe que lon
nomme classe de base (par exemple Employe, FigureGeometrique ou PointDeCommunication).
Une classe drive est une classe qui hrite des membres dune classe de base. Concrtement si
la classe Technicien hrite de la classe Employe, la classe Technicien hrite des champs Nom et
Age et des mthodes Evalue() et ModifieLesHoraires(). On dit aussi que la classe drive est
une spcialisation de la clase de base et quune classe de base dune classe drive est une super
classe de cette classe drive.
Une des dicults dans la conception dun programme est de cerner correctement les similitudes entre classes, et ce le plus tt possible dans le cycle de dveloppement. Lautre dicult
majeure est de dfinir comment les objets interagissent entre eux. Voici quelques phrases se
dire pour confirmer lintuition.

Un Technicien est un employ, un Rectangle est une figure gomtrique, une socket est
un point de communication.

Un Technicien a un nom, un Rectangle a une couleur de trait, une socket a un nombre


doctets envoys depuis sa cration.

Un Technicien peut tre valu, un Rectangle peut tre grossi, une socket peut tre utilise pour envoyer un stream.

Lhritage dimplmentation

445

Lhritage dimplmentation
La syntaxe
C++ C  La syntaxe pour lhritage dimplmentation est la mme en C  et C++. Conceptuellement il existe une grosse dirence entre C++ et C  . C  ne supporte pas lhritage multiple. Une classe C  ne peut driver que dune seule classe de base. Nous verrons que ce manque
est en partie compens par la formalisation, en C  , du concept, plus simple dinterface et dimplmentation de plusieurs interfaces par une classe.
C  En C  , la syntaxe pour indiquer que la classe Technicien drive de la classe Employe est
la suivante :
// La classe Technicien herite de la classe Employe.
class Technicien : Employe { ... }
Une classe ne peut driver que dune seule classe de base. En langage objet, on dit que C 
supporte lhritage dimplmentation simple mais pas lhritage dimplmentation multiple. Ce nest
pas le cas de tous les langages orients objet. Les langages C++ et EIFFEL supportent lhritage
dimplmentation multiple alors que le langage Java ne le supporte pas.
En C  , si une classe C drive dune classe B, rien nempche la classe B de driver dune classe A.
On dit que C drive indirectement de A. En outre la classe B est la fois une classe drive et une
classe de base.

Les niveaux de visibilit protg et interne protg


C++ C  Comme C++, C  permet de protger des membres dune classe de base. La notion
de protection dun membre est identique entre les deux langages. Le mot-cl est aussi le mot
protected, et la seule dirence est quen C  il faut crire ce mot-cl pour chaque membre
protg. C  ajoute la notion de membre interne protg (mots-cls internal protected).
C  Rappelons les dfinitions des niveaux de visibilit protg et interne protg que nous
prsentons en page 418 :

Le niveau de visibilit protg : mot-cl protected.


Un membre qui a le niveau de visibilit protg et qui est dfini dans un type T est accessible
seulement partir du code contenu dans T (types encapsuls de T compris) ainsi qu partir
du code des types drivs de T. Ceci reste valable pour le code des types drivs de T dfinis
dans dautres assemblages.

Le niveau de visibilit interne protg : mot-cl internal protected.


Un membre qui a le niveau de visibilit interne protg et qui est dfini dans un type T est
accessible partout dans le code de lassemblage courant. Cependant, si le type T est utilis
dans dautres assemblages le membre qualifi dinterne protg nest visible que dans le
code des mthodes des types drivant de T. Notez que cest le seul cas o deux mots-cls
peuvent tre combins pour dclarer un niveau de visibilit.

446

Chapitre 12 : Hritage/drivation polymorphisme et abstraction


BergerAllemand
Humain
Mammifre
Animal

Labrador
Chien

Reptile

Caniche
Lzard

Figure 12 -2 : Exemple de schma de drivation

Schma de drivation
Une classe de base peut tre aussi une classe drive. Tout ceci peut tre illustr dans un schma
de drivation. Par exemple :
Il existe des langages de modlisation pour les schmas objets, le plus courant tant le langage
UML (Unified Modelling Langage). Nous avons utilis la notation du diagramme de classes de ce
langage pour reprsenter le schma de la Figure 12-2. Comme vous le constatez, en UML, une
flche blanche pointe vers une classe de base, et se divise vers les classes drives.
Visual Studio 2005 prsente la possibilit de visualiser un diagramme de classes ressemblant un
diagramme de classes UML. Vous pouvez avoir accs cette possibilit en cliquant droit sur un
projet et en slectionnant View Class Diagram. Un fichier XML dextension .cd est alors insr
au projet. Il contient la mise en forme de votre diagramme. Voici par exemple un extrait de la
hirarchie des classes de contrles de Windows Forms prsentes en page 679 :

Figure 12 -3 : Diagramme de classe de Visual Studio 2005

Appel aux constructeurs dune classe de base


C++ C  Comme C++, C  permet dappeler explicitement un constructeur de la classe de
base dans chaque constructeur dune classe drive. Comme C++, si lappel nest pas explicite-

Lhritage dimplmentation

447

ment dclar, le compilateur appelle le constructeur sans argument de la classe de base sil y en
a un, sinon il y a une erreur de compilation.
Cependant, la syntaxe du langage C  est lgrement dirente de celle du langage C++. Comme
il ny a pas dhritage dimplmentation multiple en C  , la classe de base est unique. Donc il nest
pas ncessaire de prciser le nom de la classe de base. Dans ce contexte, en C  le nom de la classe
de base est remplac par le mot-cl base.
C  En C  , chaque constructeur dune classe drive appelle un constructeur de la classe de
base. Cet appel peut tre implicite, cest--dire quil ny a pas de code qui montre clairement que
cet appel doit se faire. Dans ce cas cest le constructeur sans argument de la classe de base qui est
appel. Si la classe de base na pas de constructeur sans argument, alors une erreur est produite
la compilation. Rappelons les principes suivants :

Si une classe ne dclare pas de constructeur : le compilateur fournis automatiquement un


constructeur par dfaut, qui naccepte pas dargument.

Si une classe dclare un seul constructeur : il ny a quun seul constructeur, celui-ci. Le compilateur ne fournit pas de constructeur par dfaut.

Si une classe dclare plusieurs constructeurs : ils dirent selon leurs signatures et on dit que
le constructeur est surcharg. Le compilateur ne fournit pas de constructeur par dfaut.

Lappel au constructeur de la classe de base peut tre explicite partir dun constructeur
dune classe drive. Pour cela le mot-cl base doit tre utilis avec la mme syntaxe que dans
lexemple suivant :
Exemple 12-1 :
public class Employe {
string m_Nom ;
short m_Age ;
// Constructeur acceptant comme liste darguments : string, short
public Employe(string Nom, short Age) {
m_Nom = Nom ;
m_Age = Age ;
}
// Constructeur sans arguments.
public Employe() {
m_Nom = "n/a" ;
m_Age = 0 ; // <- Pas necessaire, un short est initialis
e `
a 0.
}
}
class Technicien : Employe { // Technicien h
erite de Employe.
string m_Competences ;
// Appel au constructeur de la classe de base acceptant comme liste
// darguments : string, short
public Technicien(string nom, short age, string competences)
: base(nom,age) {
m_Competences = competences ;
}
}

448

Chapitre 12 : Hritage/drivation polymorphisme et abstraction


class Program {
static void Main() {
Technicien roger = new Technicien("Roger", 45, "D
epanneur PC") ;
}
}

Niveau de visibilit des membres de la classe de base


C++ C  En C  vous ne pouvez pas spcifier le niveau de visibilit qua une classe drive
sur les membres de sa classe de base. En C  tout se passe comme en C++ lorsque ce contrle
daccs est public.

Les classes dont on ne peut driver (sealed)


C++ C  Contrairement C++, le langage C  permet de spcifier clairement quune classe
ne peut en aucun cas tre une classe de base.
C  C  permet de spcifier quune classe ne peut en aucun cas tre une classe de base. Cest-dire quaucune classe ne peut driver de celle-ci. Il sut de mettre le mot-cl sealed devant la
dclaration de la classe. On utilise parfois le terme de classe finalise pour nommer cette possibilit. Par exemple :
sealed class Foo{

/* Ici les membres de la classe. */ }

Une classe finalise peut tre une classe drive. Dailleurs, comme tous les types, les classes finalises drivent de la classe Object. De plus, les structures peuvent tre vues comme des classes
finalises de type valeur.

Mthodes virtuelles et polymorphisme


C++ C  Attention nous ne parlons ici que de mthodes virtuelles, cest--dire ayant une
implmentation dans la classe de base. Les mthodes virtuelles pures du C++, appeles mthodes abstraites en C  seront vues un peu plus loin.
Les concepts de polymorphisme et de mthodes virtuelles sont identiques en C++ et en C  .
Cependant C  permet dempcher que le polymorphisme sapplique sur une certaine mthode
virtuelle dune certaine classe drive. De plus et il y a quelques petites dirences au niveau
syntaxique :

En C++, pour avoir accs au polymorphisme, on utilise le plus souvent des pointeurs de
type classe de base sur lesquels on appelle des mthodes virtuelles. En C  , les pointeurs
ntant pas trs populaires, on utilise la plupart du temps des rfrences types par une
classe de base, rfrenant des objets dimplmentations drives. Notez quen C++ le polymorphisme est aussi accessible par lintermdiaire de rfrences, mais cette possibilit est
moins utilise que les pointeurs.

En C++ une mthode virtuelle est dclare avec le mot-cl virtual dans la premire classe
de base qui implmente cette mthode. Les implmentations de cette mthode dans les
classes drives peuvent optionnellement rutiliser ce mot pour signaler que cette mthode
est virtuelle. la lecture du code il nest donc pas toujours vident quune mthode est

Mthodes virtuelles et polymorphisme

449

virtuelle. Le langage C  rsout ce problme. Une mthode virtuelle est toujours dclare
avec le mot-cl virtual, dans la premire classe de base qui limplmente. Dans chaque
classe drive o la mthode virtuelle est redfinie, il faut faire prcder la mthode du
mot-cl override.
Une dirence conceptuelle :

La dirence conceptuelle au niveau des mthodes virtuelles, entre C++ et C  , se situe dans
le fait quen C  , le dveloppeur peut redfinir une mthode virtuelle tout en interdisant
le polymorphisme de sappliquer. Pour cela on doit utiliser le mot-cl new au lieu du motcl override. Si on ne met rien aucun de ces deux mots-cls, le compilateur produira seulement un avertissement du type : vous devriez utiliser new mais lexcution tout se passe
comme si on avait utilis new. Soyez vigilant !

La problmatique
En programmation objet, on est souvent confront au problme suivant : on cre des objets,
instances de plusieurs classes drives dune classe de base, puis on veut leur appliquer un traitement de base, cest--dire, un traitement dfini dans la classe de base. Le problme est que ce
traitement dire selon la classe drive. Par exemple :

On veut obtenir une description de tous les employs (traitement de base : obtenir une
description dun employ, quelle que soit sa catgorie).
On veut dessiner toutes les figures gomtriques (traitement de base : dessiner une figure
gomtrique, quel que soit le type de figure).
On veut envoyer des donnes par lintermdiaire dun point de communication (traitement
de base : envoyer des donnes, quel que soit le protocole de communication sous-jacent).

La solution : les mthodes virtuelles et le polymorphisme


Il est trs utile de rassembler les objets qui doivent subir le traitement de base, puis de leur faire
subir le traitement de base chacun (par exemple dans une boucle).

Toute llgance de cette mthode vient du fait que le traitement de base sapplique sur un
objet, sans connatre prcisment sa classe, on ne le connat que par une rfrence de type
sa classe de base : cest une illustration du polymorphisme.
Ce que nous avons appel traitement de base, sappelle mthode virtuelle. Cest une mthode
dfinie la fois dans la classe de base (prcde du mot cl virtual) et dans la classe drive
(prcde du mot-cl override).
Il existe plusieurs corps pour cette mthode, un dans la classe de base et ventuellement un dans
chacune des classes drives. Lors de lappel de cette mthode sur une rfrence de type classe
de base, le corps adquat est choisi lexcution par le programme. La notion de plusieurs corps
pour une mme mthode se traduit par le mot polymorphisme (plusieurs formes).
Une classe drive nest pas oblige de redfinir le corps dune mthode virtuelle de sa classe de
base. Une classe de base qui contient au moins une mthode virtuelle est une classe polymorphe.
En outre, il est possible de redfinir le corps dune mthode virtuelle avec lutilisation conjointe
des mots-cls override sealed. Dans ce cas, cela signifie que cette mthode virtuelle ne peut
plus tre redfinie dans les classes drives de la classe drive. On parle de mthode finalise.

450

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

Un exemple
Voici un exemple o lon a :

Une classe de base Employe et deux classes drives Technicien et Secretaire

Un traitement de base qui est dacher la description de lemploy. Ce traitement dire


selon la classe : Pour un objet de type Employe ce traitement se limite acher son nom.
Pour un objet de type Techicien ce traitement excute lachage de lemploy suivi du
texte Fonction :Technicien . Pour un objet de type Secretaire ce traitement excute lachage de lemploy suivi du texte Fonction :Secrtaire .

On peut illustrer ceci avec un diagramme de classe obtenu avec Visual Studio 2005 :

Figure 12 -4 : Diagramme de nos classes


Le corps du programme consiste crer trois employs rfrencs dans un tableau, dont les
lments sont des rfrences de type Employe. Ensuite, on applique le traitement de base (lachage de la description) pour chaque employ rfrenc dans le tableau.
Exemple 12-2 :
public class Employe {
// m_Nom peut etre accede dans les m
ethodes des classes d
eriv
ees.
protected string m_Nom ;
public Employe(string nom) {m_Nom = nom;}
public virtual void DisplayDescription() {
System.Console.Write("Nom : {0}",m_Nom) ;
}
}
class Secretaire : Employe { // Secretaire h
erite de Employe.
public Secretaire(string nom):base(nom) {}
public override void DisplayDescription() {
// Appel de la methode DisplayDescription() de Employe.
base.DisplayDescription();
System.Console.Write( " Fonction : Secr
etaire\n") ;
}
}

Mthodes virtuelles et polymorphisme

451

class Technicien : Employe { // Technicien h


erite de Employe.
public Technicien(string nom):base(nom) {}
public override void DisplayDescription(){
// Appel de la methode DisplayDescription() de Employe.
base.DisplayDescription();
System.Console.Write( " Fonction : Technicien\n") ;
}
}
class Program {
static void Main() {
Employe[] tableau = new Employe[3] ;
tableau[0] = new Technicien("Line") ;
tableau[1] = new Secretaire("Lisanette") ;
tableau[2] = new Secretaire("Anne-Mette") ;
foreach( Employe employe in tableau )
employe.DisplayDescription();
}
}
Voici ce quache ce programme :
Nom : Line Fonction : Technicien
Nom : Lisanette Fonction : Secr
etaire
Nom : Anne-Mette Fonction : Secr
etaire
La mthode DisplayDescription() est toujours appele sur une rfrence de type Employe.
Pourtant on voit bien que le programme excute les mthodes DisplayDescription() de
Technicien et Secretaire. Cest la magie du polymorphisme. Il vite un test de type fastidieux
et facilite la maintenance. En eet lors de lajout dune autre classe drive de Employe (une
classe Cadre par exemple) il faudrait mettre jour le test, alors quavec le polymorphisme, il ny
a absolument rien faire pour que la fonction Cadre::DisplayDescription() soit appele.
Les mthodes virtuelles DisplayDescription() appellent la mthode DisplayDescription()
de la classe de base avec le mot-cl base. Pour chacune de vos classes drives, nous vous
conseillons de vrifier que chaque mthode virtuelle appelle un moment ou un autre
la mthode qui lui correspond dans la classe de base. Si ce nest pas le cas il y a srement un
problme de conception dans votre modle objet.

Redfinition dune mthode et dsactivation du polymorphisme


C++ C  Attention, la dsactivation du polymorphisme nest pas possible en C++ alors que
la rcriture dune mthode non virtuelle dune classe de base dans une classe drive est permise.
C  C  permet de cacher une mthode (virtuelle ou non) dune classe de base dans une classe
drive, par la redfinition avec le mot-cl new plac devant la mthode dans la classe drive.
Attention, dans ce cas le polymorphisme ne sapplique pas, y compris si la mthode est dclare
comme virtuelle dans la classe de base. Notez que si on ne met pas le mot-cl new le compilateur
produira seulement un avertissement du type : vous devriez mettre new mais lexcution
tout se passe comme si on avait mis new. On comprend alors, que lutilisation explicite de new
sert clarifier le code.

452

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

Reprenons lexemple prcdent et observons leet de lutilisation de new pour redfinir une
mthode virtuelle :
Exemple 12-3 :
public class Employe{
...
}
class Secretaire : Employe{ // Secretaire h
erite de Employe.
...
}
class Technicien : Employe{ // Technicien h
erite de Employe.
public Technicien(string nom):base(nom) {}
public new void DisplayDescription(){
base.DisplayDescription() ;
System.Console.Write( " Fonction : Technicien\n") ;
}
}
class Program {
static void Main(){
Employe[] tableau = new Employe[3] ;
tableau[0] = new Technicien("Line") ;
tableau[1] = new Secretaire("Lisanette") ;
tableau[2] = new Secretaire("Anne-Mette") ;
foreach( Employe employe in tableau )
employe.DisplayDescription();
// Appel de la methode DisplayDescription() de Technicien
((Technicien)tableau[0]).DisplayDescription();
}
}
Voici ce quache ce programme :
Nom : LineNom : Lisanette
Fonction : Secr
etaire
Nom : Anne-Mette
Fonction : Secr
etaire
Nom : Line
Fonction : Technicien
Remarquez que la mthode DisplayDescription() est appele deux fois pour lobjet dcrivant
Line. Cependant la premire fois, il sagit de la mthode DisplayDescription() de Employe
alors que la deuxime fois, cest la mthode DisplayDescription() de Technicien. Dans le
premier cas on voit bien que le polymorphisme a t dsactiv. Dans le deuxime cas, on a
explicitement transtyp notre rfrence de type Employe vers lobjet qui reprsente Line, en une
rfrence de type Technicien. Il ny a donc pas eu l aussi, dapplication de polymorphisme. Le
fait de transtyper explicitement une rfrence de type une classe de base vers une rfrence, de
type classe drive se nomme downcast . Nous reviendrons un peu plus loin sur ce sujet.

Labstraction
C++ C  Le concept dabstraction est compltement similaire entre les langages C  et C++.
Cependant des dirences de syntaxe apparaissent, et mme de vocabulaire.

Labstraction

453

Les mthodes virtuelles pures du langage C++ sappellent, en langage C  , mthodes abstraites
et se dclarent avec le mot-cl abstract en prfixe.
De plus il ne sut plus quun classe abstraite ait au moins une mthode abstraite pour tre
abstraite. Il faut quelle soit dclare avec le mot-cl abstract en prfixe. Contrairement au C++,
en C  il peut donc y avoir des classes abstraites sans mthodes abstraites.
Malgr les apparences, le concept dinterface existe en C++ : rien nempche de dclarer une
classe avec seulement des mthodes abstraites et sans aucun champ. Cependant C  met en vidence ce concept avec le mot-cl interface. La dirence est quen C  , le fait dutiliser le mot-cl
interface oblige les dveloppeurs respecter des contraintes (pas dajout de champ, pas dajout
de corps de mthode etc).
Les interfaces sont trs importantes en C  puisquune classe peut implmenter plusieurs interfaces, ce qui permet de combler en partie le manque dhritage dimplmentation multiple.

La problmatique
Dans lexemple de la section prcdente, acher la description dun employ (i.e acher son
nom) a un sens. Il est donc utile de mettre du code dans la mthode virtuelle DisplayDescription() de la classe de base Employe. Lorsquelle est instancie, la classe Employe a quelque chose
acher.
Il arrive que lon nait pas de code mettre dans la mthode virtuelle parce quil y a un manque
dinformation ce niveau de larbre dhritage.
Par exemple pour la classe FigureGeometrique il ny a rien mettre dans la mthode virtuelle
Dessine(). En eet, ce niveau de lhritage on ne sait pas quel type de figure gomtrique on
instancie.
De mme pour la classe PointDeCommunication (aussi dfinie plus haut) il ny a rien mettre
dans la mthode EnvoieUnStream() puisque ce niveau de lhritage on ne connat pas le protocole de communication sous-jacent.
On pourrait trs bien dclarer une fonction virtuelle sans code lintrieur. Mais on sent bien
que lon a besoin de mcanismes plus performants pour dclarer proprement ces classes de base
qui ont un manque dinformation sur ce quelles reprsentent lorsquune de leurs classes drives est instancie. Une telle classe de base veut imposer des oprations ces classes drives,
alors quelle mme na pas assez dinformations pour implmenter ces oprations, mme en
partie.

La solution : les classes abstraites et les mthodes abstraites


La solution sappelle labstraction. Une classe abstraite est une classe qui doit dlguer compltement limplmentation de certaines de ces mthodes ses classes drives. On la vu, la raison pour laquelle ces mthodes ne peuvent tre implmentes est que la classe abstraite a un
manque dinformation, mais souhaite imposer des oprations ces classes drives.
Ces mthodes qui ne peuvent tre implmentes sappellent des mthodes abstraites (ou virtuelles
pures). Elles se dclarent avec le mot-cl abstract en prfixe et doivent tre contenues dans des
classes abstraites, dclares aussi avec le mot-cl abstract en prfixe.
Une mthode abstraite nest quune mthode virtuelle particulire, et doit tre implmente
dans les classes drives avec le mot-cl override en prfixe (ou override sealed). Si une classe

454

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

drive dune classe abstraite nimplmente pas toutes les mthodes abstraites, elle est ellemme abstraite.
La consquence immdiate et fondamentale de tout ceci est que : Une classe abstraite nest pas
instanciable.
Il ne peut y avoir dobjets instances dune classe abstraite, mais une rfrence peut avoir pour
type une classe abstraite. Une telle rfrence rfrence alors un objet dune classe drive non
abstraite et lapplication du polymorphisme lors de lappel des mthodes virtuelles et abstraites
est alors automatiquement mise en uvre. Le polymorphisme est encore plus vident lorsquil
est utilis sur des mthodes abstraites puisquon est certain que ce ne peut tre le corps de la
mthode abstraite qui est appele, puisquil nexiste pas !
Remarquez quune mthode abstraite ne doit pas avoir une visibilit prive. En eet, les classes
drives seraient dans limpossibilit de limplmenter.

Un exemple
Voici un exemple o lon a : une classe de base abstraite FigureGeometrique, deux classes drives Cercle et Rectangle, un traitement de base qui est de dessiner la figure.
Ce traitement dire selon la classe. Il est abstrait pour la classe FigureGeometrique (i.e il nest
pas implmentable dans cette classe car il y a un manque dinformations concrtes sur la nature
de la forme gomtrique). Pour un objet de type Cercle, ce traitement excute lachage dun
cercle en fonction du centre et du rayon. Pour un objet de type Rectangle, ce traitement excute
lachage dun rectangle en fonction de trois de ses sommets.
Exemple 12-4 :
class Point{
public Point (int x,int y){this.x = x;this.y = y;}
int x ; int y ;
}
abstract class FigureGeometrique{
// On verifie quune methode abstraite na pas de corps.
public abstract void Dessine();
}
class Cercle : FigureGeometrique {
private
Point
m_Centre ;
private
double
m_Rayon ;
public Cercle(Point centre, double rayon) {
m_Centre = centre ;
m_Rayon = rayon ;
}
public override void Dessine (){
// Dessine un Cercle `a partir de son centre et de son rayon.
}
}
class Rectangle : FigureGeometrique {
private Point m_Sommet1 ;
private Point m_Sommet2 ;
private Point m_Sommet3 ;

Labstraction

455

public Rectangle(Point s1, Point s2, Point s3 ) {


m_Sommet1 = s1 ; m_Sommet2 = s2 ; m_Sommet3 = s3 ;
}
public override void Dessine (){
// Dessine un Rectangle `a partir de trois de ses sommets.
}
}
class Program {
static void Main() {
FigureGeometrique[] tableau = new FigureGeometrique[3] ;
tableau [0] = new Cercle(new Point(0,0),3.2) ;
tableau [1] = new Rectangle(
new Point(0,0),new Point(0,2),new Point(1,2)) ;
tableau [2] = new Cercle(new Point(1,1),4.1) ;
// Le polymorphisme sapplique `
a lappel
// de la methode abstraite Dessine().
foreach(FigureGeometrique f in tableau )
f.Dessine() ;
// Erreur de compilation !
// On ne peut instancier une classe abstraite !
FigureGeometrique figure = new FigureGeometrique();
}
}
Notez quune classe abstraite peut avoir des constructeurs, des champs et des proprits.

Usage simultan des mots-cl abstract et override


Il est possible de marquer une mthode simultanment avec ces deux mots-cls. Imaginez une
classe C qui drive dune classe B qui elle-mme drive dune classe A qui dclare une mthode
Foo() comme abstraite. B peut elle-mme tre une classe abstraite et ne pas implmenter la mthode Foo(). Dans ce cas la dclaration de la mthode B.Foo() doit tre prcde des mots-cls
abstract override. Cela signifie que la classe B repousse limplmentation de cette mthode
ces classes drives :

Exemple 12-5 :
abstract class A { public abstract void Foo();}
abstract class B : A { public abstract override void Foo();}
class C : B { public override void Foo() { /*...*/ } }
Comme lillustre lexemple suivant, lassociation des mots cls abstract et override peut aussi
permettre B de forcer ses classes drives rimplmenter la mthode Foo(), dj implmente par la classe A :

456

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

Exemple 12-6 :
class A { public virtual void Foo() { /*...*/ } }
abstract class B : A { public abstract override void Foo();}
// La classe C est forcee de reimpl
ementer la m
ethode Foo.
class C : B { public override void Foo() { /*...*/ } }

Les interfaces
C++ C  Une interface peut tre vue comme une classe abstraite sans champ avec seulement des mthodes, des proprits, des vnements ou des indexeurs abstraits. Ce concept est
tout fait implmentable en C++, mais C  va plus loin en orant la possibilit dutiliser le motcl interface la place du mot-cl class. Cette distinction est importante en C  . En eet une
classe peut ventuellement driver de plusieurs interfaces.
C  Il existe des classes abstraites trs particulires. Ce sont celles qui nont que des mthodes,
des proprits, des vnements ou des indexeurs abstraits. La thorie de la programmation objet
les appelle interfaces ou bien abstractions. On dit quune classe implmente une interface au lieu
de dire quune classe drive dune interface. On dit aussi quune classe qui implmente une
interface est une implmentation de linterface. Les structures peuvent aussi implmenter des
interfaces mais cette possibilit est dangereuse comme nous lexpliquerons un peu plus loin.
Pour dclarer une interface en langage C  , il sut de dclarer une classe avec le mot-cl
interface au lieu du mot-cl class. Une interface ne peut avoir que quatre types de membres :
des mthodes (des spcifications de mthodes), des proprits, des vnements et des indexeurs.
Les niveaux de visibilit ne peuvent sappliquer aux mthodes dune interface. Cest la classe
qui implmente linterface den dcider. En fait, on ne peut utiliser que les mots-cls virtual
et public devant la dclaration dun membre dune interface. Les consquences de ces rgles
dutilisation sont dtailles dans les sections suivantes. Voici un point souligner propos des
interfaces :
Une classe peut ventuellement implmenter plusieurs interfaces (en plus de driver de sa classe
de base). De mme une interface peut driver dune ou plusieurs autres interfaces. On dit dune
telle interface quelle tend les autres interfaces.
Cette possibilit permet de rajouter vos propres comportements une classe, mais aussi des
comportements dfinis par des interfaces standard de larchitecture .NET. Par exemple linterface IEnumerator permet un objet dtre utilis par la structure de contrle de boucle foreach,
comme sil sagissait dun tableau. Dautres interfaces .NET utilisables dans vos classes sont prsentes dans cet ouvrage. Nous vous conseillons de faire commencer le nom de vos interfaces
par un I majuscule, linstar des interfaces standard.
Une rfrence peut tre de type interface. Dans ce cas on ne peut accder lobjet quavec le
comportement spcifique de linterface, par exemple :
Exemple 12-7 :
interface
interface
class C :
public
public

IA { void f(int i) ; }
IB { void g(double d) ; }
IA, IB {
void f(int i) { System.Console.WriteLine("f de C {0}", i) ; }
void g(double d){ System.Console.WriteLine("g de C {0}", d) ; }

Les interfaces

457

}
class Program {
static void Main() {
// Une reference interface sur un objet.
IA obj1 = new C() ;
// Une reference interface sur un objet.
IB obj2 = new C() ;
// Recuperation de lobjet dapr`
es une r
eference interface.
C _obj2 = (C)obj2 ;
obj1.f(5) ;
}
}
Contrairement aux classes et aux structures, les interfaces ne drivent pas de la classe Object.
Cependant comme seules les classes et les structures sont mme dimplmenter une interface,
il est permis dappeler une mthode de la classe Object sur une interface. Lexemple suivant
montre quil est aussi permis de transtyper une rfrence de type interface en rfrence de type
Object.
Exemple 12-8 :
interface I {}
class C : I {}
class Program {
static void Main() {
I c = new C() ;
c.GetHashCode();
object o = c;
}
}

Obliger un client utiliser une abstraction


plutt quune implmentation
.NET ore une possibilit indite par rapport aux langages C++ et Java. Vous pouvez forcer les
clients de vos classes utiliser une abstraction (i.e une rfrence de type une interface) plutt
quune implmentation (i.e une rfrence vers une instance dune classe qui implmente linterface). Cette possibilit se rvle extrmement utile pour les concepteurs de bibliothques de
classes. Ils ont leur disposition une technique qui fait partie intgrante du langage pour obliger
le code de leurs clients tre dcoupl des implmentations. Or, le dcouplage des classes est un
des principes fondamentaux de la POO avec la cohrence du code.
Voici un exemple. La mthode fct2() ne peut tre appele partir dune rfrence de type C
mais peut tre appele partir dune rfrence de type I. Dans un cas rel, on aurait srement
forc lutilisation de linterface sur toutes les mthodes prsentes par linterface.
Exemple 12-9 :
interface I {
void fct1() ;

458

Chapitre 12 : Hritage/drivation polymorphisme et abstraction


void fct2() ;
}
public class C : I {
public void fct1() { System.Console.WriteLine("fct1 appel
ee") ; }
void I.fct2() { System.Console.WriteLine("fct2 appel
ee") ; }
}
public class Program {
public static void Main() {
C refImpl = new C() ;
I refAbst = (I)refImpl ;
refAbst.fct1(); // Compilateur OK
refAbst.fct2(); // Compilateur OK
refImpl.fct1(); // Compilateur OK
refImpl.fct2() ; // Compilateur KO : Le message derreur est :
// C does not contain a definition for fct2
}
}

Conflits de noms de mthodes


Puisquune classe peut implmenter plusieurs interfaces et driver dune classe, il se peut quil
y ait conflit entre des mthodes ayant le mme nom et la mme signature. Pour rsoudre ces
conflits il faut utiliser la syntaxe dcrite dans la section prcdente :
Exemple 12-10 :
interface IA { void f(int i); }
interface IB { void f(int i); }
abstract class FooBase { public abstract void f(int i); }
class FooDeriv : FooBase, IA, IB {
void IA.f(int i) { System.Console.WriteLine("IA.f({0})", i) ; }
void IB.f(int i) { System.Console.WriteLine("IB.f({0})", i) ; }
public override void f(int i) {
System.Console.WriteLine("f({0})", i) ;
}
}
class Program {
static void Main() {
FooDeriv refImpl = new FooDeriv() ;
FooBase refAbst = (FooBase)refImpl ;
IA refA = (IA)refImpl ;
IB refB = (IB)refImpl ;
refImpl.f(1) ;
refAbst.f(2) ;
refA.f(3) ;
refB.f(4) ;
}
}
Ce programme ache :

Les interfaces

459

f(1)
f(2)
IA.f(3)
IB.f(4)
Une mme classe peut donc avoir plusieurs implmentations pour une mme mthode. Telle
ou telle implmentation sera excute en fonction du type de la rfrence sur laquelle la mthode est appele. Cest une forme de polymorphisme.

Conflits dextensions dinterfaces


C++ C  En C++, on parle dhritage virtuel lorsquune classe hrite plusieurs fois de la
mme classe. Par exemple, supposez que la classe D drive des classes C et B, qui elles mmes
drivent de la classe A. La classe D drive donc deux fois de la classe A. En utilisant la notion
dhritage virtuel, vous pouvez prciser si vous souhaitez quune instance de D soit constitue
de deux instances de A ou dune seule.
Le sujet de cette section se rapproche conceptuellement de la possibilit dhritage virtuel du
C++, bien quici, on parle dhritage dabstraction multiple, et non dhritage dimplmentation
multiple.
C  Une classe a la possibilit dimplmenter plusieurs interfaces qui tendent la mme interface. Cette possibilit est utile lorsquon a un diagramme UML qui ressemble celui de la
Figure 12 -5.
Interface IB
Interface IA

Classe Foo
Interface IC

Figure 12 -5 : Conflit dextension dinterfaces


Lexemple suivant illustre cette possibilit et ses consquences :
Exemple 12-11 :
interface IA { void fa() ; }
interface IB : IA { new void fa(); void fb() ; }
interface IC : IA { new void fa(); void fc() ; }
class Foo : IB, IC {
void IA.fa() { System.Console.WriteLine("IA.fa") ; }
void IB.fa() { System.Console.WriteLine("IB.fa") ; }
void IC.fa() { System.Console.WriteLine("IC.fa") ; }
public void fa() { System.Console.WriteLine("Foo.fa") ; }
public void fb() { System.Console.WriteLine("Foo.fb") ; }
public void fc() { System.Console.WriteLine("Foo.fc") ; }
}
class Program {
static void Main() {
Foo r = new Foo() ; r.fa() ;
IA rA = r ;
rA.fa() ;

460

Chapitre 12 : Hritage/drivation polymorphisme et abstraction


IB rB = r ;
IC rC = r ;

rB.fa() ;
rC.fa() ;

}
}
Ce programme ache :
Foo.fa
IA.fa
IB.fa
IC.fa

Rcriture de limplmentation dune interface


Vous avez la possibilit de dclarer une mthode dune interface implmente dans une classe,
comme virtuelle. Elle pourra ainsi tre rcrite dans une classe drive de la classe qui implmente linterface. Voici un exemple pour illustrer cette possibilit :
Exemple 12-12 :
interface I {
void f(int i);
void g(int i) ;
}
class FooBase : I {
public virtual void f(int i) {
System.Console.WriteLine("FooBase.f({0})", i) ;
}
public void g(int i) {
System.Console.WriteLine("FooBase.g({0})", i) ;
}
}
class FooDeriv : FooBase {
public override void f(int i) {
System.Console.WriteLine("FooDeriv.f({0})", i) ;
}
}
class Program {
static void Main() {
FooBase refB1 = new FooBase() ;
I
refI1 = refB1 ;
FooDeriv refD = new FooDeriv() ;
FooBase refB2 = refD ;
I
refI2 = refD ;
refB1.f(1) ;
refI1.f(2) ;
refD.f(3) ;
refB2.f(4) ;
refI2.f(5) ;
}
}

Les interfaces

461

Ce programme ache :
FooBase.f(1)
FooBase.f(2)
FooDeriv.f(3)
FooDeriv.f(4)
FooDeriv.f(5)

Implmentation dune interface dans une structure


Pour comprendre cette section il est ncessaire davoir assimil ce que reprsentent les oprations de boxing et de unboxing dcrit dans la section page 350.
La prsente section a pour but de vous sensibiliser un problme classique qui survient lorsquune structure implmente une interface. Si vous essayez daccder aux membres de linterface implments par la structure partir dune rfrence vers linterface vous nobtiendrez certainement pas le rsultat escompt. En eet, lors du transtypage de la structure vers linterface,
une opration de boxing est implicitement ralise car linterface z besoin dune rfrence. Pour
vous en convaincre, analysez lexemple suivant :
Exemple 12-13 :
interface I {
void SetState(int i) ;
int GetState() ;
}
struct Struct : I {
private int i ;
public void SetState(int i) { this.i = i ; }
public int GetState() { return i ; }
}
class Program {
static void Main() {
Struct s = new Struct() ;
// Ici un boxing de la structure est r
ealis
e implicitement.
I i = (I)s;
s.SetState(10) ;
i.SetState(20) ;
System.Console.WriteLine("Retour de s.GetState():"+ s.GetState()) ;
System.Console.WriteLine("Retour de i.GetState():"+ i.GetState()) ;
}
}
Ce programme ache :
Retour de s.GetState():10
Retour de i.GetState():20

En rgle gnrale il vaut mieux ne pas implmenter dinterface dans les structures. Si vous
navez pas dautres possibilits, il vaut mieux appeler les mthodes de linterface partir
de limplmentation, contrairement ce qui est conseill pour les classes.

462

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

Pour les plus sceptiques, lanalyse avec ildasm.exe du code IL gnr pour la mthode Main()
montre clairement quil y a une opration de boxing ralise. Notez que les deux autres oprations de boxing sont ncessaires pour pouvoir acher ltat lentier retourn par la mthode
GetState() :
.method private hidebysig static void Main() cil managed {
.entrypoint
// Code size
86 (0x56)
.maxstack 2
.locals ([0] valuetype Struct s,
[1] class IInterface I)
IL_0000: ldloca.s
s
IL_0002: initobj
Struct
IL_0008: ldloc.0
IL_0009: box
Struct // <- cest ici que lop
eration de boxing
//
de la structure a implicitement lieu.
IL_000e: stloc.1
IL_000f: ldloca.s
s
IL_0011: ldc.i4.s
10
IL_0013: call
instance void Struct::SetState(int32)
IL_0018: ldloc.1
IL_0019: ldc.i4.s
20
IL_001b: callvirt
instance void IInterface::SetState(int32)
IL_0020: ldstr
"Retour de s.GetState():"
IL_0025: ldloca.s
S
IL_0027: call
instance int32 Struct::GetState()
IL_002c: box
[mscorlib]System.Int32
IL_0031: call
string
[mscorlib]System.String::Concat(object,object)
IL_0036: call
void [mscorlib]System.Console::WriteLine(string)
IL_003b: ldstr
"Retour de i.GetState():"
IL_0040: ldloc.1
IL_0041: callvirt
instance int32 I::GetState()
IL_0046: box
[mscorlib]System.Int32
IL_004b: call
string
[mscorlib]System.String::Concat(object,object)
IL_0050: call
void [mscorlib]System.Console::WriteLine(string)
IL_0055: ret
} // end of method Program::Main

Proprits, vnements et indexeurs virtuels et abstraits


Les proprits, les vnements et les indexeurs dune classe ont la possibilit dtre virtuels ou
abstraits. En fait, lide sous-jacente est que ce sont les accesseurs de ces membres, considrs
alors comme des mthodes, qui peuvent tre virtuels ou abstraits. Rappelons que les accesseurs
possibles pour une proprit ou un indexeurs sont get et set tandis que les accesseurs possibles
pour un vnement sont add et remove.

Proprits, vnements et indexeurs virtuels et abstraits

463

Comme les mthodes virtuelles et abstraites les proprits, les vnements et les indexeurs virtuels et abstraits peuvent tre :

(Re)dfinies dans une classe drive avec le mot-cl override. Dans ce cas le polymorphisme
sapplique sur les accesseurs.

(Re)dfinies dans une classe drive avec les mots-cls override sealed. Dans ce cas le polymorphisme sapplique sur les accesseurs, et le membre concern ne peut tre redfini dans
les classes drives de la classe drive.

Redfinies dans une classe drive avec le mot-cl new. Dans ce cas le polymorphisme ne
sappliquera pas sur les accesseurs.

Comme pour les mthodes abstraites, pour contenir une proprit, un vnement ou un indexeur abstrait, une classe doit tre abstraite. De plus comme les mthodes abstraites, un tel
membre abstrait ne doit pas avoir une visibilit prive. Voici un exemple avec des proprits
virtuelles et abstraites :
Exemple 12-14 :
abstract class FooBase {
protected int valA = 0 ;
public virtual int Prop1 {
get { return valA ; }
set { valA = value ; }
}
public virtual int Prop2 {
get { return 43 ; }
}
public abstract int Prop3 {
get ;
set ;
}
}
class FooDeriv : FooBase {
private int valB = 0 ;
public override int Prop1 {
get { return base.Prop1 * 2 ; }
set { base.Prop1 = value * 2 ; }
}
public override sealed int Prop2 {
get { return valA > valB ? valA : valB ; }
}
public override int Prop3 {
get { return valA + valB ; }
set { valA = value - valB ; }
}
}
Il nest pas ncessaire de faire de la classe FooDeriv une classe abstraite, puisquelle na pas de
membres abstraits. Notez lutilisation du mot-cl base pour appeler les accesseurs dfinis dans
la classe de base.

464

Chapitre 12 : Hritage/drivation polymorphisme et abstraction

Les oprateurs is et as
C++ C  La possibilit de permettre lvaluation ou le transtypage du type dune expression lexcution (permis en C  avec les oprateurs is et as) est implmente en C++ avec
loprateur typeid(expression) qui renvoie un objet de type type_info.
Cette possibilit rentre dans le cadre plus gnral du RTTI (RunTime Type Information en anglais, information de type lexcution en franais).
Le RTTI est la fois plus facile utiliser en C  quen C++, et incomparablement plus complet,
grce aux mtadonnes de type et au mcanisme de rflexion, prsents en page 233.

Loprateur is
Loprateur is sert dterminer lexcution si une expression peut tre transtype (caste) dans
un type donn. Cet oprateur retourne un boolen. Son oprande de gauche est une expression
et son oprande de droite un type.
Concrtement un objet de classe A drivant dune classe B et implmentant les interfaces I1, I2,
...In, peut tre utilis par lintermdiaire :

Dune rfrence de type A, B, I1, I2, ...In.

Dune rfrence de type, une classe de base situe dans la hirarchie des classes de base de
B.

Dune rfrence de type, une interface supporte par une classe de base situe dans la hirarchie des classes de base de A.

Si la rfrence est nulle loprateur is retourne false. Voici un exemple dutilisation du motcl is :
Exemple 12-15 :
using System ;
interface IA { void f(int i) ; }
interface IB { void g(int i) ; }
abstract class FooBase { public abstract void h(int i) ; }
class FooDeriv : FooBase, IA {
public void f(int i) { /*...*/ }
public override void h(int i){ /*...*/ }
}
class Program {
static void Main() {
IA refA = new FooDeriv() ;
IB refB = null ;
FooBase refAbst = null ;
FooDeriv refC = null ;
// Ici, le transtypage peut se faire.
if ( refA is FooBase ){
refAbst = (FooBase)refA ;
// utilise refAbst...
}

Les oprateurs is et as

465

// Ici, le transtypage peut se faire.


if ( refA is FooDeriv ){
refC = (FooDeriv)refA ;
// utilise refC...
}
// Ici le transtypage ne peut pas se faire,
if ( refA is IB ){
refB = (IB)refA ;
// utilise refB...
}
// Cette expression est toujours vraie si IA nest pas nulle.
// Le compilateur linterp`ete comme : if( refA != null )
if ( refA is IA ){ /*...*/ }
}
}

Faites attention car bien souvent loprateur is est utilis tort, dans des situations o le
polymorphisme aurait vit bien des tests.

Loprateur as
Aprs avoir dtermin lexcution si une expression peut tre transtype (caste) dans un type
donn avec loprateur is, on ralise eectivement le transtypage la plupart du temps. Loprateur as permet deectuer ces deux tapes dun seul coup. Si le transtypage ne peut avoir lieu,
la rfrence nulle est retourne. Il faut donc toujours tester la rfrence retourne.
Lavantage dutiliser loprateur as au lieu de loprateur is lorsque ceci est possible est double :
le code est plus lisible, les performances sont meilleures. Voici la mthode Main() de lexemple
de la section prcdente, rcrite en utilisant loprateur as :
Exemple 12-16 :
...
class Program {
static void Main() {
IA refA = new FooDeriv() ;
IB refB = null ;
FooBase refAbst = null ;
FooDeriv refDeriv = null ;
// Ici, le transtypage peut se faire.
refAbst = refA as FooBase ;
if ( refAbst != null ) {
// utilise refAbst...
}
// Ici, le transtypage peut se faire.
refDeriv = refA as FooDeriv ;
if ( refDeriv != null ) {
// utilise refC...
}

466

Chapitre 12 : Hritage/drivation polymorphisme et abstraction


// Ici, le transtypage ne peut pas se faire.
refB = refA as IB ;
if ( refB != null ) {
// utilise refB...
}
}
}

Techniques de rutilisation de code


Lhritage de classe nest pas la seule solution pour rutiliser du code. En fait, jai rdig un
article disponible sur le site dotnetguru.org pour exprimer mon avis ce sujet : Faut il interdire lhritage dimplmentation dans les langages objets ? http://www.dotnetguru.org/articles/
dossiers/heritageimpl/FragileBaseClass.htm.
Le fil directeur de cet article est que lhritage dimplmentation est trs souvent utilis mauvais escient. Jai constat que lorsque les dveloppeurs sont forms, on leur prsente ce mcanisme comme la pierre angulaire de la POO. La POO englobe dautres principes tout aussi importants qui sont principalement lencapsulation, la composition dobjet, labstraction avec les
interfaces et la gnricit. Mon article montre quavec un peu dastuce, on peut utiliser conjointement les interfaces et la composition dobjet pour rutiliser du code dune manire bien plus
ecace quavec lhritage dimplmentation.
Le chapitre suivant traite de la gnricit. Ce mcanisme introduit avec C  2.0 se rvle tre un
puissant outil pour rutiliser du code. Nous verrons quil est particulirement adapt lcriture
dalgorithmes gnriques tels que les implmentations de collections qui ont besoin de stocker
des objets instances dune mme classe, sans ncessairement cibler une classe en particulier.
Depuis quelques annes, il y a une certaine eervescence autour de la programmation oriente
aspect (AOP pour Aspect Oriented Programming en anglais). Jai collaborer la rdaction dun
article sur le sujet tlchargeable lURL : http://www.dotnetguru.org/articles/dossiers/
aop/quid/AOP15.htm. LAOP est une technique de rutilisation du code implmentant les aspects dun logiciel (synchronisation, scurit, persistance etc). lheure actuelle, AspectDNG
constitue loutil le plus avanc pour faire de lAOP en .NET (home page : http://sourceforge.
net/projects/aspectdng/).
Enfin, nous prcisons que la version 3.0 de C  introduira un nouveau mcanisme de rutilisation de code permettant en quelques sortes dtendre des classes avec des mthodes dfinies
dans dautres classes.

13
La gnricit

Sans conteste la gnricit constitue la fonctionnalit phare de .NET 2005 au niveau des langages.
Aprs avoir expos en quoi consiste la gnricit, nous examinerons les implications du support
de la gnricit, au niveau du langage C  2, du CLR et du framework. Sachez demble que les
types et les mthodes gnriques sont CLS compliant et que par consquent, ils sont supports
par tous les langages ciblant le CLR 2.0.

Un problme de C  1 et sa rsolution
par les types gnriques de C  2
Le problme du typage des lments dune collection en C  1
Supposons que nous ayons implmenter une classe Stack (pile en franais) qui permet dempiler et de dpiler des lments. Pour simplifier, nous considrons que la pile ne peut contenir
plus quun certain nombre dlments ce qui nous permet dutiliser en interne un tableau C  .
Voici une implmentation de la classe Stack qui satisfait ces contraintes :
Exemple 13-1 :
class Stack{
private object[] m_ItemsArray ;
private int m_Index = 0 ;
public const int MAX_SIZE = 100 ;
public Stack() { m_ItemsArray = new object[MAX_SIZE] ; }
public object Pop() {
if (m_Index ==0 )
throw new System.InvalidOperationException(
"Impossible de depiler un
el
ement dune pile vide.") ;

468

Chapitre 13 : La gnricit
return m_ItemsArray[--m_Index] ;
}
public void Push( object item ) {
if(m_Index == MAX_SIZE)
throw new System.StackOverflowException(
"Impossible dempiler un
el
ement sur une pile pleine.") ;
m_ItemsArray[m_Index++] = item ;
}
}

Cette implmentation soure de trois dfauts majeurs.

Premirement, les clients de la classe Stack doivent transtyper explicitement tout lment
obtenu partir de la pile. Par exemple :
...
Stack stack = new Stack() ;
stack.Push(1234) ;
int number = (int)stack.Pop() ;
...

Un deuxime problme moins flagrant se situe au niveau des performances. Il faut tre
conscient que lorsque lon utilise notre classe Stack avec des lments de type valeur, nous
ralisons implicitement une opration de boxing linsertion dun lment et une opration de unboxing la rcupration dun lment. Ce phnomne est mis en vidence par
la version IL du client ci-dessus :
L_0000:
L_0005:
L_0006:
L_0007:
L_000c:
L_0011:
L_0016:
L_0017:
L_0018:
L_001d:
L_0022:
L_0023:
L_0024:

newobj instance void Stack::.ctor()


stloc.0
ldloc.0
ldc.i4 1234
box int32
callvirt instance void Stack::Push(object)
nop
ldloc.0
callvirt instance object Stack::Pop()
unbox int32
ldind.i4
stloc.1
ret

Enfin, un troisime problme vient du fait que lon peut empiler des lments de types diffrents dans une mme instance de la classe Stack. Or, en gnral nous souhaitons avoir des
piles dlments qui partagent un mme type. Cette possibilit peut facilement mener des
problmes de transtypages qui ne sont dcouverts qu lexcution comme dans lexemple
ci-dessous :
...
Stack stack = new Stack() ;
stack.Push("1234");
int number = (int)stack.Pop() ; // Provoque une exception de type

Un problme de C  1 et sa rsolution par les types gnriques de C  2

469

// InvalidCastException.
...
Lorsquun problme de transtypage nest pas dtect la compilation mais quil provoque une
exception lexcution, on dit que le code nest pas type-safe. Or, dans le dveloppement logiciel
comme dans toute discipline, plus une erreur est dtecte tt dans le processus de production
moins elle est nuisible. Il faut donc dans la mesure du possible avoir du code type-safe puisque
celui-ci permet la dtection derreurs au plus tt, lors de la compilation.
Il est possible dimplmenter notre concept de pile dune manire type-safe. Nous pourrions en
eet dcider dimplmenter une classe StackOfInt pour dcrire une pile contenant des entiers,
une classe StackOfSring pour dcrire une pile contenant des chanes de caractres etc.
Exemple 13-2 :
class StackOfInt{
private int[] m_ItemsArray ;
private int m_Index = 0 ;
public const int MAX_SIZE = 100 ;
public StackOfInt(){m_ItemsArray = new int[MAX_SIZE];}
public int Pop() { /*...*/ return -1 ; }
public void Push(int item) { /*...*/ }
}
class StackOfString{
private string[] m_ItemsArray ;
private int m_Index = 0 ;
public const int MAX_SIZE = 100 ;
public StackOfString(){m_ItemsArray = new string[MAX_SIZE];}
public string Pop() {/*...*/ return null ; }
public void Push(string item) {/*...*/}
}
Bien quelle soit type-safe et quelle rsolve la fois le problme de transtypage et le problme
de performance cette solution nest clairement pas satisfaisante. Elle implique de la duplication
de code puisque la logique dune pile est implmente par plusieurs classes. Les consquences
sont plus de code maintenir et donc une baisse de la productivit.

Rsolution lgante du problme laide


dune classe gnrique de C  2
C  2 permet de rsoudre lgamment le problme de la section prcdente grce lintroduction
des types gnriques. Concrtement, nous pouvons implmenter une liste dlments de type T
en laissant la libert aux clients de spcifier le type T lorsquils instancient la classe. Par exemple :
Exemple 13-3 :
class Stack<T>{
private T[] m_ItemsArray ;
private int m_Index = 0 ;
public const int MAX_SIZE = 100 ;

470

Chapitre 13 : La gnricit
public Stack(){ m_ItemsArray = new T[MAX_SIZE] ; }
public T Pop(){
if (m_Index ==0 )
throw new System.InvalidOperationException(
"Impossible de depiler un
el
ement dune pile vide.") ;
return m_ItemsArray[--m_Index] ;
}
public void Push(T item) {
if(m_Index == MAX_SIZE)
throw new System.StackOverflowException(
"Impossible dempiler un
el
ement sur une pile pleine.") ;
m_ItemsArray[m_Index++] = item ;
}
}
class Program{
static void Main(){
Stack<int> stack = new Stack<int>();
stack.Push(1234) ;
int number = stack.Pop() ; // Plus besoin de casting.
stack.Push(5678) ;
string sNumber = stack.Pop() ; // Erreur de compilation :
// Cannot implicitly convert type int to string.
}
}

Cette solution ne soure daucun des problmes vus prcdemment :

Le client na plus besoin de transtyper un lment rcupr de la pile.

Cette solution est performante car elle nentrane aucune opration de boxing/unboxing.

Le client crit du code type-safe. Il na pas la possibilit davoir lexcution une pile dlments de types dirents. Dans notre exemple, le compilateur interdit toute insertion ou rcupration dun lment dun type dirent que int ou qui nest pas implicitement convertible en int.

Il ny a aucune duplication de code.

Comprenez bien que dans notre exemple la classe gnrique est Stack<T> alors que T est le type
qui paramtre notre classe gnrique. On dit que T est un type paramtre. On utilise parfois le
terme polymorphisme paramtr (parametric polymorphism en anglais) pour dsigner la gnricit.
En eet, notre classe Stack<T> peut prendre plusieurs formes (Stack<int>, Stack<string> etc).
Elle est donc polymorphe et paramtre par un type. Attention, il ne faut pas confondre ceci
avec le polymorphisme des langages objets qui permet de manipuler direntes formes dobjets
(i.e des objets instances de classes direntes) au travers dune mme interface.
En bref, la classe Stack<T> reprsente nimporte quelle pile alors quune pile dobjets est une
pile de nimporte quoi.

Vue densemble de la gnricit de C  2

471

Vue densemble de la gnricit de C  2


Possibilit pour un type dtre gnrique sur plusieurs types
Il peut tre utile de paramtrer un type par plusieurs types. C  2 prsente cette possibilit. Par
exemple, comme le montre lexemple ci dessous, il est possible dimplmenter une classe dictionnaire qui laisse la possibilit aux clients de choisir le type des cls et le type des valeurs :
class DictionaryEntry<K,V>{
public K Key ;
public V Value ;
}
class Dictionary<K,V>{
private DictionaryEntry<K,V>[] m_ItemsArray ;
public void Insert(DictionaryEntry<K,V> entry) {...}
public V Get(K key) {...}
...
}

Types gnriques ouverts et ferms


Un type gnrique (parfois aussi nomm type construit) est un type paramtr par un ou plusieurs
autres types. Par exemple Stack<T>, Stack<int>, Dictionary<K,V>, Dictionary<int,V>, Dictionary<int,string> et Stack<Stack<T>> sont des types gnriques.
Un type gnrique ferm (parfois aussi nomm type construit ferm) est un type gnrique pour lequel tous les types paramtres sont prciss : Par exemple Stack<int>, Dictionary<int,string>
et Stack<Stack<int>> sont des types gnriques ferms.
Un type gnrique ouvert (parfois aussi nomm type construit ouvert) est un type gnrique
pour lequel au moins un type paramtre nest pas prcis : Par exemple Stack<T>, Dictionary<int,V>, Dictionary<K,V>, et Stack<Stack<T>> sont des types gnriques ouverts.
Un type gnrique se compile en un seul type dans son assemblage. Concrtement, si lon analyse lassemblage qui contient le type gnrique ouvert Stack<T>, on saperoit que la compilation na produit quune seule classe indpendamment du fait quun client peut par exemple
utiliser les types gnriques ferms Stack<int>, Stack<bool>, Stack<double>, Stack<string>,
Stack<object> et Stack<IDispose>.
En revanche, lexcution, le CLR cre et utilise plusieurs versions de la classe Stack<T>. Plus
prcisment, le CLR utilise une mme version de Stack<T> commune tous les types paramtres rfrences et une version de Stack<T> pour chaque type paramtre valeur.

La gnricit de .NET vs. le mcanisme de templates de C++


C  C++ Ceux qui connaissent le C++ auront certainement rapproch les types gnriques
de C  aux templates de C++. Bien que ces fonctionnalits soient conceptuellement proches, la
prsente section exhibe une dirence fondamentale :

Les types gnriques ferms engendrs par les templates C++ sont produits par le compilateur C++ et sont contenus dans le composant produit de la compilation.

472

Chapitre 13 : La gnricit
Vue du code source
Une seule classe gnrique

Vue de lassemblage
Une seule classe gnrique
Compilateur C

class Stack<T> {
private T[] m_ItemsArray;
private int m_Index=0;
public const int MAX_SIZE=100;
public Stack() {...}
public void Push(T item) {...}
}
class Program
static void Main() {
Stack<int>
s1=
new Stack<int>();
Stack<bool>
s2=
new Stack<bool>();
Stack<double>
s3=
new Stack<double>();
Stack<string>
s4=
new Stack<string>();
Stack<object>
s5=
new Stack<object>();
Stack<IDisposable>
s6=
new Stack<IDisposable>();
...
}
}

Compilateur JIT du CLR

Vue lexcution
Plusieurs classes gnriques
Stack<T>
Program
Stack<int>

Stack<bool>

Utilise pour tous les


types rfrences, en
loccurence string,
object et IDisposable

Stack<double>

Figure 13 -1 : Interprtation dun type gnrique selon le contexte

Les types gnriques ferms engendrs par la gnricit .NET sont produits lexcution
par le compilateur JIT et le type gnrique sous-jacent nest prsent quen une seule version
dans lassemblage produit de la compilation.

Autrement dit, la notion de type gnrique ouvert existe en C  /.NET au niveau du code source,
du composant et du runtime alors quen C++, elle nexiste quau niveau du code source.
Cette remarque souligne clairement un atout la gnricit de C  puisque la taille des composants .NET est dautant rduite. Cela nest pas ngligeable puisque le phnomne de gonflement de la taille des composants C++, connu sous le nom de code-bloat, peut tre parfois trs
pnalisant (sans compter lavalanche davertissements produite par certains compilateurs C++).
En outre le modle de programmation par composant propos par .NET est plus puissant avec
cette implmentation de la gnricit puisquun type gnrique ouvert peut tre ferm par un
type dun autre composant.
Il peut y avoir cependant du code-bloat en .NET dans une moindre mesure. En eet, les types
gnriques ferms crs lexcution par le CLR ne sont jamais collects ni par le ramassemiettes ni par une autre entit du CLR. Ils rsident dans leurs domaines dapplication jusqu ce
que celui-ci soit dtruit. Dans certains cas rares rsolubles en dchargeant la main un domaine
dapplication, il peut donc y avoir un encombrement de la mmoire. Un bon point pour la gnricit en .NET est quun type gnrique ferm nest eectivement cr que le plus tard possible,
lorsquil va tre utilis pour la premire fois. De plus, il faut bien tre conscient que le nombre de
classes construites lexcution est forcment born par le nombre de classes gnriques fermes
utilises dans le code source.

Vue densemble de la gnricit de C  2

473

Un problme similaire plus gnant survient lorsque lon utilise loutil ngen.exe pour amliorer
les performances globales en eectuant le travail du compilateur JIT avant lexcution. Dans ce
cas, tous les types gnriques ferms mentionns dans votre code sources seront crs. Loutil
ngen.exe est dailleurs incapable de distinguer si certains types gnriques ferms mentionns
dans le code source ne seront jamais utiliss.

Visibilit dun type gnrique


La visibilit dun type gnrique est lintersection de la visibilit du type gnrique avec celles de
ses types paramtres. Si les visibilits des types C, T1, T2 et T3 sont toutes gales public alors la
visibilit du type C<T1,T2,T3> est public ; mais si la visibilit dun seul de ces types est private,
alors la visibilit du type C<T1,T2,T3> est private.
Le lecteur astucieux subodore dj que lon peut obtenir un type gnrique avec une visibilit
jusquici inconnue en C  mais connue du CLR qui est protected and internal (visible seulement dans les classes drives situes dans le mme assemblage, voir page 418). Sachez cependant quun tel type est forcment construit lexcution par le CLR, et cela nentranant donc
aucune incohrence dans la langage C  .
Exemple 13-4 :
internal class ClassInternal { }
public class ClassFoo{
protected class ClassProtected { }
public class ClassPublic<U,V> { }
// Le compilateur verifie que le type
// ClassPublic<ClassInternal,ClassProtected> nest pas utilis
e
// ailleurs que dans cette classe et dans ses classes d
eriv
ee
// definie dans le meme composant, mais vous ne pouvez fournir
// une autre visibilite que private pour ce champ.
private ClassPublic<ClassInternal,ClassProtected> foo ;
}

Structures et interfaces gnriques


En plus des classes gnriques, C  2 permet de dfinir des structures et des interfaces gnriques.
Ces possibilits najoutent pas de remarques particulires mis part le fait quun type ne peut
implmenter plusieurs fois la mme interface gnrique avec des types paramtres dirents.
Concrtement, le programme suivant ne compile pas :
Exemple 13-5 :
interface I<T> { void Fct() ; }
// Erreur de compilation :
// C<U,V> cannot implement both I<U> and I<V> because they
// may unify for some type parameter substitutions.
class C<U, V> : I<U>, I<V>{
void I<U>.Fct() { }
void I<V>.Fct() { }
}

474

Chapitre 13 : La gnricit

Possibilit de crer des alias sur le nom dun type gnrique ferm
La directive using peut tre utilise pour crer un alias sur le nom dun type gnrique ferm.
La porte dune telle directive est le fichier courant si elle est utilise hors de tous espace de
noms, sinon la porte est lintersection entre le fichier courant et lespace de noms dans lequel
lalias est dfini. Par exemple :
using Annuaire = Dictionary<TelephoneNumber, string>;
class TelephoneNumber { }
class Dictionary<K, V>{ }
...
Annuaire annuaire = new Annuaire();

Possibilit de contraindre un type paramtre


C  2 prsente la possibilit dimposer des contraintes sur un type paramtre dun type gnrique.
Sans cette possibilit, la gnricit de C  2 ne serait pratiquement pas exploitable. En eet, on ne
peut pratiquement rien faire avec un type paramtre sur lequel on ne connat rien. On ne sait
mme pas sil est instanciable (puisquil peut prendre la forme dune interface ou dune classe
abstraite). De plus, on ne peut pas appeler une mthode particulire sur une instance dun tel
type, on ne peut pas comparer les instances dun tel type etc.
Pour pouvoir utiliser un type paramtre au sein dun type gnrique, vous pouvez lui apposer
une ou plusieurs contraintes parmi trois sortes de contraintes :

La contrainte davoir un constructeur par dfaut.

La contrainte dimplmenter une certaine interface ou (non exclusif) de driver dune certaine classe.

La contrainte dtre un type valeur ou (exclusif) un type rfrence.

C  C++ Le mcanisme de template de C++ na pas besoin de contraintes pour exploiter les
types paramtres puisque les types paramtres sont forcment rsolus au moment de la compilation. Dans ce cas, toute tentative dutilisation dun membre absent est donc dtecte la
compilation.

La contrainte du constructeur par dfaut


Si vous souhaitez pouvoir instancier un type paramtre au sein dun type gnrique vous
navez pas dautre choix que de lui apposer une contrainte du constructeur par dfaut. Voici un
exemple qui illustre la syntaxe :
Exemple 13-6 :
class Factory<U> where U : new() {
public static U GetNew() { return new U() ; }
}
class Program {
static void Main(){
int i = Factory<int>.GetNew() ;
object obj = Factory<object>.GetNew() ;

Possibilit de contraindre un type paramtre

475

// Ici i vaut 0 et obj est une instance de object.


}
}

Contraintes de drivation
Si vous souhaitez utiliser certains membres des instances dun type paramtre au sein dun type
gnrique, vous devez lui apposer une contrainte de drivation. Voici un exemple qui illustre
la syntaxe :
Exemple 13-7 :
interface ICustomInterface { int Fct() ; }
class C<U> where U : ICustomInterface {
public int AutreFct(U u) { return u.Fct(); }
}
Vous pouvez apposer plusieurs contraintes dimplmentation dinterfaces et une contrainte de
drivation dune classe de base sur un mme type paramtre. Le cas chant, la classe de base
doit apparatre en premier dans la liste des types. Vous pouvez aussi utiliser conjointement la
contrainte du constructeur par dfaut avec une ou plusieurs contraintes de drivations. Dans ce
cas la contrainte du constructeur par dfaut doit apparatre en dernier :
Exemple 13-8 :
interface ICustomInterface1 { int Fct1() ; }
interface ICustomInterface2 { string Fct2() ; }
class BaseClass{}
class C<U>
where U : BaseClass, ICustomInterface1, ICustomInterface2, new() {
public string Fct(U u) { return u.Fct2() ; }
}
Vous ne pouvez pas utiliser une classe sealed ou une des classes System.Object, System.Array,
System.Delegate, System.Enum ou System.ValueType comme classe de base pour un type paramtre.
Vous ne pouvez pas non plus utiliser les membres statiques de T comme ceci :
Exemple 13-9 :
class BaseClass { public static void Fct(){} }
class C<T> where T : BaseClass {
void F(){
// Erreur de compilation : T is a type parameter,
// which is not valid in the given context.
T.Fct();
}
}
Un type utilis dans une contrainte de drivation peut tre un type gnrique ouvert ou ferm.
Illustrons cette possibilit avec linterface System.IComparable<T>. Rappelons que les types qui
implmentent cette interface peuvent voir leurs instances compares une instance de type T.

476

Chapitre 13 : La gnricit

Exemple 13-10 :
class C1<U> where U : System.IComparable<int> {
public bool Egaux(U u,int i) { return u.Equals(i) ; }
}
class C2<U> where U : System.IComparable<U> {
public int Compare(U u1,U u2) { return u1.CompareTo(u2) ; }
}
class C3<U,V> where U : System.IComparable<V> {
public int Compare(U u, V v) { return u.CompareTo(v) ; }
}
class C4<U, V> where U : System.IComparable<V>, System.IComparable<int>
{ public int Compare(U u, int i) { return u.CompareTo(i) ; }
}
Notez quun type utilis dans une contrainte de drivation doit avoir une visibilit gale ou
suprieure celle du type gnrique qui contient le type paramtre concern. Par exemple :
Exemple 13-11 :
internal class BaseClass{}
// Erreur de compilation : Inconsistent accessibility :
// constraint type BaseClass is less accessible than C<T>
public class C<T> where T : BaseClass{}
Pour pouvoir tre exploites dans un type gnrique, certaines fonctionnalits peuvent vous
obliger apposer certaines contraintes de drivation. Par exemple si vous souhaitez utiliser
un type paramtre T dans une clause catch, vous devez contraindre T driver de la classe
System.Exception ou dune de ses classes drives. De mme si vous souhaitez utiliser le motcl using pour disposer automatiquement une instance dun type paramtre, celui-ci doit tre
contraint dimplmenter linterface System.IDisposable. Enfin, si vous souhaitez utiliser le
mot-cl foreach pour numrer les lments dune instance dun type paramtre, celui-ci doit
tre contraint dimplmenter une des deux interfaces System.Collections.IEnumerable ou
System.Collections.Generic.IEnumerable<T>.
Notons enfin que dans le cas particulier o T est contraint dimplmenter une interface et T est
un type valeur, lappel dun membre de linterface sur une instance de T ne provoque pas de
boxing. Lexemple suivant met en vidence ce phnomne :
Exemple 13-12 :
interface ICompteur{
void Increment() ;
int Val{get;}
}
struct Compteur : ICompteur {
private int i ;
public void Increment() { i++ ; }
public int Val { get { return i ; } }
}
class C<T> where T : ICompteur, new() {
public void Fct(){

Les membres dun type gnrique

477

T t = new T() ;
t.Increment() ; // Modifie l
etat de t.
System.Console.WriteLine( t.Val.ToString() ) ;
// Modifie letat dune copie box
ee de t.
(t as ICompteur).Increment() ;
System.Console.WriteLine(t.Val.ToString()) ;
}
}
class Program {
static void Main() {
C<Compteur> c = new C<Compteur>() ;
c.Fct() ;
}
}
Ce programme ache :
1
1

La contrainte type valeur/type rfrence


La contrainte type valeur/type rfrence permet de contraindre un type paramtre tre un
type valeur ou un type rfrence. Cette contrainte, qui doit tre utilise en premier dans la liste
des contraintes sur un type paramtre donn, utilise les mots-cls struct pour contraindre un
type valeur et class pour contraindre un type rfrence. Attention, cette syntaxe peut prter
confusion puisque les classes reprsentent un sous ensemble des types rfrences (il y a aussi
les interfaces) et les structures reprsentent un sous ensemble des types valeurs (il y a aussi les
numrations). Cette contrainte peut tre utile dans certains cas particuliers o lon souhaite
utiliser des tests de nullit de rfrences (une instance de type valeur ne peut jamais tre nulle)
ou lorsque lon veut sassurer quun type paramtre utilis avec le mot-cl lock est de type rfrence.
Exemple 13-13 :
class C<U> where U : class, new () {
U u = new U() ;
void Fct(){ lock(u){ } }
}

Les membres dun type gnrique


Surcharge de mthode
Les proprits, les constructeurs, les mthodes et les indexeurs peuvent tre surchargs dans
une classe gnrique. Cependant il peut y avoir ambigut lorsquune certaine combinaison des
types paramtres amne plusieurs surcharges avoir une mme signature. Dans ce cas, la prfrence ira la surcharge qui a la signature avec le moins de types paramtres. Si une telle mthode
ne peut tre trouve, alors le compilateur met une erreur au niveau de lappel ambigu. Voici
un exemple pour clarifier ces rgles :

478

Chapitre 13 : La gnricit

Exemple 13-14 :
interface I1<T> {}
interface I2<T> {}
class C1<U> {
public void Fct1(U u){} // Cette fct ne peut
etre appel
ee si U est int.
public void Fct1(int i){}
public void Fct2(U u1, U u2){}
// Pas dambiguit
e.
public void Fct2(int i, string s){}
public void Fct3(I1<U> a){}
// Pas dambiguit
e.
public void Fct3(I2<U> a){}
public void Fct4(U a){}
// Pas dambiguit
e.
public void Fct4(U[] a){}
}
class C2<U,V> {
public void Fct5(U u, V v){}
// Possibilit
e dambiguit
e si
public void Fct5(V v, U u){}
// le type U = le type V.
public void Fct6(U u, V v){}
// Possibilit
e dambiguit
e si
public void Fct6(V v, U u){}
// le type U = le type V != int .
public void Fct6(int u, V v){}
public void Fct7(int u, V v){}
// Possibilit
e dambiguit
e si
public void Fct7(U u, int v){}
// le type U = le type V = int.
public void Fct8(U u, I1<V> v){} // Possibilit
e dambiguit
e
public void Fct8(I1<V> v, U u){} // par exemple pour c2<I1<int>,int>.
public void Fct9(U u1, I1<V> v2){} // Pas dambiguit
e.
public void Fct9(V v1, U u2){}
public void Fct10(ref U u){}
// Pas dambiguit
e.
public void Fct10(out V v){ v = default(V) ; }
}
class Program {
static void Main(){
C1<int> a = new C1<int>() ;
a.Fct1(34) ; // Appelle
Fct1(int i)
C2<int, int> b = new C2<int, int>() ;
b.Fct5(13, 14) ; // Erreur de compilation : This call is ambiguous.
b.Fct6(13, 14) ; // Appelle Fct6(int u, V v)
b.Fct7(13, 14) ; // Erreur de compilation : This call is ambiguous.
C2<I1<int>,int> c = new C2<I1<int>,int>() ;
c.Fct8(null,null) ; //Erreur de compilation:This call is ambiguous.
}
}

Les champs statiques


Lorsquun type gnrique contient un champ statique, celui-ci existe lexcution en autant de
versions quil y a de types gnriques ferms fabriqus partir du type gnrique concern. Cette
rgle sapplique indpendamment du fait que le type du champ statique est fonction dun type
paramtre ou pas. Cette rgle sapplique aussi indpendamment du fait que les types paramtres
des types gnriques ferms sont des types valeurs ou rfrences. Cette dernire remarque est

Les membres dun type gnrique

479

pertinente car le fait que les types gnriques ferms ayant des types paramtres rfrences se
partagent la mme implmentation lexcution amne se poser la question. Tout ceci est
illustr par lexemple suivant :
Exemple 13-15 :
using System ;
class C<T> {
private static int m_NInst = 0;
public C() { m_NInst++ ; }
public int NInst { get { return m_NInst ; } }
}
class Program {
static void Main() {
C<int>
c1 = new C<int>() ;
C<int>
c2 = new C<int>() ;
C<int>
c3 = new C<int>() ;
C<string> c4 = new C<string>() ;
C<string> c5 = new C<string>() ;
C<object> c6 = new C<object>() ;
Console.WriteLine( "NInst C<int>
: " + c1.NInst.ToString() ) ;
Console.WriteLine( "NInst C<string> : " + c4.NInst.ToString() ) ;
Console.WriteLine( "NInst C<object> : " + c6.NInst.ToString() ) ;
}
}
Ce programme ache :
NInst C<int>
: 3
NInst C<string> : 2
NInst C<object> : 1

Les mthodes statiques


Un type gnrique peut avoir des mthodes statiques. Dans ce cas il est obligatoire de rsoudre
les types paramtres lors de linvocation dune telle mthode. Par exemple :
Exemple 13-16 :
class C<T> {
private static T t ;
public static void ChangeState(T t_){ t = t_ ; }
}
class Program {
static void Main() {
C<int>.ChangeState(5);
}
}
La mthode statique Main(), point dentre dun programme, ne peut tre dans une classe gnrique.

480

Chapitre 13 : La gnricit

Le constructeur statique
Si un type gnrique contient un constructeur statique, celui-ci est appel par le CLR chaque
cration dun de ses types gnriques ferms. Nous pouvons exploiter cette proprit pour ajouter nos propres contraintes sur les types paramtres. Par exemple, on ne peut pas strictement
contraindre un type paramtre ne pas tre le type int. On peut donc profiter du constructeur
statique pour vrifier une telle contrainte comme ceci :
Exemple 13-17 :
using System ;
class C<T> {
static C() {
int a=0;
if( ((object) default(T) != null) && a is T )
throw new ArgumentException("Ne pas utiliser le type C<int>.") ;
}
}
Notez le test de la non nullit de la valeur par dfaut de T. En eet, lexpression (a is T) est
vrai lorsque T est le type object et lorsque T est le type int. Pour liminer le premier cas, nous
comptons sur le fait que lexpression (object)defaut(object) renvoie la valeur nulle.

Surcharge des oprateurs


Bien que cela puisse mener du code peu lisible, un type gnrique peut surcharger les oprateurs. Il ny a pas de remarques particulires concernant les oprateurs arithmtiques et les
oprateurs de comparaisons.
En revanche, lorsque lon dfinit un oprateur de conversion (i.e oprateur de transtypage) dun
type source Src vers un type destination Dest, le compilateur ne doit pas pouvoir trouver de relation dhritage entre les deux types au moment o le type gnrique est compil. Par exemple :
Exemple 13-18 :
class C<T>{}
class D<T> : C<T>{
public static implicit operator C<int>(D<T> val) {} // OK
// Erreur de compilation : D<T>.implicit operator C<T>(D<T>) :
// user-defined conversion to/from base class.
public static implicit operator C<T>(D<T> val) {}
}
class Program{
static void Main() {
D<int> dd = new D<int>() ; // OK
}
}
Une consquence du fait que lon peut redfinir certains oprateurs de conversion dans un type
gnrique est quil devient possible de redfinir certains oprateurs de conversions de types
prdfinis. Dans lexemple suivant, si le type paramtr U est le type objet nous redfinissons
loprateur implicite de conversion de D<object> vers object :

Les oprateurs et les types gnriques

481

class D<U> {
public static implicit operator U(D<U> val) { return default(U) ; }
}
Dans ce cas deux rgles sont appliques par le CLR :

Si une conversion implicite prdfinie existe du type Src vers le type Dest, alors toute redfinition (implicite ou explicite) de cette conversion est ignore.

Si une conversion explicite prdfinie existe du type Src vers le type Dest, alors toute redfinition de cette conversion est ignore. En revanche, les redfinitions implicites de la
conversion du type Src vers le type Dest sont utilises.

Les types encapsuls


Un type encapsul dans un type gnrique est implicitement un type gnrique. Les types paramtres du type gnrique encapsulant peuvent tre librement utiliss au sein du type encapsul.
Un type encapsul dans un type gnrique a la possibilit davoir ses propres types paramtres.
Dans ce cas il y aura un type encapsul gnrique ferm construit par le CLR pour chaque combinaison dirente utilise de lensemble des types paramtres.
Exemple 13-19 :
using System ;
class Outer<U>{
static Outer(){Console.WriteLine("Hello du .cctor de Outer.");}
public class Inner<V>{
static Inner(){Console.WriteLine("Hello du .cctor de Inner.");}
}
}
class Program{
static void Main() {
Outer<string>.Inner<int> a = new Outer<string>.Inner<int>();
Outer<int>.Inner<int> b = new Outer<int>.Inner<int>();
}
}
Ce programme ache :
Hello du .cctor de Inner.
Hello du .cctor de Inner.

Les oprateurs et les types gnriques


Utilisation des oprateurs dgalit, dingalit et de comparaison
avec une instance dun type paramtre
Les oprateurs dgalit et dingalit ne peuvent tre utilis avec une instance ou une rfrence
dun type paramtre T que dans les cas suivants :

482

Chapitre 13 : La gnricit

Si T a une contrainte de drivation dune classe ou T a une contrainte de type rfrence,


alors les oprateurs dgalit et dingalit peuvent tre utiliss entre une rfrence de type
T et nimporte quelle rfrence.

Si T na pas de contrainte de type valeur, alors les oprateurs dgalit et dingalit peuvent
tre utiliss entre une rfrence de type T et la rfrence null. Si T prend la forme dun type
valeur, le test dgalit sera faux et le test dingalit sera vrai.

Exposons ces rgles au moyen du programme suivant :


Exemple 13-20 :
class C<T,U,V> where T : class
where V :struct {
public void Fct1( T t , U u , V v , object o, int i) {
if (t == o) { } // OK
if (u == o) { } // Erreur de compilation
if (v == o) { } // Erreur de compilation
if (v == i) { } // Erreur de compilation
if (u == null) { } // OK
if (v == null) { } // Erreur de compilation
}
public void Fct2(T t1, U u1, V v1, T t2, U u2, V v2) {
if (t1 == t2) { } // OK
if (u1 == u2) { } // Erreur de compilation
if (v1 == v2) { } // Erreur de compilation
}
}
Les oprateurs de comparaisons ne peuvent jamais tre utiliss avec une instance ou une rfrence dun type paramtre T.

Loprateur typeof
Loprateur typeof utilis sur un type paramtre retourne linstance du type Type correspondant
la valeur courante du type paramtre.
Loprateur typeof utilis sur un type gnrique retourne linstance du type Type correspondant
lensemble des valeurs courantes des types paramtres.
Ce comportement nest pas flagrant puisque la proprit Name des types retourns nache pas
les noms des types paramtres.
Exemple 13-21 :
class C<T>{
public static void PrintTypes(){
System.Console.WriteLine( typeof(T).Name ) ;
System.Console.WriteLine( typeof(C<T>).Name ) ;
System.Console.WriteLine( typeof(C<C<T>>).Name ) ;
if(typeof(C<T>) != typeof(C<C<T>>))
System.Console.WriteLine("Malgr
e un nom similaire ce ne sont" +
" pas les m
emes instances de Type.") ;
}

Les oprateurs et les types gnriques

483

}
class Program {
static void Main() {
C<string>.PrintTypes() ;
C<int>.PrintTypes() ;
}
}
Ce programme ache :
String
C1
C1
Malgre un nom similaire ce ne sont pas les m
emes instances de Type.
Int32
C1
C1
Malgre un nom similaire ce ne sont pas les m
emes instances de Type.
Il nen est pas de mme si lon utilise la proprit FullName :
Exemple 13-22 :
...
public static void PrintTypes(){
System.Console.WriteLine( typeof(C<T>).FullName ) ;
System.Console.WriteLine( typeof(C<C<T>>).FullName ) ;
}
...
Ce programme ache :
C1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]]
C1[[C1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]], AsmTest, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null]]
C1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]]
C1[[C1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]], AsmTest, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null]]

Les mots cls params et lock


Un type paramtre peut tre utilis comme type pour un paramtre params de la signature
dune mthode dun type gnrique.
Le mot cl lock peut tre utilis avec une variable dun type paramtre. Cette possibilit prsente un danger lorsque le type paramtre est un type valeur. Il faut bien tre conscient que dans
ce cas, le mot cl lock naura aucun eet. Il est dailleurs assez surprenant que le compilateur
ne force pas un type paramtre utilis dans une clause lock avoir une contrainte qui le force
tre de type rfrence.

484

Chapitre 13 : La gnricit

Loprateur default
Dans lexemple de la pile, nous avons considr lopration Pop() sur une pile vide comme une
erreur dutilisation de la classe Stack<T> de la part du client (i.e une violation du contrat prsent par une pile). Nous aurions pu aaiblir le contrat et considrer cette opration comme un
vnement possible. Dans le premier cas, lancer une exception est le traitement adapt. Dans le
second, il serait plus judicieux de retourner un lment vide que le client interprtera comme :
il ny a plus dlment dans ma pile. Cependant nous ne connaissons rien du type T de llment
retourner. Si T est un type rfrence nous souhaiterions retourner une rfrence nulle alors
que si T est le type int nous souhaiterions peut tre retourner 0. Le mot cl default de C  2
permet dobtenir la valeur par dfaut dun type, i.e la rfrence nulle pour un type rfrence ou
un block de mmoire de la taille adquate mis 0 pour un type valeur.
Exemple 13-23 :
class Stack<T>{
...
public T Pop(){
if (m_Index == 0)
return default(T);
return m_ItemsArray[--m_Index] ;
}
...
}
La notion de type nullable constitue une manire plus lgante de dfinir la valeur par dfaut
dun type valeur.

Le transtypage (casting) et la gnricit


Les rgles de base
Dans la suite, nous supposerons que T est un type paramtre. Le compilateur C  2 accepte de :

Transtyper implicitement une instance dun type T (si T est de type valeur sinon une rfrence de type T) vers une rfrence de type objet. Si T est de type valeur, il y a une opration
de boxing.

Transtyper explicitement une rfrence de type objet vers une instance dun type T. Si T est
de type valeur, il y a une opration de unboxing.

Transtyper explicitement une instance dun type T vers une rfrence de type une interface
quelconque. Si T est de type valeur, il y a une opration de boxing.

Transtyper explicitement une rfrence de type une interface quelconque vers une instance
dun type T. Si T est de type valeur, il y a une opration de boxing.

Dans les trois derniers cas, si le transtypage est impossible, une exception de type InvalidCastException
est lance.

Dautres rgles de transtypage sajoutent si lon utilise des contraintes de drivations :

Le transtypage (casting) et la gnricit

485

Si T est contraint dimplmenter linterface I, vous pouvez transtyper implicitement une


instance de T en I ou en toute interface implmente par I et vice versa. Si T est de type
valeur, il y a une opration de boxing (ou de unboxing).

Si T est contraint de driver de la classe C, vous pouvez transtyper implicitement une instance de T en C ou en toute sous-classe de C et vice versa. Si une conversion propritaire
implicite existe de C vers un type A alors le compilateur accepte une conversion implicite
de T vers A. Si une conversion propritaire explicite existe de A vers C alors le compilateur
accepte une conversion explicite de A vers T.

Transtypage entre tableaux


Si T est un type paramtre dune classe gnrique et si T la contrainte de driver de C alors le
compilateur C  2 accepte de :

Transtyper implicitement un tableau de T en un tableau de C. Autrement dit, le compilateur


C  2 accepte de transtyper implicitement une rfrence de type T[] vers une rfrence de
type C[]. On dit que les tableaux de C  acceptent la covariance sur leurs lments.

Transtyper explicitement un tableau de C en un tableau de T. Autrement dit, le compilateur


C  2 accepte de transtyper explicitement une rfrence de type C[] vers une rfrence de type
T []. On dit que les tableaux de C  acceptent la contravariance sur leurs lments.

Ces deux rgles sont illustres par lexemple suivant :


Exemple 13-24 :
class C { }
class ClassGenerique<T> where T : C {
T[] arrOfT = new T[10] ;
public void Fct(){
C[] arrOfC = arrOfT;
T[] arrOfT2 = (T[]) arrOfC;
}
}
Il ny a pas de rgles quivalentes si T est contraint dimplmenter une interface I. En outre,
la covariance et la contravariance ne sont pas supportes sur les types paramtres dune classe
gnrique. Autrement dit, si la classe D drive de la classe B, il nexiste pas de conversion implicite
ou explicite entre une rfrence de type List<D> et une rfrence de type List<B>.

Les oprateurs is et as
Pour viter une exception de type InvalidCastException lorsque vous ntes pas certain dune
conversion de type impliquant un type paramtre T, il est conseill dutiliser loprateur is pour
tester si la conversion est possible et loprateur as pour tenter de raliser la conversion. Rappelons que loprateur as retourne la rfrence null si la conversion est impossible. Par exemple :

486

Chapitre 13 : La gnricit

Exemple 13-25 :
using System.Collections.Generic ;
class C<T> {
public void Fct(T t){
int i = t as int ; // Erreur de compilation :
// The as operator must be used with a reference type.
string s = t as string ;
if( s!= null ) { /*...*/ }
if( t is IEnumerable<int> ){
IEnumerable<int> enumerable = t as IEnumerable<int> ;
foreach( int j in enumerable) { /*...*/ }
}
}
}

Lhritage et la gnricit
Les dirents cas
Une classe non gnrique peut driver dune classe gnrique. Dans ce cas tous les types paramtres doivent tre rsolus :
class B<T> {...}
class D : B<double> {...}
Une classe gnrique peut driver dune classe gnrique. Dans ce cas il est optionnel de rsoudre tous les paramtres. En revanche il est obligatoire de rappeler toutes les contraintes sur
les types paramtres non rsolus. Par exemple :
class B<T> where T : struct { }
class D1<T> : B<T> where T : struct { }
class D2<T> : B<int> { } // Maladroit T est ici un type param
etre
// diff
erent.
class D3<U,V> : B<int> { }
Enfin, sachez quune classe gnrique peut driver dune classe non gnrique.

Redfinition dune mthode virtuelle dun type gnrique


Une classe gnrique de base peut avoir des mthodes abstraites ou virtuelles qui utilisent ou
non les types paramtres dans leur signature. Dans ce cas, le compilateur oblige les rcritures de
telles mthodes dans les classes drives utiliser les types paramtres adquates. Par exemple :
Exemple 13-26 :
abstract class B<T> {
public abstract T Fct(T t) ;
}
class D1 : B<string>{
public override string Fct( string t ) { return "hello" ; }

Les mthodes gnriques

487

}
class D2<T> : B<T>{
public override T Fct(T t) { return default (T) ; }
}
// Erreur de compilation :
// does not implement inherited abstract member B<U>.Fct(U)
class D3<T, U> : B<U> {
// Erreur de compilation : no suitable method found to override
public override T Fct(T t) { return default(T) ; }
}
On profite de lexemple pour souligner le fait quune classe gnrique peut aussi tre abstraite.
Cet exemple montre aussi le genre derreur de compilation que lon obtient lorsque lon nomme
maladroitement les types paramtres.
Il est intressant de noter que les types paramtres dune classe gnrique drive peuvent tre
utiliss dans le corps dune mthode virtuelle rcrite, mme si la classe de base nest pas gnrique.
Exemple 13-27 :
class B {
public virtual void Fct() { }
}
class D<T> : B where T : new(){
public override void Fct() {
T t = new T();
}
}
Toutes les rgles nonces dans la prsente section restent valables pour limplmentation
dinterfaces ventuellement gnriques, par des classes ou des structures ventuellement gnriques.

Les mthodes gnriques


Introduction
Quelle soit dfinie dans un type gnrique ou non, quelle soit statique ou non, une mthode a
la possibilit de dfinir ses propres types paramtres. chaque invocation dune telle mthode
un type doit tre fourni pour chaque type paramtre. On parle de mthode gnrique.
Les types paramtres propres une mthode ne sont utilisables que dans le scope de la mthode (i.e valeur de retour + liste des arguments + corps de la mthode). Dans la classe C2<T> de
lexemple suivant, il ny a pas de corrlation entre le type paramtre U de la mthode Fct<U>()
et le type paramtre U de la mthode FctStatic<U>().
Un type paramtre dune mthode peut avoir le mme nom quun type paramtre de la classe
qui dfinie la mthode. Dans ce cas, le type paramtre de la classe est cach dans le scope de la
mthode. Dans la mthode C3<T>.Fct<T>() de lexemple suivant, le type paramtre T dfini par
la mthode cache le type paramtre T dfini par la classe. Cette pratique est plutt maladroite
et le compilateur produit un avertissement lorsquil la dtecte.

488

Chapitre 13 : La gnricit

Exemple 13-28 :
class C1 {
public U Fct<U>(U u) { return u ; }
}
class C2<T> {
public U Fct<U>(U u) { return u ; }
public static U FctStatic<U>(U u) { return u ; }
}
class C3<T> {
// Avertissement de compilation : Type parameter T has same
// name as type parameter from outer type C3<T>.
public T Fct<T>(T t) { return t ; }
}
class Program {
static void Main() {
C1 c1 = new C1() ;
c1.Fct<double>(3.4);
C2<int> c2 = new C2<int>() ;
c2.Fct<double>(3.4);
c2.Fct<string>("hello");
C3<int> c3 = new C3<int>() ;
c3.Fct<double>(3.4) ;
}
}
Cette possibilit nest pas utilisable ni sur les oprateurs, ni sur les mthodes extern ni sur les
mthodes particulires que constituent les accesseurs des proprits, des indexeurs et des vnements.

Mthodes gnriques et contraintes


Une mthode gnrique peut dfinir toutes sortes de contraintes pour chacun de ses types paramtres. La syntaxe est identique celle de la dfinition de contraintes sur un type gnrique.
Exemple 13-29 :
class C {
public int Fct<U>(U u) where U : class, System.IComparable<U> ,new(){
if (u == null) return 0 ;
U unew = new U() ;
return u.CompareTo(unew) ;
}
}
Bien videmment, une mthode gnrique ne peut redfinir lensemble des contraintes dun
type paramtre dfini par sa classe.

Mthodes virtuelles gnriques


Les mthodes abstraites, virtuelles et dinterface peuvent tre gnriques. Dans ce cas, les rcritures de telles mthodes ne sont pas obliges de respecter le nom des types paramtres. Dans le

Les mthodes gnriques

489

cas dune rcriture dune mthode gnrique virtuelle ou abstraite qui a des contraintes sur
ses types paramtres, vous ne devez pas rcrire lensemble des contraintes. Dans le cas dune
implmentation dune mthode dinterface qui a des contraintes sur ses types paramtres, vous
devez rcrire lensemble des contraintes. Ces rgles sont illustres par lexemple suivant qui
compile sans erreurs ni avertissements :
Exemple 13-30 :
using System ;
abstract class B {
public virtual A Fct1<A, C>(A a, C c) { return a ; }
public abstract int Fct2<U>(U u) where U:class,IComparable<U>,new() ;
}
class D1 : B {
public override X Fct1<X, Y>(X x, Y y) { return x ; }
public override int Fct2<U>(U u) { return 0 ; }
}
interface I {
A Fct1<A, C>(A a, C b) ;
int Fct2<U>(U u) where U : class, IComparable<U>, new() ;
}
class D2 : I {
public X Fct1<X, Y>(X x, Y y) { return x ; }
public int Fct2<U>(U u) where U : class, IComparable<U>, new()
{ return 0 ; }
}

Infrence des types paramtres selon les types des paramtres


dune mthode gnrique
Lors de linvocation dune mthode gnrique, le compilateur C  2 a la possibilit dinfrer les
types paramtres dune mthode gnrique partir des types des paramtres fournis. Le fait de
fournir explicitement des types pour les types paramtres prvaut sur les rgles dinfrences.
Les rgles dinfrences ne tiennent pas comptes du type de la valeur de retour. En revanche le
compilateur est capable dinfrer un type paramtre partir du type des lments dun tableau.
Le programme suivant illustre tout ceci :
Exemple 13-31 :
class C {
public static U Fct1<U>() { return default(U) ; }
public static void Fct2<U>(U u) { return ; }
public static U Fct3<U>(U u) { return default(U) ; }
public static void Fct4<U>(U u1, U u2) { return ; }
public static void Fct5<U>(U[] arrayOfU) { return ; }
}
class Program {
static void Main() {
// Erreur de compilation : The type arguments for method

490

Chapitre 13 : La gnricit
// C.Fct1<U>() cannot be inferred from the usage.
string s = C.Fct1() ;
// Erreur de compilation : Cannot implicitly convert type
// System.IDisposable to string.
string s = C.Fct1<System.IDisposable>() ;
s = C.Fct1<string>() ; // OK
C.Fct2("hello") ; // Inf`ere : le type param`
etre U est string.
// Erreur de compilation : The type arguments for
// method C.Fct2<U>(U) cannot be inferred from the usage.
C.Fct2(null) ;
int i = C.Fct3(6) ; // Inf`
ere : le type param`
etre U est int.
double d = C.Fct3(6) ; // ATTENTION : Inf`
ere : le type param`
etre
// U est int et non pas double.
// Erreur de compilation : Cannot implicitly convert int
// to System.IDisposable.
System.IDisposable dispose = C.Fct3(6) ;
// Inf`ere le type param`etre U est string.
C.Fct4("hello", "bonjour") ;
// Erreur de compilation : The type arguments for method
// C.Fct4<U>(U,U) cannot be inferred from the usage.
C.Fct4(5, "bonjour") ;
C.Fct5(new int[6]) ; // Inf`
ere : le type param`
etre U est int.
}
}

Ambigut dans la grammaire de C  2


On trouve une ambigut dans la grammaire de C  2 car les caractres infrieur < et suprieur
> peuvent dans certains cas trs prcis tre interprts la fois comme la dfinition dune
liste de types paramtres et deux utilisations de loprateur de comparaison. Ce cas extrme est
illustr par lexemple suivant :
Exemple 13-32 :
class C<U,V> {
public static void Fct1() {
int U = 6 ;
int V = 7 ;
int Fct2 = 9 ;
Fct3(Fct2 < U, V > (20)) ; // Appelle Fct3(int)
Fct3(Fct2 < U, V > 20) ; // Appelle Fct3(bool,bool)
}
public static int Fct2<A, B>(int i) { return 0;}
public static void Fct3(int i) { return ; }
public static void Fct3(bool b1, bool b2) { return ; }
}
La rgle est que lorsque le compilateur rencontre un tel dilemne, il analyse le caractre situ
immdiatement aprs > . Si celui-ci est dans la liste suivante, alors le compilateur infre une
liste de type paramtre :

Les dlgus, les vnements et la gnricit


(

>

: ;

491

Les dlgus, les vnements et la gnricit


Introduction
Comme tous les types encapsuls, une dlgation (i.e une classe dont les instances sont des dlgus) peut exploiter les types paramtres du type qui lencapsule :
Exemple 13-33 :
class C<T> {
public delegate T GenericDelegate(T t);
public static T Fct(T t) { return t ; }
}
class Program {
static void Main() {
C<string>.GenericDelegate genericDelegate = C<string>.Fct ;
string s = genericDelegate("hello") ;
}
}
Une dlgation peut aussi dfinir ses propres types paramtres ainsi que leurs contraintes :
Exemple 13-34 :
public delegate U GenericDelegate<U>(U u) where U : class ;
class C<T> {
public static T Fct(T t) { return t ; }
}
class Program {
static void Main() {
GenericDelegate<string> genericDelegate = C<string>.Fct ;
string s = genericDelegate( "hello" ) ;
}
}

Dlgus gnriques et mthodes gnriques


Lors dune aectation dune mthode gnrique un dlgu gnrique, le compilateur C  2 est
capable dinfrer les types paramtres de la mthode gnrique partir des types paramtres du
dlgu gnrique. Cette possibilit est illustre par lexemple suivant :
Exemple 13-35 :
delegate void GenericDelegateA<U>(U u) ;
delegate void GenericDelegateB(int i) ;
delegate U
GenericDelegateC<U>() ;
class Program {
static void Fct1<T>(T t) { return ; }

492

Chapitre 13 : La gnricit
static T
Fct2<T>() { return default(T) ; }
static void Main() {
GenericDelegateA<string> d1 = Fct1 ; // Le compilateur inf
ere
// Fct1<string>.
GenericDelegateB d2 = Fct1 ; // Le compilateur inf
ere Fct1<int>.
GenericDelegateC<string> d3 = Fct2<string> ; // OK mais pas
// dinf
erence.
// Erreur de compilation : The type arguments for
// method Program.Fct2<T>() cannot be inferred from the usage.
GenericDelegateC<string> d4 = Fct2 ;
}
}

Comme lillustre cet exemple, il ny a jamais dinfrence sur les types paramtres dun dlgu
gnrique.

Contravariance, covariance, dlgus et gnricit


Nous exposons ici une nouvelle fonctionnalit des dlgus qui va nous tre utile par la suite.
En C  2, les dlgus supportent la contravariance sur leurs arguments et la covariance sur leur
type de retour. Cette possibilit est expose ci dessous :
Exemple 13-36 :
class Base { }
class Derived : Base { }
delegate Base DelegateType ( Derived d ) ;
class Program{
static Derived Handler ( Base b ) { return b as Derived ; }
static void Main() {
// Remarquez que la signature de la m
ethode Handler() ne
// satisfait pas la signature de la d
el
egation DelegateType.
DelegateType delegateInstance = Handler;
Base b = delegateInstance( new Derived() ) ;
}
}
Il est lgitime que ce programme compile. Rflchissez en terme de contrat :

La mthode Handler(Base) a un contrat moins contraignant sur ses entres que celui propos par la dlgation DelegateType(Derived). Une instance de DelegateType peut donc
rfrencer la mthode Handler() sans risque de downcasting illgal. Cest la contravariance.

La mthode Derived Handler() a un contrat plus contraignant sur ses sorties que celui propos par la dlgation Base DelegateType(). L aussi, une instance de DelegateType peut
donc rfrencer la mthode Handler() sans risque de downcasting illgal. Cest la covariance.

Supposons maintenant que ces possibilits de covariance et de contravariance nexistent pas


et supposons que nous souhaitons appeler la mthode Derived Handler(Base) par lintermdiaire dun dlgu de type Base delegate(Derived). Nous pourrions tout fait utiliser un
dlgu gnrique comme ceci :

Rflexion, attribut, IL et gnricit

493

Exemple 13-37 :
class Base { }
class Derived : Base { }
delegate B DelegateType<B,D>(D d) ;
class Program {
static Derived Handler(Base b){return b as Derived;}
static void Main() {
DelegateType<Base, Derived> delegateInstance = Handler ;
// La reference en entree est implictement cast
ee de Derived vers Base.
// La reference en sortie est implictement cast
ee de Derived vers Base.
Base b = delegateInstance( new Derived() ) ;
}
}

vnements et dlgus gnriques


Les dlgus gnriques peuvent tre utiles pour viter de dfinir de multiples dlgus pour
typer les vnements. Lexemple suivant montre quavec une seule dlgation gnrique, vous
pouvez typer tous les vnements qui prennent en paramtre un sender et un argument :
Exemple 13-38 :
delegate void GenericEventHandler<U,V>(U sender, V arg) ;
class Publisher {
public event GenericEventHandler<Publisher,System.EventArgs> Event ;
public void TriggerEvent() { Event(this, System.EventArgs.Empty) ; }
}
class Subscriber {
public void EventHandler(Publisher sender, System.EventArgs arg){}
}
class Program {
static void Main() {
Publisher publisher = new Publisher() ;
Subscriber subscriber = new Subscriber() ;
publisher.Event += subscriber.EventHandler ;
}
}

Rflexion, attribut, IL et gnricit


volution de la classe System.Type
Rappelons quune instance de System.Type sobtient soit avec loprateur typeof soit en appelant la mthode object.GetType(). En .NET 2005, une instance de System.Type peut rfrencer
un type gnrique ouvert ou ferm.

494

Chapitre 13 : La gnricit

Exemple 13-39 :
using System ;
using System.Collections.Generic ;
class Program {
static void Main() {
List<int> list = new List<int>() ;
Type type1 = list.GetType() ;
Type type2 = typeof(List<int>) ;
Type type3 = typeof(List<double>) ;
// type4 represente un type g
en
erique ouvert
Type type4 = type3.GetGenericTypeDefinition();
System.Diagnostics.Debug.Assert(type1 == type2) ;
System.Diagnostics.Debug.Assert(type1 != type3) ;
System.Diagnostics.Debug.Assert(type3 != type4) ;
}
}
La classe System.Type supporte de nouvelles mthodes et proprits ddies la gnricit :
public abstract class Type : System.Reflection.MemberInfo,
System.Runtime.InteropServices._Type,
System.Reflection.IReflect
{
// Utilise lors de la construction dun type avec lAPI Emit
// pour ajouter des types generics param`
etres.
public virtual System.Type MakeGenericType(
params System.Type[] typeArgs) ;
// Obtient les types param`etres quils soient ouverts ou ferm
es.
public virtual System.Type[] GetGenericArguments() ;
// Obtient la forme ouverte dun type g
en
erique
public virtual System.Type GetGenericTypeDefinition() ;
// Retourne true si contient des types param`
etres non pr
ecis
es.
public virtual bool IsGenericTypeDefinition { get ; }
// Retourne true si appelee sur un type g
en
erique ouvert.
// Contrairement `a IsGenericTypeDefinition la recherche dun type
// non precise se fait recursivement sur les tous les types
// param`etres precises.
public virtual bool ContainsGenericParameters { get ; }
// Retourne true si appelee sur un type qui est un type param`
etre
// dun type generique ou dune methode g
en
erique.
public virtual bool IsGenericParameter { get ; }
//--------------------------------------------------------------// Les membres suivants ne peuvent
etre appel
ee que sur les types
// pour lesquels IsGenericParameter est true
// Obtient la position (0 based) dun type param`
etre ouvert.
public virtual int GenericParameterPosition { get ; }
// Obtient la methode generique qui d
eclare le type param`
etre
// ouvert, null si non declaree dans une m
ethode g
en
erique.
public virtual System.Reflection.MethodBase DeclaringMethod { get ; }

Rflexion, attribut, IL et gnricit

495

// Obtient les contraintes de d


erivations dun type param`
etre ouvert.
public virtual System.Type[] GetGenericParameterConstraints() ;
// Obtient les contraintes autres que celles de d
erivation.
public virtual System.GenericParameterAttributes
GenericParameterAttributes { get ; }
...
}
Voici un exemple dutilisation de cette classe pour retrouver la dfinition dun type gnrique :
Exemple 13-40 :
using System ;
using System.Reflection ;
class Program {
static void WriteTypeConstraints(Type type ){
string[] results = new string[type.GetGenericArguments().Length] ;
foreach (Type t in type.GetGenericArguments()) {
if ( t.IsGenericParameter ) {
int pos = t.GenericParameterPosition ;
Type[] derivConstraints =
t.GetGenericParameterConstraints() ;
MethodBase methodBase = t.DeclaringMethod ;
GenericParameterAttributes attributes =
t.GenericParameterAttributes ;
results[pos] = " where " + t.Name + ":" ;
if ((GenericParameterAttributes.ReferenceTypeConstraint &
attributes) != 0 ) {
results[pos] += "class," ;
}
if((GenericParameterAttributes.
NotNullableValueTypeConstraint & attributes) != 0 ) {
results[pos] += "struct," ;
}
foreach (Type derivConstraint in derivConstraints) {
results[pos] += derivConstraint.Name + "," ;
}
if ((GenericParameterAttributes.
DefaultConstructorConstraint & attributes) != 0 ) {
results[pos] += "new()" ;
}
}
}
Console.WriteLine(type.Name) ;
foreach (string result in results)
if (result != null)
Console.WriteLine(result) ;
}
class Bar{}
class Foo : Bar, IDisposable{ public void Dispose() {} }

496

Chapitre 13 : La gnricit
class C<U, V>
where U : Bar, IDisposable, new()
where V : struct {}
static void Main() {
WriteTypeConstraints( typeof(C<Foo,int>) ) ;
WriteTypeConstraints(
typeof(C<Foo,int>).GetGenericTypeDefinition() ) ;
}
}

Ce programme ache :
C2
C2
where U:Bar,IDisposable,new()
where V:struct,ValueType,new()
On saperoit que la contrainte dtre un type valeur oblige le compilateur ajouter les
contraintes de drivation de ValueType et dimplmentation dun constructeur par dfaut.

volution des classes System.Reflection.MethodBase


et System.Reflection.MethodInfo
Les classes System.Reflection.MethodBase et System.Reflection.MethodInfo (qui drive de
MethodBase) ont volu de faon supporter le concept de mthode gnrique. Voici les nouveaux membres :
public abstract class MethodBase :
System.Reflection.MemberInfo,
System.Runtime.InteropServices._MethodBase {
// Retourne un tableau de types contenant les types param`
etres.
public virtual System.Type[] GetGenericArguments() ;
// Retourne true si contient des types param`
etres ouverts.
public virtual bool IsGenericMethodDefinition { get ; }
// Retourne true si appelee sur une m
ethode g
en
erique ouverte.
// Contrairement `a IsGenericMethodDefinition la recherche dun type
// non precise se fait recursivement sur les tous les types
// param`etres precises.
public virtual bool ContainsGenericParameters { get ; }
...
}
public abstract class MethodInfo :
System.Reflection.MemberBase,
System.Runtime.InteropServices._MethodInfo {
// Obtient une methode generique ferm
ee `
a partir dune m
ethode
// generique ouverte, en resolvant les types param`
etres `
a partir
// de typeArgs.
public virtual System.Reflection.MethodInfo MakeGenericMethod(
params System.Type[] typeArgs) ;
// Obtient une methode generique ouverte `
a partir dune m
ethode

Rflexion, attribut, IL et gnricit

497

// generique fermee.
public virtual System.Reflection.MethodInfo
GetGenericMethodDefinition() ;
...
}
Le programme suivant montre comment se lier tardivement une mthode gnrique et comment linvoquer aprs avoir rsolu les types paramtres :
Exemple 13-41 :
using System ;
using System.Reflection ;
class Program{
public class Bar{}
public class Foo : Bar, IDisposable{ public void Dispose() { } }
public static void Fct<U, V>()
where U : Bar, IDisposable, new()
where V : struct {
Console.WriteLine(typeof(U).Name);
Console.WriteLine(typeof(V).Name);
}
static void Main() {
Type typeProgram = typeof(Program) ;
MethodInfo methodGenericOpen = typeProgram.GetMethod("Fct",
BindingFlags.Static | BindingFlags.Public) ;
// Resoud les types param`
etres.
MethodInfo methodGenericClosed =
methodGenericOpen.MakeGenericMethod (
new Type[] { typeof(Foo), typeof(int) } ) ;
System.Diagnostics.Debug.Assert (
methodGenericClosed.GetGenericMethodDefinition() ==
methodGenericOpen ) ;
methodGenericClosed.Invoke (
null, // null car m
ethode statique -> pas dinstance
new object[0]) ; // new object[0] car pas de param`
etres
}
}
Ce programme ache :
Foo
Int32

Les attributs et la gnricit


Un attribut qui peut marquer une mthode quelconque peut aussi marquer une mthode gnrique.
Un attribut qui peut tagger un type quelconque peut aussi tagger un type gnrique.
Lnumration System.AttributeTargets a la nouvelle valeur GenericParameter qui permet
de prciser quun attribut peut tagger un type paramtre. Par exemple :

498

Chapitre 13 : La gnricit

Exemple 13-42 :
[System.AttributeUsage(System.AttributeTargets.GenericParameter)]
public class A : System.Attribute{}
class C<[A]U, V> { }
Une classe dattribut ne peut tre une classe gnrique.
Une classe gnrique ne peut driver directement ou indirectement de la classe System.
Attribute.
Une classe dattribut peut utiliser des types gnriques et dfinir des mthodes gnriques :
Exemple 13-43 :
class C<U, V> { }
public class A : System.Attribute{
void Fct1(C<int, string> c) { }
void Fct2<X>() { }
}

La gnricit et le langage IL
Le support de la gnricit implique des changements au niveau du CLR mais aussi, au niveau
du langage IL, au niveau du CTS et au niveau des mtadonnes contenues dans les assemblages.
Dans les corps des mthodes dun type gnrique ou dans le scope dune mthode gnrique, le
langage IL utilise la notation !x pour nommer un type paramtre situ en position x (0 indexe)
dans la liste des types paramtres.
De nouvelles instructions IL ont t rajoute tel que stelem.any ou ldelem.any pour laccs aux
lments dun tableau dlments dun type paramtre (elles viennent ainsi complter la famille
dinstructions stelem.i2, ldelem.i2, stelem.i4, ldelem.i4, stelem.ref, ldelem.ref...). Certaines instructions IL telles que stloc.x ou ldloc.x (qui permettent de manipuler les variables
locales) ne tenaient dj pas compte du type des valeurs manipules. Elles taient donc prtes
pour la gnricit et seule leur interprtation par le compilateur JIT a volu.
Seulement deux tables de mtadonnes sont rajoutes dans la liste des tables de mtadonnes
dun assemblage contenant des types ou des mthodes gnriques :

La table GenericParam permet de dcrire les types paramtres.


La table GenericParamConstraint permet de dcrire les contraintes de drivation.

Les contraintes type valeur/rfrence et constructeur par dfaut ne sont pas stockes dans un
attribut ou dans une table de mtadonnes quelconque. Elles sont tout simplement contenues
dans le nom des types paramtres dans le nom du type ou de la mthode gnrique. Ainsi les
classes suivantes...
class C1<T> where T : new() {...}
class C2<T> where T : class {...}
class C3<T> where T : struct {...}
...sont nommes en IL :
... C1<(.ctor) T> {...}
... C2<(class) T> {...}
... C3<(value type, .ctor, [mscorlib]System.ValueType) T> {...}

La gnricit et le framework .NET

499

La gnricit et le framework .NET


La srialisation et la gnricit
Il est possible de srialiser et dsrialiser une instance dun type gnrique. Dans ce cas, il est
obligatoire que la liste des types paramtres du type gnrique de lobjet srialis soit strictement
identique la liste des types paramtres du type gnrique de lobjet dsrialis. Par exemple :
Exemple 13-13 :
using System ;
using System.Runtime.Serialization ;
using System.Runtime.Serialization.Formatters.Binary ;
using System.IO ;
[Serializable]
public class C<T>{
private T m_t ;
public T t { get { return m_t ; } set { m_t = value ; } }
}
class Program{
static void Main() {
C<int> objIn = new C<int>() ;
objIn.t = 691 ;
IFormatter formatter = new BinaryFormatter() ;
Stream stream = new FileStream("obj.bin", FileMode.Create,
FileAccess.ReadWrite) ;
formatter.Serialize(stream, objIn) ;
stream.Seek(0, SeekOrigin.Begin) ;
C<int> objOut = (C<int>)formatter.Deserialize(stream) ;
// Ici, objOut.t est egale `
a 691.
// Cette ligne provoque lenvoi dune exception de type
// SerializationException.
C<long> objOut2 = (C<long>)formatter.Deserialize(stream) ;
stream.Close() ;
}
}

.NET Remoting et la gnricit


Il est possible de consommer une instance dun type gnrique ferm avec la technologie .NET
Remoting, que vous soyez en mode CAO ou WKO :
// d
efinition de la classe generique utilisable avec .NET Remoting
public class Serveur<T> : MarshalByRefObject{}
...
// cote serveur
RemotingConfiguration.RegisterActivatedServiceType(
typeof(Serveur<int>)) ;
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(Serveur<string>), "MonService",

500

Chapitre 13 : La gnricit
WellKnownObjectMode.SingleCall) ;
...
// cote client
RemotingConfiguration.RegisterActivatedClientType(
typeof(Serveur<int>), url) ;
RemotingConfiguration.RegisterWellKnownClientType(
typeof(Serveur<string>), url) ;

Si vous souhaitez utiliser les fichiers de configuration cot client ou cot serveur, il faut imprativement prciser les types paramtres utiliss :
// c
ote serveur
<service>
<activated
type="ServeurAssembly.Serveur[[System.Int32]],ServeurAssembly"/>
</service>
// c
ote client
<client url="...">
<activated
type="ServeurAssembly.Serveur[[System.Int32]],ServeurAssembly"/>
</client>
La syntaxe avec doubles crochets (double square brackets) permet de prciser une liste de type paramtres :
type="ServeurAssembly.Serveur[[System.Int32],[System.String]],
ServeurAssembly"
La classe System.Activator supporte aussi la gnricit. Sachez juste que lorsque vous utilisez
conjointement cette classe avec un type gnrique, vous ne pouvez pas utiliser les surcharges
des mthodes CreateInstance() et CreateInstanceFrom() dans lesquelles vous devez prciser
les noms des types dans des chanes de caractres.

Les collections et la gnricit


Lensemble des collections du framework .NET fait lobjet du chapitre 15. Cet ensemble a t
compltement revu en tenant compte des bnfices du support de la gnricit. Lespace de
noms System.Collections est support pour des raisons de compatibilit. Il ny a plus aucune
raison de prfrer un type de System.Collections un type de System.Collections.Generic.
En page 595 nous exposons un tableau de correspondance entre les types des deux espaces de
noms System.Collections et System.Collections.Generic.

Les domaines ne supportant pas la gnricit


Les notions de services web et de composants servis (i.e COM+, Entreprise Services) ne supportent pas le concept de type gnrique car ni les standards de dfinitions de service web, ni la
technologie COM ne supporte la gnricit.

14
Les mcanismes utilisables dans C 

Nous allons voir que C  permet de suspendre ponctuellement la gestion du code par le CLR
pour permettre aux dveloppeurs des accs mmoires directs laide de pointeurs. Ainsi avec
C  , vous pouvez raliser dune manire standard certaines optimisations qui jusquici ntaient
possibles que dans les environnements non grs tels que C++. Ces optimisations concernent
par exemple le traitement de donnes volumineuses en mmoire, tel que les bitmaps.
linstar des langages C++ et Java, C  propose un mcanisme de gestion dexceptions simple et
classique mais puissant.
Nous verrons enfin que le langage C  2 ajoute deux possibilits syntaxiques proches du concept
de programmation fonctionnelle qui, dans certains cas prcis, amliorent grandement la lisibilit du code.

Les pointeurs et les zones de code non vrifiable


C  C++ C++ ne connat pas la notion de gestion de code. Cela est un atout de C++, puisque
ceci permet dutiliser les pointeurs, donc dcrire du code trs proche de la machine, trs optimis.
Cela est aussi un dsavantage de C++, puisque lutilisation des pointeurs est pnible et dangereuse et allonge donc considrablement les dures de dveloppement et de maintenance.
C  Avant la plateforme .NET, 100% du code excut sous les systmes dexploitation Windows tait non gr. Concrtement, les excutables contenaient directement le code compatible
avec les instructions machines dun type de processeur (i.e du code en langage machine). Lintroduction du mode dexcution gr avec larchitecture .NET est une rvolution. En eet, les
principales causes de bug dicilement identifiables sont systmatiquement dtectes et signales, voire rsolues, par le CLR. Parmi ces causes citons :

Les dbordements daccs aux lments dun tableau. (Maintenant gr dynamiquement


par le CLR).

502

Chapitre 14 : Les mcanismes utilisables dans C 

Les fuites de mmoire (memory leak en anglais). (Maintenant gr pour la plupart, par le
ramasse-miettes).

Lutilisation dun pointeur invalide. Cette contrainte est rsolue dune manire radicale :
LA MANIPULATION DE POINTEURS EST INTERDITE EN MODE GR.

Cependant, lors de la prsentation du CTS en page 342 nous avons montr que le CLR sait
manipuler trois sortes de pointeurs :

Les pointeurs grs. Ces pointeurs peuvent pointer vers une donne contenue dans le tas des
objets grs par le ramasse-miettes. Ces pointeurs ne peuvent pas tre utiliss explicitement
par du code C  . Ils sont cependant exploits implicitement par le compilateur C  lorsque
ce dernier compile des mthodes avec des arguments out et ref.

Les pointeurs non grs de fonction. La section en page 272 expose lutilisation de ces pointeurs.

Les pointeurs non grs. Ces pointeurs peuvent pointer vers toute donne contenue dans
la zone dadressage utilisateur du processus. Le langage C  permet dutiliser cette sorte de
pointeur dans un mode dexcution spcialement prvu cet eet : le mode dexcution
non vrifiable (parfois nomm mode dexcution non protg). Le code IL mis par le compilateur C  correspondant aux zones o vous manipulez des pointeurs non grs contient
des instructions IL spciales. Leur eet sur la mmoire du processus ne peut tre vrifi par
le compilateur JIT du CLR (do le terme zone de code non vrifiable). En consquence, un
individu mal intentionn peut profiter des zones de code non vrifiables pour eectuer des
actions malicieuses. Pour pallier cette faiblesse, lexcution le CLR ne se permet dexcuter
du code non vrifiable que si ce code a la mta permission CAS SkipVerification.

Puisquil permet de manipuler directement la mmoire du processus par lintermdiaire de


pointeurs non grs, le code non vrifiable est particulirement utile pour optimiser certains
traitements de donnes volumineuses stockes dans des structures. Un exemple doptimisation
de traitement dimage, grce aux pointeurs, est disponible en page 699.

Option de compilation pour le mode dexcution non vrifiable


Le code non vrifiable doit tre utilis en toute connaissance de cause. Aussi, vous devez obligatoirement fournir loption de compilation /unsafe au compilateur csc.exe pour lui indiquer
que vous tes bien conscient que le code que vous demandez de compiler contient des zones qui
seront non vrifiables par le compilateur JIT. Visual Studio prsente la proprit de projet Build
 Allow unsafe code pour savoir sil doit utiliser cette option de compilation.

Dclaration dune zone de code non vrifiable


En C  le mot-cl unsafe annonce au compilateur une zone de code non vrifiable. Il peut tre
utilis dans trois situations :

Devant la dclaration dune classe ou dune structure. Dans ce cas le code de toutes les mthodes (statiques ou non) de la classe peut utiliser des pointeurs.

Devant la dclaration dune mthode (statique ou non). Dans ce cas les pointeurs peuvent
tre utiliss dans tout le corps de la mthode.

Manipulation des pointeurs en C 

503

lintrieur du corps dune mthode (statique ou non). Dans ce cas les pointeurs peuvent
tre utiliss dans le bloc de code signal. Par exemple :
unsafe{
...
}

Prcisons que si une mthode accepte au moins un pointeur en argument ou en retour, il faut
que la mthode (ou sa classe) soit non vrifiable, mais aussi que toutes les zones de codes appelant cette mthode soient non vrifiables.

Manipulation des pointeurs en C 


C++ C  La syntaxe et lutilisation des pointeurs sont identiques en C  et en C++, mis part
les points particuliers suivants : en C  , la dclaration int *p1,p2; fait que p1 est un pointeur
sur un entier et p2 est un entier.
Seuls certains types peuvent tre points.
En C  il est ncessaire dpingler en mmoire les objets auxquels on veut accder avec des pointeurs.
C  Chaque objet, quil soit de type valeur ou rfrence, admet une adresse mmoire qui le
localise physiquement dans le processus. Cette adresse nest pas ncessairement constante au
cours de la vie de lobjet puisque le ramasse-miettes peut dplacer physiquement les objets qui
sont stocks sur le tas.

Les types que lon peut pointer


Pour certains types, il existe un type dual, le type pointeur non gr du type concern. Une
variable de type pointeur est en fait ladresse dune variable du type concerne. Lensemble des
types qui autorisent lutilisation de pointeurs se limite tous les types valeur, mis part les
structures ayant au moins un champ de type rfrence. En consquence, seules les instances des
types suivants peuvent tre pointes :

les types primitifs,

les numrations,

les structures nayant aucun champ de type rfrence,

les pointeurs.

Dclaration des pointeurs


Un pointeur peut ne pointer sur rien du tout. Dans ce cas il est extrmement important
que sa valeur soit nulle (0). En eet la majorit des bugs dus aux pointeurs viennent de
pointeurs non nuls, qui ne pointent sur aucune donne valide.
La dclaration dun pointeur sur le type FooType se fait comme suit :

Chapitre 14 : Les mcanismes utilisables dans C 

504
FooType * pointeur ;
Par exemple :
long * pUnEntier = 0 ;
Notez que la dclaration ...
int * p1,p2 ;

...fait que p1 est un pointeur sur un entier et p2 est un entier.

Oprateurs dindirection et de drfrencement


En C  , on peut obtenir un pointeur sur une variable en utilisant loprateur dindirection &. Par
exemple :
long unEntier = 98 ;
long * pUnEntier = &unEntier;
On peut accder lobjet point par loprateur de drfrencement *. Par exemple :
long unEntier = 98 ;
long * pUnEntier = &unEntier ;
long unAutreEntier = *pUnEntier ;
// unAutreEntier vaut ici 98

Loprateur sizeof
Loprateur sizeof permet dobtenir la taille, en octets, dun type valeur. Cet oprateur ne peut
tre utilis quen mode non vrifiable. Par exemple :
int i = sizeof(int) // i vaut 4
int j = sizeof(double) // j vaut 8

Arithmtique des pointeurs


Un pointeur sur un type T peut tre modifi au moyen des oprateurs unaires ++ et -- .
Loprateur binaire - peut aussi tre utilis avec les pointeurs.

Loprateur ++ incrmente ladresse de sizeof(T) octets.

Loprateur -- dcrmente ladresse de sizeof(T) octets.

Loprateur - utilis entre deux pointeurs de mme type T, retourne une valeur de type
long. Cette valeur est gale la dirence doctets entre les deux pointeurs divise par sizeof(T).

Les oprateurs de comparaison peuvent tre utiliss sur deux pointeurs de mme type ou de
types dirents. Rappelons la liste des oprateurs de comparaison :
==

!=

<

>

<=

>=

Manipulation des pointeurs en C 

505

Casting de pointeurs
Les pointeurs en C  ne drivent pas de la classe Object. Les oprations de boxing et unboxing
(voir page 350) nexistent donc pas avec les pointeurs. Cependant les pointeurs supportent la
fois le transtypage (casting) explicite et implicite.
Les transtypages implicites se font de nimporte quel type pointeur vers le type de pointeur void*.
Les transtypages explicites se font de :

Nimporte quel type de pointeur vers nimporte quel type de pointeur.

Nimporte quel type de pointeur vers un des types sbyte, byte, short, ushort, int, uint,
long, ulong (attention, nous ne parlons pas ici des type sbyte*, byte* ,short* etc).

Un des types sbyte, byte, short, ushort, int, uint, long, ulong vers nimporte quel type de
pointeur.

Pointeurs de pointeurs
Notons la possibilit dutiliser un pointeur vers un pointeur (bien que celle-ci soit quasi inutile
en C  ). On appelle ceci un double pointeur. Par exemple :
long unLong = 98 ;
long * pUnLong = &unLong ;
long ** ppUnLong = &pUnLong ;
Il est trs important davoir une convention de nommage des pointeurs et des doubles pointeurs. En gnral, pour le nom dun pointeur on a un p minuscule comme premier caractre,
et pp pour un double pointeur.

Ncessit dpingler les objets en mmoire


C++ C  La notion dpinglage dun objet est absolument trangre au C++, puisquelle
dcoule du fait que la plateforme .NET confie la gestion du tas gr un ramasse-miettes.
C  En page 119 nous expliquons que le ramasse-miettes se rserve la possibilit de dplacer
physiquement les objets dont il a la responsabilit. Les objets grs par le ramasse-miettes sont
de type rfrence alors que les objets points sont de type valeur. Si un pointeur pointe vers un
champ de type valeur dune instance dun type rfrence, il y a potentiellement un problme,
car linstance de type rfrence peut tre dplace tout moment par le ramasse-miettes. Le compilateur oblige les dveloppeurs utiliser le mot-cl fixed afin de signaler au ramasse-miettes
de ne pas dplacer les instances de type rfrence qui ont un champ de type valeur point. La
syntaxe du mot-cl fixed est la suivante :
Exemple 14-1 :
class Article { public long Prix = 0;}
unsafe class Program {
unsafe public static void Main() {
Article article = new Article() ;
fixed (long* pPrix = &article.Prix){
// Ici pPrix peut etre utilis
e et lobjet article ne
// peut pas etre bouge par le ramasse-miettes.

Chapitre 14 : Les mcanismes utilisables dans C 

506

}
//Ici pPrix nexiste plus et lobjet article nest plus
epingl
e.
}
}
Si nous navions pas utilis le mot-cl fixed dans cet exemple, le compilateur aurait produit une
erreur car il sait dtecter que lobjet qui sera rfrenc par la rfrence article sera susceptible
dtre dplac lexcution.
On peut pingler plusieurs objets de mme type, dans une mme clause fixed. Si lon a besoin
dpingler des objets de types dirents il faut utiliser des clauses fixed imbriques.
Il faut pingler des objets le moins souvent possible, et pour le moins longtemps possible. En
eet, lorsque des objets sont pingls, le travail du ramasse-miettes est considrablement moins
ecace.
Les variables de type valeur dclares en tant que variables locales dans une mthode (statique
ou non) nont pas besoin dtre pingls puisquelles ne sont pas prises en compte par le ramassemiettes. En consquence il ne faut pingler que les objets de types pointables , dfinis en tant
que champs (statiques ou non) dune classe (et non dune structure).
En anglais le fait dpingler un objet se dit to pin an object .

Les pointeurs et les tableaux


C++ C  Les notions de pointeurs et de tableaux sont assez proches en C++, du fait que les
lments dun tableau sont contigus en mmoire. En C  cette similitude existe aussi.
C  En C  , les lments dun tableau dlments dun type pointable peuvent tre accds
au moyen de pointeurs. Nous rappelons quun tableau est une instance de la classe System.
Array et en tant que tel, est stock sur le tas gr par le ramasse-miettes. Voici un exemple qui
nous montre la fois la syntaxe mais aussi, un dpassement de limite (qui nest pas dtect ni
la compilation ni lexcution !) d lutilisation des pointeurs :
Exemple 14-2 :
using System ;
public class Program {
unsafe public static void Main(){
// Creation dun tableau `a quatre
el
ements.
int [] tableau = new int[4] ;
for(int i=0 ; i < 4 ; i++)
tableau[i] = i*i ;
Console.WriteLine("Affichage de 6
el
ements du tableau oups !!") ;
fixed( int *ptr = tableau )
for( int j = 0 ; j< 6 ; j++ )
Console.WriteLine( *(ptr+j) );
Console.WriteLine("Affichage de tous les
el
ements du tableau:") ;
foreach(int k in tableau)
Console.WriteLine(k) ;
}
}

Manipulation des pointeurs en C 

507

Voici lachage :
Affichage de 6 elements du tableau oups !!
0
1
4
9
0
2042318948
Affichage de tous les elements du tableau:
0
1
4
9
Notez quil est ncessaire dpingler seulement le tableau et non pas chacun des lments du
tableau. Ceci confirme le fait qu lexcution les lments de type valeur dun tableau sont
stocks dune manire contigu en mmoire.

Les tableaux fixs de taille fixe


C  2.0 permet de dclarer un champ de type tableau de taille fixe dlments de types primitifs
au sein dune structure. Pour cela il sut de dclarer le tableau avec le mot cl fixed et la structure avec le mot cl unsafe. Le type du champ nest alors pas System.Array mais un pointeur
vers le type primitif (i.e le champ TableauFixe est de type int* dans lexemple suivant) :
Exemple 14-3 :
unsafe struct Foo {
public fixed int TableauFixe[10];
public int Overflow ;
}
unsafe class Program {
unsafe public static void Main() {
Foo foo = new Foo() ;
foo.Overflow = -1 ;
System.Console.WriteLine(foo.Overflow) ;
foo.TableauFixe[10] = 99999 ;
System.Console.WriteLine(foo.Overflow) ;
}
}
Cet exemple ache ceci :
-1
99999
Comprenez bien que laccs TableauFixe[10] rfrence un onzime lment du tableau
puisque les lments sont zro indexs. Ainsi, nous aectons en fait la valeur 99999 lentier
Overflow.

Chapitre 14 : Les mcanismes utilisables dans C 

508

Rservation de mmoire sur la pile avec stackalloc


C++ C  C  permet, avec une nouvelle syntaxe, dallouer un tableau dlments de type
pointable sur la pile. Le rsultat est le mme quune allocation statique dun tableau en C++.
C  C  permet dallouer un tableau dlments de type pointable sur la pile. Le mot-cl
stackalloc est utilis cette fin, avec la syntaxe suivante :
Exemple 14-4 :
public class Program {
unsafe public static void Main(){
int * tableau = stackalloc int[100];
for( int i = 0 ; i< 100 ; i++ )
tableau[i] = i*i ;
}
}
Aucun des lments du tableau nest initialis. La responsabilit de linitialisation incombe au
dveloppeur. Sil ny a pas assez de mmoire sur la pile, lexception System.StackOverflowException
est lance.
La taille de la pile est relativement petite, et on ne peut y allouer des tableaux de plus de
quelques milliers dlments. La dsallocation dun tel tableau se fait implicitement lorsque la
mthode retourne.

Chane de caractres et pointeurs


Le compilateur C  accepte lobtention dun pointeur de type char partir dune instance de
la classe System.String. Vous pouvez exploiter cette possibilit pour vous aranchir du caractre immuable des chanes de caractres gres. Limmuabilit des chanes de caractres gres
permet de faciliter grandement la manipulation de chanes de caractres. Nanmoins, ceci peut
nuire aux performances. La classe System.StringBuiler ne prsente pas toujours une solution
convenable aussi il peut tre utile de pouvoir aller directement modifier les caractres dune
chanes. Lexemple suivant montre comment exploiter cette possibilit pour crire une mthode
qui met en majuscule les caractres dune chane :
Exemple 14-5 :
public class Program {
static unsafe void ToUpper(string str) {
fixed (char* pfixed = str)
for (char* p = pfixed ; *p != 0 ; p++)
*p = char.ToUpper(*p);
}
static void Main() {
string str = "Bonjour" ;
System.Console.WriteLine(str) ;
ToUpper(str) ;
System.Console.WriteLine(str) ;
}
}

Les exceptions et le traitement des erreurs

509

Les exceptions et le traitement des erreurs


La problmatique : Grer toutes les erreurs dans un programme
Les applications doivent faire face des situations exceptionnelles indpendantes du programmeur. Par exemple :

Accder un fichier qui nexiste pas ou plus.

Faire face une demande dallocation de mmoire alors quil ny en a plus.

Accder un serveur qui nest plus disponible.

Accder une ressource sans en avoir les droits.

Saisie dun paramtre invalide par lutilisateur (une date de naissance en lan 3000 par
exemple).

Ces situations, qui ne sont pas des bugs mais que lon peut appeler erreurs, engendrent cependant un arrt du programme, moins quelles ne soient traites. Pour les traiter on peut tester
les codes derreur retourns par les fonctions, mais ceci prsente deux inconvnients :

Le code devient lourd, puisque chaque appel une fonction est suivi de nombreux tests.
Les tests ne sont pas centraliss. Cela viole le principe de cohrence du code, trs important
en architecture logicielle.

Le programmeur doit prvoir toutes les situations possibles ds la conception du programme. Il doit aussi dfinir les ractions du programme et les traitements eectuer
pour chaque type derreur. Il ne peut pas simplement factoriser plusieurs types derreur en
un seul traitement.

En fait ces inconvnients sont majeurs, et il a fallu trouver une solution ce problme : cest la
gestion des exceptions.

Principe de la gestion des exceptions


C++ C  Au niveau de la gestion des exceptions, aucun principe majeur ne change par
rapport C++. En revanche, nous verrons que de nombreux dtails ont volu notamment parce
que les exceptions .NET sont compltement gres par le CLR et non par le systme dexploitation.
C  Voici les tapes dans la gestion dune exception :

Une erreur survient.

Le CLR, le code du framework .NET ou notre propre code construit un objet qui contient,
ventuellement, des paramtres descriptifs de lerreur. Le dtail de limplmentation dun
tel objet sera prsent un peu plus loin.

Lexception est lance. Elle est paramtre par lobjet.

Deux possibilits peuvent alors survenir ce moment :

Un gestionnaire dexception rattrape lexception. Il lanalyse et a la possibilit dexcuter du code, par exemple pour sauver des donnes ou avertir lutilisateur.

Aucun gestionnaire dexception ne rattrape lexception. Le programme se termine.

Chapitre 14 : Les mcanismes utilisables dans C 

510

Voici un exemple o une exception de type System.DivideByZeroException est lance : (notez


que la division par zro de nombres rels ne dclenche pas de lancement dune exception).
Exemple 14-6 :
public class Program {
public static void Main() {
int i = 1 ;
int j = 0 ;
int k = i/j ;
}
}
Le programme sarrte puisque lexception nest pas rattrape et la fentre de la Figure 14 -1 saffiche (que lassemblage ait t compil en mode Debug ou Release), vous proposant de dboguer votre programme :

Figure 14 -1 : Consquence dune exception non rattrape


Une fois que vous tes en mode Debug Visual Studio 2005 prsente un assistant trs pratique
pour visualiser les donnes relatives lexception :
Voici le mme exemple o lexception est rattrape par un gestionnaire dexception :
Exemple 14-7 :
using System ;
public class Program {
public static void Main(){
try {
int i = 1 ;
int j = 0 ;

Objet associ une exception et lancement de vos propres exceptions

511

Figure 14 -2 : Assistant de la gestion des exceptions


int k = i/j ;
} catch( System.DivideByZeroException ) {
Console.WriteLine("Une division enti`
ere par z
ero a eu lieu !") ;
}
}
}
Lexception tant rattrape, le programme ne sarrte pas et ache sur la console :
Une division enti`ere par zero a eu lieu !
La syntaxe pour dfinir un gestionnaire dexceptions ne fait donc appel qu deux mots-cls :
try, catch. Cette syntaxe est la mme que celle dautres langages comme C++, ADA ou Java.
Nous prcisons que lon peut imbriquer les blocs try/catch.

Objet associ une exception et lancement de vos propres


exceptions
C++ C  linstar du C++, en C  , une exception est reprsente par un objet. Lexception
peut tre lance avec la syntaxe throw new objet, qui existe aussi en C++. En C++, on avait la
possibilit de lancer une exception sans la matrialiser par un objet. En C  il est obligatoire
dallouer un nouvel objet pour chaque exception lance explicitement avec throw (sauf si le
lancement seectue dans une clause catch, auquel cas lexception couramment traite est renvoye).
En C  , cet objet est forcment instance dune classe drive de la classe System.Exception. Rappelons que C++ accepte nimporte quel type dobjet.

Chapitre 14 : Les mcanismes utilisables dans C 

512

De plus, en tant quobjet C  , ce dernier est obligatoirement allou dynamiquement (ce qui nest
pas le cas en C++).
C  Une exception est toujours reprsente par un objet. Cet objet contient en gnral des
informations de description relatives au problme qui a provoqu lexception. Cet objet est
obligatoirement une instance dune classe qui drive de la classe System.Exception. Il existe
deux types de classes drivant de System.Exception :

Celles fournies par C  /.NET qui sont lance par le systme, mais peuvent aussi tre lances
dans vos propres mthodes (par exemple System.DivideByZeroException vue prcdemment).

Celles que vous dfinissez vous-mme et qui ne peuvent tre lances que dans vos propres
mthodes.

La classe System.Exception
La classe System.Exception contient des proprits que vous pouvez utiliser pour vos propres
classes :

public string Message{get;}


Cette chane de caractres doit contenir un message descriptif de lexception. Cette proprit
est accessible en lecture seule, mais vous pouvez linitialiser en appelant le constructeur
de System.Exception acceptant une chane de caractres en paramtre (la proprit prend
alors la valeur de largument de ce constructeur).

public string Source{get;set;}


Cette chane de caractres contient le nom de lobjet ou de lapplication qui a gnr lerreur.

public string HelpLink{get;set;}


Cette chane de caractres contient une rfrence vers une page dexplication de lexception.
Vous pouvez vous en servir si vous mettez en ligne des informations sur vos propres exceptions.

public Exception InnerException{get;}


Cette proprit rfrence une exception. Elle est utilise lorsquune exception rattrape provoque le lancement dune nouvelle exception. La nouvelle exception rfrence lexception
rattrape au moyen de cette proprit.

Il est conseill dutiliser ces proprits mais ce nest pas une obligation. Par exemple, dans le cas
des exceptions qui instancient la classe System.DivideByZeroException :

La proprit Message contient la chane de caractres "Attempted to divide by zero.".

La proprit Source est une chane de caractres gale au nom de lassemblage do lexception est lance.

La proprit HelpLink est une chane de caractres vide.

Dautres proprits intressantes et initialises automatiquement par le CLR, sont disponibles


dans cette classe :

Objet associ une exception et lancement de vos propres exceptions

513

public string StackTrace{get;}


Contient la reprsentation des appels de mthodes sur la pile au moment o lexception a
t lance (la mthode la plus rcente apparat en premier). Il se peut que cette chane ne
contienne pas ce qui logiquement devrait y tre, cause des optimisations du compilateur
qui parfois modifie la structure du code. Si le programme a t compil en mode Debug,
cette chane contient aussi le numro de la ligne et le nom du fichier source de linstruction
qui a lanc lexception.

public MethodBase TargetSite{get;}


Retourne un objet de type MethodBase qui rfrence la mthode qui a lance lexception.

Dfinition de vos propres classes dexceptions


Comme nous lavons mentionn, nous pouvons fabriquer nos propres exceptions laide de
classes qui drivent de la classe System.Exception. En fait, il est plutt conseill de faire driver vos propres classes dexceptions de la classe System.ApplicationException (qui elle-mme
drive directement de la classe Exception). Voici par exemple la dfinition dune exception qui
pourrait tre utilise lorsquun argument entier dune fonction sort dun intervalle.
Exemple 14-8 :
using System ;
public class ExceptionArgEntierHorsLimite : ApplicationException {
public ExceptionArgEntierHorsLimite (int argVal,int inf,int sup):
base(string.Format(
"Largument {0} est hors de lintervalle [{1},{2}]",
argVal, inf, sup)){}
}
Nous aurions ventuellement pu sauver les trois valeurs entires dans trois champs.

Lancement dexceptions dans vos propres mthodes


Vous avez la possibilit de lancer une exception (propritaire ou non) avec le mot-cl C  throw.
Il est obligatoire de reprsenter une exception par un objet, sauf si lexception est lance dans
une clause catch, auquel cas lexception couramment traite est renvoye. Voici un exemple
dutilisation de throw avec notre propre classe dexception :
Exemple 14-9 :
using System ;
public class ExceptionArgEntierHorsLimite : ApplicationException{
public ExceptionArgEntierHorsLimite (int argVal,int inf,int sup):
base(string.Format(
"Largument {0} est hors de lintervalle [{1},{2}]",
argVal, inf, sup)){}
}
class Program {
static void f( int i ){
// Supposons que i doive
etre entre 10 et 50 (inclus).
if( i<10 || i>50 )

Chapitre 14 : Les mcanismes utilisables dans C 

514

throw new ExceptionArgEntierHorsLimite (i,10,50) ;


// Ici, nous sommes certains que i est dans le bon intervalle
}
public static void Main() {
try{
f(60) ;
}
catch( ExceptionArgEntierHorsLimite e ){
Console.WriteLine( "Exception : " + e.Message ) ;
Console.WriteLine( "Etat de la pile lors du lancement:"
+ e.StackTrace) ;
}
}
}
Lexcution de ce programme, lorsque ce dernier est compil en mode Debug, ache ceci :
Exception : Largument 60 est hors de lintervalle [10,50]
Etat de la pile lors du lancement :
at Program.f(Int32 i) in d:\mes documents\visual studio projects
\test_exception\program.cs:line 11
at Program.Main() in d:\mes documents\visual studio projects
\test_exception\program.cs:line 17
Lexcution de ce programme, lorsque ce dernier est compil en mode Release, ache ceci :
Exception : Largument 60 est hors de lintervalle [10,50]
Etat de la pile lors du lancement :
at Program.f(Int32 i)
at Program.Main()
Vous pouvez aussi lancer des exceptions dfinies par le framework .NET. Par exemple, on aurait
pu utiliser la classe System.ArgumentOutOfRangeException comme ceci :
Exemple 14-10 :
public class Program {
static void f(int i) {
if( i<10 || i>50 )
throw new System.ArgumentOutOfRangeException("i") ;
}
public static void Main() {
try{
f(60) ;
}
catch(System.ArgumentOutOfRangeException e){
System.Console.WriteLine("Exception : " + e.Message) ;
}
}
}
Ce programme ache ceci :

Le gestionnaire dexceptions et la clause finally

515

Exception: Specified argument was out of the range of valid values.


Parameter name: i

Pas dexceptions contrles en C 


Les dveloppeurs connaissant le langage Java peuvent stonner de labsence dexceptions contrles (aussi appele exception vrifie ou checked exception en anglais) en C  . Dans larticle
lURL http://www.artima.com/intv/handcuffsP.html Anders Hejlsberg un des concepteurs
principaux de C  , expose deux problmes potentiels engendrs par les exceptions contrles.
Ces problmes surviennent lorsquun grand nombre dAPI est appel et lorsque lon doit
maintenir une nouvelle version dune application. En labsence de solutions ne prsentant
pas ces problmes, les concepteurs du langage C  ont prfr ne pas fournir de mcanisme
pour contrler les exceptions.

Le gestionnaire dexceptions et la clause finally


C++ C  Les gestionnaires dexception du langage C  sont assez similaires ceux du langage C++ mis part les points suivants.
En C  , on nutilise pas la syntaxe catch(...) pour rattraper toutes les exceptions mais la syntaxe
catch(Exception e). Lavantage est que lon a ainsi un accs lexception.
La clause finally de C  nexiste pas en C++. Elle remplace le fait quen C++ on libre souvent les
ressources critiques dans des destructeurs dobjets statiquement allous. Ceci nest pas possible
en C  puisquon ne peut coder les destructeurs des types valeur. Un autre problme est quen
C  le moment de lappel dun destructeur est indtermin.

Remarques sur la clause catch


C  Un gestionnaire dexception peut contenir une ou plusieurs clauses catch, et/ou une
clause finally.
Dans le cas o vous avez plusieurs clauses catch, les types des exceptions des clauses doivent tre
dirents deux deux. De plus C  testera si le type de lexception rattraper correspond au type
dexception prcis dans la clause catch, de la premire clause catch la dernire. Au plus une
clause catch sera excute.
Notez quune exception lance, instance dune classe drive D de la classe de base B, matche
la fois la clause catch(D d) et la clause catch(B b). Les consquences de cette remarque sont
les suivantes :

Dans le mme gestionnaire dexception, le compilateur interdit les clauses catch de classes
drives de B, aprs la dfinition dune clause catch(B b). En eet les clauses catch des
classes drives de B nauraient aucune chance dtre excutes.

La clause catch(System.Exception) rattrape toutes les exceptions puisque toutes les classes
dexception drivent de la classe System.Exception.

Vous avez la possibilit davoir une clause catch vide. Vous navez qu crire le mot-cl
catch suivit directement de laccolade ouvrante du bloc catch. Ce type de clause est quivalent une clause catch(System.Exception).

Chapitre 14 : Les mcanismes utilisables dans C 

516

Si un gestionnaire dexception a une clause catch vide celle-ci est forcment en dernire position. Mme remarque pour une clause catch(System.Exception). Si ces deux clauses sont
prsentes, la clause catch vide doit tre en dernire position.

La clause finally
Si la clause finally est prsente, elle se trouve obligatoirement aprs toutes les clauses catch.
La clause finally est un bloc dinstructions excut dans tous les cas possibles qui sont :

Aucune exception na t lance dans le bloc try.


Une exception a t lance dans le bloc try et a t rattrape par un gestionnaire dexception
catch.
Une exception a t lance dans le bloc try et a t rattrape par un gestionnaire dexception
catch. Une exception est alors leve lors de lexcution de ce gestionnaire dexception.
Une exception a t lance dans le bloc try et na pas t rattrape par le gestionnaire dexception.

La clause finally est en gnral utilise pour librer des ressources critiques (une connexion
avec une base de donnes, un fichier ouvert, etc) indpendamment du fait quune exception
ait t lance ou pas. Notez que si une exception est lance dans un bloc finally (ce qui est dconseill) alors quune exception lance na pas pu tre rattrape par le gestionnaire dexception
courant, elle remplace cette dernire. Par exemple :
Exemple 14-11 :
using System ;
public class Program {
public static void Main(){
try {
try {
throw new ArgumentOutOfRangeException();
}
catch(DivideByZeroException e){
Console.WriteLine("Gestionnaire 1:") ;
Console.WriteLine("Exception: "+e.Message) ;
}
finally {
Console.WriteLine("finally 1") ;
throw new DivideByZeroException();
}
}
catch(Exception e) {
Console.WriteLine("Gestionnaire 2:") ;
Console.WriteLine("Exception: "+e.Message) ;
}
finally {
Console.WriteLine("finally 2") ;
}
}
}

Exceptions lances dans un constructeur ou dans la mthode Finalize()

517

Ce programme ache :
finally 1
Gestionnaire 2:
Exception: Attempted to divide by zero.
finally 2

Amliorer la smantique dune exception


Lorsquune exception remonte les dirents appels de mthodes imbriques, elle a tendance
perdre de sa signification car elle traverse plusieurs couches de code. Il est donc souvent ncessaire de rattraper lexception pour en relancer une nouvelle, dont le contenu dpendra de
lexception rattrape. Pour ne rien perdre de lexception initiale, vous avez la possibilit de la
rfrencer directement dans la nouvelle exception au moyen de la proprit InnerException
de la classe Sytem.Exception.

Exceptions lances dans un constructeur


ou dans la mthode Finalize()
C++ C  En C  une exception lance dans un constructeur non statique est traite exactement comme toutes les autres exceptions, et lobjet nest pas cr. On va sintresser au comportement du CLR lorsquune exception est lance partir des constructeurs statiques.
En C  , une exception lance dans la mthode Finalize() et qui est non rattrape dans le
constructeur, ne provoque pas de problmes majeurs, comme cest le cas en C++.

Exception lance dans un constructeur dinstance


C  Une exception lance dans un constructeur dinstance, est traite exactement comme
toutes les exceptions et lobjet nest pas cr. Cependant il reste dconseill de lancer une exception dans un constructeur. Voici un petit programme pour illustrer ceci :
Exemple 14-12 :
using System ;
public class Article {
public Article(){ i=3 ; throw new ArgumentOutOfRangeException(); }
public Article(int j){ i=j ; }
public int i=0 ;
}
public class Program {
public static void Main() {
Article article = new Article( 2 ) ;
try{
article = new Article() ;
}
catch(Exception e) {
Console.WriteLine("Exception: "+e.Message) ;

Chapitre 14 : Les mcanismes utilisables dans C 

518

}
Console.WriteLine(article.i) ;
}
}
Ce programme ache :
Exception: Specified argument was out of the range of valid values.
2
Comprenez bien que lallocation dune seconde instance de Article, dans le bloc try, a chou
du fait quune exception a t lance sans tre rattrape, dans le constructeur.

Exception lance dans un constructeur statique


ou lors de linitialisation dun champ statique
Lappel des constructeurs statiques et linitialisation des champs statiques est non dterministe,
cest--dire que les rgles qui rgissent le moment de leurs appels dpendent de limplmentation du langage et ne sont en gnral pas documentes. Plus de dtails ce sujet sont disponibles
page 431. Cependant, une exception peut tre lance dans ces mthodes et non rattrape. Dans
ce cas, tout se passe comme si une exception de type TypeInitializationException est lance
lendroit (non dtermin a priori) du programme qui a dclench lappel au constructeur
statique. En gnral cet endroit est la premire instanciation de cette classe ou le premier
accs un de ses membres statiques. Lexception qui a t lance dans le constructeur statique
est maintenant rfrence par le champ Exception.InnerException de lexception de type
TypeInitializationException. Ce comportement est automatiquement pris en charge par le
CLR. Voici un exemple pour illustrer ceci :
Exemple 14-13 :
using System ;
public class Article {
static Article() {
throw new ArgumentOutOfRangeException() ;
}
}
public class Program {
public static void Main() {
try{
Article article = new Article() ;
}
catch(Exception e) {
Console.WriteLine("Exception: " + e.Message) ;
Console.WriteLine("Inner Exception: "+e.InnerException.Message) ;
}
}
}
Ce programme ache :
Exception: The type initializer for "Article" threw an exception.
Inner Exception: Specified argument was out of the range of valid values.

Le CLR et la gestion des exceptions

519

Exception lance dans la mthode Finalize()


Une exception lance dans la mthode Finalize() et non rattrape dans cette mthode a
pour eet de faire sortir immdiatement le thread ddi lexcution des finaliseurs de cette
mthode. Cependant la mmoire alloue lobjet est quand mme rendue au systme par le
ramasse-miettes.
Si lexception a t lance dans la mthode Finalize() dune classe drive et non rattrape, le
finaliseur de la classe de base est quand mme automatiquement excut.
Il est trs fortement dconseill de lancer une exception dans la mthode Finalize().

Le CLR et la gestion des exceptions


Il faut bien comprendre que le CLR soccupe compltement de la gestion des exceptions .NET.
Il a notamment les responsabilits suivantes :

Le CLR a la responsabilit de trouver le gestionnaire dexception adquat (i.e le bloc catch()


adquat) en remontant la liste dappels imbriqus de mthodes qui se trouve dans la pile.
Pendant cette recherche, le CLR sauve plusieurs informations dans lobjet reprsentant lexception. Parmi ces informations, on trouve la pile dappels, qui reprsente lenchanement
des appels de mthodes. Ainsi le dveloppeur a sa disposition ces informations pour laider
comprendre les causes de lexception.

Lorsque lexception est lance dans un environnement distribu, le CLR se charge de la


srialiser et de la propager au domaine dapplication contenant le client. Soyez conscient
quil y a cependant quelques prcautions prendre si vous souhaitez faire transiter par .NET
Remoting vos propres types exceptions (plus dinformations ce sujet sont disponibles sur
cette page http://www.thinktecture.com/Resources/RemotingFAQ/CustomExceptions.
html).

Le CLR a parfois la responsabilit de rattraper une exception pour en relancer une autre,
considre comme plus significative. Par exemple nous avons vu dans la section prcdente
que le CLR propage une exception de type TypeInitializationException quel que soit le
type de lexception qui a t lance dans le constructeur statique. Notez que dans ce genre
de cas, lexception initialement lance est stocke dans la proprit InnerException de lexception. Cest aussi le cas des exceptions lances partir dune mthode appele avec un
lien tardif explicite.

En page 285 nous expliquons que le CLR transforme les codes derreur HRESULT retourns
par les mthodes des objets COM en exception gre. De mme, lorsquun objet gr est
considr comme un objet COM, le CLR produit un code derreur HRESULT partir dune
exception gre qui remonte dans le code natif. Nous verrons dans la prochaine section
comment le CLR se comporte avec le systme dexception natif de Windows.

En page 92nous expliquons que la classe AppDomain prsente lvnement dinstance


UnhandledException. Lexemple suivant montre que ces vnements peuvent tre exploits
pour excuter du code au moment o le CLR ralise que le processus courant va crasher
parce quune exception na pas t rattrape. Nous prcisons en commentaire le genre
daction faire avant de terminer le processus :

Chapitre 14 : Les mcanismes utilisables dans C 

520
Exemple 14-14 :

using System ;
using System.Threading ;
public class Program {
public static void Fct() {
try {
Console.WriteLine("Fct() Bloc try.") ;
throw new Exception("Hello from Fct()...") ;
} finally {
Console.WriteLine("Fct() Bloc finally.") ;
}
}
public static void Main() {
Console.WriteLine("Thread{0}: Hello world...",
Thread.CurrentThread.ManagedThreadId ) ;
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += UnhandledExceptionHandler;
try{
Console.WriteLine("Main() Bloc try.") ;
Fct() ;
}
catch{
Console.WriteLine("Main() Bloc catch.") ;
}
throw new Exception ("Bonjour...") ;
}
static void UnhandledExceptionHandler(
object s, UnhandledExceptionEventArgs e) {
Console.WriteLine("Thread{0}: UnhandledExceptionHandler: {1}",
Thread.CurrentThread.ManagedThreadId ,
(e.ExceptionObject as Exception).Message) ;
// a) Sauvegarder un rapport derreur.
// b) Proposer `a lutilisateur de sauvegarder l
etat courant.
// c) Proposer `a lutilisateur denvoyer automatiquement le
//
rapport derreur `a l
equipe de d
eveloppement.
}
}

Ce programme ache ceci avant de crasher :


Thread1: Hello world...
Main() Bloc try.
Fct() Bloc try.
Fct() Bloc finally.
Main() Bloc catch.
Thread1: UnhandledExceptionHandler: Bonjour...

En page 125 nous exposons des techniques propres au CLR permettant damliorer la fiabilit dune application susceptible de faire face des exceptions asynchrones.

Le CLR et la gestion des exceptions

521

Exceptions non gres


Typiquement, le thread Windows sous jacent un thread gr traverse du code gr (compil en
code natif) et du code natif. Lorsquune exception gre survient dans du code gr le CLR fait
en sorte de trouver le bloc catch adquat dans le code gr. Les zones de code natif ventuelles
qui se situent entre le dclenchement de lexception et la clause catch gre trouve sont alors
dpiles normalement.
Windows prsente un mcanisme dexception nomm Structured Exception Handling (SEH) pour
grer les exception natives qui surviennent. Une description dtaille de ce mcanisme est
disponible dans cet article de Matt Pietrek http://www.microsoft.com/msj/0197/exception/
exception.aspx.
Le CLR est capable de dtecter lorsquune exception native remonte dans du code gr. Dans
cette situation il produit alors une exception gre dont le type dpend du code de lexception
native :
Code de lexception native

Type de lexception gr

STATUS_FLOAT_INEXACT_RESULT

System.ArithmeticException

STATUS_FLOAT_INVALID_OPERATION
STATUS_FLOAT_STACK_CHECK
STATUS_FLOAT_UNDERFLOW
STATUS_FLOAT_OVERFLOW

System.OverflowException

STATUS_INTEGER_OVERFLOW
STATUS_FLOAT_DIVIDE_BY_ZERO

System.DivideByZeroException

STATUS_INTEGER_DIVIDE_BY_ZERO
STATUS_FLOAT_DENORMAL_OPERAND

System.FormatException

STATUS_ACCESS_VIOLATION

System.NullReferenceException

STATUS_ARRAY_BOUNDS_EXCEEDED

System.IndexOutOfRangeException

STATUS_NO_MEMORY

System.OutOfMemoryException

STATUS_STACK_OVERFLOW

System.StackOverflowException

Tous les autres code

System.Runtime.InteropServices.
SEHException

Le mcanisme SEH fonctionne avec un modle de filtres dexceptions enregistrs auprs des
threads. Ces filtres permettent de prciser Windows des fonctions natives appeler lorsquune
exception native est dtecte sur un thread. Le CLR enregistre son propre filtre sur chaque
thread excutant du code gr. La fonction native enregistre a la responsabilit de dclencher
lvnement AppDomain.UnhandledException. Si un thread na pas t cr par le CLR (i.e sil

Chapitre 14 : Les mcanismes utilisables dans C 

522

na pas t cr partir dun appel la mthode Thread.Start()) il y a toujours un risque que


les filtres dexceptions enregistrs sur ce thread par dautres composants viennent perturber le
comportement du filtre du CLR. Aussi, soyez conscient que dans cette situation lvnement
UnhandledException ne sera pas ncessairement dclench.

Les exceptions et lenvironnement Visual Studio


Lorsque lenvironnement Visual Studio dbogue une application, il ore la possibilit de suspendre lexcution en donnant la main au dbogueur lorsquune exception (gre ou non) est
lance ou lorsquelle nest pas rattrape. Ce paramtrage se fait en fonction du type de lexception. Pour accder cette fonctionnalit, il faut aller dans le menu Debug Exceptions.... La liste
des exceptions est divise en cinq catgories :

C++ Exceptions : liste les exceptions C++ non gres.

Common Language Runtime Exceptions : liste les exceptions gres (trier par espaces de
noms).

Managed Debugging Assistants : liste des vnements problmatiques connus du CLR qui
se traduisent parfois par une exception gre. Ainsi, si vous souhaitez confirmer quune telle
exception est la consquence dun tel vnement ou si vous souhaitez tre inform dun tel
vnement lorsquil ne se traduit pas par une exception vous devez vous servir de cette liste.
Chacun de ces vnements est expliqu dans les MSDN dans larticle Diagnosing Errors
with Managed Debugging Assistants.

Native Run-Time Checks : liste des exceptions critiques qui peuvent survenir dans un programme C/C++.

Win32 Exceptions : liste les code dexceptions SEH.

Visual Studio vous permet notamment dajouter vos propres types dexceptions dans une de ces
listes.

Conseils dutilisation des exceptions


Quand lancer une exception ?
Le mcanisme dexception est en gnral bien compris mais mal utilis. Le principe de base
est quune application qui fonctionne en mode normal ne devrait pas lancer dexception.
Cela repousse le problme sur la dfinition des situations anormales. Il y en a de trois types :

Celles qui surviennent cause dun problme denvironnement dexcution et qui peuvent
tre rgles par une modification de cet environnement (absence dun fichier de configuration, mauvais mot de passe, rseau indisponible, permissions restreintes etc). On parle
dexception applicative.

Celles qui surviennent cause dun problme denvironnement dexcution qui ne peut
tre rsolu. Par exemple, les applications gourmandes en mmoire telles que SQL Server
2005 peuvent tre limites par les 2 ou 3Go despace utilisateur dun processus Windows
32 bits. On parle dexception asynchrone du fait quune telle exception ne dpend pas de la
smantique de la portion de code qui la lev. Pour grer ce genre de problme vous devez
avoir recours des mcanismes complexes prsents en page 125. Cela revient considrer

Conseils dutilisation des exceptions

523

ces situations anormales comme normales ! Soyez conscient que seuls les gros serveurs
qui poussent le systme dans ses retranchement en utilisant au mieux toute la mmoire disponible devraient rencontrer des exceptions asynchrones et donc avoir recours ce type de mcanisme.

Celles qui surviennent cause dun bug et qui ne peuvent tre rgles que par une nouvelle
version corrigeant ce bug.

Que faire lorsque lon rattrape une exception ?


Lorsque vous rattrapez une exception vous pouvez envisager 3 scnarios :

Soit vous avez faire un rel problme mais vous pouvez y remdier en corrigeant les
conditions qui lont engendr. cette fin, vous pouvez avoir besoin de nouvelles informations (mot de passe invalide  redemander lutilisateur...).

Soit vous avez faire un problme que vous ne pouvez rsoudre ce niveau. Dans ce cas la
seule bonne attitude est de relancer lexception. Il se peut quil ny ait plus de gestionnaire
dexception appropri et dans ce cas vous dlguez la dcision prendre lhte du moteur
dexcution. Dans les applications consoles ou fentres ce dernier dcide de faire tomber
le processus tout entier. Notez que vous pouvez avoir recours lvnement AppDomain.
UnhandledException dclench dans cette situation de faon matriser la terminaison du
processus. Vous pouvez alors profiter de cette dernire chance pour sauver des donnes
( la Word) qui sans cela seraient dfinitivement perdues. Dans une application ASP.NET
un mcanisme de traitement des erreurs dcrit en page 904 est mis en place.

En thorie un troisime scnario est envisageable. Il se peut que lexception reue reprsente une fausse alerte. En pratique, ce cas narrive jamais.

Il ne faut pas rattraper une exception pour la loguer puis la relancer. Pour loguer les exceptions
et le code quelles ont traverses, nous vous conseillons davoir recours des techniques non
intrusives telles que lutilisation des vnements spcialiss de la classe AppDomain ou lanalyse
des appels de mthodes sur la pile au moment o lexception a t lance.
Il ne faut pas nettoyer les ressources que lon a allou lorsque lon rattrape une exception.
Dailleurs, soyez conscient quen gnral, seules les ressources non gres sont susceptibles de
vous poser des problmes (fuite de mmoire etc). Ce genre de code de libration de ressources
doit tre plac dans des clauses finally ou dans des mthode Dispose(). En C  , les clauses
finally sont souvent implicitement encapsules dans une clause using qui agit sur des objets
qui implmentent linterface IDisposable.

Quand rattraper une exception ?


Pour un type dexception donn, se poser cette question revient se demander quelle profondeur de mthode ce type dexception doit tre rattrap et quest ce qui doit tre fait. Nous
entendons par profondeur dune mthode, le nombre dappels imbriqus partir du point
dentr (en gnral la mthode Main()). Ainsi la mthode reprsentant le point dentre est la
moins profonde. Les rponses ces deux questions dpendent de la smantique dune exception.
Demandez-vous pour chaque type dexception quel est le niveau de votre code le plus adapt
pour pouvoir corriger les conditions qui lont dclenches et reprendre lexcution ou pour
terminer proprement lapplication.

Chapitre 14 : Les mcanismes utilisables dans C 

524

En gnral, plus la mthode est profonde, moins elle doit rattraper dexceptions propritaires.
La raison est que les exceptions propritaires ont souvent une signification dans le mtier de
votre application. Ainsi si vous dveloppez une bibliothque de classes, il faut laisser remonter
les exceptions significatives pour les applications clientes de la bibliothque.

Exceptions vs. code retour


Vous pourriez tre tent dutiliser les exceptions la place des codes de retour de vos mthodes,
pour signaler un ventuel problme. Il faut tre vigilant car lutilisation des exceptions soure
des deux inconvnients majeurs suivants :

Le code est trs peu lisible. En eet, pour comprendre le code il faut faire la main le travail du CLR qui consiste remonter les appels jusqu trouver un gestionnaire dexception.
Mme si vous sparez proprement votre code en couches dappel, le code reste peu lisible.

La gestion dune exception par le CLR est beaucoup plus coteuse que lanalyse dun code
de retour.

La rgle fondamentale nonce en dbut de section peut aussi vous aider dcider : une application qui fonctionne dans des conditions normales dexcution ne lance pas dexception.

Ne pas sous estimer les bugs dont les consquences sont rattrapes
Une utilisation abusive des exceptions arrive lorsque lon se dit que, puisquon rattrape toutes
les exceptions, celles provoques par dventuels bugs seront rattrapes aussi. On se dit quelles
ne provoqueront pas de plantage du programme. Ce raisonnement ne tient pas compte du fait
que les principales nuisances des bugs sont celles qui passent inaperues, comme des rsultats
indtermins, inattendus et faux.

Les mthodes anonymes


La section courante ainsi que la section suivante prsentent deux possibilits ajoutes la version 2 du langage C  . Contrairement aux gnriques, ces deux possibilits nimpliquent aucune
nouvelles instructions IL. Toute la magie se situe au niveau du compilateur.
Chacune de ces deux sections aura la mme structure : une prsentation classique de la fonctionnalit suivie de lanalyse du travail du compilateur pour enfin commenter des utilisations
avances.

Introduction aux mthodes anonymes de C  2


Commenons par un premier exemple de rcriture de code C  1 au moyen des mthodes anonymes de C  2. Voici du code compilable en C  1 qui montre comment rfrencer une mthode
laide dun dlgu :
Exemple 14-15 :
class Program {
delegate void DelegateType() ;
static DelegateType GetMethod(){
return new DelegateType(MethodBody) ;

Les mthodes anonymes

525

}
static void MethodBody() {
System.Console.WriteLine("Hello") ;
}
static void Main() {
DelegateType delegateInstance = GetMethod() ;
delegateInstance() ;
delegateInstance() ;
}
}
Voici le mme code rcrit en C  2 laide dune mthode anonyme :
Exemple 14-16 :
class Program {
delegate void DelegateType() ;
static DelegateType GetMethod() {
return delegate() { System.Console.WriteLine("Hello") ; };
}
static void Main() {
DelegateType delegateInstance = GetMethod() ;
delegateInstance() ;
delegateInstance() ;
}
}
Plusieurs remarques simposent :

Le mot-cl delegate a une nouvelle utilisation en C  2. Il est utilis au sein du corps dune
mthode pour indiquer au compilateur quil doit sattendre trouver le corps dune mthode anonyme.
On constate que lon peut assigner une mthode anonyme un dlgu.
On comprend le nom de mthode anonyme pour cette nouvelle fonctionnalit : La mthode dfinie au sein de GetMethod() na eectivement pas de nom. On peut cependant
linvoquer car elle est rfrence par un dlgu.

Notez aussi que la syntaxe dassignement de plusieurs mthodes un dlgu avec loprateur
+= est utilisable avec les mthodes anonymes :
Exemple 14-17 :
using System ;
class Program{
delegate void DelegateType() ;
static void Main(){
DelegateType delegateInstance = delegate() {
Console.WriteLine("Hello") ; };
delegateInstance += delegate() { Console.WriteLine("Bonjour") ; };
delegateInstance() ;
}
}

Chapitre 14 : Les mcanismes utilisables dans C 

526

Comme lon peut sen douter ce programme ache :


Hello
Bonjour

Mthodes anonymes et arguments


Comme le montre lexemple suivant, une mthode anonyme peut avoir des arguments. Tous
les types sont applicables, y compris les possibilits de passer des arguments par rfrence avec
le mot-cl ref et des arguments de sortie avec le mot-cl out :
Exemple 14-18 :
class Program {
delegate int DelegateType(int valTypeParam, string refTypeParam,
ref int refParam, out int outParam) ;
static DelegateType GetMethod() {
return delegate( int valTypeParam , string refTypeParam,
ref int refParam , out int outParam
) {
System.Console.WriteLine(
"Hello valParam:{0} refTypeParam:{1}",
valTypeParam, refTypeParam) ;
refParam++ ;
outParam = 9 ;
return valTypeParam ;
} ; // Fin du corps de la m
ethode anonyme.
}
static void Main() {
DelegateType delegateInstance = GetMethod() ;
int refVar = 5 ;
int outVar ;
int i = delegateInstance(1, "un", ref refVar, out outVar);
int j = delegateInstance(2, "deux", ref refVar, out outVar);
System.Console.WriteLine("i:{0} j:{1} refVar:{2} outVar:{3}",
i, j, refVar, outVar) ;
}
}
Ce programme ache :
Hello valParam:1 refTypeParam:un
Hello valParam:2 refTypeParam:deux
i:1 j:2 refVar:7 outVar:9
Soyez conscient que le type de la valeur de retour nest pas dfini par la mthode anonyme, mais
par le type du dlgu auquel elle est assigne. Celui-ci est toujours dfini car le compilateur
oblige assigner toutes mthodes anonymes un dlgu dont le type est connu la compilation. On en conclut quon ne peut assigner une mthode anonyme un dlgu rfrenc par
une rfrence de type System.Delegate.
La syntaxe C  avec le mot cl param nest pas utilisable dans la liste darguments dune mthode
anonyme. En eet, en interne le mot cl param oblige le compilateur marquer la mthode

Les mthodes anonymes

527

concerne avec lattribut ParamArrayAttribute. Or les mthodes anonymes ne peuvent supporter dattribut.
Exemple 14-19 :
using System ;
class Program {
delegate void DelegateType( params int[] arr ) ;
static DelegateType GetMethod() {
// erreur de compilation : param is not valid in this context.
return delegate(params int[] arr){ Console.WriteLine("Hello");} ;
}
}

Une subtilit syntaxique


Lorsque le mot-cl delegate est utilis sans parenthse lors de la dfinition dune mthode anonyme sans arguments dentre ni de retour, celle-ci peut tre assigne nimporte quel type de
dlgu. Bien entendu, le corps de la mthode anonyme ne peut alors pas utiliser les paramtres
dentre. En outre, une telle mthode anonyme ne peut rien retourner. En consquence, cette
syntaxe nest pas utilisable si la signature du dlgu cible retourne une valeur ou prend un
paramtre out :
Exemple 14-20 :
using System ;
class Program{
delegate void DelegateType(int valTypeParam, string refTypeParam,
ref int refParam);
static void Main(){
DelegateType delegateInstance = delegate {
Console.WriteLine("Hello") ; } ;
int refVar = 5 ;
delegateInstance(1, "un", ref refVar) ;
delegateInstance(2, "deux", ref refVar) ;
}
}

Les mthodes anonymes et la gnricit


Une mthode anonyme peut avoir des arguments de type gnrique. Par exemple :
Exemple 14-21 :
class Foo<T>{
delegate void DelegateType(T t) ;
internal void Fct(T t){
DelegateType delegateInstance = delegate(T arg){
System.Console.WriteLine("Hello arg:{0}",arg.ToString());} ;
delegateInstance(t) ;
}

Chapitre 14 : Les mcanismes utilisables dans C 

528

}
class Program{
static void Main(){
Foo<double> inst = new Foo <double>() ;
inst. Fct(5.5) ;
}
}
En C  2, les dlgations peuvent admettrent des arguments gnriques. Un dlgu instance
dune telle dlgation peut rfrencer une mthode anonyme. Il faut alors rsoudre les types
gnriques lors de la dfinition de la mthode anonyme, comme le montre lexemple suivant :
Exemple 14-22 :
class Program{
delegate void DelegateType<T>( T t );
static void Main() {
DelegateType<double> delegateInstance = delegate(double arg) {
System.Console.WriteLine( "Hello arg:{0}" , arg.ToString()) ;
} ;
delegateInstance(5.5) ;
}
}

Exemples simples dutilisation des mthodes anonymes


Lutilisation de mthodes anonymes est particulirement adapte la dfinition de petites
mthodes destines tre invoques au moyen dun dlgu. Par exemple, vous pouvez dfinir
au moyen dune mthode anonyme la procdure sur laquelle dmarrera un nouveau thread :
Exemple 14-23 :
using System.Threading ;
class Program{
static void Main(){
Thread thread = new Thread( delegate() {
System.Console.WriteLine("ManagedThreadId:{0} Hello",
Thread.CurrentThread.ManagedThreadId ) ;
} ) ;
thread.Start() ;
System.Console.WriteLine("ManagedThreadId:{0} Bonjour",
Thread.CurrentThread.ManagedThreadId ) ;
}
}
Ce programme ache :
ManagedThreadId:1 Bonjour
ManagedThreadId:3 Hello
Vous pouvez aussi dfinir au moyen dune mthode anonyme toute sorte de call-back, comme
la rponse aux vnements des contrles Windows Forms :

Le compilateur C  2 et les mthodes anonymes

529

Exemple 14-24 :
public class FooForm : System.Windows.Forms.Form{
System.Windows.Forms.Button m_Button ;
public FooForm(){
InitializeComponent() ;
m_Button.Click += delegate(object sender, System.EventArgs args){
System.Windows.Forms.MessageBox.Show("m_Button Clicked") ;
} ;
}
void InitializeComponent() {/*...*/}
}
En apparence les mthodes anonymes constituent une possibilit simple ajoute C  2. En apparence seulement : entrons dans les arcanes du compilateur C  2 pour saisir toute la puissance
de ce mcanisme.

Le compilateur C  2 et les mthodes anonymes


Le cas simple
Comme lon peut sen douter, lorsque le compilateur C  2 rencontre une mthode anonyme, il
cre une mthode dans la classe de la mthode qui lencapsule :
Exemple 14-25 :
class Program {
delegate void DelegateType() ;
static void Main() {
DelegateType delegateInstance = delegate() {
System.Console.WriteLine("Hello") ; } ;
delegateInstance() ;
}
}
Ainsi le compilateur C  2 produit lassemblage suivant partir du code prcdent (lassemblage
est visualis avec loutil Reflector dcrit en page 24) :
On vrifie bien quune mthode prive, statique et nomme <Main>b__0() a t cre dans la
classe encapsulante (i.e la classe Program) et contient le code de notre mthode anonyme. Une
mthode anonyme est une mthode dinstance si elle est dfinie dans le corps dune mthode
dinstance.
On note aussi que cette mthode est rfrence par le dlgu <>9_CachedAnonymousMethodDelegate1, instance de DelegateType, qui est un champ statique de la classe encapsulante.
Il est intressant de remarquer que le nom de cette mthode contient une paire de <>. De ce fait
le compilateur ne vous autorise pas linvoquer directement partir de votre code et lintellisense ne lache pas. En revanche le CLR est tout fait capable dutiliser une mthode avec un
tel nom.

Chapitre 14 : Les mcanismes utilisables dans C 

530

Figure 14 -3 : Cas simple de compilation dune mthode anonyme

Une mthode anonyme accde une variable locale de la mthode


qui lencapsule
Pour ne pas compliquer notre expos, nous navons pas encore mentionn le fait quune mthode anonyme a la possibilit daccder une variable locale de la mthode qui lencapsule.
Et cest bien l que les choses commencent tre intressantes. Analysons lexemple suivant :
Exemple 14-26 :
class Program {
delegate int DelegateTypeCompteur() ;
static DelegateTypeCompteur MakeCompteur(){
int compteur = 0;
DelegateTypeCompteur delegateInstanceCompteur =
delegate { return ++compteur; } ;
return delegateInstanceCompteur ;
}
static void Main() {
DelegateTypeCompteur compteur1 = MakeCompteur() ;
DelegateTypeCompteur compteur2 = MakeCompteur() ;
System.Console.WriteLine(compteur1()) ;
System.Console.WriteLine(compteur1()) ;
System.Console.WriteLine(compteur2()) ;
System.Console.WriteLine(compteur2()) ;
}
}
Ce programme ache :

Le compilateur C  2 et les mthodes anonymes

531

1
2
1
2
Cela peut laisser perplexe car la variable compteur locale la mthode MakeCompteur() semble
survivre linvocation de celle-ci. Cela contredit compltement la notion de variable locale
telle que nous la connaissons. Il semble quil existe deux instances de la variable locale compteur.
Comme nous lavons mentionn, en .NET 2.0 il ny a pas de nouvelles instructions IL pour la
gestion des mthodes anonymes. Ce mystre doit donc forcment provenir du compilateur. Une
analyse de lassemblage produit par le compilateur simpose :

Figure 14 -4 : Cas o une mthode anonyme accde une variable locale


Tout devient alors clair :

Contrairement la section prcdente, le compilateur ne se contente pas de crer une nouvelle mthode. Il cre une nouvelle classe nomme ici <>c__DisplayClass1.

Cette classe contient une mthode dinstance nomme <MakeCompteur>b__0() qui a le


corps de notre mthode anonyme.

Cette classe contient aussi un champ dinstance nomm compteur qui garde ltat de la variable locale du mme nom. On dit que la variable compteur est capture par la mthode
anonyme.

La mthode MakeCompteur() instancie la classe <>c__DisplayClass1 et initialise son champ


compteur.

Il est important de remarquer que la mthode MakeCompteur() na plus de variable locale


compteur. Elle utilise le champ compteur de la nouvelle instance de <>c__DisplayClass1.
Avant daller plus loin et dexpliquer pourquoi le compilateur a ce comportement pour le moins
inattendu, continuons minutieusement notre analyse.

Chapitre 14 : Les mcanismes utilisables dans C 

532

Variable locale capture et complexit du code


Lexemple suivant est plus subtil quil ny parait :
Exemple 14-27 :
using System.Threading ;
class Program {
static void Main() {
for (int i = 0 ; i < 5 ; i++)
ThreadPool.QueueUserWorkItem( delegate {
System.Console.WriteLine(i) ; }, null) ;
}
}
Ce programme ache dune manire non dterministe ceci :
0
1
5
5
5
On comprend alors que toute les instances de notre mthode anonyme partagent la variable locale i. Le comportement non dterministe vient du fait que la mthode Main() et nos
instances de notre mthode anonyme sont excutes par des threads dirents. Pour sen
convaincre voici le code dcompil de la mthode Main() :
private static void Main(){
bool flag1 ;
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1() ;
class1.i = 0 ;
goto Label_0030 ;
Label_000F:
ThreadPool.QueueUserWorkItem(new WaitCallback(class1.<Main>b__0), null) ;
class1.i++ ;
Label_0030:
flag1 = class1.i < 5 ;
if (flag1){
goto Label_000F ;
}
}
Notez enfin que la valeur 5 est obtenue lorsque la mthode anonyme est excute aprs que la
mthode Main() a finit dexcuter la boucle.
Pour obtenir un comportement dterministe, il sut de modifier ce programme comme ceci :
Exemple 14-28 :
using System.Threading ;
class Program {
static void Main() {

Le compilateur C  2 et les mthodes anonymes

533

for (int i = 0 ; i < 5 ; i++){


int j = i;
ThreadPool.QueueUserWorkItem(delegate {
System.Console.WriteLine(j) ; }, null) ;
}
}
}
Cette fois ci, le programme ache bien :
0
1
2
3
4
Ce comportement vient du fait que la variable locale j est capture chaque itration comme
le montre cette dcompilation de la mthode Main() :
private static void Main(){
Program.<>c__DisplayClass1 class1 ;
bool flag1 ;
int num1 = 0 ;
goto Label_0029 ;
Label_0004:
class1 = new Program.<>c__DisplayClass1() ;
class1.j = num1 ;
ThreadPool.QueueUserWorkItem(new WaitCallback(class1.<Main>b__0), null) ;
num1++ ;
Label_0029:
flag1 = num1 < 5 ;
if (flag1){
goto Label_0004 ;
}
}
En matire de capture de variable par une mthode anonyme, rien nest vident. Cette fonctionnalit est donc utiliser avec circonspection puisquelle peut complexifier la lisibilit de votre
code.
Notez enfin quune variable locale capture ntant plus une variable locale, tout code unsafe
qui accde une telle variable doit au pralable lavoir fixe avec le mot-cl fixed.

Une mthode anonyme accde un argument de la mthode qui


lencapsule
Les arguments dune mthode peuvent tre considrs comme des variables locales. Ainsi, C  2
autorise une mthode anonyme a utiliser des arguments dune mthode qui lencapsule. Tout
se passe comme si largument tait une variable locale. Par exemple :

Chapitre 14 : Les mcanismes utilisables dans C 

534
Exemple 14-29 :

using System ;
class Program {
delegate void DelegateTypeCompteur() ;
static DelegateTypeCompteur MakeCompteur(string compteurName) {
int compteur = 0 ;
DelegateTypeCompteur delegateInstanceCompteur = delegate{
Console.WriteLine(compteurName + (++compteur).ToString()) ;
} ;
return delegateInstanceCompteur ;
}
static void Main() {
DelegateTypeCompteur compteurA = MakeCompteur("Compteur A:") ;
DelegateTypeCompteur compteurB = MakeCompteur("Compteur B:") ;
compteurA() ;
compteurA() ;
compteurB() ;
compteurB() ;
}
}
Ce programme ache :
Compteur
Compteur
Compteur
Compteur

A:1
A:2
B:1
B:2

Deux restrictions existent cependant quant la capture dun argument par une mthode anonyme. Largument ne doit pas tre out ou ref. Cette consquence est logique puisquun tel argument ne peut plus tre considr comme une variable locale car il survit lexcution de
la mthode.

Une mthode anonyme accde un membre de la classe qui dfinit


la mthode qui lencapsule
Une mthode anonyme peut accder aux membres de la classe qui lencapsule. Dans le cas de
membres statiques il ny a aucun problme de comprhension. Un champ statique existe en
une seule version au sein dun domaine dapplication, et cest cette version qui est accde par
la mthode anonyme.
Pour bien saisir le cas o une mthode anonyme accde un membre dinstance de la classe
qui lencapsule, il sut de considrer la rfrence this comme une variable locale la mthode
dinstance qui dfinit la mthode anonyme (ce qui est dailleurs bien le cas). Par exemple :
Exemple 14-30 :
delegate void DelegateTypeCompteur() ;
class CompteurBuilder{
string m_Name ; // Un champ dinstance

Le compilateur C  2 et les mthodes anonymes

535

internal CompteurBuilder(string name) { m_Name = name ; }


internal DelegateTypeCompteur BuildCompteur(string compteurName) {
int compteur = 0 ;
DelegateTypeCompteur delegateInstanceCompteur = delegate {
System.Console.Write(compteurName +(++compteur).ToString()) ;
// On aurait pu ecrire this.m_Name.
System.Console.WriteLine(" Compteur fabriqu
e par:" + m_Name) ;
} ;
return delegateInstanceCompteur ;
}
}
class Program {
static void Main() {
CompteurBuilder cBuilder1
CompteurBuilder cBuilder2
DelegateTypeCompteur cA =
DelegateTypeCompteur cB =
DelegateTypeCompteur cC =
cA() ; cA () ;
cB() ; cB() ;
cC() ; cC() ;
}
}

= new CompteurBuilder("Fabrique1") ;
= new CompteurBuilder("Fabrique2") ;
cBuilder1.BuildCompteur("Compteur A:") ;
cBuilder1.BuildCompteur("Compteur B:") ;
cBuilder2.BuildCompteur("Compteur C:") ;

Ce programme ache :
Compteur
Compteur
Compteur
Compteur
Compteur
Compteur

A:1
A:2
B:1
B:2
C:1
C:2

Compteur
Compteur
Compteur
Compteur
Compteur
Compteur

fabrique
fabrique
fabrique
fabrique
fabrique
fabrique

par:Fabrique1
par:Fabrique1
par:Fabrique1
par:Fabrique1
par:Fabrique2
par:Fabrique2

Dcompilons la mthode BuildCompteur() pour bien montrer que la rfrence this est capture :
internal DelegateTypeCompteur BuildCompteur(string compteurName){
CompteurBuilder.<>c__DisplayClass1 class1 = new
CompteurBuilder.<>c__DisplayClass1() ;
class1.<>4__this = this ;
class1.compteurName = compteurName ;
class1.compteur = 0 ;
return new DelegateTypeCompteur(class1.<BuildCompteur>b__0) ;
}
La rfrence this ne peut pas tre utilise dans une mthode anonyme dont le type encapsulant
est une structure (i.e un type valeur). Voici lerreur gnre par le compilateur :
Les methodes anonymes definies dans des structures ne peuvent
acc`eder au membre this. Neanmoins, vous pouvez copier le membre
this dans une variable locale ext
erieure `
a la m
ethode anonyme
et lutiliser `a partir de celle-ci.

Chapitre 14 : Les mcanismes utilisables dans C 

536

Exemples avancs dutilisation des mthodes anonymes


Dfinitions : fermeture (closure en anglais)
et environnement lexical
Une fermeture (closure en anglais) est une fonction qui capture les valeurs des variables de
lenvironnement lexical au moment o elle est cre. Lenvironnement lexical dune fonction
dsigne lensemble des variables visibles lendroit o la fonction est dclare.
Notez bien lutilisation de au moment et de lendroit . Cela nous indique que la fermeture est un concept dynamique (i.e qui nexiste qu lexcution) alors que lenvironnement
lexical est un concept statique (i.e qui nexiste quavant la compilation).
Comprenez bien aussi que la fermeture ne concerne que les variables de lenvironnement lexical
qui sont eectivement utilises par la fonction.
La dfinition dune fermeture parle de cration de fonction. Dans les langages impratifs tels
que C, C++, C  1, Java ou VB.NET1 cette notion de cration de fonction est absente. Cette notion
nous vient des langages fonctionnels tels que Haskell ou Lisp o tout est fonction et o ltat des
variables locales des fonctions peut survivre lexcution dune fonction.
Avec ce que lon a vu concernant le travail du compilateur, la notion de mthode anonyme de
C  2 est bien une implmentation du concept de fermeture. C  2 est donc plus quun langage
impratif objet. Notez que ce nest pas la premire fois quun langage non fonctionnel intgre
la notion de fermeture puisque par exemple les langages Perl et Ruby supportent cette fonctionnalit.
C  C++ Le concept de fermeture se rapproche aussi du concept de foncteur ou de fonctionobjet du C++ dcrit en page 591.

Un peu plus loin dans la comprhension des fermetures


Lorsquune fonction est appele, elle calcule son rsultat en fonction des valeurs de ses arguments mais aussi en fonction du contexte dans lequel elle est invoque. Le contexte peut tre
vu comme un ensemble de donnes darrire plan. Ainsi, passer des arguments une fonction
revient mettre en avant certaines donnes cls essentielles lexcution de la fonction (i.e ses
arguments).
Dans un langage objet, pour une mthode dinstance ce contexte est en gnral ltat de lobjet
sur lequel elle est invoque (tat qui est accessible implicitement ou explicitement au travers
de la rfrence this). Pour une fonction crite en C, le contexte est dfini par les valeurs des
variables globales. Pour une mthode anonyme (i.e une fermeture), ce contexte est dfini par
les valeurs des variables de son environnement lexical au moment o elle est cre.
Ainsi, de mme que la notion de classe, la notion de fermeture est un moyen dassocier un comportement un tat. Dans les langages objets, on associe un ou plusieurs comportements (i.e
les mthodes dinstances) un tat (i.e les champs) en passant la rfrence this comme premier
argument de la mthode. Ceci nous est cach par le compilateur mais cest bien ce qui se passe.
Pour clarifier tout ceci :

On peut voir un objet comme un ensemble de donnes auquel des fonctions sont attaches
(par lastuce du passage de la rfrence this en premier argument).

Exemples avancs dutilisation des mthodes anonymes

537

On peut voir une fermeture comme une fonction auquel des donnes sont attaches (par
lastuce de la capture des valeurs des variables de lenvironnement lexical).

Utilisation des fermetures


Daprs la section prcdente, il semblerait que lon puisse utiliser une mthode anonyme la
place de certaines classes simples comportement unique. Cest bien ce que lon faisait dans nos
exemples prcdents en implmentant un compteur o le comportement est lincrment du
compteur et ltat la valeur du compteur. Dailleurs nous avons vrifi que la compilation dune
mthode anonyme qui capture un tat rsulte par la cration dune classe par le compilateur
qui na quune seule mthode.
Lexemple du compteur nexploite pas la possibilit de passer un argument une mthode anonyme. En ayant recours cette possibilit, nous pouvons concevoir une fermeture pour crer
un comportement paramtr qui agit sur les instances dune classe. Lexemple simple est un
multiplicateur paramtr dentiers :
Exemple 14-31 :
class Program {
delegate void DelegateMultiplicateur(ref int entierAMultiplier) ;
static DelegateMultiplicateur BuildMultiplicateur (
int multiplicateurParam){
return delegate(ref int entierAMultiplier) {
entierAMultiplier *= multiplicateurParam ;
} ;
}
static void Main() {
DelegateMultiplicateur multiplicateurPar8=BuildMultiplicateur(8) ;
DelegateMultiplicateur multiplicateurPar2=BuildMultiplicateur(2) ;
int entier = 3 ;
multiplicateurPar8(ref entier) ;
// Ici entier vaut 24.
multiplicateurPar2(ref entier) ;
// Ici entier vaut 48.
}
}
En ayant recours la possibilit dutiliser une valeur de retour pour la mthode anonyme, on
peut crer des comportements paramtrs qui calculent un rsultat en fonction dun objet. Par
exemple :
Exemple 14-32 :
using System ;
class Article {
public Article(decimal prix) { m_Prix = prix ; }
private decimal m_Prix ;
public decimal Prix { get { return m_Prix ; } }
}
class Program {

Chapitre 14 : Les mcanismes utilisables dans C 

538

delegate decimal DelegateTvaComputer(Article article) ;


static DelegateTvaComputer BuildTvaComputer(decimal tva){
return delegate(Article article){
return (article.Prix * (100 + tva)) / 100 ;
} ;
}
static void Main(){
DelegateTvaComputer tvaComputer19_6 = BuildTvaComputer(19.6m) ;
DelegateTvaComputer tvaComputer5_5 = BuildTvaComputer(5.5m) ;
Article article = new Article(97) ;
Console.WriteLine("Prix TVA 19.6% : "+tvaComputer19_6(article)) ;
Console.WriteLine("Prix TVA 5.5% : "+tvaComputer5_5(article)) ;
}
}
Comprenez bien que toute la puissance de lutilisation de fermetures dans les deux exemples
prcdents vient du fait quelles nous vitent de crer des petites classes (qui sont en fait crer
implicitement par le compilateur).

Dlgu et fermeture
En y regardant de plus prs, on saperoit que la notion de dlgu utilis sur une mthode
dinstance en .NET 1.1 est conceptuellement proche de la notion de fermeture. En eet, un
tel dlgu rfrence la fois des donnes (ltat de lobjet cible) et un comportement. Une
contrainte existe cependant : le comportement doit tre une mthode dinstance de la classe
dfinissant le type de la rfrence this.
Cette contrainte est aaiblie en .NET 2005. Grce certaines surcharges de la mthode Delegate.CreateDelegate() vous pouvez maintenant rfrencer le premier argument dune mthode statique dans un dlgu. Par exemple :
Exemple 14-33 :
class Program {
delegate void DelegateType(int writeNTime) ;
// Cette methode est declaree publique pour
eviter des
// probl`emes de reflexion sur membres non publics.
public static void WriteLineNTimes(string s, int nTime) {
for(int i=0;i<nTime;i++)
System.Console.WriteLine(s) ;
}
static void Main() {
DelegateType deleg = System.Delegate.CreateDelegate(
typeof(DelegateType),
"Bonjour",
typeof(Program).GetMethod("WriteLineNTimes")) as DelegateType ;
deleg(4) ;
}
}
Ce programme ache :

Les itrateurs avec C  1

539

Bonjour
Bonjour
Bonjour
Bonjour
Notez enfin quen interne limplmentation des dlgus a compltement t revue dans la
version 2.0 du framework et du CLR. La bonne nouvelle est que linvocation de mthodes au
travers de dlgus est maintenant beaucoup plus performante.

Mthodes anonymes et manipulation de collections


En page 592 nous montrons comment lutilisation de mthodes anonymes peut amliorer grandement la syntaxe ncessaire pour manipuler des collections.

Les itrateurs avec C  1


Comprendre les concepts dnumrable et dnumrateur
Avant de nous lancer dans un exemple dutilisation des itrateurs en C  1, il est ncessaire de
comprendre les concepts dnumrable et dnumrateur.
On dit dun objet quil est numrable sil constitue lui-mme une collection dobjets et si on
peut numrer cette collection au moyen du mot-cl foreach. Concrtement, une condition
susante (mais pas forcment ncessaire) pour quun objet soit numrable est quil implmente linterface System.Collections.IEnumerable. On dit dun objet que cest un numrateur sil implmente linterface System.Collections.IEnumerator. Voici la dfinition de ces
deux interfaces :
public interface System.Collections.IEnumerable{
System.Collections.IEnumerator GetEnumerator() ;
}
public interface System.Collections.IEnumerator{
object Current { get ; }
bool MoveNext() ;
void Reset() ;
}
En analysant ces interfaces, on comprend quun client qui veut numrer la collection dobjets
dtenue par un objet numrable, demande ce dernier un numrateur au moyen de la mthode IEnumerator IEnumerable.GetEnumerator(). Ensuite, le client peut utiliser les mthode
IEnumerator.MoveNext() et IEnumerator.Reset() sur lnumrateur retourn pour se dplacer
dans la collection dobjet. Lorsquil veut obtenir un objet, il lobtient en appelant laccesseur get
de la proprit Current. Dans un diagramme UML cela donne :
On vient en fait de dcrire exactement le design pattern itrateur dcrit dans louvrage Gof prsent en page 1031.

Un exemple
Voici un exemple dimplmentation des itrateurs en C  1. La classe Personnes joue le rle
dnumrable tandis que la classe PersonnesEnumerator joue le rle de lnumrateur. La classe

Chapitre 14 : Les mcanismes utilisables dans C 

540

IEnumerable
IEnumerator GetEnumerator()

IEnumerator
Client

EnumerableConcret

object Current
bool MoveNext()

EnumeratorConcret

IEnumerator GetEnumerator()

return new EnumeratorConcret();

Figure 14 -5 : Le design pattern itrateur


Personnes aurait pu jouer la fois le rle de lnumrable et de lnumrateur. Il aurait sut
quelle implmente en plus linterface IEnumerator. Il est prfrable disoler lnumrateur dans
une classe tierce ddie, pour pouvoir implmenter plusieurs numrateurs pour un mme
numrable :
Exemple 14-34 :
public class Personnes : System.Collections.IEnumerable {
private class PersonnesEnumerator : System.Collections.IEnumerator {
private int index = -1 ;
private Personnes P ;
public PersonnesEnumerator(Personnes P){ this.P = P ; }
public bool MoveNext() {
index++ ;
return index < P.m_Noms.Length ;
}
public void Reset() { index = -1 ; }
public object Current { get { return P.m_Noms[index] ; } }
}
// La methode GetEnumerator() de IEnumerable.
public System.Collections.IEnumerator GetEnumerator(){
return new PersonnesEnumerator(this) ;
}
string[] m_Noms ;
// Le constructeur qui initialise le tableau.
public Personnes(params string[] Noms){
m_Noms = new string[Noms.Length] ;
// Copie le tableau.
Noms.CopyTo(m_Noms, 0) ;
}
// Lindexeur qui retourne le Nom `
a partir de lindex.
private string this[int index]{
get { return m_Noms[index] ; }
set { m_Noms[index] = value ; }
}
}

Les itrateurs avec C  1

541

class Program {
static void Main() {
Personnes arrPersonnes = new Personnes(
"Michel", "Christine", "Mathieu", "Julien") ;
foreach (string s in arrPersonnes)
System.Console.WriteLine(s) ;
}
}
La syntaxe est un peu lourde au regard de lenvergure de la fonctionnalit. Il est bienvenu que
la syntaxe C  2 simplifie ce point. Ce programme ache :
Michel
Christine
Mathieu
Julien
Il est clair que le compilateur C  a interprt le mot-cl foreach comme ceci :
Exemple 14-35 :
...
class Program{
static void Main(){
Personnes arrPersonnes = new Personnes(
"Michel", "Christine", "Mathieu", "Julien") ;
System.Collections.IEnumerator e = arrPersonnes.GetEnumerator();
while (e.MoveNext())
System.Console.WriteLine((string)e.Current) ;
}
}

Plusieurs itrateurs sur une mme classe


En fait, lnumrable nest pas oblig dimplmenter linterface IEnumerable. On peut dlgu
cette responsabilit une classe tierce, PersonnesEnumerable par exemple. Voici le programme
prcdent rcrit :
Exemple 14-36 :
using System.Collections ;
public class Personnes // Nimpl
emente pas IEnumerable !
private class PersonnesEnumerator : IEnumerator {
...
}
private class PersonnesEnumerable : IEnumerable {
private Personnes m_Personnes ;
internal PersonnesEnumerable(Personnes personnes) {
m_Personnes = personnes ;
}
IEnumerator IEnumerable.GetEnumerator(){

Chapitre 14 : Les mcanismes utilisables dans C 

542

return new PersonnesEnumerator(m_Personnes) ;


}
}
public IEnumerable InOrder{ get {
return new PersonnesEnumerable(this) ; } }
...
}
class Program{
static void Main() {
Personnes arrPersonnes = new Personnes(
"Michel", "Christine", "Mathieu", "Julien") ;
foreach (string s in arrPersonnes.InOrder)
System.Console.WriteLine(s) ;
}
}
On saperoit que de cette faon il devient possible dimplmenter plusieurs numrateurs pour
notre classe Personnes. Par exemple un numrateur Reverse pour parcourir la collection de
personnes lenvers ou bien un numrateur Shuffle pour la traverser de faon alatoire ou
encore un numrateur EvenPosOnly pour la parcourir quen ne tenant compte des lment qui
ont des positions paires.

Problmes avec les itrateurs de C  v1.x


En plus dtre assez lourde et malgr le fait que le design pattern iterateur est appliqu la lettre,
limplmentation des itrateurs en C  1 est peu puissante. En eet, il devient vite dicile dimplmenter des itrateurs rcursifs pour des collections peine plus exotiques telles que des
arbres.

Les itrateurs avec C  2


Un premier exemple avec le mot-cl yield return
C  2 introduit un nouveau mot-cl yield return pour implmenter trs simplement le concept
ditrateur. Vous navez plus crer de classe spciale pour implmenter un numrateur ou un
numrable. Voici lExemple 14-34 rcrit :
Exemple 14-37 :
public class Personnes : System.Collections.IEnumerable{
string[] m_Noms ;
public Personnes(params string[] Noms){
m_Noms = new string[Noms.Length] ;
Noms.CopyTo(m_Noms, 0) ;
}
// La methode GetEnumerator() de IEnumerable.
public System.Collections.IEnumerator GetEnumerator(){
foreach (string s in m_Noms)
yield return s;

Les itrateurs avec C  2

543

}
}
class Program {
static void Main() {
Personnes arrPersonnes = new Personnes(
"Michel", "Christine", "Mathieu", "Julien") ;
foreach (string s in arrPersonnes)
System.Console.WriteLine(s) ;
}
}
Il est clair que la syntaxe est plus claire quen C  1. Cependant laction du mot-cl yield return
doit vous paratre trange. Comment yield return peut retourner un numrateur alors quil
semble retourner une chane de caractres ? Et puis, quelle est limplmentation dun tel numrateur puisque clairement, ce programme ne fournit explicitement aucune classe qui implmente IEnumerator ? Tout deviendra limpide lorsque lon procdera une analyse du travail du
compilateur C  2. Prsentons dabord ltendue des possibilits.
Une mthode peut parfaitement contenir plusieurs fois le mot-cl yield return. Ainsi, nous
pouvons aussi crire :
Exemple 14-38 :
public class Personnes : System.Collections.IEnumerable {
public System.Collections.IEnumerator GetEnumerator() {
yield return "Michel" ;
yield return "Christine" ;
yield return "Mathieu" ;
yield return "Julien" ;
}
}
class Program {
static void Main() {
Personnes arrPersonnes = new Personnes() ;
foreach (string s in arrPersonnes)
System.Console.WriteLine(s) ;
}
}
Daprs les deux exemples prcdents, il semble que lon peut considrer que le programme se
branche juste aprs la dernire instruction yield return chaque itration (et au dbut pour
la premire itration). Nous vrifierons que cette intuition est la bonne.

Les itrateurs et la gnricit


Il eut t dommage que lon continue utiliser la proprit object Current de linterface IEnumerator alors que la gnricit de C  2 permet dviter lutilisation du type object
pour signifier, nimporte quel type. Ainsi, le framework redfinit les interfaces IEnumerable et
IEnumerator avec la gnricit comme ceci :

Chapitre 14 : Les mcanismes utilisables dans C 

544

public interface System.Collections.Generic.IEnumerable<T> :


System.Collections.IEnumerable {
System.Collections.Generic.IEnumerator<T> GetEnumerator() ;
}
public interface System.Collections.Generic.IEnumerator<T> :
System.Collections.IEnumerator, System.IDisposable {
T Current { get ; }
}
On voit que les objets implmentant IEnumerator<T> sont disposables. En outre, ces deux interfaces implmentent chacune leurs homologues non gnrique. Grce ces interfaces gnriques, nous pouvons maintenant tre plus prcis et spcifier au compilateur que notre numrable contient une collection de chanes de caractres et non dobjets :
Exemple 14-39 :
using System.Collections.Generic ;
using System.Collections ;
public class Personnes : IEnumerable<string> {
string[] m_Noms ;
public Personnes(params string[] Noms) {
m_Noms = new string[Noms.Length] ;
Noms.CopyTo(m_Noms, 0) ;
}
IEnumerator<string> IEnumerable<string>.GetEnumerator() {
return PRIVGetEnumerator() ;
}
IEnumerator IEnumerable.GetEnumerator() {
return PRIVGetEnumerator() ;
}
private IEnumerator<string> PRIVGetEnumerator() {
foreach (string s in m_Noms)
yield return s ;
}
}
Il est dommage de devoir implmenter deux versions de la mthode GetEnumeror(). Cest une
consquence immdiate du fait que IEnumerable<T> implmente IEnumerable. Les ingnieurs
de Microsoft ont fait ce choix pour sassurer que tous les types numrables puissent tre utiliss sous une forme non gnrique. En eet, beaucoup dAPI ne prennent en paramtre quun
IEnumerable tandis que beaucoup dAPI ne retournent quun IEnumerable<T>. Sans cette astuce il ny aurait pas de conversion implicite dun IEnumerable<T> en un IEnumerable et les
dveloppeurs auraient du souvent prciser explicitement une telle conversion. Ce choix a donc
t fait pour privilgier ceux qui utilisent les classes numrables au dtriment de ceux qui les
dveloppent. Empiriquement, en tant que dveloppeur, vous vous retrouvez bien plus souvent
dans la premire situation que dans la deuxime.

Plusieurs itrateurs pour une mme classe conteneur


Comme en C  1, il est possible dimplmenter plusieurs numrateurs pour un mme numrable. Voici un exemple :

Les itrateurs avec C  2


Exemple 14-40 :
public class Personnes{
string[] m_Noms ;
public Personnes(params string[] Noms){
m_Noms = new string[Noms.Length] ;
Noms.CopyTo(m_Noms, 0) ;
}
public System.Collections.Generic.IEnumerable<string> Reverse {
get {
for (int i = m_Noms.Length - 1 ; i >= 0 ; i--)
yield return m_Noms[i] ;
}
}
public System.Collections.Generic.IEnumerable<string> PosPaires {
get{
for (int i = 0 ; i < m_Noms.Length ; i++,i++)
yield return m_Noms[i] ;
}
}
public System.Collections.Generic.IEnumerable<string> Concat {
get{
foreach (string s in Reverse)
yield return s ;
foreach (string s in PosPaires)
yield return s ;
}
}
}
class Program {
static void Main() {
Personnes arrPersonnes = new Personnes(
"Michel", "Christine", "Mathieu", "Julien") ;
System.Console.WriteLine("-->It
erateur Reverse") ;
foreach (string s in arrPersonnes.Reverse)
System.Console.WriteLine(s) ;
System.Console.WriteLine("-->It
erateur PosPaires") ;
foreach (string s in arrPersonnes.PosPaires)
System.Console.WriteLine(s) ;
System.Console.WriteLine("-->It
erateur Concat") ;
foreach (string s in arrPersonnes.Concat)
System.Console.WriteLine(s) ;
}
}
Ce programme ache :
-->Iterateur Reverse
Julien
Mathieu

545

Chapitre 14 : Les mcanismes utilisables dans C 

546
Christine
Michel
-->Iterateur PosPaires
Michel
Mathieu
-->Iterateur Concat
Julien
Mathieu
Christine
Michel
Michel
Mathieu

Le mot-cl yield break


Il est possible que lon ne souhaite numrer quune partie des objets dun numrable. Pour
signifier une boucle foreach de sarrter il sut demployer le mot-cl yield break.
Exemple 14-41 :
...
public IEnumerator<string> GetEnumerator() {
for (int i = 0 ; i < 2;i++ )
yield return m_Noms[i] ;
yield break;
// Warning : Unreachable code detected.
System.Console.WriteLine("hello") ;
}
...
Ce programme ache :
Michel
Christine
Le code situ dans le corps dune mthode aprs le mot-cl yield break nest pas atteignable
(i.e il ne sera jamais excut). Si linstar de cet exemple vous crivez du code non atteignable,
le compilateur met un avertissement.

Contraintes syntaxique imposes par lutilisation des mot-cls yield


return et yield break
Les mots-cls yield break et yield return ne peuvent apparatre que dans le corps dune mthode, le corps dun accesseur dune proprit ou dans le corps dun oprateur.
Les mots-cls yield break et yield return ne peuvent apparatre dans le corps dune mthode
anonyme.
Les mots-cls yield break et yield return ne peuvent apparatre dans une clause finally.
Les mots-cls yield break et yield return ne peuvent apparatre dans une clause try qui admet au moins une clause catch.

Les itrateurs avec C  2

547

Les mots-cls yield break et yield return ne peuvent apparatre dans une mthode qui a des
arguments ref ou out. Concrtement, il ne faut pas quune telle mthode puisse retourner autre
chose quun numrateur.

Exemple dun itrateur rcursif


Voici un exemple qui montre toute la puissance des itrateurs de C  2 pour numrer une structure de donnes non plate, telle quun arbre binaire :
Exemple 14-42 :
using System.Collections.Generic ;
public class Node<T>{
public Node(T item , Node<T> leftNode , Node<T> rightNode){
m_Item = item ;
m_LeftNode = leftNode ;
m_RightNode = rightNode ;
}
public Node<T> m_LeftNode ;
public Node<T> m_RightNode ;
public T m_Item ;
}
public class BinaryTree<T> {
Node<T> m_Root ;
public BinaryTree(Node<T> Root){
m_Root = Root ;
}
public IEnumerable<T> InOrder {
get{
return PrivateScanInOrder(m_Root) ;
}
}
private IEnumerable<T> PrivateScanInOrder(Node<T> root) {
if (root.m_LeftNode != null) {
foreach (T item in PrivateScanInOrder(root.m_LeftNode)){
yield return item ;
}
}
yield return root.m_Item ;
if (root.m_RightNode != null){
foreach (T item in PrivateScanInOrder(root.m_RightNode)){
yield return item ;
}
}
}
}
class Program {
static void Main() {
BinaryTree<string> binaryTree = new BinaryTree<string> (
new Node<string>( "A",

Chapitre 14 : Les mcanismes utilisables dans C 

548

new Node<string>( "B" , null , null ),


new Node<string>( "C" ,
new Node<string>( "D" , null , null ),
new Node<string>( "E" , null , null ) ) ) ) ;
foreach (string s in binaryTree.InOrder)
System.Console.WriteLine(s) ;
}
}
Larbre binaire construit dans la mthode Main() est celui ci :
B
A

D
C

Sens du parcours de
larbre par litrateur

Figure 14 -6 : Arbre binaire


Le programme ache ceci :
B
A
D
C
E

Interprtation des itrateurs par le compilateur de C  2


Voici la section qui va expliquer ce que fait le compilateur C  2 lorsquil rencontre les mots-cl
yield break et yield return. Il faut garder lesprit quaucune instruction IL na t rajoute
par rapport .NET 1.1 pour implmenter cette fonctionnalit (contrairement aux gnriques
o le Common Type System ainsi que le jeu dinstruction IL ont t revus).

La classe numrateur est implmente automatiquement par le


compilateur
Il est temps de prciser que toute mthode qui contient une instruction yield doit retourner un
numrable ou un numrateur. En consquence, toute mthode qui contient une instruction
yield doit avoir pour type de retour une des interfaces System.Collections.Generic.IEnumerable<T>, System.Collections.IEnumerable, System.Collections.Generic.IEnumerator<T>
ou System.Collections.IEnumerator. Dans tous les cas, cela donne lieu au retour dun numrateur puisque la seule raison dimplmenter un numrable et de pouvoir fournir un numrateur.
Comme vous pouvez vous en doutez si vous avec lu les sections concernant les mthodes anonymes, pour toute mthode qui contient une instruction yield le compilateur cre une classe.
Cette classe implmente les quatre interfaces dnumrations si la mthode retourne une des
deux interfaces numrables. Dans ce cas, une instance de cette classe se retourne elle-mme

Interprtation des itrateurs par le compilateur de C  2

549

lorsquon la considre comme un numrable et quon lui demande un numrateur. Cette


classe nimplmente que les deux interfaces dnumrations si la mthode retourne une des
deux interfaces dnumration.
La mthode nexiste plus en tant que telle, mais son corps est alors dans la mthode MoveNext()
de la nouvelle classe. Vrifions tout ceci sur un exemple :
Exemple 14-43 :
class Foo{
public System.Collections.Generic.IEnumerable<string> UnIterateur(){
yield return "str1" ;
yield return "str2" ;
yield return "str3" ;
}
}
class Program {
static void Main() {
Foo collec = new Foo() ;
foreach (string s in collec.UnIterateur())
System.Console.WriteLine(s) ;
}
}
Le compilateur C  2 produit lassemblage suivant partir du code prcdent (lassemblage est
visualis avec loutil Reflector dcrit en page 24) :
Dcompilons la mthode UnIterateur() pour vrifier quelle cre bien une instance de la classe
Foo.<UnIterateur>d__0 (notez lutilisation automatique du pattern using/dispose pour disposer
litrateur) :
class Foo {
public System.Collections.Generic.IEnumerable<string> UnIterateur() {
Foo.<UnIterateur>d__0 d__1 = new Foo.<UnIterateur>d__0(-2);
d__1.<>4__this = this ;
return d__1 ;
}
...
}
class Program {
private static void Main() {
Foo foo1 = new Foo() ;
using (IEnumerator<string> enumerator1 =
( ( IEnumerator<string>)foo1.UnIterateur().GetEnumerator() ) ) {
while (enumerator1.MoveNext()) {
string text1 = enumerator1.get_Current() ;
Console.WriteLine(text1) ;
}
}
}
}

Chapitre 14 : Les mcanismes utilisables dans C 

550

Figure 14 -7 : Interprtation des itrateurs par le compilateur C  2.0 (1)


Voici un autre exemple tout aussi instructif :
Exemple 14-44 :
class Foo {
public System.Collections.Generic.IEnumerable<int> UnIterateur() {
for (int i = 0 ; i < 5 ; i++){
if (i == 3) yield break ;
yield return i ;
}
}

Interprtation des itrateurs par le compilateur de C  2

551

}
class Program {
static void Main() {
Foo collec = new Foo() ;
foreach (int i in collec.UnIterateur())
System.Console.WriteLine(i) ;
}
}
Le compilateur C  2 produit lassemblage suivant partir du code prcdent :

Figure 14 -8 : Interprtation des itrateurs par le compilateur C  2.0 (2)

Une machine tat est fabrique pour chaque numrateur


Il est clair que le compilateur implmente notre mthode contenant un mot-cl yield, comme
une machine tat. Pour cela, il a besoin de deux champs dinstances <>1__state et <>2__current. <>1__state dfinit ltat dans lequel se trouve la machine. Rappelez vous, nous avions
dduit que le programme se branche juste aprs la dernire instruction yield return chaque

Chapitre 14 : Les mcanismes utilisables dans C 

552

itration et au dbut pour la premire itration. Cette magie est possible grce au champ
<>1__state. Le compilateur insre une instruction switch ds le dbut de la mthode pour
que le programme se branche au bon endroit. Le compilateur positionne <>1__state pour la
prochaine itration avant de quitter une itration.
Le champ <>2__current reprsente la valeur calcule par la dernire itration. Il est donc forcment du type de ce quon numre (int32 dans nos deux derniers exemples).
Si notre entit numrable est un objet, i.e si la mthode qui contient une instruction yield
est une mthode dinstance (comme dans le premier cas), le compilateur produit un champ
<>4__this qui rfrence lobjet numrable. Dans le cas contraire aucun champ nest cr.
Enfin, remarquez que si la mthode qui contient une instruction yield a des variables locales ou
des arguments, ceux-ci sont capturs par le compilateur qui en fait alors des champs de la classe
quil cre. Par exemple le champs <i>5__1 capture la variable locale i dans le second exemple.
Ceci est tout fait comparable la capture de lenvironnement lexical au moyen dune fermeture que lon a vu dans le cas des mthodes anonymes. La classe fabrique par le compilateur
est donc bien une fermeture et nul doute que lon va pouvoir exploiter tout ceci pour aller plus
loin que le concept ditration.

Exemples avancs de lutilisation des itrateurs de C  2


Daprs la section prcdente nous pouvons dduire deux proprits intressantes des itrateurs
de C  2 absentes du mcanisme ditration de C  1 :

Les itrateurs de C  2 supportent le pattern lazy valuation. Cest--dire que les lments de
la collection peuvent ntre produits que si besoin est. Autrement dit, comme un itrateur
a un tat ( linstar dune mthode anonyme), les lments peuvent tre calculs un par un,
au fur et mesure des demandes du client. Ils nont pas besoin dtre rsidents en mmoire.
Les itrateurs de C  2 peuvent itrer sur une collection dlments priori infinie (i.e borne
par les limites de la machine).

Nous allons exploiter ces proprits afin dutiliser les itrateurs dans dautres contextes que litration sur les lments dun numrable.

Dfinitions : coroutine et de continuation


Les itrateurs de C  2 sont en fait une implmentation de la notion de coroutine. Une coroutine
est une fonction qui a la particularit de reprendre son excution l o elle stait termine
la dernire fois quon la excut. La notion de coroutine soppose la notion plus connue de
subroutine. Une subroutine est une fonction qui recommence au dbut chaque excution.
La notion de coroutine est une spcialisation de la notion de fermeture, puisque pour reprendre
l o lon stait arrt, il faut bien pouvoir sauver ltat des variables locales (i.e le contexte
dexcution).
Le terme continuation dsigne un contexte partir duquel un programme peut poursuivre son
excution. Une continuation est donc lensemble des valeurs des variables locales union loset
dune instruction dans le corps dune coroutine. Une continuation est donc comparable une
pile dun thread au niveau dune fonction (stack frame en anglais). Cette remarque se relvera
pertinente.

Exemples avancs de lutilisation des itrateurs de C  2

553

Un exemple de continuation avec les itrateurs


Supposons une implmentation simplifie dun jeu dchec. Les blancs commencent, puis les
noirs jouent, puis les blancs jouent, puis les noirs jouent etc. Juste avant que les blancs jouent
un coup, il peut tre utile de sauver les rsultats des calculs afin de pouvoir reprendre l o
lon en tait lorsque les noirs auront jous. En C  1 il faudrait prvoir une classe pour stocker
ces rsultats. En C  2, grce aux notions de coroutine et de continuation, il sut juste dune
mthode contenant le mot-cl yield :
Exemple 14-45 :
using System ;
using System.Collections ;
public class Program{
static IEnumerator White(){
int resultatCalcul = 0 ;
while (true){
Console.WriteLine("white move, r
esultatCalcul=" +
resultatCalcul) ;
resultatCalcul++ ;
yield return black;
}
}
static IEnumerator Black(){
while (true){
Console.WriteLine("black move") ;
yield return white ;
}
}
static IEnumerator black;
static IEnumerator white;
static void Main() {
black = Black();
white = White();
IEnumerator enumerator = white ; // Honneur aux blancs.
// On dispatche 5 fois.
for (int i = 0 ; i < 5;i++){
enumerator.MoveNext() ;
enumerator = (IEnumerator)enumerator.Current ;
}
}
}
Ce programme ache :
white
black
white
black
white

move, resultatCalcul=0
move
move, resultatCalcul=1
move
move, resultatCalcul=2

Chapitre 14 : Les mcanismes utilisables dans C 

554

Il faut bien comprendre le rle des champs statiques black et white. Chaque fois que lon appelle
la mthode White(), un nouvel itrateur est cr. Il faut donc nappeler cette mthode quune
seule fois et rfrencer litrateur retourn avec le champ white.
Remarquez aussi que si nous ne limitions pas artificiellement le nombre de coups dans la
mthode Main() (i.e si lon remplaait la boucle for(int i=0 : i<5 ;i++) par la boucle
while(true)) les mthodes White() et Black() sappelleraient en alternance sans fin. Si ces mthodes sappelaient dune manire plus traditionnelle , ce comportement ferait rapidement
exploser la pile du thread. Ici il nen ait rien. Cela vient du fait que la notion de continuation
peut se comprendre aussi comme une sorte de goto inter mthodes (rappelons quen C  le
lutilisation du mot-cl goto doit tre confine dans le corps dune mthode).

Le pattern Pipeline
Les itrateurs sont particulirement adapts au pattern pipeline que vous connaissez tous pour
lavoir utilisez dans vos fentres de commande shell. Par exemple :
Exemple 14-46 :
using System.Collections.Generic ;
class Program{
static public IEnumerable<int> PipelineIntRange(int begin,int end) {
System.Diagnostics.Debug.Assert(begin < end) ;
for(int i=begin;i<=end ;i++)
yield return i ;
}
static public IEnumerable<int> PipelineMultiply(int factor ,
IEnumerable<int> input){
foreach (int i in input)
yield return i * factor ;
}
static public IEnumerable<int> PipelineFilterModulo(int modulo ,
IEnumerable<int> input ){
foreach (int i in input)
if( i%modulo == 0 )
yield return i ;
}
static public IEnumerable<int> PipelineJoin(IEnumerable<int> input1,
IEnumerable<int> input2){
foreach (int i in input1)
yield return i ;
foreach (int i in input2)
yield return i ;
}
static void Main(){
foreach (int i in PipelineJoin(
PipelineIntRange(-4, -2), PipelineFilterModulo( 3,
PipelineMultiply( 2,
PipelineIntRange(1, 10) ) ) ) )
System.Console.WriteLine(i) ;

Exemples avancs de lutilisation des itrateurs de C  2

555

}
}
Le programme ache ceci :
-4
-3
-2
6
12
18
On comprend bien -4,-3 et -2 mais il est un peu plus compliqu de saisir le pourquoi du 6,12 et
18. Voici une explication schmatise :
PipelineIntRange(1,10) produit
PipelineMultiply(2) produit
PipelineFilterModulo(3) produit

1
2
6

2
4

3
6

4 5 6 7 8 9 10
8 10 12 14 16 18 20
12
18

En faisant lexprience de modifier le PipelineIntRange comme ceci...


Exemple 14-47 :
...
static public IEnumerable<int> PipelineIntRange(int begin, int end){
System.Diagnostics.Debug.Assert(begin < end) ;
for (int i = begin ; i <= end ; i++){
System.Console.WriteLine("Production de:" + i) ;
yield return i ;
}
}
...
using System.Collections.Generic ;
class Program{
static public IEnumerable<int> PipelineIntRange(int begin, int end){
System.Diagnostics.Debug.Assert(begin < end) ;
for (int i = begin ; i <= end ; i++){
System.Console.WriteLine("Production de:" + i);
yield return i ;
}
}
static public IEnumerable<int> PipelineMultiply(int factor ,
IEnumerable<int> input){
foreach (int i in input)
yield return i * factor ;
}
static public IEnumerable<int> PipelineFilterModulo(int modulo ,
IEnumerable<int> input ){
foreach (int i in input)
if( i%modulo == 0 )
yield return i ;

Chapitre 14 : Les mcanismes utilisables dans C 

556

}
static public IEnumerable<int> PipelineJoin(IEnumerable<int> input1,
IEnumerable<int> input2){
foreach (int i in input1)
yield return i ;
foreach (int i in input2)
yield return i ;
}
static void Main(){
foreach (int i in PipelineJoin(
PipelineIntRange(-4, -2), PipelineFilterModulo( 3,
PipelineMultiply( 2,
PipelineIntRange(1, 10) ) ) ) )
System.Console.WriteLine(i) ;
}
}
...on obtient cette sortie :
Production
-4
Production
-3
Production
-2
Production
Production
Production
6
Production
Production
Production
12
Production
Production
Production
18
Production

de:-4
de:-3
de:-2
de:1
de:2
de:3
de:4
de:5
de:6
de:7
de:8
de:9
de:10

On voit bien qu aucun moment les entiers produits par un maillon du pipeline ne sont stocks
dune quelconque manire. Ds que le producteur initial (i.e PipelineIntRange) cre un entier,
ce dernier est consomm immdiatement. Chaque maillon est un producteur et un consommateur dentier (mis part PipelineIntRange qui nest que producteur dentier).

Continuation vs. Threading


Lorsque lon parle de modle producteur/consommateur, on est en gnral dans un contexte
multithread. Cela fait deux fois que lon parle de thread (rappelez vous, nous avons vu quune
continuation est comparable une pile dun thread en train dexcuter une fonction).

Exemples avancs de lutilisation des itrateurs de C  2

557

En fait, il est possible dimplmenter certains patterns de concurrence avec les itrateurs de C  2.
Voici un autre exemple simple o un producteur calcule les nombres de la suite de Fibonacci
tandis quun consommateur ache ces valeurs sur la console :
Exemple 14-48 :
using System.Collections ;
using System.Threading ;
public class Program{
static AutoResetEvent eventProducterDone=new AutoResetEvent(false) ;
static AutoResetEvent eventConsumerDone =new AutoResetEvent(false) ;
static int currentFibo ;
static void Fibo(){
int i1 = 1 ;
int i2 = 1 ;
currentFibo = 0 ;
// Le producteur enclanche le m
ecanisme.
eventProducterDone.Set() ;
while(true) {
// On attend que le consommateur ait fini.
eventConsumerDone.WaitOne() ;
// On produit.
currentFibo = i1 + i2 ;
i1 = i2 ;
i2 = currentFibo ;
// On signale que lon a produit.
eventProducterDone.Set() ;
}
}
static void Main() {
Thread threadProducteur = new Thread(Fibo);
threadProducteur.Start() ;
for (int i = 1 ; i < 10 ; i++) {
// On attend que le producteur ait fini.
eventProducterDone.WaitOne();
// On consomme.
System.Console.WriteLine(currentFibo);
// On signale que lon a consomm
e.
eventConsumerDone.Set();
}
}
}
Remarquez que ltat du thread producteur (i.e les valeurs de i1 et i2) est stock tous moment
sur sa pile. Voici la mme problmatique implmente laide dun itrateur :
Exemple 14-49 :
using System.Collections.Generic ;
public class Program {
static IEnumerator<int> Fibo(){

Chapitre 14 : Les mcanismes utilisables dans C 

558

int i1 = 1 ;
int i2 = 1 ;
int currentFibo = 0 ;
while (true){
currentFibo = i1 + i2 ;
i1 = i2 ;
i2 = currentFibo ;
// On signale que lon a produit.
yield return currentFibo ;
}
}
static void Main() {
IEnumerator<int> e = Fibo() ;
for (int i = 1 ; i < 10 ; i++){
// On donne la main au producteur pour quil produise.
e.MoveNext() ;
// On consomme.
System.Console.WriteLine(e.Current) ;
}
}
}
Cette fois ci, les valeurs de i1 et i2 sont tous moment stockes dans lnumrateur cr par
lappel la mthode Fibo().

Une limitation des itrateurs C  2


La limitation prsente dans la prsente section ne sera pas gnante dans limmense majorit
des itrateurs crs. Pour la mettre en vidence, essayons dutiliser un itrateurs rcursifs pour
appliquer le crible dEratosthne qui permet de calculer les nombres premiers. Le principe du
crible est simple. Voici un schma explicatif :
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
3 5 7 9
11
13
15
17 | 3 est premier,
| 4,6,8,10,12,14,16,18 et
| 20 sont multiples de 2.
5 7
11
13
17 | 5 est premier, 9 et 21
| sont multiples de 3.
7
11
13
17 | 7 est premier, il ny
| a pas de multiple de 5.
11
13
17 | 11 est premier, il ny
| a pas de multiple de 7.
13
17 | 13 est premier, il ny
| a pas de multiple de 11.
17 | 17 est premier, il ny
| a pas de multiple de 13.
Le premier nombre de chaque tape est un nombre premier. chaque tape on enlve ce
nombre ainsi que tous ses multiples.

Exemples avancs de lutilisation des itrateurs de C  2

559

Lide est davoir autant de pipeline que de nombres premiers et de faire la cascade sur un premier
itrateur qui produit les entiers de 2 n. Pour valuer le nombre de pipeline ncessaire (i.e le
nombre de nombres premiers entre 2 et n) on se sert dun thorme conjectur par Gauss et
dmontr par de la Valle Poussin qui arme que si P(n) dsigne le nombre de nombres premiers
infrieurs n alors :
P(n)

n
ln n

Voici le programme :
Exemple 14-50 :
using System ;
using System.Collections.Generic ;
class Program {
static public IEnumerable<int> PipelineIntRange(int begin, int end){
System.Diagnostics.Debug.Assert(begin < end) ;
for (int i = begin ; i <= end ; i++)
yield return i ;
}
static public IEnumerable<int> PipelinePrime(IEnumerable<int> input){
using (IEnumerator<int> e = input.GetEnumerator()){
e.MoveNext() ;
int premier = e.Current ;
// Le premier nombre obtenu est forc
ement un premier.
Console.WriteLine(premier) ;
if (premier != 0){
while (e.MoveNext()){
// Elimine tous les multiples de premier.
if (e.Current % premier != 0)
yield return e.Current ;
}
}
}
}
const int N = 100 ;
static void Main() {
// Applique la formule de Gauss/de la Vall
ee Poussin
// pour obtenir le nombre dit
erateur.
int N_PREMIER = (int)Math.Floor( ((double)N)/Math.Log(N) ) ;
// Produit un pipeline de N_PREMIER PipelinePrime
// chaine avec un PipelineIntegerRange.
// Chaque appel `a PipelinePrime produit un it
erateur.
List<IEnumerable<int>> list = new List<IEnumerable<int>>();
list.Add(PipelinePrime( PipelineIntRange(2, N)) ) ;
for( int i=1 ; i<N_PREMIER ; i++ )
list.Add( PipelinePrime(list[i-1]) ) ;
// Traverse toutes les valeurs du dernier it
erateur dans la chaine
// afin de provoquer la cascade des calculs.

Chapitre 14 : Les mcanismes utilisables dans C 

560

foreach ( int i in list[N_PREMIER-1]) ;


}
}
La limitation de C  2 mise en vidence ici est limpossibilit pour un itrateur de se rfrencer
lui-mme. En eet, on ne peut pas utiliser le mot cl this dans le corps dune mthode contenant une instruction yield. Dans le cas dune mthode dinstance, le mot cl this rfrence
linstance courante. Dans le cas dune mthode statique le compilateur gnre une erreur. Tous
se passe comme si lon ntait pas sens savoir quune classe est produite par le compilateur ! On
pourrait utiliser la rflexion pour rcuprer une rfrence mais cette solution nest pas satisfaisante.

Le framework .NET

15
Collections

Lutilisation de collections est fondamentale dans le dveloppement. On a trs souvent manipuler des donnes structures en listes, en tableaux, en dictionnaires etc. Ce chapitre prsente
les direntes classes du framework qui rpondent aux dirents besoins des dveloppeurs.
Nous prsentons dabord les similitudes dans la manipulation des dirents types de collections.
Ensuite, nous prsentons les tableaux, leur utilisation en C  ainsi que les concepts sous-jacents
de .NET. Puis nous prsentons les collections qui stockent leurs lments dans une squence tel
quune liste, une file dattente ou une pile. Enfin nous prsentons des collections particulires
appeles dictionnaires.
Durant notre expos, nous nous eorons de souligner les dirences entre chaque implmentation afin de vous aider choisir quel type de collection est le mieux adapt chacun de vos
besoins. Vous pouvez aussi consulter larticle Selecting a Collection Class des MSDN lorsque
vous hsitez entre plusieurs implmentations.
Lensemble des types de collections propos par le framework peut savrer assez limit dans le
cadre de certaines applications. Par exemple, il ny a pas dimplmentation des classiques arbres
binaires ou ensemble. En cherchant sur internet, vous trouverez srement des implmentations
directement rutilisables. Notamment, vous pouvez avoir recours au framework open-source Power Collections de lentreprise Wintellect qui a pour but de combler certaines lacunes du framework (http://www.wintellect.com/powercollections/).

Parcours des lments dune collection


avec foreach et in
Grce aux mots-cls foreach ( pour chaque en franais) et in, C  dispose dune syntaxe particulirement conviviale pour parcourir les lments dune collection. Lutilisation de foreach
et de in est possible sur nimporte quel type qui implmente linterface System.Collections.
IEnumerable.

564

Chapitre 15 : Collections

Il est de votre responsabilit de veiller ce que la taille dune collection ne change pas durant
le parcours des lments de celle-ci. En dautres termes, vous devez synchroniser les accs vos
collections entre les dirents threads dun programme.

Exemple dutilisation de foreach et in sur un tableau


Par exemple, pour calculer la somme des lments dun tableau dentiers une dimension il
sut dcrire :
Exemple 15-1 :
using System ;
class Program {
static void Main() {
int[] tab = { 1, 3, 4, 8, 2 } ;
// Calcul de la somme des
el
ements du tableau.
int somme = 0 ;
foreach ( int i in tab )
somme += i ;
// somme vaut : 1+3+4+8+2 = 18
}
}
foreach permet aussi le parcours des lments de tableaux plusieurs dimensions. Lordre dans
lequel les lments sont parcourus est illustr par lexemple suivant :
Exemple 15-2 :
class Program {
static void Main() {
string[,] tab = { { "do", "r
e", "mi" }, { "fa", "sol", "la" } } ;
foreach ( string s in tab )
System.Console.Write(s + ",") ;
}
}
Lachage de ce programme est :
do,re,mi,fa,sol,la,

foreach et les tableaux irrguliers


La notion de tableau irrgulier est dfinie un peu plus loin dans ce chapitre. foreach permet
aussi le parcours dun tableau irrgulier. Nanmoins, il faut prendre en compte quun tableau
irrgulier est un tableau de tableaux. En gnral on utilise des boucles foreach imbriques pour
parcourir les lments dun tableau irrgulier. Par exemple :
Exemple 15-3 :
class Program {
static void Main() {
int[][] t1 = new int[3][] ;

Les tableaux

565

t1[0] = new int[12] ;


t1[1] = new int[5] ;
t1[2] = new int[9] ;
int somme = 0 ;
foreach ( int[] tab in t1 )
foreach ( int i in tab )
somme += i ;
}
}
Le bnfice de lutilisation de foreach est que lon na pas besoin de connatre la taille des dimensions dun tableau pour pouvoir le parcourir. Il ny a aucun risque de dbordement. Ce
bnfice est encore plus significatif lors du parcours de tableaux irrguliers.
La variable dfinie dans une boucle foreach nest accessible quen lecture. Cela veut dire que
dans notre exemple, nous ne pouvons raliser aucune aectation ni sur la rfrence tab, ni sur
lentier i. En revanche, nous pourrions trs bien appeler une mthode ou une proprit sur ces
objets qui modifie leur tat interne.

Implmenter lutilisation de la syntaxe foreach et in sur vos propres


classes
C  2.0 prsente le concept ditrateur qui permet dimplmenter facilement lutilisation de la
syntaxe foreach et in sur vos propres classes. Ce concept ditrateur fait lobjet de la section page
539.

Les tableaux
Rfrences vers un tableau et cration de tableaux
C  permet la cration et lutilisation de tableaux une ou plusieurs dimensions. Voici la syntaxe
qui permet de dclarer une rfrence vers un type de tableau :
int [] r1 ;

// r1 est une r
ef
erence vers un tableau dentiers de

int [,] r2 ;

// dimension 1.
// r1 est une r
ef
erence vers un tableau dentiers de

int [,,] r3 ;

// dimension 2.
// r1 est une r
ef
erence vers un tableau dentiers de

// dimension 3.
double [,,,] r4;// r4 est une ref
erence vers un tableau de doubles de
// dimension 4.
Il est essentiel de comprendre que les types int[], int[,], int[,,] et double[,,,] reprsentent
des classes (donc des types rfrence) qui sont fabriques et gres lexcution par le CLR. De
plus, chacune de ces classes drive de la classe abstraite System.Array que nous dcrivons un
peu plus loin. Cela entrane les remarques suivantes :

566

Chapitre 15 : Collections
Lallocation dun tableau ne se fait que lorsque vous utilisez loprateur new. Les lignes de
code ci-dessus nallouent pas un seul tableau. Elles dfinissent quatre rfrences, chacune
vers un type de tableau particulier. Lorsque vous crez un tableau, la taille de chacune des
dimensions doit tre prcise soit statiquement par une constante entire, soit dynamiquement par une variable dun type entier. Par exemple :
Exemple 15-4 :
public class Program {
public static void Main() {
byte i = 2 ;
long j = 3 ;
// t1 est un tableau `a une dimension d
el
ements de type int.
// Il contient 6 elements.
int [] t1 = new int [6] ;
// t2 est un tableau a` deux dimensions d
el
ements
// de type double. Il contient 12
el
ements (j=3 et 3x4=12).
double [,] t2 = new double [j,4] ;
// t3 est un tableau `a 3 dimensions d
el
ements des r
ef
erences de
// type object.Il contient 30
el
ements(j=3 et i=2 et 3x5x2=30).
object [,,] t3 = new object [j,5,i] ;
}
}

Les tableaux sont allous sur le tas et non sur la pile. Lallocation de certains tableaux sur la
pile est possible dans des situations trs particulires (voir page 508).

La responsabilit de la dsallocation dun tableau incombe au ramasse-miettes.

Chaque type de tableau est driv de la classe object puisque la classe System.Array drive
de la classe object.

On ne copie pas physiquement un tableau avec loprateur daectation = . Avec cet oprateur on ne fait quobtenir une autre rfrence vers le mme tableau.

Le passage dun tableau une mthode se fait toujours par rfrence, mme si le mot-cl
ref nest pas utilis.

C  oblige tous les lments dun tableau avoir le mme type. Cependant cette contrainte peut
tre facilement contourne. Il sut de spcifier que les lments sont des rfrences vers une
classe de base (resp. vers une interface), pour quen fait, chaque lment puisse tre une rfrence vers un objet de nimporte quelle classe drive (resp. de nimporte quelle classe qui
implmente linterface).

Accs aux lments dun tableau et dtection des dbordements


Comme dans la plupart des langages, la syntaxe daccs aux lments dun tableau est la mme
que ce soit en criture ou en lecture. Cette syntaxe est assez similaire celle de lutilisation des
indexeurs dune classe. Par exemple :

Les tableaux

567

int [,] tab = new int[2,3] ;


tab[2,1] = 5 ;
// Acces en
ecriture.
int i = tab[2,1] ; // Acces en lecture.
Notez surtout qu linstar de la plupart des langages, en C  , les tableaux sont zro indexs .
Cela veut dire que si la taille dune dimension est un nombre entier N, un index sur cette dimension prend les N valeurs entires 0,1,2...,N-1. De plus le type dun index est forcment un type
entier. Enfin sachez que laccs aux lments dun tableau partir dindex se fait en un temps
constant, (i.e indpendamment du nombre dlments du tableau).
Un avantage dtre excut par une machine virtuelle telle que le CLR est quelle vrifie
systmatiquement lors dun accs un lment quil ny a pas de dbordement du tableau
dans aucune des dimensions. Dans le cas dun dbordement, une exception de type System.
IndexOutOfRangeException est automatiquement lance par le CLR. Par exemple :
int [,] tab = new int[2,3] ;
// Debordement sur la premi`ere dimension, une exception est lanc
ee.
tab[13,1] = 5 ; short i = 14 ;
// Debordement sur la deuxi`eme dimension, une exception est lanc
ee.
int e = tab[0,i] ;

Les tableaux irrguliers ( jagged arrays)


On dfinit par le terme tableau irrgulier (jagged array en anglais) un tableau unidimensionnel
dont les lments sont des tableaux. Certains articles utilisent aussi le terme de tableau dchiquet
pour dsigner un tableau irrgulier. La Figure 15 -1 montre un tableau de trois lments dont
le premier lment est un tableau 12 lments, le deuxime un tableau 5 lments et le
troisime un tableau 9 lments.

Figure 15 -1 : Reprsentation dun tableau irrgulier


C  a une syntaxe intuitive pour dfinir les tableaux irrguliers. Par exemple on pourrait rfrencer le tableau de la Figure 15-1 avec la rfrence t1 suivante (en considrant que les lments
sont des entiers) :
int [][] t1 ;
La construction de ce tableau est eectue par le code suivant :
int [][] t1
t1[0] = new
t1[1] = new
t1[2] = new

= new int [3][] ;


int[12];
int[5] ;
int[9] ;

Voici dautres types de tableaux irrguliers :

568

Chapitre 15 : Collections
int [][,,] t1 ;
string [,,][][] t2 ;
double [][][,,][,,,] t3 ;

Comme vous pouvez le constater, la reprsentation mentale des tableaux irrguliers est plus
complique que lide de tableau multidimensionnel. En outre, contrairement aux tableaux
multidimensionnels, les tableaux irrguliers ne sont pas CLS compliant . Les autres langages
ne les implmentent pas ncessairement. Il faut donc restreindre leur utilisation lintrieur
dun assemblage C  et ne jamais les mettre en arguments de mthodes exposes par un assemblage. Cependant, lutilisation de tableaux irrguliers est plus performante car le langage IL
contient des instructions spcialement optimises pour la manipulation de tableau unidimensionnel. Lexemple suivant exhibe un facteur doptimisation suprieur 2 :
Exemple 15-5 :
using System;
using System.Diagnostics;
class Program {
const int N_ITERATION = 10000;
const int N_ELEM = 100;
static void Main() {
int tmp = 0;
int[][] arrayJagged = new int[N_ELEM][];
for (int i = 0; i < N_ELEM; i++)
arrayJagged[i] = new int[N_ELEM];
Stopwatch sw = Stopwatch.StartNew();
for (int k = 0; k < N_ITERATION; k++)
for (int i = 0; i < N_ELEM; i++)
for (int j = 0; j < N_ELEM; j++){
tmp = arrayJagged[i][j];
arrayJagged[i][j] = i * j;
}
Console.WriteLine("Tableau irr
egulier: " + sw.Elapsed );
int[,] arrayMultiDim = new int[N_ELEM, N_ELEM];
sw = Stopwatch.StartNew();
for (int k = 0; k < N_ITERATION; k++)
for (int i = 0; i < N_ELEM; i++)
for (int j = 0; j < N_ELEM; j++){
tmp = arrayMultiDim[i, j];
arrayMultiDim[i, j] = i * j;
}
Console.WriteLine("Tableau multidimensionnel:" + sw.Elapsed );
}
}
Enfin notez que le CLR dtecte les dbordements daccs sur les tableaux irrguliers. Par
exemple :

Les tableaux

569

Exemple 15-6 :
class Program{
static void
int[][]
t1[0] =
t1[1] =
t1[2] =
int i =
int j =
}
}

Main() {
t1 = new int[3][] ;
new int[12] ;
new int[5] ;
new int[9] ;
t1[0][7] ; // OK, ici il ny a pas de d
ebordement.
t1[1][7] ; // D
ebordement sur la deuxi`
eme dimension,
// une exception est lanc
ee.

Initialisation des lments dun tableau


Lorsquun tableau a des lments dun type valeur, ces lments sont automatiquement crs
lors de la cration du tableau. Ils sont par dfaut initialiss zro.
int [] tab = new int [3] ;
int i = tab[0] ;
// Ici i vaut zero.
Dans le cas dun tableau avec des lments dun type rfrence, les rfrences sont nulles par
dfaut. Il faut donc initialiser les rfrences une une avec les objets adquats.
Exemple 15-7 :
class Article {
private decimal m_Prix ;
public Article(decimal prix){m_Prix = prix;}
}
public class Program {
public static void Main() {
Article [] tab = new Article[3] ;
// Ici aucun article na
et
e cr
e
e.
tab[1] = new Article(98.5M) ;
tab[2] = new Article(190M) ;
tab[3] = new Article(299.0M) ;
// Ici le tableau reference les trois articles.
}
}
Ces remarques sont valables que le tableau soit irrgulier ou pas. En revanche ce qui suit nest
valable que pour les tableaux non irrguliers. C  autorise lcriture de tableaux constants. Par
exemple :
// tabval est de dimension deux.
int [,] tabval = { {3,4,5} , {7,8,9} } ;
// tabref est de dimension un.
Article [] tabref =
{ new Article(98.5M), new Article(190M), new Article(299.0M) } ;

570

Chapitre 15 : Collections

On ne prcise pas la taille de chacune des dimensions puisquelles sont implicitement contenues
dans les tableaux constants. En outre, le compilateur dtecte et sanctionne dune erreur, lcriture dun tableau constant irrgulier :
// Erreur de compilation.
int [][] tabval = { {3,4} , {7,8,9} } ;

Covariance sur les tableaux


Lorsquil existe une relation dhritage entre deux types rfrence B et D (D drive de B si B est une
classe ou D implmente B si B est une interface) alors, il est possible de convertir implicitement
un tableau de type D[] en un tableau de type B[]. Il est aussi possible de convertir explicitement
un tableau de type B[] en un tableau de type D[]. Dans ce cas, le CLR vrifie la validit de la
conversion pour chaque lment et produit une exception de type InvalidCastException si
une telle conversion choue. Cette possibilit de conversion est appele covariance sur les tableaux.
Exemple 15-8 :
class B { }
class D1 : B { }
class D2 : B { }
public class Program {
public static void Main() {
D2[] td2 = { new D2(), null , new D2() } ;
B[] tb = td2 ;
// Conversion implicite de D2[] vers B[].
D1[] td1 = (D1[])tb ; // Conversion explicite de B[] vers D1[].
// Compile mais une exception de type InvalidCastException
} // est lancee `a lexecutition pour la seconde conversion.
}

La classe System.Array
Comme nous lavons vu, chaque type de tableau est une classe fabrique lexcution par le CLR
qui drive de la classe abstraite System.Array. Ceci permet dutiliser les nombreux membres de
cette classe.

Membres de System.Array
Les proprits de System.Array les plus couramment utilises sont :

int Length{ get ; }


Retourne le nombre dlments du tableau. Dans les tableaux irrguliers Length retourne
le nombre de sous tableaux.

int Rank{ get ; }


Retourne le nombre de dimensions du tableau. Attention, dans les tableaux irrguliers Rank
retourne le nombre de dimensions du tableau de sous tableaux.

Parmi les mthodes de System.Array les plus utilises on remarque :

Les tableaux

571

int GetLength(int dimension)


Retourne la taille de la dimension prcise, dimension tant le numro zro index de la
dimension du tableau (i.e spcifier 0 pour avoir la taille de la premire dimension). Si la dimension est ngative ou suprieure au rang du tableau, alors lexception IndexOutOfRange
est lance.

static void Copy(Array src, Array dest, ...)


void CopyTo(Array dest, ...)
Les direntes surcharges de ces mthodes permettent de copier tous les lments ou une
sous squence dlments dun tableau unidimensionnel dans un autre tableau unidimensionnel.

static int IndexOf<T>(Array array, T value, ...)


static int LastIndexOf<T>(Array array, T value, ...)
Les direntes surcharges de ces mthodes renvoient lindex de la premire (ou de la dernire) occurrence de value dans un tableau unidimensionnel (ou dans une sous squence
dlments). Attention, soyez conscient que la notion dgalit est dirente selon que T
est un type valeur ou rfrence. Si la sous squence dlments prcise nest pas valide,
lexception IndexOutOfRange est lance. Si array a plus dune dimension, lexception
RankException est lance.

static void Reverse(Array array, ...)


Les direntes surcharges de cette mthode inversent la squence (ou une sous squence)
des lments de array. Si la sous squence dlments prcise nest pas valide, lexception
IndexOutOfRange est lance. Si array a plus dune dimension, lexception RankException
est lance.

static int BinarySearch<T>(T[] array,T value, ... )


Eectue une recherche dichotomique dun lment dans un tableau dj tri (ou dans une
sous squence selon les surcharges). Si value est trouve, son index est retourn. Sinon, la
valeur retourne reprsente loppos de lindex+1 du plus petit lment plus grand que
value. Si value est plus grand que tous les lments, la valeur retourne reprsente loppos
de la taille du tableau+1 . Par exemple:
Exemple 15-9 :
public class Program {
public static void Main() {
int[] t = new int[2] ;
t[0] = 10 ;
t[1] = 20 ;
int a = System.Array.BinarySearch(t, 5) ;
int b = System.Array.BinarySearch(t, 10) ;
int c = System.Array.BinarySearch(t, 15) ;
int d = System.Array.BinarySearch(t, 25) ;
// Ici, a vaut -1
b vaut 0
c vaut -2
}
}

d vaut -3.

Certaines surcharges de BinarySearch<T>() acceptent un objet implmentant IComparer<T> pour spcifier lopration de comparaison.

572

Chapitre 15 : Collections

Les membres de System.Array ajouts avec la version 2.0

static void Resize<T>(ref T[] array, int newSize)


Redimensionne un tableau unidimensionnel. Si la nouvelle taille est plus grande que la
taille initiale, les nouveaux lments prennent la valeur par dfaut du type T (0 pour si type
valeur, null si type rfrence). Sinon les lments en bout de tableau sont supprims. Si la
rfrence array est nulle, alors cette mthode cre un nouveau tableau unidimensionnel
de la taille spcifie.

static void ConstrainedCopy(Array src, int srcIndex, Array dest, int destIndex, int length)
Cette nouvelle mthode ralise une copie dun tableau unidimensionnel ou multidimensionnel avec la garantie que le tableau destination nest pas modifi si lopration chouait.
Pour cela elle utilise le mcanisme de rgion dexcution contrainte dcrit en page 125.
Cette mthode a un cot et ne doit tre utilise qu bon escient (dans un contexte multithreads). Dans le cas dune copie dune sous squence dun tableau multidimensionnel, il
faut considrer que vous avez faire des tableaux unidimensionnels avec les lments des
direntes dimensions mis bout bout.

static IList<T> AsReadOnly(T[] array)


Retourne une rfrence de type IList<T> qui permet de naccder au tableau sous-jacent
quen lecture seule. Attention, il sagit bien du mme tableau. Comme lillustre lexemple
suivant, tous changements sur le tableau initial est visible de la rfrence IList<T> :
Exemple 15-10 :
using System.Collections.Generic ;
public class Program {
public static void Main() {
int[] t = new int[2] ;
t[0] = 10 ;
t[1] = 20 ;
IList<int> tReadOnly = System.Array.AsReadOnly(t) ;
t[1] = 25 ;
// Ici t[1] vaut 25.
tReadOnly[1] = 30 ; // Ici une exception de type
}
// NotSupportedException est lanc
ee.
}

Un peu plus loin, nous dcrivons de nombreuses nouvelles mthodes de la classe System.Array
permettant de manipuler simplement et ecacement les lments dun tableau.

Tableaux non zro-indexs


La classe prsente plusieurs surcharges de la mthode CreateInstance(). Ces surcharges sont
utilises par le compilateur C  pour instancier des tableaux multidimensionnels. Une de ces
surcharges nest jamais utilise par le compilateur C  . Nanmoins, vous pouvez utiliser explicitement cette surcharge afin de crer des tableaux bidimensionnels non zros indexs. Lexemple
suivant illustre cette possibilit :

Les tableaux

573

Exemple 15-11 :
public class Program {
static void Main() {
int[] lengths = { 4, 5 } ;
int[] lowerBounds = { -2, 3 } ;
double[,] arrBiDim = System.Array.CreateInstance(
typeof(double), lengths, lowerBounds) as double[,] ;
double d1 = arrBiDim[-2, 5] ; // OK, les indexes sont valides.
double d2 = arrBiDim[0, 0] ; // Lancement de lexception
}
// IndexOutOfRangeException.
}
Le tableau arrayBiDim contient 20 lments (4x5) dont les coordonnes vont de [-2,3] [1,7]
inclus. Lexemple illustre aussi le fait que le CLR dtecte les dbordements ventuels.

Tableaux de bits
La classe System.Collection.BitArray
Vous pouvez utiliser la classe System.Array pour stocker un tableau unidimensionnel de boolens, mais le framework .NET met votre disposition la classe System.Collections.BitArray
spcialement prvue cet eet. Les avantages dutiliser cette classe la place de System.Array
sont multiples :

Pour stocker un tableau de N boolens, limplmentation Microsoft de la classe BitArray


a besoin dau plus (N/32)+1 entiers (= 4 octets). En eet, les boolens y sont stocks sous
une forme compacte, raison de 32 boolens par entier. En revanche, chaque boolen est
stock sur un octet lorsque vous utilisez la classe Array pour les stocker.

La classe BitArray a des indexeurs entiers. La syntaxe conviviale daccs aux lments (en
lecture et criture) avec loprateur [] reste possible sur les tableaux de type BitArray.

La classe BitArray admet plusieurs constructeurs trs pratiques pour initialiser des tableaux
de boolens.

BitArray(int)

Largument spcifie le nombre de bits dans le tableau de


bits. Ils sont tous initialiss false.

BitArray(int,bool)

Le premier argument spcifie le nombre de bits dans le tableau de bits. Le second spcifie la valeur initiale des arguments.

BitArray(bool[])

Le tableau de type BitArray est initialis partir dun tableau de boolens de type Array.

BitArray(byte[])

Le tableau de type BitArray est initialis partir dun tableau doctets de type Array.

La classe BitArray admet plusieurs mthodes trs pratiques pour travailler avec des boolens. En voici quelques-unes :

574

Chapitre 15 : Collections

BitArray Not()

Inverse chaque boolen du tableau. Ceux positionns


false sont positionns true et vice-versa.

Void SetAll(bool)

Positionne tous les boolens du tableau la valeur passe


en argument.

BitArray And(BitArray)

Eectue un et logique avec les boolens du tableau


pass en argument. Si les deux tableaux ne sont pas de
mme taille lexception System.ArgumentException est
lance. Les boolens modifis sont ceux du tableau sur lequel est appel cette mthode et la rfrence renvoye rfrence ce mme tableau.

BitArray Or(BitArray)

Eectue un ou logique avec les boolens du tableau


pass en argument. Mme remarques que pour la mthode And()

BitArray Xor(BitArray)

Eectue un ou exclusif avec les boolens du tableau


pass en argument. Mme remarques que pour la mthode And()

Voici un exemple pour exposer tout ceci :


Exemple 15-12 :
using System.Collections ;
public class Program {
public static void Main() {
BitArray bArr1 = new BitArray (10,true) ;
BitArray bArr2 = new BitArray (10,false) ;
BitArray bArr3 = bArr1.And(bArr2) ;
// Ici, bArr3[0] vaut false et bArr1[0] vaut false.
bArr3[0] = true ;
// Ici, bArr3[0] vaut true et bArr1[0] vaut true.
}
}

La structure System.Collections.Specialized.BitVector32
La structure BitVector32 permet de contenir un tableau dexactement 32 bits. Si elle est adapte
vos besoins, prfrez utiliser cette structure plus performante que la classe BitArray. Vous
pouvez construire une instance de BitVector32 partir dune autre instance de BitVector32 ou
partir dun entier. Cette structure prsente un indexeur qui vous permet daccder en lecture
ou en criture un bit en spcifiant sa position dans lintervalle ferm [0,31]. Elle prsente aussi
des oprations spcifiques la manipulation de bits avec des masques ou avec des sections.

Les squences

575

Les squences
Nous prsentons ici des interfaces puis des classes spcifiques pour la manipulation de tableaux
dont la taille peut varier au fur et mesure de lajout ou de la suppression dlments. On appelle de tels tableaux des squences.

Linterface System.Collections.Generic.ICollection<T>
public interface ICollection<T> : IEnumerable<T>
Linterface System.Collections.Generic.ICollection<T> est implmente par toutes les
classes reprsentant des collections. Voici les membres de linterface ICollection<T> :

int Count{ get ; }


Cette proprit retourne le nombre dlments dans la collection.

bool IsReadOnly{ get ; }


Cette proprit retourne true si la collection nest accessible quen lecture seule.

void Add( T item )


Ajoute un lment la collection.

void Clear()
Supprime tous les lments de la collection.

bool Contain( T item )


Retourne true si la collection contient llment spcifi.

bool Remove( T item )


Supprime la premire occurrence de llment spcifi. Retourne true si une occurrence
de llment spcifi a t eectivement supprime.

public virtual void CopyTo( T[] array, int index )


Copie les lments de la collection dans le tableau array. Il faut que le tableau array ait une
seule dimension. Si le tableau array ne respecte pas la contrainte de la dimension unique,
lexception System.ArgumentException est lance.

index spcifie lindice du tableau array partir duquel commence la copie. Si le tableau
destination nest pas assez grand (i.e si la taille de array index est infrieure au nombre
dlments de la collection) lexception System.ArgumentException est lance.

Si les lments sont de type rfrence, ce sont simplement les rfrences qui sont copies et
pas les objets. Cest ce quon appelle une copie superficielle. Voici un exemple o les lments
de type rfrence dune liste, sont copis dans un tableau :

Exemple 15-13 :
using System.Collections.Generic ;
class Article{
public decimal Prix ;
public Article(decimal Prix) { this.Prix = Prix ; }
}
public class Program {
public static void Main() {

576

Chapitre 15 : Collections
Article a1 = new Article(98.5M) ;
Article a2 = new Article(190M) ;
ICollection<Article> collection = new List<Article>() ;
collection.Add(a1) ;
collection.Add(a2) ;
Article[] tableau = new Article[2] ;
// Copie les elements de collection dans tableau.
collection.CopyTo(tableau, 0) ;
tableau[0].Prix = 80M ;
decimal d = a1.Prix ;
// Ici d vaut 80.
}
}

Linterface System.Collections.Generic.IList<T>
public interface IList<T> : ICollection<T>, IEnumerable<T>
Linterface System.Collections.Generic.IList<T> permet de considrer quune collection est
une liste. Linterface IList<T> tend linterface ICollection<T>. Limplmentation de prdilection de cette interface est la classe System.Collections.Generic.List<T>, prsente ci-aprs.
Les membres de linterface IList<T> sont :

int IndexOf( T item )


Retourne lindex de llment spcifi (-1 si llment nest pas trouv).

void Insert( int index , T item )

Cette mthode insre un lment dans la liste la position reprsente par lindex,
augmentant ainsi la taille de la liste dune unit. Si lindex nest pas valide, lexception
ArgumentOutOfRangeException est lance. Si linsertion choue, en gnral parce que la
liste nest accessible quen lecture seule, lexception NotSupportedException est lance.

void RemoveAt( int index )


Supprime llment situ la position spcifie par lindex. Les exceptions ArgumentOutOfRangeException et NotSupportedException peuvent tre lances pour les mmes raisons
nonces dans la mthode prcdente.

T this[int index] { get ; set ; }

Indexeur qui permet daccder en lecture ou en criture llment situ la position spcifie par lindex. Les exceptions ArgumentOutOfRangeException et NotSupportedException
peuvent tre lances pour les mmes raisons nonces dans les mthodes prcdentes.

La classe System.Collections.Generic.List<T>
La classe System.Collections.ArrayList trs utilise dans les applications dveloppes avec
.NET 1.x doit maintenant tre abandonne au profit de la classe System.Collections.Generic.List<T> dfinie comme ceci :
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>,
IList,
ICollection,
IEnumerable

Les squences

577

Cette classe gnrique est trs optimise. Notamment, lutilisation de List<object> est bien
plus performante que lutilisation de ArrayList.
Contrairement une instance de la classe Array, une instance de List<T> a un nombre dlments variable. Comme dans tout compromis, cet avantage des listes sur les tableaux se paye.
En loccurrence, cet avantage se paye par une demande dallocation mmoire lorsque le nombre
dlments de la liste grandit. En revanche, linstar des tableaux, laccs aux lments dune
liste partir dun index se fait en un temps constant (i.e indpendant du nombre dlments).
Nous verrons dans la prochaine section un autre modle de liste implment par la classe LinkedList<T> o linsertion se fait un temps constant et o laccs aux lments se fait en un temps
proportionnel au nombre dlments. Vous connaissez maintenant les points cls pour dcider
si vous devez utiliser la classe Array, la classe List<T> ou la classe LinkedList<T>, selon vos
besoins spcifiques.
La classe List<T> prsente les constructeurs suivants (la notion de capacit est explique un peu
plus loin dans cette section) :

List<T>()
Dans limplmentation Microsoft, ce constructeur sans argument construit une instance de
capacit 0.
List<T>(int capacity)
Ce constructeur construit une instance de List<T> de capacit capacity.
ArrayList(IEnumerable<T> collection)
Ce constructeur construit une instance de ArrayList avec les lments de la collection numrable collection.

En plus des membres des interfaces IList<T> et ICollection<T>, la classe List<T> prsente les
membres suivants :

T[] ToArray()
Equivalent ICollection<T>.CopyTo(T[],0).
int IndexOf(T value, ...)
int LastIndexOf(T value, ...)
Mme comportement que les direntes surcharges des mthodes IndexOf() et LastIndexOf() de la classe Array., mis part quici ce sont des mthodes dinstance et non des
mthodes statiques.
int BinarySearch(T value, ...)
Mme comportement que les direntes surcharges de la mthode BinarySearch() de la
classe Array.
IList<T> AsReadOnly()
Mmes remarques que pour la mthode AsReadOnly() de la classe Array.
void AddRange(IEnumerable collection)
void InsertRange(int index, IEnumerable collection)
List<T> GetRange(int index, int count)
void RemoveRange(int index, int count)
Ces mthodes permettent dinsrer, dajouter, dobtenir ou de supprimer un ensemble
dlments contigus de la liste sur laquelle elles sont appeles. La dirence entre linsertion
et lajout est que lajout insre les lments la fin de la liste.

578

Chapitre 15 : Collections

Capacit dune instance de List<T>


La capacit dune instance de List<T> peut tre gre au moyen de la proprit Capacity. Le
problme avec les collections de taille variable est que lallocation/dsallocation de mmoire
lors de la modification du nombre des lments nuit aux performances. Pour attnuer les eets
de ce problme, il faut diminuer le nombre de demandes dallocation/dsallocation. Ceci est
possible en allouant plus de mmoire que ncessaire, pour anticiper sur les futures insertions
dlments. De mme il faut dsallouer la mmoire que lorsquun certain nombre dlments
ont t dsallous. La mme problmatique est expose pour la classe StringBuilder en page
376.
Une consquence de lutilisation dun mcanisme de capacit, est qu un instant donn, le
nombre dlments eectivement contenu dans la liste est dirent du nombre dlments que
la liste peut contenir sans avoir besoin de nouvelles allocations. Le nombre dlments eectivement contenus dans la liste, est reprsent par la proprit Count de linterface ICollection.
Le nombre dlments que la liste peut contenir sans avoir besoin de nouvelle allocation est
reprsent par la proprit Capacity.
La valeur de Capacity est par dfinition constamment suprieure ou gale la valeur de Count.
Aussi, si vous essayez daecter une valeur Capacity infrieure la valeur de Count lexception
ArgumentOutOfRangeException est lance. Notez que vous pouvez tout moment supprimer les
lments non utiliss en appelant la mthode void TrimExcess(). La gestion de lexpansion de
la capacit est, en gnral, laisse aux algorithmes internes du framework .NET comme le montre
lexemple suivant :
Exemple 15-14 :
using System.Collections.Generic ;
public class Program {
public static void Main() {
List<int> list = new List<int>(3) ;
for (int i = 0 ; i < 8 ; i++){
list.Add(i) ;
System.Console.WriteLine("Count:{0} Capacit
e:{1}",
list.Count, list.Capacity ) ;
}
list.TrimExcess();
System.Console.WriteLine("Count:{0} Capacit
e:{1}",
list.Count, list.Capacity ) ;
}
}
Cet exemple ache :
Count:1
Count:2
Count:3
Count:4
Count:5
Count:6
Count:7
Count:8
Count:8

Capacite:3
Capacite:3
Capacite:3
Capacite:6
Capacite:6
Capacite:6
Capacite:12
Capacite:12
Capacite:8

Les squences

579

Le constructeur de List<T> que nous utilisons dans lexemple prcdent accepte une valeur
entire qui reprsente la capacit initiale. En outre, il est clair que limplmentation Microsoft
double le nombre dlments chaque fois quune allocation est ncessaire. On dit de la classe
List<T> quelle a un facteur dexpansion (grow factor en anglais) gal 2.

Les interfaces System.ComponentModel.IBindingList


et System.ComponentModel.IListSource
Les frameworks Windows Forms 2.0 ainsi quASP.NET 2.0 prsentent des facilits pour lier
une source de donnes des contrles dachage de donnes. Une source de donnes est
souvent une liste de donnes. Pour lier une telle liste de donnes un contrle dachage
ces technologies requirent une classe qui implmente IBindingList telle que la classe System.ComponentModel.BindingList<T> (voir page 693) ou une classe qui implmente IListSource tel que la classe DataSet (voir page 929).

La classe System.Collections.Generic.LinkedList<T>
La classe System.Collections.Generic.LinkedList<T> reprsente le concept de liste doublement lie. Une liste doublement lie est une collection de nuds lis les uns aux autres la faon
des maillons dune chane. Chaque nud est li au nud qui le prcde et au nud qui le suit.
Chaque nud connat la liste lie qui le contient. Chaque nud contient un lment de la liste.
La liste doublement lie ne connat que le premier et le dernier nud. Tout ceci est illustr par
la figure suivante :
Liste doublement lie

Nud 6

Nud 5

Nud 1

Nud 2

Nud 3

Nud 4

Figure 15 -2 : Une liste doublement lie


En consquence, laccs un lment se fait en un temps proportionnel au nombre dlments
tandis que linsertion ou la suppression dun lment se fait en un temps constant (i.e indpendant du nombre dlments).
La classe System.Collections.Generic.LinkedList<T> est dfinie comme ceci :
public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection,
IEnumerable, ISerializable, IDeserializationCallback
La classe System.Collections.Generic.LinkedListNode<T> qui reprsente le concept de
noeud est dfinie comme ceci :
public sealed class LinkedListNode<T> {
public LinkedListNode(T value) ;
public System.Collections.Generic.LinkedList<T> List { get ; }
public System.Collections.Generic.LinkedListNode<T> Next { get ; }

580

Chapitre 15 : Collections
public System.Collections.Generic.LinkedListNode<T> Previous { get ; }
public T Value { get ; set ; }
}

Remarquez que les proprits List, Next et Previous ne sont pas accessibles en criture, ce qui
implique que seules les oprations dune liste doublement lie permettent de lier un nud
une liste.
En plus des mthodes de linterface ICollection<T> la classe LinkedList<T> prsente les
membres suivants :

void AddHead(T value)


void AddHead(LinkedListNode<T> node)
void AddTail(T value)
void AddTail(LinkedListNode<T> node)
Ajoute un lment en tte de liste avec AddHead() ou en bout de liste AddTail().

void AddAfter(LinkedListNode<T> node, T value)


void AddAfter(LinkedListNode<T> node,LinkedListNode<T> _node)
void AddBefore(LinkedListNode<T> node, T value)
void AddBefore(LinkedListNode<T> node,LinkedListNode<T> _node)
Ajoute un lment avant ou aprs un lment donn. Lexception InvalidOperationException est lance si node nappartient pas la liste concerne.

void RemoveHead()
void RemoveTail()
Supprime llment en tte de liste avec RemoveHead() ou en bout de liste avec RemoveTail(). Lexception InvalidOperationException est lance si la liste est vide.

Liste de chanes de caractres


Vous pouvez hsiter entre deux implmentations de listes de chanes de caractres proposes
par le framework : La classe System.Collections.Specialized.StringCollection (disponible
depuis la version 1.0) et la classe System.Collections.Generic.List<string> (disponible seulement depuis la version 2.0).
La rponse est simple. Il faut prfrer la version gnrique List<string>. Pas tant cause de
ses performances (qui sont peine meilleures que celles de StringCollection) mais plutt
pour des raisons de cohrence de design puisque peu peu, toutes vos collections vont tre
de type gnrique. En outre, List<string> implmente des interfaces gnriques telles que
IEnumerable<string> non supportes par StringCollection.
Lutilisation de la classe StringCollection na maintenant de sens que si vous souhaitez rester
cohrent avec du code utilisant dj cette classe.

La classe System.Collections.Generic.Queue<T>
La notion de file dattente est souvent utilise en programmation. Une file dattente sert en gnral traiter des messages dans leur ordre darriv, de la mme manire quun commerant sert
ses clients dans lordre dans lequel ils sont arrivs. Le terme classique pour dsigner ce comportement est FIFO (First In First Out que lon peut traduire par premier arriv premier servi).

Les squences

581

linstar dune file dattente chez un commerant, nous parlerons de dbut de file dattente et de
fin de file dattente.
Pour reproduire un tel comportement, il est conseill dutiliser la classe System.Collections.Generic.Queue<T> prvue cet eet et dfinie comme ceci :
public class Queue<T> : ICollection<T>, IEnumerable<T>,
ICollection,
IEnumerable
Cette classe prsente principalement les trois mthodes suivantes :

void Enqueue(T item)


Cette mthode ajoute llment item la fin de la file dattente.

T Dequeue()
Cette mthode retire llment qui est au dbut de la file dattente et le renvoie. Si la file
dattente est vide, lexception InvalidOperationException est lance.

T Peek()
Cette mthode renvoie llment qui est au dbut de la file dattente sans le retirer. Si la file
dattente est vide, lexception InvalidOperationException est lance.

linstar de la classe List<T>, la classe Queue<T> a une capacit et implmente la mthode TrimToSize() pour minimiser les baisses de performances provoques par les allocations/dsallocations
induites par les changements de taille de la file dattente. La classe Queue<T> prsente aussi un
constructeur qui permet de fixer la capacit initiale.

La classe System.Collections.Generic.Stack<T>
La notion de pile (stack en anglais) est souvent utilise en programmation. Par exemple chaque
thread utilise une pile pour stocker ses donnes de traitement. Une pile sert en gnral traiter
des messages dans lordre de dpt sur la pile, de la mme manire quune pile de dossiers est
traite. En gnral on commence par le dossier du dessus de la pile. Le terme classique pour
dsigner ce comportement est LIFO (Last In First Out que lon peut traduire par dernier arriv
premier servi). linstar dune pile de dossier, nous parlerons du sommet de la pile.
Pour reproduire un tel comportement il est conseill dutiliser la classe System.Collections.Generic.Stack<T> prvue cet eet et dfinie comme ceci :
public class Queue<T> : ICollection<T>, IEnumerable<T>,
ICollection,
IEnumerable
Cette classe prsente principalement les trois mthodes suivantes :

void Push(T item)


Cette mthode ajoute llment item sur le sommet de la pile.

T Pop()
Cette mthode retire llment qui est situ au sommet de la pile et le renvoie. Si la pile est
vide, lexception InvalidOperationException est lance.

T Peek()
Cette mthode renvoie llment qui est situ au sommet de la pile sans le retirer. Si la pile
est vide, lexception InvalidOperationException est lance.

582

Chapitre 15 : Collections

linstar de la classe List<T> et de la classe Queue<T>, la classe Stack<T> dispose dune capacit
et implmente la mthode TrimExcess() pour minimiser les baisses de performances provoques par les allocations/dsallocations induites par les changements de taille de la pile. La classe
Stack<T> prsente aussi un constructeur qui permet de fixer la capacit initiale.

Les dictionnaires
Les dictionnaires sont des collections dont les lments sont des couples cl valeur. Dans un
couple, la cl sert indexer la valeur. Par exemple dans les dictionnaires dune langue, les mots
sont les cls et les dfinitions les valeurs. Dans les classes que nous prsentons dans cette section,
les cls et les valeurs peuvent tre de types quelconques. Prcisons quun dictionnaire ne peut
contenir plusieurs couples cl/valeur ayant la mme cl.
Les deux principales caractristiques dun dictionnaire sont :

linsertion rapide dun couple cl/valeur.

la recherche rapide dune valeur partir de sa cl.

La rapidit est cruciale, car on utilise des dictionnaires avant tout pour des raisons de performance. Pour optimiser linsertion et la recherche, deux familles dalgorithmes existent. Dans le
framework .NET ils donnent lieu aux deux implmentations prsentes dans cette section.

Linterface Sytem.Collections.Generic.IDictionary<K,V>
Les deux implmentations de dictionnaires, savoir les classes System.Collections.Generic.SortedDictionary<K,V> et System.Collections.Generic.Dictionary<K,V>, implmentent linterface System.Collections.Generic.IDictionary<K,V> dfinie comme suit :
public interface IDictionary<K, V> : ICollection<KeyValuePair<K, V>>,
IEnumerable<KeyValuePair<K, V>>
Ainsi, un dictionnaire peut tre vu comme une collection dinstances de la structure System.Collections.Generic.KeyValuePair<K,V> dfinie comme suit :
public struct KeyValuePair<K, V>{
public K Key ;
public V Value ;
}
Les mthodes de linterface IDictionary<K,V> sont les suivantes :

void Add(K key, V value)


Ajoute le couple key/value au dictionnaire. Si un couple cl/valeur existe dj avec cette
cl, lexception ArgumentException est lance. Si le dictionnaire sous-jacent est en lecture
seule, lexception NotSupportedException est lance.

bool Remove(K key)


Supprime le couple ayant pour cl largument key. Si un tel couple nest pas prsent, cette
mthode retourne false. Si le dictionnaire sous-jacent est en lecture seule, lexception
NotSupportedException est lance.

Les dictionnaires

583

bool ContainsKey(K key)


Retourne true si le dictionnaire contient un couple ayant pour cl largument key.

Linterface IDictionary<K,V> prsente aussi les proprits suivantes :

V this[ K key ] {get;set;}


Indexeur pour accder en lecture et en criture aux couples cl/valeur partir des cls.

ICollection<K> Keys {get;}


Retourne une collection contenant toutes les cls du dictionnaire.

ICollection<V> Values {get;}


Retourne une collection contenant toutes les valeurs du dictionnaire.

La classe System.Collections.Generic.SortedDictionary<K,V>
Limplmentation System.Collections.Generic.SortedDictionary<K,V> du concept de dictionnaire, repose sur le fait quil doit exister une relation dordre total sur les cls. Les couples
cl/valeur sont constamment ordonns selon lordre total des cls. Lalgorithme dichotomique
peut ainsi tre appliqu lors de linsertion dun couple cl/valeur et lors de la recherche dune
cl. Lavantage principal de cet algorithme et quil est performant puisquil aboutit en un temps
logarithmique. Cest cet algorithme que lon applique intuitivement lorsque lon recherche un
mot dans un dictionnaire de la langue Franaise par exemple. Dans ce cas, lordre total sur les
cls est lordre alphabtique des mots du dictionnaire. Il nous permet en quelques secondes
de trouver parmi des dizaines de milliers de dfinitions, la dfinition correspondante au mot
cherch.
Clairement, limplmentation de SortedDictionary<K,V> doit tre mme de comparer deux
instances du type K. Si les cls ne supportent pas linterface IComparable<K> ou si le comparateur
par dfaut Comparer<K>.Default ne convient pas, vous pouvez utiliser certains constructeurs de
SortedDictionary<K,V> qui acceptent un paramtre de type IComparer<K>. Cette interface est
dcrite un peu plus loin.
La classe SortedDictionary<K,V> est dfinie comme suit :
public class SortedDictionary<K, V> : IDictionary<K, V>,
ICollection<KeyValuePair<K, V>>, IEnumerable<KeyValuePair<K, V>>,
IDictionary, ICollection, IEnumerable
En plus des membres de linterface IDictionary<K,V>, la classe SortedDictionary<K,V> prsente la mthode bool TryGetValue(K key, out V value) qui retourne une valeur en fonction de sa cl. Si la cl est trouve, la valeur de retour est true. Sinon, elle vaut false.

La classe System.Collections.Generic.Dictionary<K,V>
Limplmentation de la classe System.Collections.Generic.Dictionary<K,V> est base sur le
concept de table de hachage que nous allons dcrire. Ainsi, elle corrige certains problmes potentiels inhrents limplmentation de la classe SortedDictionary<K,V> :

Nous navons pas toujours une relation dordre total sur les cls du dictionnaire.

Si lopration de comparaison de deux cls est une opration coteuse, les performances
dutilisation de SortedList<K,V> sont dgrades.

584

Chapitre 15 : Collections
Linsertion et la recherche dans une table de hachage se font en un temps en gnral
constant (i.e indpendamment du nombre dlments), ce qui est mieux que le temps logarithmique de lalgorithme dichotomique de limplmentation de SortedDictionary<K,V>.

Voici la dfinition de la classe Dictionary<K,V> :


public class Dictionary<K, V> : IDictionary<K, V>,
ICollection<KeyValuePair<K, V>>, IEnumerable<KeyValuePair<K, V>>,
IDictionary, ICollection, IEnumerable,
ISerializable, IDeserializationCallback

Les tables de hachage


Lide dune table de hachage est de calculer une valeur de hachage sur la cl du dictionnaire,
linsertion ou la recherche. La valeur de hachage correspondant la cl est un entier. Une
fois que la cl est hache en un entier, on utilise un algorithme de recherche en temps constant,
bas sur certaines proprits des groupes finis dordre premier.
Cependant il peut toujours arriver que deux cls direntes aient la mme valeur de hachage
entire. On nomme ce phnomne collision entre cls. Pour pallier ce problme, le concept de
table de hachage introduit la notion de panier (bucket en anglais). La table de hachage contient
plusieurs paniers indexs par les valeurs de hachage. Un panier index par la valeur de hachage
H contient tous les couples cl/valeur dont la cl a pour valeur de hachage H. Ces relations
dinclusion sont exposes par la Figure 15-3.
Table de hachage
Panier index par la valeur 693
Panier index par la valeur 692
... 691
Panier index par la valeur

...

...
...

Panier index par la valeur 691


Une cl dont la valeur de hachage est 691

Valeur associe cette cl

Une autre cl dont la valeur de hachage est aussi 691

Valeur associe cette cl

Figure 15 -3 : Table de hachage, paniers et couples cl/valeur


Cette notion de panier est interne limplmentation de la table de hachage et le dveloppeur
naura jamais manipuler de paniers. Pour les curieux sachez que la magie de la recherche en
temps constant dcoule dun thorme darithmtique qui dit que lordre de tout lment dun
groupe fini dordre premier est gal lordre du groupe. Ainsi, limplmentation de Dictionary<K,V> maintient en interne un nombre premier de paniers qui varie dans le temps selon
le nombre de paniers courant. En appliquant le thorme prcdent, le panier dans lequel doit
tre contenu une valeur dont la cl admet une valeur de hachage H a pour index H modulo le
nombre courant de paniers.
Avec cet algorithme, lopration dinsertion dun couple cl/valeur est une opration en temps
constant sil ny a pas de collision. Lopration de recherche dune cl se fait en trois tapes :

Les dictionnaires

585

Dabord lobtention de la valeur de hachage de la cl ;

ensuite la recherche du panier (en temps constant) ;

enfin, une fois le panier trouv, la recherche squentielle dans le panier sil y a collision.

Notion de facteur de chargement


Nous souhaitons vous sensibiliser ici sur un dtail important des algorithmes de tables de hachage. Daprs ce que nous avons prsent, dans une table de hachage, plus le rapport (nombre
de couple cl/valeur) / (nombre de panier) est petit, plus les recherches seront rapides, car moins
il y aura de collisions. Malheureusement plus ce rapport est petit, plus la table de hachage
consomme de la place mmoire et donc, en dfinitive, nuit au performance. Ce compromis est
gr en interne dans la classe Dictionary<K,V> par ce que lon nomme le facteur de chargement
(load factor en anglais).
chaque insertion dun couple cl/valeur dans une table de hachage, le rapport #couples/#paniers
augmente. Lorsque ce rapport dpasse le facteur de chargement, le nombre de panier est
automatiquement augment pour tre gal au plus petit nombre premier plus grand que deux
fois le nombre de paniers courant.
La classe Dictionary<K,V> est optimise et vous navez pas la possibilit dagir sur le facteur
de chargement. En revanche, certains constructeurs protgs de la classe Dictionary<K,V> acceptent la valeur de la capacit initiale. Si la valeur initiale de la capacit nest pas un nombre
premier, elle sera automatiquement positionne un nombre premier suprieur cette valeur.

Mthodes GetHashCode() de la classe object


La classe object prsente les deux mthodes Equals() et GetHashCode(), utilises sur le type
K par les algorithmes de la classe Dictionary<K,V>. Si vous dcidez de redfinir la mthode
Equals() sur une classe dont les instances sont susceptibles de reprsenter des cls dans une
table de hachage, il est impratif que vous redfinissiez la mthode GetHashCode() de faon
satisfaire la contrainte suivante : pour toutes instances x et y de votre type si x.GetHashCode()
et y.GetHashCode() sont deux entiers dirents, alors les expressions x.Equals(y) et y.Equals(x)
doivent valoir toutes les deux false. Si vous ne suivez pas cette contrainte, vous vous exposez
des comportements errons des tables de hachage. Pour vous aider satisfaire cette contrainte,
le compilateur C  2 met un avertissement lorsque vous redfinissez la mthode Equals() sans
redfinir la mthode GetHashCode().

Valeurs de hachages obtenues sur les chanes de caractres


Limplmentation par dfaut de la mthode String.GetHashCode() tient compte de la casse
mais ne tient pas compte de la culture. Si vous souhaitez paramtrer la faon dont la culture
est prise en compte ou si vous souhaitez ne pas tenir compte de la casse lors de lobtention
dune valeur de hachage partir dune chane de caractres, il faut que vous utilisiez une instance dune classe drive de la classe abstraite System.StringComparer. Une telle instance est
obtenue partir dune des proprits de cette classe telle que StringComparer CurrentCultureIgnoreCase{get;} ou StringComparer Ordinal{get;}. On remarque que cette astuce permet que les
classes du framework drives de la classe StringComparer sont en fait dclares comme interne.

586

Chapitre 15 : Collections

Linterface System.Collections.Generic.IEqualityComparer<T>
Dans le cas o vous ne pouvez pas (ou ne souhaitez pas) modifier la classe reprsentant les cls
pour introduire votre algorithme de hachage, vous pouvez implmenter linterface IEqualityComparer<K> dans une classe propritaire spcialement prvue cet eet. Cette interface est
dfinie comme suit :
public interface IEqualityComparer<T> {
bool Equals(T x, T y) ;
int
GetHashCode(T obj) ;
}
Certains constructeurs de la classe Dictionary<K,V> acceptent cette interface en argument.

Conseils pour les algorithmes de hachage


Il est conseill que votre algorithme de hachage ait les proprits suivantes :

Lalgorithme doit avoir une bonne distribution alatoire afin dviter les collisions.

Lalgorithme doit tre rapide excuter.

Deux objets ayant le mme tat doivent avoir la mme valeur de hachage.

Le calcul de la valeur de hachage doit utiliser des champs immuables. Des champs immuables sont des champs initialiss la construction de lobjet, dont la valeur ne change
pas durant la vie de lobjet. Si ce conseil nest pas respect, la valeur de hachage dun mme
objet risque de ne pas tre constante dans le temps.

Un exemple
Voici un exemple ou nous implmentons notre propre algorithme de hachage pour les instances de la classe Personne. Notre algorithme multiplie la valeur du code de hachage de la
chane de caractres du nom de la personne par son anne de naissance. Notez que lon ne craint
pas les dbordements lors de la multiplication car par dfaut, les dpassements de capacit de
multiplication dentiers ne provoquent pas dexception.
Exemple 15-15 :
using System.Collections.Generic ;
class Personne {
public Personne(string nom, int anneeNaissance) {
m_Nom = nom ;
m_AnneeNaissance = anneeNaissance ;
}
public override int GetHashCode() {
return m_AnneeNaissance * m_Nom.GetHashCode();
}
public override bool Equals(object o) {
Personne personne = o as Personne;
if (personne != null)
return (personne.GetHashCode() == GetHashCode());
return false;
}

Trier les lments dune collection

587

private string m_Nom ;


private int m_AnneeNaissance ;
}
class Program {
public static void Main() {
Dictionary<Personne,string> dico = new
Dictionary<Personne,string>() ;
Personne julien = new Personne( "Julien" , 2002) ;
Personne mathieu = new Personne( "Mathieu" , 2001) ;
dico.Add( julien, "20 rue Arson" ) ;
dico.Add( mathieu, "90 Rue Barberis" ) ;
bool b = dico.ContainsKey(julien) ;
// Ici, b vaut true.
}
}

Parcours des lments dun dictionnaire


Lorsque nous avons prsent linterface IDictionary<K,V> implmente par tous les dictionnaires, nous avons vu que cette interface implmente linterface IEnumerator<KeyValuePair<K,V>>.
Ainsi, lorsque lon parcourt les lments dun dictionnaire, nous numrons en fait les couples
cls/valeurs et non seulement les cls ou seulement les valeurs. Voici un exemple illustrant le
parcours des lments dun dictionnaire :
Exemple 15-16 :
using System.Collections.Generic ;
class Program {
public static void Main() {
Dictionary<string,string> dico = new Dictionary<string,string>() ;
dico.Add("France", "20 Rue Arson") ;
dico.Add("Francis", "90 Rue Barberis") ;
foreach (KeyValuePair<string,string> elem in dico)
System.Console.WriteLine(elem.Key + " : " + elem.Value);
}
}
Cet exemple ache :
France : 20 Rue Arson
Francis : 90 Rue Barberis
Grce lutilisation de types gnriques, le parcours des lments des dictionnaires en .NET v2
avec une boucle de type foreach nutilise pas une opration de unboxing superflue et pnalisante
pour les performances comme ctait le cas en .NET v1.x.

Trier les lments dune collection


Vous avez la possibilit de trier les lments dune collection de type System.Array ou System.Collections.Generic.List<T>. Les autres types de collections nont pas besoin dtre tris
pour les raisons suivantes :

588

Chapitre 15 : Collections

System.Collections.BitArray et System.Collections.Specialized.BitVector32
Il ny a aucun sens trier des bits !

System.Collections.Generic.Queue<T> et System.Collections.Generic.Stack<T>
Ces types de collections sont utiliss car laccs leurs lments est trs particulier (accs
FIFO ou accs LIFO). Le tri dune de ces collections modifierait laccs ses lments, et donc
sa raison dtre.

System.Collections.Generic.LinkedList<T>
Si vous avez trier les lments dune liste lie, mieux vaut utiliser la classe List<T> pour
reprsenter votre liste.

System.Collections.Generic.SortedDictionary<T>
Les lments dune telle collection sont par dfinition constamment tris par rapport
leurs cls. Il nest donc pas ncessaire de prvoir une opration de tri.

System.Collections.Generic.Dictionary<K,T>
Cette implmentation utilise un algorithme bas sur les valeurs de hachages des cls pour
avoir un accs rapide aux lments. Le tri des lments perturberait irrmdiablement lorganisation interne ncessaire la rapidit de ces accs.

Il existe de nombreux algorithmes de tri. Lalgorithme retenu par limplmentation Microsoft


de .NET est lalgorithme QuickSort. Cet algorithme sexcute en un temps Q (n log2 (n)), si n est
le nombre dlments de la collection trier. Cet algorithme est un algorithme de tri instable.
Un tri instable ne conserve pas forcment lordre de dpart des lments de mme valeur.

Les interfaces IComparer<T> et IComparable<T>


Pour pouvoir trier des objets, il faut pouvoir les comparer. Il y a plusieurs manires de comparer
les instances dune classe :

Soit la classe implmente une des interfaces System.IComparable ou System.IComparable<T> qui, grce la mthode int CompareTo(T other) permet dordonnancer linstance
courante avec linstance passe en argument.

Soit une autre classe implmente linterface System.Collections.IComparer ou System.Collections.Generic.IComparer<T> pour ordonnancer les instances de la classe
concerne (grce la mthode int Compare(object/T,object/T)). Dans ce cas, cette autre
classe doit tre encapsule dans la classe concerne si la comparaison doit se faire entirement ou partiellement sur des membres privs. Cette autre classe est en gnral drive
de la classe abstraite System.Collections.Generic.Comparer<T> qui, bien videmment,
implmente linterface IComparer<T>.

Signalons enfin que certaines mthodes de comparaison utilisent un dlgu de type System.Comparison<T> pour comparer les lments. Cette dlgation est dfinie comme ceci :
public delegate int System.Comparison<T>(T x, T y) ;

Lorsque vous le pouvez, il est prfrable dutiliser la premire manire. Vous tes cependant
oblig dutiliser une des deux autres manires si vous ne pouvez ou ne souhaitez pas modifier
la classe dont les instances doivent tre compares.
Tous les types primitifs du CTS reprsentant des nombres supportent les interfaces IComparable
et IComparable<T>.

Trier les lments dune collection

589

Trier les lments dun tableau


La classe System.Array prsente de nombreuses surcharges de la mthode Sort() pour trier
des lments. Les mthodes non prsentes ici permettent de ne trier quune partie du tableau.
Notez que les mthodes de tri dun tableau sont statiques et quil est prfrable dutiliser les
surcharges gnriques pour bnficier du typage fort.

public static void Sort<K,V>(K[] arrayKeys, V[] arrayValues)


Trie arrayValues en se servant des lments de arrayKeys comme cls de tri. La classe des
lments de arrayKeys doit implmenter linterface IComparable<T> :
Exemple 15-17 :
public class Program {
public static void Main() {
string[] tabNoms = { "Jean", "Seb", "Eva", "Paul" } ;
int[] tabKeys = { 3, 1, 6, 2 } ;
System.Array.Sort<int,string>(tabKeys, tabNoms) ;
// Ici tabNoms vaut {"Seb","Paul","Jean","Eva"}.
// Ici tabKeys vaut {1,2,3,6}.
}
}

public static void Sort<T>(T[] array)


Trie array, en supposant que la classe des lments de array implmente linterface IComparable<T>. Voici un exemple de tri sur un tableau dentiers :
Exemple 15-18 :
public class Program {
public static void Main() {
int[] tab = { 3, 1, 6, 2 } ;
System.Array.Sort(tab) ;
// Ici tab vaut {1,2,3,6}.
}
}

Voici un exemple de tri sur un tableau avec des lments dune classe propritaire qui implmente linterface IComparable :
Exemple 15-19 :
using System ;
class Article : IComparable<Article>{
public decimal prix ;
public Article(decimal prix) { this.prix = prix ; }
int IComparable<Article>.CompareTo(Article other){
return prix.CompareTo(other.prix) ;
}
}
public class Program {
public static void Main() {
Article[] tab = { new Article(98M) , new Article(19M) ,

590

Chapitre 15 : Collections
new Article(9.5M) } ;
Array.Sort<Article>(tab) ;
// Ici, tab[0].prix vaut 9.5 ; tab[1].prix vaut 19 ;
// tab[2].prix vaut 98
}
}

public static void Sort<T>(T[] array, IComparer<T> comparer)


Trie array, en utilisant lobjet comparer pour comparer les lments. Voici lexemple prcdent rcrit avec cette syntaxe de Sort() :
Exemple 15-20 :
using System.Collections.Generic ;
class Article {
public class CmpArticle : IComparer<Article>{
int IComparer<Article>.Compare(Article a1, Article a2){
return a1.prix.CompareTo(a2.prix);
}
}
public decimal prix ;
public Article(decimal prix) { this.prix = prix ; }
}
public class Prog{
public static void Main(){
Article[] tab = { new Article(98M) , new Article(19M) ,
new Article(9.5M) } ;
System.Array.Sort<Article>( tab , new Article.CmpArticle() ) ;
// Ici, tab[0].prix vaut 9.5 ; tab[1].prix vaut 19 ;
// tab[2].prix vaut 98
}
}

public static void Sort<T>(T[] array, Comparison<T> comparison)


Trie array, en utilisant la mthode rfrence par le dlgu comparison pour comparer
les lments. Voici lexemple prcdent rcrit avec cette syntaxe de Sort() :
Exemple 15-21 :
class Article {
public static int MethodCmp(Article a1, Article a2){
return a1.prix.CompareTo(a2.prix);
}
public decimal prix ;
public Article(decimal prix) { this.prix = prix ; }
}
public class Program {
public static void Main(){
Article[] tab = { new Article(98M) , new Article(19M) ,
new Article(9.5M) } ;
System.Array.Sort<Article>( tab , Article.MethodCmp ) ;

Foncteurs et manipulation des collections


//
//

591

Ici, tab[0].prix vaut 9.5 ; tab[1].prix vaut 19 ;


tab[2].prix vaut 98

}
}

Trier les lments dune instance de List<T>


La classe System.Collections.Generic.List<T> prsente les mthodes de tri suivantes. Notez
que les mthodes de tri dune liste sont non statiques.

void Sort()
void Sort(Comparison<T> comparison)
void Sort(IComparer<T> comparer)
void Sort(int index, int count, IComparer<T> comparer)
Trie une partie des lments dune liste. Cette partie est contenue entre les lments index
et index+count.

Foncteurs et manipulation des collections


Il est prfrable davoir assimil les notions de mthodes anonymes et ditrateurs introduites
par C  2 avant daborder la prsente section.

Nouvelles dlgations spcialises et foncteurs


Lespace de noms System contient quatre nouvelles dlgations particulirement utiles pour
manipuler et obtenir des informations partir des collections :
namespace
public
public
public
public
}

System {
delegate
delegate
delegate
delegate

void
bool
U
int

Action<T> ( T obj ) ;
Predicate<T> ( T obj ) ;
Converter<T,U> ( T from ) ;
Comparison<T> ( T x, T y ) ;

Dans lExemple 15-21 nous avons eu loccasion de nous servir dun dlgu instance de Comparison<T> pour trier les lments dun tableau. Lexemple suivant expose quatre dirents
traitements sur des listes (une requte, un calcul, un tri et une conversion), eectus grce
des instances de ces dlgations :
Exemple 15-22 :
using System.Collections.Generic ;
class Program {
class Article {
public Article(decimal prix,string name){Prix=prix ; Name=name;}
public readonly decimal Prix ;
public readonly string Name ;
}
static bool IsEven(int i) { return i % 2 == 0 ; }

592

Chapitre 15 : Collections
static int sum = 0 ;
static void AddToSum(int i) { sum += i ; }
static int CompareArticle(Article x, Article y){
return Comparer<decimal>.Default.Compare(x.Prix, y.Prix) ;
}
static decimal ConvertArticle(Article article){
return (decimal)article.Prix ;
}
static void Main(){
// Recherche de tous les entiers pairs.
// Utilisation implicite dun d
el
egu
e de type Predicate<T>.
List<int> integers = new List<int>() ;
for(int i=1 ; i<=10 ; i++)
integers.Add(i) ;
List<int> even = integers.FindAll( IsEven );
// Somme les elements de la liste dans le champ statique sum.
// Utilisation implicite dun d
el
egu
e de type Action<T>.
integers.ForEach( AddToSum );
// Tri dune liste delements dun type complexe.
// Utilisation implicite dun d
el
egu
e de type Comparison<T>.
List<Article> articles = new List<Article>() ;
articles.Add( new Article(5,"Tongues") ) ;
articles.Add( new Article(3,"Ballon") ) ;
articles.Sort( CompareArticle );
// Cast des elements dune liste d
el
ements dun type complexe.
// Utilisation implicite dun d
el
egu
e de type Converter<T,U>.
List<decimal> artPrix =
articles.ConvertAll<decimal>( ConvertArticle );
}
}

Les lecteurs qui ont eu loccasion dutiliser la Standard Template Library (STL) de C++ reconnaissent la notion de foncteur (fonctor en anglais) aussi nomme fonction-objet. Un foncteur est
un traitement paramtr. Les foncteurs sont particulirement utiles pour eectuer un mme
traitement sur chacun des lments dune collection. En C++ on surchargeait loprateur parenthse pour implmenter la notion de foncteurs. En .NET, un foncteur prend la forme dun
dlgu. En eet, dans le programme prcdent, les quatre dlgus crs implicitement sont
autant dexemples de foncteurs.

Utilisation des mthodes anonymes


Comme le montre lexemple suivant, les mthodes anonymes de C  se rvlent tre particulirement adaptes pour implmenter la notion de foncteur. Notez qu linstar du deuxime
foncteur qui stocke la somme des lments dune liste dentiers, un foncteur peut encapsuler
un tat.

Foncteurs et manipulation des collections

593

Exemple 15-23 :
using System.Collections.Generic ;
class Program {
class Article {
public Article(decimal prix,string name){Prix=prix ; Name=name;}
public readonly decimal Prix ;
public readonly string Name ;
}
static void Main(){
// Recherche de tous les entiers pairs.
// Utilisation implicite dun d
el
egu
e de type Predicate<T>.
List<int> integers = new List<int>() ;
for(int i=1 ; i<=10 ; i++)
integers.Add(i) ;
List<int> even =integers.FindAll(delegate(int i){return i%2==0;});
// Somme les elements de la liste.
// Utilisation implicite dun d
el
egu
e de type Action<T>.
int sum = 0 ;
integers.ForEach(delegate(int i) { sum += i ; });
// Tri dune liste delements dun type complexe.
// Utilisation implicite dun d
el
egu
e de type Comparison<T>.
List<Article> articles = new List<Article>() ;
articles.Add(new Article(5,"Tongues")) ;
articles.Add(new Article(3,"Ballon")) ;
articles.Sort(delegate(Article x, Article y){
return Comparer<decimal>.Default.Compare(x.Prix,y.Prix) ; });
// Cast des elements dune liste d
el
ements dun type complexe.
// Utilisation implicite dun d
el
egu
e de type Converter<T,U>.
List<decimal> artPrix = articles.ConvertAll<decimal>(
delegate(Article article) { return (decimal)article.Prix ; });
}
}

Support des classes List<T> et Array


Lutilisation des foncteurs nest possible que sur les collections de type List<T> et Array. En eet,
seules ces collections prsentent des mthodes qui acceptent des foncteurs pour traiter leurs
lments. Ces mthodes aux noms susamment loquents pour se passer de commentaires,
sont listes ci-dessous :
public class List<T> : ... {
public int FindIndex(Predicate<T> match) ;
public int FindIndex(int index, Predicate<T> match) ;
public int FindIndex(int index, int count, Predicate<T> match) ;
public int FindLastIndex(Predicate<T> match) ;
public int FindLastIndex(int index, Predicate<T> match) ;

594

Chapitre 15 : Collections
public
public
public
public
public
public

int FindLastIndex(int index, int count, Predicate<T> match) ;


List<T> FindAll(Predicate<T> match) ;
T Find(Predicate<T> match) ;
T FindLast(Predicate match) ;
bool Exists(Predicate<T> match) ;
bool TrueForAll(Predicate<T> match) ;

public
public
public
public
...

int RemoveAll(Predicate<T> match) ;


void ForEach(Action<T> action) ;
void Sort(Comparison<T> comparison) ;
List<U> ConvertAll<U>(Converter<T,U> converter) ;

}
public class Array {
public static int FindIndex<T>(T[] array, int startIndex,
int count, Predicate<T> match) ;
public static int FindIndex<T>(T[] array, int startIndex,
Predicate<T> match) ;
public static int FindIndex<T>(T[] array, Predicate<T> match) ;
public static int FindLastIndex<T>(T[] array, int startIndex,
int count, Predicate<T> match) ;
public static int FindLastIndex<T>(T[] array, int startIndex,
Predicate<T> match) ;
public static int FindLastIndex<T>(T[] array, Predicate<T> match) ;
public static T[] FindAll<T>(T[] array, Predicate<T> match) ;
public static T Find<T>(T[] array, Predicate<T> match) ;
public static T FindLast<T>(T[] array, Predicate<T> match) ;
public static bool Exists<T>(T[] array, Predicate<T> match) ;
public static bool TrueForAll<T>(T[] array, Predicate<T> match) ;
public static void ForEach<T>(T[] array, Action<T> action) ;
public static void Sort<T>(T[] array, System.Comparison<T> comparison) ;
public static U[] ConvertAll<T, U>( T[] array,
Converter<T, U> converter) ;
...
}

Itrateurs et collections
Il est ais dtendre ce genre de fonctionnalits tous types de collection grce aux itrateurs
comme le montre le programme suivant (bas sur le pattern pipeline vue en page 554) :
Exemple 15-24 :
using System ;
using System.Collections.Generic ;
class Program{
static public IEnumerable<T> Filter<T> (
Predicate<T> predicate, IEnumerable<T> collection) {
foreach (T item in collection)

Correspondance entre System.Collections.Generic et System.Collections

595

if (predicate(item))
yield return item ;
}
static public IEnumerable<T> Transform<T> (
Converter<T,T> transformer, IEnumerable<T> collection) {
foreach (T item in collection)
yield return transformer(item) ;
}
static public IEnumerable<U> Converter<T, U> (
Converter<T, U> converter, IEnumerable<T> collection) {
foreach (T item in collection)
yield return converter(item) ;
}
static public IEnumerable<int> PipelineIntRange(int begin, int end) {
System.Diagnostics.Debug.Assert( begin < end ) ;
for ( int i = begin ; i <= end ; i++ )
yield return i ;
}
static void Main(){
int modulo = 3 ;
int factor = 2 ;
foreach (string s in
Converter<int,string>( delegate(int item) {
return "Hello:" + item.ToString() ; },
Filter( delegate(int item) {
return (item % modulo == 0) ; },
Transform( delegate(int item) {
return item * factor ; },
PipelineIntRange(1, 10) ) ) ) )
Console.WriteLine(s) ;
}
}
Ce programme ache :
Hello:6
Hello:12
Hello:18

Correspondance entre System.Collections.Generic


et System.Collections
Voici un tableau de correspondance entre ces deux espaces de noms. Rappelons que lespace de
noms System.Collections nest support que pour des raisons de compatibilit ascendante avec
le framework .NET 1.x et quil ny a pas lieu de lexploiter dans des dveloppements spcifiques
avec .NET 2.0 :

596

Chapitre 15 : Collections

System.Collections.Generics

System.Collections

Comparer<T>

Comparer

Dictionary<K,T>

HashTable

List<T>LinkedList<T>

ArrayList

Queue<T>

Queue

SortedDictionary<K,T> SortedList<K,T>

SortedList

Stack<T>

Stack

ICollection<T>

ICollection

IComparable<T>

System.IComparable

IComparer<T>

IComparer

IDictionary<K,T>

IDictionary

IEnumerable<T>

IEnumerable

IEnumerator<T>

IEnumerator

IList<T>

IList

16
Bibliothques de classes

Fonctions mathmatiques
La classe System.Math
La classe System.Math ne contient que des champs et des mthodes statiques.
Il y a deux champs statiques : les deux constantes mathmatiques e (champ statique E ) et p
(champ statique PI ). Ces champs sont de type double.
Les mthodes statiques reprsentent lensemble des fonctions mathmatiques classiques, savoir les fonctions trigonomtriques, les fonctions logarithmiques et puissances, les arrondis etc.
Si une valeur dentre est hors de lensemble, la dfinition de la fonction mathmatique correspondante, la valeur double.NaN est retourne mais aucune exception nest lance ( part pour
la mthode Round()). Les comportements de ces fonctions aux limites infinies dcoulent logiquement des comportements des fonctions mathmatiques correspondantes (grce aux valeurs
double.NegativeInfinity et double.PositiveInfinity qui peuvent tre en entre ou en sortie
selon les fonctions).
Voici la liste exhaustive de ces fonctions :

Les mthodes dfinies pour plusieurs types numriques :

Type Abs(Type a)

Cette mthode renvoie la valeur absolue de a. Type peut


tre nimporte quel type numrique sign, rel ou entier.

Type Min(Type a, Type b)Type


Max(Type a, Type b)

Ces mthodes renvoient le minimum (respectivement le


maximum) de a et de b. Type peut tre nimporte quel
type numrique sign ou non, rel ou entier.

598

Chapitre 16 : Bibliothques de classes

int Sign(Type a)

Cette mthode renvoie le signe de a, sous la forme dun


entier : 1 si a est ngatif ; 0 si a est gal 0 ; 1 si a est
positifType peut tre nimporte quel type numrique sign, rel ou entier.

Les mthodes trigonomtriques. Tous les angles sont prciss en radians. Si un argument est
hors de lensemble de dfinition dune telle fonction, la valeur double.NaN est retourne.

double Cos(double d)

Retourne le cosinus de d. Lensemble de dfinition est tous


les nombres rels et lensemble image est [ -1 ; 1 ].

double Sin(double d)

Retourne le sinus de d. Lensemble de dfinition est tous les


nombres rels et lensemble image est [ -1 ; 1 ].

double Tan(double d)

Retourne la tangente de d. Lensemble de dfinition est


tous les nombres rels (contrairement la tangente mathmatique qui nest pas dfinie pour un ensemble de points
discrets) et lensemble image est tous les nombres rels.

double Acos(double d)

Retourne larc cosinus de d. Lensemble de dfinition est [


-1 ; 1 ] et lensemble image est [ 0 ; p ].

double Asin(double d)

Retourne larc sinus de d. Lensemble de dfinition est [ -1 ;


1 ] et lensemble image est [ -p/2 ; p/2 ].

double Atan(double d)

Retourne larc tangente de d. Lensemble de dfinition est tous les nombres rels et lensemble
image est [ -p/2 ; p/2 ]. p/2 est atteint pour la valeur
double.PositiveInfinity alors que -p/2 est atteint pour
la valeur double.NegativeInfinity.

double Atan2(double y,double


x)

Retourne langle entre la demi-droite positive des abscisses


et le vecteur qui part de lorigine jusquau point (x,y).
Lensemble image est donc [ -p ; p ].

double Cosh(double d)

Retourne le cosinus hyperbolique de d. Lensemble de dfinition est tous les nombres rels et lensemble image est [
1 ; double.PositiveInfinity]. double.PositiveInfinity
est atteint pour les valeurs double.NegativeInfinity et
double.PositiveInfinity.

double Sinh(double d)

Retourne le sinus hyperbolique de d. Lensemble de


dfinition est tous les nombres rels et lensemble image est
[double.NegativeInfinity ; double.PositiveInfinity].
double.PositiveInfinity est atteint pour la valeur
double.PositiveInfinity et double.NegativeInfinity
est atteint pour la valeur double.NegativeInfinity.

Fonctions mathmatiques

double Tanh(double d)

599

Retourne la tangente hyperbolique de d. Lensemble


de dfinition est tous les nombres rels et lensemble
image est [ -1 ; 1 ]. 1 est atteint pour la valeur
double.PositiveInfinity et -1 est atteint pour la
valeur double.NegativeInfinity.

Les mthodes puissances, exponentielles et logarithmiques :

double Sqrt(double d)

Retourne la racine carre de d. Lensemble de dfinition est


tous les nombres rels positifs ou nul. Si une valeur ngative est passe, la valeur double.Nan est retourne.

double
y)

Retourne x puissance y.

Pow(double x,double

double Exp(double d)

Retourne lexponentielle du nombre d (i.e le nombre e


puissance d).

double Log(double d)

Retourne le logarithme nprien du nombre d. Si une valeur ngative est passe, la valeur double.Nan est retourne.

double
b)

Retourne le logarithme en base b du nombre d. Si une valeur ngative est passe pour d ou b, la valeur double.Nan
est retourne.

Log(double d,double

double Log10(double d)

Retourne le logarithme en base dcimale du nombre d. Si


une valeur ngative est passe, la valeur double.Nan est retourne.

Type Round(Type d)
Type Round(Type d, int n)
Type est un type rel. La mthode Round() permet darrondir un nombre rel la prcision
n. Par exemple :
Math.Round(3.1415)
// retourne 3.0
Math.Round(3.1415,2) // retourne 3.14

Si n est ngatif lexception System.ArgumentOutOfRangeException est lance.

double Ceiling(double d)
double Floor(double d)
Ceiling veut dire plafond en anglais, et floor veut dire sol. La mthode Floor() renvoie donc
la partie entire de d, soit le plus grand entier infrieur d. De mme, la mthode Ceiling()
renvoie la partie entire de d +1, soit le plus petit entier suprieur d.

600

Chapitre 16 : Bibliothques de classes


double IEEERemainder(double x,double y)
Retourne le nombre x-(yQ) ou Q est gal au quotient de x par y arrondi lentier le plus
proche. Si ce quotient est pile entre deux nombres entiers, la valeur entire paire est choisie.
Si y est nul, double.NaN est retourne.

De grands domaines mathmatiques comme le calcul matriciel ou les nombres complexes ne


sont pas nativement couverts par le framework .NET. Gageons que des librairies vont tre cres.
Notons que Fortran.NET sera srement le langage de choix pour les applications calculatoires.

La classe System.Random
Lutilisation de la classe System.Random permet de gnrer des nombres alatoires. Les mthodes
de cette classe ne sont pas statiques. Autrement dit, cette classe doit tre instancie pour tre
utilise. Les mthodes de cette classe sont :
Random()

Constructeur dun objet Random. La valeur de base de lalgorithme de gnration alatoire (aussi appele graine ou
seed en anglais) est calcule partir de lheure.

Random(int seed)

Constructeur dun objet Random. La valeur de base de lalgorithme de gnration alatoire est seed.

int Next()

Renvoie un nombre entier alatoirement choisi dans lensemble 0 et int.MaxValue.

int Next(int a)

Renvoie un nombre entier alatoirement choisi dans lensemble 0 et a-1.

int Next(int a,int b)

Renvoie un nombre entier alatoirement choisi dans lensemble a et b-1.

double NextDouble()

Renvoie un nombre rel alatoirement choisi dans lensemble [ 0 ; 1.0 [.

void NextBytes(Byte[] Tab)

Rempli le tableau doctets avec pour chaque octet un


nombre entier alatoirement choisi dans lensemble 0 et
255.

Donnes temporelles (dates, dures...)


Avec le framework .NET, les dveloppeurs Microsoft disposent enfin dune gestion des donnes
temporelles cohrente. Jusquici, dans le monde Microsoft, il fallait se contenter de plusieurs
classes qui reprsentaient les dates sur quatre octets (pour les classes CTime ou time_t) ou huit
octets (pour la classe COleDateTime). Les fonctionnalits de ces classes taient redondantes avec
des mthodes avec des noms et des arguments dirents.
En .NET les donnes temporelles sont principalement manipules laide de deux structures :

La structure System.DateTime dont les instances reprsentent une date.

Donnes temporelles (dates, dures...)

601

La structure System.TimeSpan dont les instances reprsentent un intervalle de temps. Par


exemple il existe une opration qui permet de rcuprer une instance de System.TimeSpan
partir de deux instances de System.DateTime.

La structure System.DateTime
Les dates reprsentes par les instances de System.DateTime sont dans lintervalle de temps compris entre les deux dates suivantes :

minuit (dbut de la journe) 1er janvier 0001 et

minuit (fin de la journe) du 31 dcembre 9999.

Dans une instance de System.DateTime, en interne, la date est reprsente par un entier de type
long (un entier sign sur huit octets). La correspondance est dune unit pour un intervalle de
temps de 100 nanosecondes. Cet entier est accessible en lecture/criture avec la proprit non
statique Ticks. Cet entier est compris entre les deux champs statiques constants MinValue et
MaxValue de type DateTime. Les deux entiers correspondants aux dates extrmales cites plus
haut sont 0 et 3.155.378.975.999.999.999.
Dans tout louvrage nous ne considrons que le calendrier grgorien, utilis dans le monde
occidental. Sachez que lespace de noms System.Globalization contient en plus de la classe
GregorianCalendar, dautres classes qui reprsentent dautres calendriers comme HebrewCalendar,
JapaneseCalendar, JulianCalendar etc.
La classe DateTime comprend de nombreux membres, statiques ou non. La liste exhaustive de
ces membres se trouve larticle DateTime Members des MSDN. Nanmoins, nous vous prsentons quelques-uns de ces membres les plus couramment utiliss :

Les constructeurs :
DateTime(int annee, int mois, int jour)
DateTime(int annee, int mois, int jour, int heure, int minute, int seconde)
DateTime(int annee, int mois, int jours, int heure, int minute, int seconde,
int millisec)
DateTime(long Ticks)
annee est entre 1 et 9999. mois est entre 1 et 12. jour est entre 1 et 28, 29, 30 ou 31 (en
fonction du mois). heure est entre 0 et 23. minute est entre 0 et 59. seconde est entre 0 et
59. millisec est entre 0 et 999.

Les reprsentations du temps courant (trs utiles, notamment pour les tests de performances) :

static DateTime Now

Cette proprit statique accessible en lecture seulement retourne une instance de DateTime qui reprsente la date et
lheure courante. La prcision est de lordre du centime de
seconde.

static DateTime Today

Cette proprit statique, accessible en lecture seulement,


retourne une instance de DateTime qui reprsente la date
courante avec lheure initialise minuit au dbut de la
journe.

602

Chapitre 16 : Bibliothques de classes

static DateTime UtcNow

Cette proprit statique, accessible en lecture seulement,


retourne une instance de DateTime qui reprsente la date
et lheure courante reprsente en temps universel.

Les extractions dinformation en units temporelles familires dune instance de DateTime


(membres non statiques). Sauf remarque contraire, ces proprits sont accessibles en lecture/criture :

DateTime Date

Renvoie la date avec la partie temps initialise minuit au dbut


de la journe. Cette proprit est accessible en lecture seulement.

int Day

Compris entre 1 et 31.

DayOfWeek DayOfWeek

Renvoie une des valeurs de lnumration System.DayOfWeek


qui sont : Sunday Monday Tuesday Wednesday Thursday Friday
Saturday

int DayOfYear

Compris entre 1 et 366.

int Hour

Compris entre 0 et 23.

int Millisecond

Compris entre 0 et 999.

int Minute

Compris entre 0 et 59.

int Month

Compris entre 1 et 12.

int Second

Compris entre 0 et 59.

TimeSpan TimeOfDay

Dure depuis minuit.

int Year

Compris entre 1 et 9999.

Les oprations sur les dates :

DateTime Add(TimeSpan
dT)operator+

Ajoute la dure dT la date. Loprateur + est en gnral


prfr.

TimeSpan Subtract(DateTime
date)operator-

Soustrait date la date reprsente par linstance. Loprateur --- est en gnral prfr. Remarquez que la dure
retourne peut tre ngative.

static int Compare(DateTime


d1,DateTime d2)operator< <= >
>= ==

Renvoie 0 si d1 gal d2.Renvoie 1 si d1 antrieur


d2.Renvoie -1 si d1 postrieur d2.Lutilisation des oprateurs de comparaison est en gnral prfre.

static bool IsLeapYear(int


annee)

Renvoie true si lanne spcifie est bissextile.

Donnes temporelles (dates, dures...)

static int DayInMonths(int


annee,int mois)

603

Renvoie le nombre de jour pour le mois spcifi. Par


exemple DayInMonths(2000,2) renvoie 29 car lanne
2000 est bissextile.

string ToString(string Format)


Renvoie une date dans une chane de caractres. La chane Format spcifie la faon dont
la prsentation de la date est formate. Dans la suite nous supposons lachage du programme suivant (avec la chane XXX variable) :
// le 15 octobre 2002 `a 14 heures 30 minutes 0 seconde
DateTime d = new DateTime(2002,10,15,14,30,0) ;
Console.WriteLine( d.ToString(XXX) ) ;

Indicateurs pour la reprsentation gnrale dune date


XXX

Affichage

"d"

15/10/2002

"D"

mardi 15 octobre 2002

"f"

mardi 15 octobre 2002 14:30

"F"

mardi 15 octobre 2002 14:30:00

"g"

15/10/2002 14:30

"G"

15/10/2002 14:30:00

"M"

15 octobre

"R"

Tue, 15 Oct 2002 14:30:00 GMT

"s"

Dates formates pour tre tries simplement. Par exemple :2002-10-15T14:30:00

"t"

14:30

"T"

14:30:00

"u"

2002-10-15 14:30:00Z

"U"

mardi 15 octobre 2002 12:30:00 Temps universel : cette date la France a deux
heures davance sur le temps universel.

"y"

octobre 2002

Indicateurs pour la reprsentation personnalise dune date


XXX

Affichage

604

Chapitre 16 : Bibliothques de classes

Sparateur des heures:minutes:secondes. Ce sparateur dpend de la culture choisie dans Windows.

Sparateur des annes/mois/jours. Ce sparateur dpend de la culture choisie dans


Windows.

Anne, reprsente par un numro de 0 99.

yy

Anne, reprsente par un numro de 00 99.

yyy

Anne, reprsente par un numro de 0000 9999.

Mois, reprsent par un numro de 1 12.

MM

Mois, reprsent par un numro de 01 12.

MMM

Mois, reprsent par une abrviation de trois lettres dans la langue dinstallation
de Windows (Par exemple oct.).

MMMM

Mois, dans la langue dinstallation de Windows

Jour, reprsent par un numro de 1 31.

dd

Jour, reprsent par un numro de 01 31.

ddd

Jour, reprsent par une abrviation de trois lettres dans la langue dinstallation
de Windows (Par exemple dim.).

dddd

Jour, dans la langue dinstallation de Windows

Heure, reprsente par un numro de 0 11.

hh

Heure, reprsente par un numro de 00 11.

Heure, reprsente par un numro de 0 23.

HH

Heure, reprsente par un numro de 00 23.

Minute, reprsente par un numro de 0 59.

mm

Minute, reprsente par un numro de 00 59.

Seconde, reprsente par un numro de 0 59.

ss

Seconde, reprsente par un numro de 00 59.

Certains indicateurs comme y ou d ont donc un double sens. Si cest le seul caractre de la chane,
le framework choisira la reprsentation gnrale de la date.

Donnes temporelles (dates, dures...)

605

Exemples de reprsentations personnalises de date utilisant les indicateurs prsents


dans le tableau ci-dessus
XXX

Affichage

"y/M/d h:m:s"

2/10/15 2:30:0

"yy/MM/dd HH:mm:ss"

02/10/15 14:30:00

"ddd d MMM yyy"

mar. 15 oct. 2002

"le dddd d MMMM yyyy"

le mardi 15 octobre 2002

"m minutes s secondes"

30 30inue0 0 0econ15e0

"m minutes s secondes"

30 minutes 0 secondes

static DateTime Parse(string s)


Fabrique une date partir dune chane de caractres. Si la date est non valide lexception
FormatException est lance. Si la chane est vide lexception ArgumentException est lance.
Par dfaut la reprsentation de la chane de caractres doit tre celle de la culture dinstallation de Windows. Par exemple si votre version de Windows est franaise, vous pouvez crire :
Exemple 16-1 :
...
string s = "15/10/2002 14:30:0" ;
DateTime d = DateTime.Parse(s) ;
Console.WriteLine( d.ToString("G")) ;
...
Qui ache : 15/10/2002 14:30:00
Si la date est reprsente dans une autre culture vous pouvez crire :
Exemple 16-2 :
...
string s = "10/15/2002 14:30:0";
DateTime d=DateTime.Parse(s,
new System.Globalization.CultureInfo("en-US") ) ;
Console.WriteLine( d.ToString("G")) ;
...
Qui ache : 15/10/2002 14:30:00
Vous pouvez aussi vous servir du nom des mois (en abrg ou non) par exemple :
Exemple 16-3 :
...
string s = "15 octobre 2002 14:30:0" ;
DateTime d = DateTime.Parse(s) ;
Console.WriteLine( d.ToString("G")) ;
...

606

Chapitre 16 : Bibliothques de classes


Ache : 15/10/2002 14:30:00
Exemple 16-4 :
...
string s = "15 oct. 2002 14:30:0" ;
DateTime d = DateTime.Parse(s) ;
Console.WriteLine( d.ToString("G")) ;
...
Ache : 15/10/2002 14:30:00
Exemple 16-5 :
...
string s = "15 october 2002 14:30:0" ;
DateTime d=DateTime.Parse(s,
new System.Globalization.CultureInfo("en-US")) ;
Console.WriteLine( d.ToString("G")) ;
...
Ache : 15/10/2002 14:30:00

La structure System.TimeSpan
Les instances de la structure System.TimeSpan reprsentent une dure. On montre dans la section prcdente que de telles instances peuvent tre obtenues partir doprations sur des dates
(par exemple une soustraction dune date une autre pour savoir quelle est la dure entre deux
dates).
La reprsentation interne de la dure dans une instance de TimeSpan est la mme que pour une
instance de DateTime. La seule dirence est quune dure peut tre ngative.
La classe TimeSpan comprend de nombreux membres, statiques ou non. La liste exhaustive de
ces membres se trouve larticle TimeSpan Members des MSDN. Nanmoins nous vous prsentons quelques-uns de ces membres les plus couramment utiliss :

Les constructeurs :
TimeSpan(int jours, int heures, int minutes, int secondes)
TimeSpan(int heures, int minutes, int secondes)
TimeSpan (long Ticks)
Tous les arguments peuvent prendre une valeur positive ou ngative quelconque.

Les extractions dinformation en units temporelles familires dune instance de TimeSpan


(membres non statiques). Ces proprits sont accessibles en lecture criture :

int Days

Nombre de jours pleins contenus dans la dure. Si la dure est


ngative, cest le nombre de jours ngatifs plein contenus dans
la dure.

double TotalDays

Nombre de jours contenus dans la dure. La partie fractionnaire


correspond la fraction de jour non plein.

Volumes, rpertoires et fichiers

607

int Hours

Nombre dheures pleines contenues dans la dure purge de tous


les jours pleins. La valeur retourne est donc entre 0 et 23 si la
dure est positive et entre 0 et -23 si la dure est ngative.

double TotalHours

Nombre dheures contenues dans la dure. La partie fractionnaire correspond la fraction de lheure non pleine.

int Minutes

Nombre de minutes pleines contenues dans la dure purge de


toutes les heures pleines. La valeur retourne est donc entre 0 et
59 si la dure est positive et entre 0 et -59 si la dure est ngative.

double TotalMinutes

Nombre de minutes contenues dans la dure. La partie fractionnaire correspond la fraction de la minute non pleine.

int Seconds

Nombre de secondes pleines contenues dans la dure purge de


toutes les minutes pleines. La valeur retourne est donc entre 0 et
59 si la dure est positive et entre 0 et -59 si la dure est ngative.

double TotalSeconds

Nombre de secondes contenues dans la dure. La partie fractionnaire correspond la fraction de la seconde non pleine.

int Milliseconds

Nombre de millisecondes pleines contenues dans la dure purge


de toutes les secondes pleines. La valeur retourne est donc entre
0 et 999 si la dure est positive et entre 0 et -999 si la dure est
ngative.

double TotalMilliseconds

Nombre de millisecondes contenues dans la dure. La partie fractionnaire correspond la fraction de la milliseconde non pleine.

long Ticks

Nombre dintervalles de 100 nanosecondes contenus dans la dure. Cest donc la reprsentation interne de la dure.

TimeSpan Duration()
Renvoie la dure en valeur absolue dun intervalle de temps.
Les oprateurs + - != == < <= > >= sont redfinis et permettent de manipuler simplement et
logiquement les instances de TimeSpan.

Volumes, rpertoires et fichiers


Les volumes, les rpertoires et les fichiers font partie intgrante du systme dexploitation. Le
framework .NET ore un ensemble de classes permettant de les manipuler dune manire indpendante du systme dexploitation sous-jacent. Ces classes sont toutes dans lespace de noms
System.IO,
La gestion des droits daccs aux rpertoires et aux fichiers fait partie du cadre plus gnral de
la gestion des droits daccs, dcrit dans le chapitre sur la scurit.

Manipulation de volumes
Le framework .NET prsente la classe System.IO.DriveInfo qui permet de reprsenter un volume (drive en anglais). Cette classe prsente la mthode statique DriveInfo[] GetDrives() qui

608

Chapitre 16 : Bibliothques de classes

permet de rcuprer tous les volumes disponibles de la machine. Lorsque vous disposez dune
instance de DriveInfo, les proprits suivantes permettent dobtenir des informations relatives
au volume sous-jacent :
Proprits de DriveInfo

Fonctionalit

long AvailableFreeSpace{get;}

Retourne le nombre doctets libres sur le volume.


Prend en compte les quotas.

long TotalFreeSpace{get;}

Retourne le nombre doctets libres sur le volume. Ne


prend pas en compte les quotas.

string DriveFormat{get;}

Retourne le nom du systme de fichier utilis (NTFS,


FAT32 etc).

DriveType DriveType{get;}

Retourne une valeur de lnumration DriveType dcrivant le type de volume (CDRom, Fixed, Removable
etc).

bool IsReady{get;}

Dtermine si le volume est prt.

string Name{get;}

Retourne le nom du volume.

directoryInfo
tory{get;}

RootDirec-

Retourne le rpertoire racine du volume.

long TotalSize{get;}

Retourne la taille en octets du volume.

string VolumeLabel{get;set;}

Retourne ou positionne le label (i.e le nom) du volume.

Cette classe admet aussi un constructeur acceptant une chane de caractres prcisant le label
du volume que lon souhaite reprsenter.

Manipulation de rpertoires
Le framework .NET prsente les deux classes System.IO.Directory et System.IO.DirectoryInfo
qui permettent de manipuler les rpertoires (directory en anglais) dun systme dexploitation. La
plupart des fonctionnalits de ces deux classes sont identiques. En fait, elles prsentent chacune
une faon de travailler avec les rpertoires :

Directory ne contient que des membres statiques. Elle permet de travailler avec les rpertoires sans avoir instancier dobjet.
DirectoryInfo ne contient que des membres non statiques. Pour travailler avec la classe
DirectoryInfo, il faut linstancier. Chaque instance de DirectoryInfo reprsente un rpertoire. La classe DirectoryInfo est drive de la classe System.IO.FileSystemInfo qui
reprsente un fichier au sens large, cest--dire un fichier ou un rpertoire.

Ces classes sont trs compltes. Voici quelques-unes unes des fonctionnalits quelles prsentent.
Pour la liste exhaustive des membres de ces classes, rfrez-vous aux MSDN :

Volumes, rpertoires et fichiers

609

Membres de Directory ou DirectoryInfo

Fonctionalit

NameAttributesLastAccessTimeLastWriteTimeCreationTime

Obtention et modification de toutes les


informations relatives un rpertoire
comme son nom (Name), ses attributs
(Attributes), la date du dernier accs
(LastAccessTime), la date de la dernire
modification (LastWriteTime) ou la date
de cration (CreationTime).

string Directory.GetCurrentDirectory()void
Directory.SetCurrentDirectory(string)

Obtention et modification du rpertoire


dexcution de lapplication.

DirectoryInfo Directory.CreateDirectory(string)DirectoryInfo
Directory.CreateSubdirectory(string)

Cration de nouveaux rpertoires.

void Directory.Delete(string[,bool bRecursive])void DirectoryInfo.Delete([bool


bRecursive])

Destruction dun rpertoire et de son


contenu.

string[] Directory.GetDirectories(string)
DirectoryInfo[] DirectoryInfo.GetDirectories()

Obtention des sous rpertoires dun rpertoire.

string[] Directory.GetFiles(string)
leInfo[] DirectoryInfo.GetFiles()

Obtention des fichiers contenus dans un


rpertoire.

Fi-

DirectoryInfo Directory.GetParent(string)DirectoryInfo DirectoryInfo. Parent

Obtention du rpertoire parent dun rpertoire.

bool Directory.Exists(string)bool DirectoryInfo.Exists

Test de lexistence dun rpertoire.

void Directory.Move(string src, string


dest)void DirectoryInfo.MoveTo(string src)

Dplacement dun rpertoire et de son


contenu.

DirectoryInfo.Refresh()

Rafrachissement des informations dune


instance de DirectoryInfo.

Voici un petit exemple permettant de lister larborescence des sous rpertoires du rpertoire
courant de lapplication. Notez lappel rcursif de la mthode DisplayDirectory(), et la manire utilise pour construire la chane de caractres dindentation :

610

Chapitre 16 : Bibliothques de classes

Exemple 16-6 :
using System ;
using System.IO ;
class Program {
static void DisplayDirectory(DirectoryInfo dir, string sIndent) {
Console.WriteLine(sIndent + dir.Name) ;
foreach (DirectoryInfo sousdir in dir.GetDirectories())
DisplayDirectory(sousdir, sIndent + " ") ;
}
static void Main() {
DirectoryInfo dir = new
DirectoryInfo( Directory.GetCurrentDirectory() );
DisplayDirectory(dir, string.Empty) ;
}
}

Manipulation de fichiers
Le framework .NET prsente les deux classes System.IO.File et System.IO.FileInfo qui permettent de manipuler les fichiers (files en anglais) dun systme dexploitation. La plupart des
fonctionnalits de ces deux classes sont identiques. En fait, elles prsentent chacune une faon
de travailler avec les fichiers :

File ne contient que des membres statiques. Elle permet de travailler avec les fichiers sans
avoir instancier dobjet.

FileInfo ne contient que des membres non statiques. Pour travailler avec FileInfo, il faut
linstancier. Chaque instance de FileInfo reprsente un fichier. FileInfo est drive de
la classe System.IO.FileSystemInfo qui reprsente un fichier au sens large, cest--dire un
fichier ou un rpertoire.

Ces classes sont trs compltes. Voici quelques-unes des fonctionnalits quelles prsentent. Pour
la liste exhaustive des membres des classes File et FileInfo, rfrez-vous aux MSDN :

Les oprations daccs et de modification du contenu dun fichier sont prsentes page 636.
En eet, laccs et la modification dun fichier font partie du cadre plus gnral de la gestion
dun flot de donnes partir dune source ou vers une destination.

Membres de File ou FileInfo

Fonctionnalit

NameAttributesLastWriteTimeCreationTime

Obtention et modification de toutes les informations relatives un fichier, comme son nom
(Name), ses attributs (Attributes), la date du dernier accs (LastAccessTime), la date de la dernire modification (LastWriteTime) ou la date
de cration (CreationTime).

Volumes, rpertoires et fichiers

611

DirectoryInfo FileInfo. Directory

Obtention du rpertoire dun fichier avec la proprit accessible en lecture seule DirectoryInfo
Directory de la classe FileInfo.

bool File.Exists(string)bool FileInfo.Exists

Test de lexistence dun fichier.

string FileInfo.Extension

Obtention de lextension dun fichier.

long FileInfo.Length

Obtention de la taille dun fichier (en octets).

FileStream File.Create(string) FileStream File.Create()

Cration dun fichier.

void File.Move(string src, string


dest)void FileInfo.MoveTo(string)

Dplacement dun fichier.

void File.Copy(string src, string


dest,[bool bOverwrire])void FileInfo.CopyTo(string,[bool bOverwrire])

Copie dun fichier.

void File.Delete(string)void FileInfo.Delete()

Destruction dun fichier.

Manipulation de chemins (Path)


Le framework .NET prsente la classe System.IO.Path qui permet de manipuler les chemins vers
les fichiers et rpertoires, dune manire indpendante du systme dexploitation sous-jacent.
En eet, le formatage dune chane de caractres reprsentant un chemin dpend du systme
dexploitation sous-jacent. Par exemple certains systmes dexploitation ne reprsentent pas un
volume par une lettre tandis que dautres nacceptent pas plus de trois caractres pour lextension dun fichier.
Les chemins sont reprsents par des chanes de caractres. Tous les membres de la classe Path
sont statiques. La classe permet de manipuler des chemins relatifs et des chemins absolus.

Faites attention, en C  le caractre \ de sparation dans un chemin doit tre crit \\


dans une chane de caractres non verbatim. Notez que la proprit accessible en lecture
seule char PathSeparator de la classe Path, reprsente le caractre de sparation utilis
par le systme dexploitation sous-jacent.

La classe Path prsente dautres proprits intressantes comme la proprit char[] InvalidPathChars qui contient tous les caractres non utilisables dans une chane de caractres dcrivant un chemin dans le systme dexploitation sous-jacent. Pour la liste exhaustive des membres
de Path, rfrez-vous aux MSDN.

612

Chapitre 16 : Bibliothques de classes

Grer les vnements survenants sur un fichier ou un rpertoire


La classe System.IO.FileSystemWatcher permet dintercepter les vnements qui surviennent
sur un fichier, sur un rpertoire ou sur un rpertoire et larborescence des sous rpertoires. Les
fichiers et rpertoires surveiller peuvent tre locaux ou distants. Cependant les fichiers et rpertoires en lecture seule comme ceux des CDs et des DVDs ne peuvent tre surveills.
Les vnements de la classe FileSystemWatcher sont les suivants. Notez que pour que les vnements soient eectivement intercepts, il faut positionner la proprit EnableRaisingEvents
true :

Changed : cet vnement est dclench lorsquun changement quelconque intervient sur un
des objets (fichiers ou rpertoires) surveills.

Created : cet vnement est dclench lorsquun objet (fichier ou rpertoire) est ajout dans
le rpertoire (ou un des sous rpertoires) surveills.

Deleted : cet vnement est dclench lorsquun objet (fichier ou rpertoire) surveill est
dtruit.

Error : en interne, la classe FileSystemWatcher contient un tampon pour stocker les vnements intercepts mais dont la procdure de rappel na pas encore t appele. Si les
limites de ce tampon sont atteintes, lvnement Error est dclench. Vous pouvez accder
et modifier la taille du tampon avec la proprit InternalBufferSize.

Renamed : cet vnement est dclench lorsquun objet (fichier ou rpertoire) surveill est
renomm.

Vous avez la possibilit daner le type des vnements que vous souhaitez intercepter avec les
techniques suivantes :

En positionnant la proprit IncludeSubdirectories pour indiquer si vous souhaitez surveiller aussi les sous rpertoires.

En modifiant la proprit string Filter de la classe FileSystemWatcher vous pouvez imposer une contrainte sur les fichiers surveiller. Par dfaut cette proprit vaut "*". Par
exemple vous pouvez dcider de ne surveiller que les fichiers textes en aectant "*.txt"
cette proprit.

Lorsque vous utilisez lvnement Changed, vous interceptez lvnement le contenu du


fichier est chang . La proprit NotifyFilter de la classe FileSystemWatcher permet de
choisir dintercepter un ou plusieurs autres types de changements. Cette proprit prend
ses valeurs dans lnumration indicateur binaire System.IO.NotifyFilters :

Valeur
de
lnumration
System.IO.NotifyFilters

Description

Attributs

Les attributs dun objet (fichier ou rpertoire) surveiller


ont chang.

CreationTime

La date de cration dun objet (fichier ou rpertoire) surveiller a chang.

DirectoryName

Le nom dun rpertoire surveiller a chang.

Base des registres

613

FileName

Le nom dun fichier surveiller a chang.

LastAccess

La date de dernier accs dun objet (fichier ou rpertoire)


surveiller a chang.

LastWrite

La date de dernier accs en criture dun objet (fichier ou


rpertoire) surveiller a chang.

Security

Les attributs de scurit dun objet (fichier ou rpertoire)


surveiller ont chang.

Size

La taille dun objet (fichier ou rpertoire) surveiller a


chang.

Pour la liste exhaustive des membres de FileSystemWatcher veuillez vous rfrer aux MSDN.
Le programme suivant surveille les changements de contenu, les changements de nom de sous
rpertoire et les accs tous les fichiers texte situs dans le rpertoire courant de lapplication
ou dans un de ses sous rpertoires :
Exemple 16-7 :
using System ;
using System.IO ;
public class Program {
public static void Main() {
FileSystemWatcher watcher = new FileSystemWatcher() ;
watcher.Path = Directory.GetCurrentDirectory() ;
watcher.NotifyFilter = NotifyFilters.LastAccess |
NotifyFilters.DirectoryName ;
watcher.Filter = "*.txt" ;
watcher.IncludeSubdirectories = true ;
watcher.Changed += new FileSystemEventHandler(OnChange) ;
watcher.EnableRaisingEvents = true ;
Console.WriteLine("Pressez \q\ pour stopper lapplication.") ;
while (Console.Read() != q) ;
}
public static void OnChange(object source, FileSystemEventArgs e) {
Console.WriteLine("Fichier : " + e.FullPath
+ " Changement:" + e.ChangeType) ;
}
}

Base des registres


Introduction
La base des registres (registry en anglais) appele aussi registre, est une base de donnes qui renferme la plupart des informations consommes par le systme dexploitation. Ces informations

614

Chapitre 16 : Bibliothques de classes

sont trs htroclites. Cela va du type de clavier utilis jusquaux prfrences utilises pour les
logiciels installs sur la machine. Le registre a t introduit sous Windows NT 3.x. Avant, on
utilisait des fichiers textes de type .ini, ce qui avait pour principal inconvnient de perturber
lorganisation du systme. La base des registres vise liminer ces inconvnients. Vous pouvez
visualiser et modifier les informations contenues dans la base des registres avec un des utilitaires regedit.exe ou regedt32.exe. Ces diteurs donnent une reprsentation hirarchique
des informations de la base des registres. Celles-ci sont regroupes comme pour un systme de
fichiers, sauf quau lieu de rpertoires on parle de cls et de sous-cls. Sous NT on prfre utiliser
regedt32.exe.
Il est dconseill daccder la base des registres partir de vos applications .NET pour deux
raisons :

Il ne faut jamais y sauver la configuration de vos applications. En eet, les fichiers de configuration XML dcrits tout au long de cet ouvrage ont t conus cette fin. Ils ont lavantage dtre physiquement stocks dans le mme rpertoire que lapplication. On peut ainsi
raliser un dploiement type XCopy.

La base des registres nexiste principalement que sous les systmes dexploitation Microsoft.
Bien que cette technique de base des registres ait t porte sous dautres systmes dexploitation, son utilisation hors du monde Microsoft reste marginale. Si vous accdez la base
des registres, votre application ne pourra pas tre porte facilement vers un autre systme
dexploitation supportant .NET.

Structure du registre
La hirarchie des informations du registre est la suivante (en partant du plus gnral au plus
particulier) :

Les cls racines (prfixe par HKEY_) ;

les cls ;

les sous-cls ;

les entres de valeur.

Les cls racines contiennent des cls qui, elles-mmes, contiennent des sous-cls. On parle aussi
de ruches qui sont des regroupements de cls. Une entre est constitue de trois parties :

le nom de la valeur ;

le type de donne ;

la valeur elle-mme.

Chaque valeur a lun des types suivants. Nous prcisons le type utilis dans la base des registres.
Nous prcisons aussi le type .NET associ une valeur rcupre de la base des registres dans
du code .NET.

Base des registres

615

Type utilis dans la


base des registres

Type .NET

Description

REG_DWORD

System.Int32

Nombre cod sur quatre octets. Les paramtres de services et des pilotes de priphriques, les adresses mmoire et les interruptions sont en gnral de ce type.

REG_SZ

System.String

Chane de caractres au format Unicode.

REG_MULTI_SZ

System.String[]

Tableau de chanes de caractres au format


Unicode.

REG_EXPAND_SZ

System.String

Chane de caractres extrapolables au format Unicode : ce format est utilis pour les
chanes de caractres formes de variables
denvironnement comme %SystemRoot%.

REG_BINARY

System.Byte[]

Donnes au format binaire. Par exemple


des informations concernant les composants matriels.

Hirarchie du registre
Le registre est structur en cinq cls racines :

HKEY_CLASSES_ROOT : Contient les sous-cls suivantes :

Les extensions de nom de fichier : on y trouve par exemple la cl .zip permettant dassocier les fichiers dextension .zip lapplication WinZip (ou une autre application)
par lintermdiaire des sous-cls de dfinition de classes.

Les PROGID des classes COM enregistres sur la machine : pour chaque PROGID on
dfinit son CLSID, et ventuellement des attributs comme le numro de version de la
classe.

CLSID : contient tous les CLSID de toutes les classes COM enregistres sur la machine.
Chaque CLSID contient le PROGID, le chemin et le nom du fichier qui contient limplmentation de la classe, et ventuellement des attributs, comme le mode dexcution
des objets de la classe.

HKEY_CURRENT_USER : comporte le profil du dernier utilisateur qui a ouvert une session sur la machine. Cette cl rfrence la cl HKEY_USERS qui contient les dernires donnes en cours, les paramtres du bureau et de limprimante, les connexions rseau et les
prfrences dapplication. Une application peut par exemple y stocker une grande quantit
de prfrences comme la taille de la fentre principale, la position des sous fentres ou les
outils disponibles par icone.

HKEY_LOCAL_MACHINE : renferme les informations matrielles et logicielles spcifiques de lordinateur local. Ces donnes sont indpendantes de lutilisateur courant. Cette
cl comporte les sous-cls suivantes :

616

Chapitre 16 : Bibliothques de classes

HARDWARE : cette base de donnes est reconstitue chaque lancement du systme.


Il sagit dune description du matriel : les donnes du BIOS, de la couche dabstraction
du matriel, les paramtres des adaptateurs SCSI et des cartes vido. Le programme de
diagnostics de Windows NT prend ici ses informations.

SAM : base de donnes de scurit des domaines (dans NT Server) et des comptes dutilisateurs de groupes (dans NT Workstation). Les informations prsentes dans cette cl
sont reproduites dans la cl HKEY_LOCAL_MACHINE\SECURITY\SAM.

SECURITY : le sous-systme de scurit de NT tire de cette sous-cl les stratgies de


scurit et les droits des utilisateurs.

SOFTWARE : contient les donnes relatives aux logiciels installs sur la machine.

SYSTEM : renseignements sur le dmarrage (services, pilotes etc) et le comportement


du systme.

HKEY_USERS : contient toutes les donnes de profil de lutilisateur courant, ainsi que les
donnes de profil de tous les utilisateurs qui se sont connects la machine. On y trouve
aussi le profil par dfaut (sous-cl .DEFAULT). La cl HKEY_USERS\.DEFAULT constitue
une ruche dont les fichiers correspondants sont DEFAULT et DEFAULT.LOG. Lditeur de
stratgie systme permet de modifier et de crer des profils dutilisateurs.

HKEY_CURRENT_CONFIG : comporte les donnes de configuration du profil matriel


actif, parmi lesquelles les paramtres dachage et des pilotes priphriques.

Lecture/criture dans la base des registres


La classe Microsoft.Win32.RegistryKey a t spcialement conue pour avoir accs en lecture
et en criture aux donnes de la base des registres. Voici un exemple qui montre comment lire
la valeur dune cl :
Exemple 16-8 :
// Acc`es en lecture `a la cle : // b vaut false
using System ;
using Microsoft.Win32 ;
class Program {
static void Main() {
string[] sTab = new String[4] ;
sTab[0] = "HKEY_LOCAL_MACHINE" ;
sTab[1] = "SOFTWARE" ;
sTab[2] = "Microsoft" ;
sTab[3] = ".NETFramework" ;
string sSubKey = "DbgManagedDebugger" ;
string sKey = sTab[0] ;
RegistryKey rKey = Registry.LocalMachine ;
for (int i = 1 ; i < sTab.Length ; i++) {
sKey += "/" + sTab[i] ;
rKey = rKey.OpenSubKey(sTab[i]) ;
}
Console.WriteLine("Valeur de la cl
e {0}/{1}", sKey, sSubKey) ;
Console.WriteLine(rKey.GetValue(sSubKey)) ;

Le dbogage

617

}
}
Cet exemple ache :
Valeur de la cle HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETFramework/
DbgManagedDebugger
C:\Program Files\Fichiers communs\Microsoft Shared\VS7Debug\vs7jit.exe
PID %d AP PDOM %d EXTEXT "%s" EVTHDL %d

Le dbogage
Attributs pour personnaliser la visualisation des tats de vos objets
Lespace de noms System.Diagnostics prsente des attributs qui vous permettent de personnaliser compltement lachage des tats de vos objets durant le dbogage :

DebuggerDisplayAttribute
Sapplique aux classes, structures, dlgations, numrations, champs, proprits et assemblages. Cet attribut permet de personnaliser la ligne de description dun objet fourni par
le dbogueur. Cette ligne peut contenir des expressions entre accolades qui peuvent tre le
nom dun champ, dune proprit ou dune mthode. Comprenez bien quune telle ligne
doit aller lessentiel en restant concise :
Exemple 16-9 :
using System.Diagnostics ;
[DebuggerDisplay("{Description} prix:{m_Prix} euros")]
class Article {
private decimal m_Prix ;
private string m_Description ;
public string Description{
get{return m_Description;}
}
public Article(string description,decimal prix) {
m_Description = description ;
m_Prix = prix ;
}
}
class Program {
static void Main() {
Article article = new Article("Chaussure", 120) ;
}
}

DebuggerBrowsable
Sapplique aux champs et proprits. Permet de signifier au dbogueur comment il doit
acher ce champ ou cette proprit laide dune des trois valeurs de lnumration
DebuggerBrowsableState savoir : Collapsed : le dbogueur ache la valeur du champ
ou de la proprit lorsque ltat de lobjet est dpli (cest le comportement par dfaut) ;

618

Chapitre 16 : Bibliothques de classes

Figure 16 -1 : Utilisation de lattribut DebuggerDisplay


RootHidden : le dbogueur nache pas la valeur lorsque ltat de lobjet est dpli mais
ache les valeurs des sous membres du champ ou de la proprit concerne ; Never : le
dbogueur nache pas la valeur lorsque ltat de lobjet est dpli.

DebuggerTypeProxyAttribute
Sapplique aux structures, classes et assemblages. Cet attribut appliqu un type T permet
de spcifier un type proxy spcialis dans lachage de ltat dune instance de T. En gnral,
ce type proxy est une classe prive encapsule dans le type T du fait quune telle classe a accs
tous les membres privs de la classe encapsulante :
Exemple 16-10 :
[System.Diagnostics.DebuggerTypeProxy(typeof(ArticleProxy))]
class Article {
private class ArticleProxy {
private Article m_Article;
public ArticleProxy(Article article) {m_Article = article;}
public string Prix { get{ return m_Article.m_Prix + " euros";}}
}
private decimal m_Prix ;
private string m_Description ;
public Article(string description,decimal prix) {
m_Description = description ;
m_Prix = prix ;
}
}
class Program {
static void Main() {
Article article = new Article("Chaussure", 120) ;
}
}
Notez la prsence de la ligne Raw View qui permet dobtenir la vue par dfaut de ltat de
lobjet :

DebuggerVisualizerAttribute

Le dbogage

619

Figure 16 -2 : Utilisation de lattribut DebuggerTypeProxy


Sapplique aux structures, classes et assemblages. Cet attribut constitue la pierre angulaire
dun systme trs puissant de visualisation dtat des objets durant le dbogage. Lide est de
dessiner ltat dun objet directement dans une fentre avec GDI+. On peut ainsi visualiser
au dbogage une image, un objet en 3D, un graphe etc. Plus dinformation concernant
lutilisation de cet attribut sont disponibles dans les MSDN.

Attributs permettant de ne se focaliser que sur son code lors du dbogage


Lespace de noms System.Diagnostics prsente des attributs vous permettant de spcifier au
dbogueur des assemblages, des modules ou des zones de code que vous ne souhaitez pas dboguer. En gnral on exploite ces attributs pour ne pas tre gn lors du dbogage par le code
des bibliothques que lon utilise :

DebuggableAttribute
Sapplique aux assemblages et aux modules. Cet attribut permet de spcifier ou non
lensemble des modes de dbogages que le compilateur JIT doit appliquer sur le code
IL de lassemblage ou du module cibl. Ces modes sont dcrits par lindicateur binaire
DebuggableAttribute.DebuggingModes. On peut citer le mode Edit and Continue, le mode
optimisations JIT dsactives, le mode ignorer les informations du fichier .pdb ou le mode qui
permet dactiver la production par le compilateur JIT des informations qui tablissent le
lien entre le code IL et le code machine gnr (JIT tracking).

DebuggerHiddenAttribute
Sapplique aux constructeurs, aux mthodes et aux proprits. Cet attribut permet de spcifier que le dbogueur ne doit pas entrer dans cette mthode. En consquence, vous ne
pouvez mettre de point darrt dans une mthode marque avec cet attribut. En revanche,
si une mthode marque avec cet attribut appelle une mthode non marque avec cet attribut, il est tout fait possible de dboguer le code de la seconde mthode. Dans ce cas, une
fentre de pile contenant External Code apparatra dans la pile dappels du dbogueur.

DebuggerStepThroughAttribute

620

Chapitre 16 : Bibliothques de classes


Sapplique aux classes, structures constructeurs et mthodes. Cet attribut est comparable
DebuggerHiddenAttribute mis part que dans la situation dcrite, la place dune fentre
de pile External Code il ny aura pas de fentre de pile du tout.

DebuggerNonUserCodeAttribute
Sapplique aux classes, structures, constructeurs, mthodes et proprits. Cet attribut est
comparable DebuggerHiddenAttribute.

Rsoudre les problmes de dbogueur


Il arrive parfois que vous ne puissiez pas dboguer votre application correctement. MinKwan
Park a crit un excellent article qui dcrit de nombreuses situations problmatiques ainsi que
leurs rsolutions. Une version traduite en franais par Frdric De Lne Mirouze est disponible
sur le site dotnetguru.org ladresse http://www.dotnetguru.org/articles/dossiers/debug/
debug.htm.

Les traces
Le framework .NET prsente les deux classes System.Diagnostics.Trace et System.Diagnostics.
Debug qui vous permettent de tracer le droulement dun programme. On parle aussi parfois
de log de lexcution dun programme. Chacune de ces classes prsente les quatre mthodes
Write(string), WriteLine(string), WriteIf(bool,string) et WriteLineIf(bool,string)
qui peuvent par exemple sutiliser comme ceci :
Exemple 16-11 :
using System.Diagnostics ;
class Program {
static void Main() {
Trace.Listeners.Add( new ConsoleTraceListener() ) ;
Trace.WriteLine("Trace hello");
for( int i=0;i<5;i++)
Debug.WriteLineIf(i > 2, "debug i=" + i.ToString() );
}
}
Voici lachage de ce programme sur la console :
Trace hello
debug i=3
debug i=4
La classe Trace est plutt ddie aux log dinformation sur le domaine fonctionnel de lapplication tandis que la classe Debug est destine tracer des informations utiles au dbogage. De ce
fait, elles sont relativement similaires bien quelles doivent tre utilises des fins direntes.
Ces deux classes ne sont oprationnelles que si lassemblage qui les utilise est compil avec respectivement la constante symbolique TRACE ou/et DEBUG.

Les traces

621

Listener
LExemple 16-11 commence par la ligne :
Trace.Listeners.Add( new ConsoleTraceListener() ) ;
Cela signifie que la liste des listeners va contenir un listener de type ConsoleTraceListener. Un
listener est une instance dune classe qui drive de la classe System.Diagnostics.TraceListener.
La liste de listener est commune aux deux classes Debug et Trace. Lorsquune trace est logue
par une de ces classes, chacun des listeners de la liste crit la trace sur son propre moyen de persistance, en loccurrence, la console pour un listener de type ConsoleTraceListener. Dautres
classes de listener sont prvues par le framework (les classes en gras ont t ajoutes par la version 2.0) :
System.Diagnostics.TraceListener
Microsoft.VisualBasic.Logging.FileLogTraceListener
System.Diagnostics.DefaultTraceListener
System.Diagnostics.EventLogTraceListener
System.Diagnostics.TextWriterTraceListener
System.Diagnostics.ConsoleTraceListener
System.Diagnostics.DelimitedListTraceListener
System.Diagnostics.XmlWriterTraceListener
System.Web.WebPageTraceListener

DefaultTraceListener : Trace dans le flot de donnes ddi au dbogage. Par dfaut, ce


flot de donnes est dirig vers la fentre de sortie de Visual Studio.

EventLogTraceListener : Trace dans le journal de log de Windows.

TextWriterTraceListener : Trace dans un flot de donnes tel quun fichier.

DelimitedListTraceListener : Trace dans un flot de donnes tel quun fichier. la dirence de TextWriterTraceListener, les traces sont spares par une chaine de caractres (tel
quun point virgule par exemple) de faon tre facilement exploitables par un programme
danalyse de trace.

XMLWriterTraceListener : Trace dans un fichier XML qui suit un certain schma XML.

WebPageTraceListener : Utilise par ASP.NET. Trace directement sur la page web en


cours de construction.

Vous avez la possibilit de construire la liste de listener par lintermdiaire du fichier de configuration. Par exemple, ce fichier de configuration ajoute un listener de type ConsoleTraceListener
la liste :
Exemple 16-12 :
<?xml version="1.0" encoding="utf-8" ?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.diagnostics>
<trace>
<listeners>
<add name="TraceOnConsole"
type="System.Diagnostics.ConsoleTraceListener"/>

622

Chapitre 16 : Bibliothques de classes


</listeners>
</trace>
</system.diagnostics>
</configuration>

Si lExemple 16-11 est utilis conjointement avec ce fichier, la liste de listeners contiendra deux
listeners qui tracent sur la console et le programme achera chaque trace en double :
Trace
Trace
debug
debug
debug
debug

hello
hello
i=3
i=3
i=4
i=4

Enfin, sachez que vous pouvez dvelopper vos propres classes de listener drives de la classe
TraceListener.

Sources de traage et gravit des traces


Le framework .NET 2.0 introduit la classe System.Diagnostics.TraceSource qui permet de
grer plusieurs sources de traage. Chaque source de traage est nomme et a sa propre liste de
listeners. Les sources de traage ne sont actives lexcution que si la constante symbolique
TRACE est prvue la compilation. En gnral, les sources de traage sont accessibles partir de
champs statiques dune classe visible partir de toute lapplication.
Un systme de filtre sur la gravit dune trace est implment. Voici un exemple dutilisation de
ce systme. Ici, le filtre accepte toutes les traces (SourceLevels.All) et nos traces ont la gravit
information (TraceInformation()) et avertissement (TraceEventType.Warning) :
Exemple 16-13 :
using System.Diagnostics ;
class Program {
static void Main() {
Trace.Listeners.Add(new ConsoleTraceListener()) ;
TraceSource trace1 = new TraceSource("Trace1") ;
trace1.Listeners.Add(new ConsoleTraceListener()) ;
trace1.Switch.Level = SourceLevels.All;
trace1.TraceInformation("Trace hello") ;
for (int i = 0 ; i < 2 ; i++)
trace1.TraceData(TraceEventType.Warning, 122,
"debug i=" + i.ToString()) ;
}
}
Ce programme ache ceci sur la console :
Trace1 Information: 0 : Trace hello
Trace1 Warning: 122 : debug i=0
Trace1 Warning: 122 : debug i=1

Les traces

623

Il naurait pas ach la trace Trace Hello si nous avions choisi SourceLevels.Warning. Le
concept de switch peut tre aussi implment avec des instances de la classe SourceSwitch :
Exemple 16-14 :
...
TraceSource trace1 = new TraceSource("Trace1") ;
SourceSwitch switch1 = new SourceSwitch("Switch1");
switch1.Level = SourceLevels.All;
trace1.Switch = switch1;
...
Une source de traage est paramtrable aprs compilation grce au fichier de configuration. Avec
le fichier de configuration suivant, nous naurions qu crer une source de traage nomme
Trace1 dans notre code :
Exemple 16-15 :
<?xml version="1.0" encoding="utf-8" ?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.diagnostics>
<sources>
<source name="Trace1" switchName="Switch1">
<listeners>
<add name="TraceOnConsole"
type="System.Diagnostics.ConsoleTraceListener"/>
</listeners>
</source>
</sources>
<switches>
<add name="Switch1" value="All" />
</switches>
</system.diagnostics>
</configuration><labelstart id="_Toc114113400"/>

Filtrage des sources de traage au niveau des listeners


Un second niveau de filtrage existe au niveau des listeners. Un listener a la possibilit de
naccepter que des traces issues dune certaines source. Ici, le filtre aect au listener de type
ConsoleTraceListener naccepte que les traces issues de la source fictive nomme TraceXXX et
en consquence, ce programme nache rien :
Exemple 16-16 :
...
TraceSource trace1 = new TraceSource("Trace1") ;
ConsoleTraceListener listener = new ConsoleTraceListener() ;
listener.Filter = new SourceFilter("TraceXXX");
trace1.Listeners.Add(listener);
...

624

Chapitre 16 : Bibliothques de classes

Indentation des traces


Les classes Debug et Trace prsentent des mthodes Indent() et Unindent() pour permettre
dincrmenter et de dcrmenter le niveau dindentation des traces. En outre la proprit
IndentSize dfinit le nombre despaces pour un niveau dindentation :
Exemple 16-17 :
using System.Diagnostics ;
class Program {
static void Main() {
Trace.Listeners.Add(new ConsoleTraceListener()) ;
Trace.IndentSize = 3;
Trace.WriteLine("Begin Main()") ;
fct( 2 ) ;
Trace.WriteLine("End Main()") ;
}
static void fct( int i ) {
Trace.Indent();
Trace.WriteLine( "Begin fct(" + i.ToString() + ")" ) ;
if (i > 0)
fct(i-1) ;
Trace.WriteLine( "End fct(" + i.ToString() + ")" ) ;
Trace.Unindent();
}
}
Ce programme ache ceci sur la console :
Begin Main()
Begin fct(2)
Begin fct(1)
Begin fct(0)
End fct(0)
End fct(1)
End fct(2)
End Main()
La classe TraceSource ne prsente pas ces facilits. Le programme suivant montre comment
pallier ce manque au moyen dune proprit IndentLevel qui calcule le niveau dindentation
en fonction de la taille courante de la pile dappel du thread courant. Ainsi, lachage de ce
programme est identique celui du prcdent :
Exemple 16-18 :
using System.Diagnostics ;
class Program {
static TraceSource trace1 ;
const int IndentSize = 3;
static string IndentLevel { get{
StackTrace stackTrace = new StackTrace();
return new string( ,(stackTrace.FrameCount - 2)* IndentSize);

Les expressions rgulires

625

}
}
static void Main() {
trace1 = new TraceSource("Trace1") ;
trace1.Listeners.Add(new ConsoleTraceListener()) ;
trace1.Switch.Level = SourceLevels.All ;
trace1.TraceInformation( IndentLevel + "Begin Main()") ;
fct(2) ;
trace1.TraceInformation( IndentLevel + "End Main()") ;
}
static void fct(int i) {
Trace.Indent() ;
trace1.TraceInformation( IndentLevel +
"Begin fct(" + i.ToString() + ")" ) ;
if (i > 0)
fct(i-1) ;
trace1.TraceInformation( IndentLevel +
"End fct(" + i.ToString() + ")" ) ;
Trace.Unindent() ;
}
}

Les expressions rgulires


Introduction
Les expressions rgulires (regular expression en anglais, souvent nommes regexp ou regex) constituent un outil puissant pour raliser trois grands types doprations sur les chanes de caractres :

Les expressions rgulires permettent de vrifier quune chane de caractres vrifie une
rgle syntaxique (par exemple si une adresse mail contient bien des caractres avant et aprs
le caractre @ et si le nom du serveur a une extension valide comme .com, .fr ou .edu).

Les expressions rgulires permettent de modifier une chane de caractres en remplaant


un lment par un autre.

Les expressions rgulires permettent dextraire des lments dune chane de caractres.

La notion dexpression rgulire a surtout t popularise par la commande grep sous Unix qui
permet le filtrage de donnes. Le filtre est alors paramtr par une expression rgulire.
Nous allons maintenant prsenter quelques points principaux sur les expressions rgulires sans
toutefois rentrer dans un expos exhaustif qui sortirait du cadre de ce livre.

Syntaxe
Une expression rgulire se prsente comme une chane de caractres. Chaque caractre de cette
chane a une signification prcise. Voici les plus communs :

626

Chapitre 16 : Bibliothques de classes

Caractre(s)

Signification

Annule le sens dun mta caractre.

Dbut de ligne.

Nimporte quel caractre.

Fin de ligne.

x|y

x ou y.

()

Groupement.

[xyz]

Ensemble de caractres (x ou y ou z)

[x-z]

Intervalle de caractres (x ou y ou z)

[x]

Tous sauf x

Vous pouvez aussi spcifier le nombre doccurrences dune expression rgulire avec les expressions suivantes :
Caractre(s)

Signification

x*

Zro, une ou plusieurs occurrences de x.

x+

Une ou plusieurs occurrences de x.

x?

Zro ou une occurrence de x.

x{n}

Exactement n occurrences de x.

x{n,}

Au moins n occurrences de x.

x{n,m}

Au moins n, au plus m occurrences de x.

Enfin, il existe des alias bien pratiques :


Caractre(s)

Alias pour

Signification

\n

Un caractre de fin de ligne.

\r

Un retour la ligne.

\t

Une tabulation.

\s

[\f\n\r\t\v]

Un espacement.

\S

[\f\n\r\t\v]

Tout ce qui nest pas un espacement

Les expressions rgulires

627

\d

[0-9]

Un chire.

\D

[0-9]

Tout, sauf un chire.

\w

[a-zA-Z0-9_]

Un caractre alphanumrique.

\W

[a-zA-Z0-9_]

Tout sauf un caractre alphanumrique.

\067

Un caractre en octal.

\x5A

Un caractre en hexadcimal.

Exemples
Voici quelques exemples dexpressions rgulires et de leurs significations :

Lexpression rgulire ci-dessous vrifie si une chane de caractres commence par une lettre
majuscule.
[A-Z].*

Lexpression rgulire ci-dessous vrifie si une chane de caractres contient au moins un


caractre non alphanumrique.
.*[^0-9A-Za-z`A-y].*

Lexpression rgulire ci-dessous vrifie si une chane de caractres se termine par el ou


elle .
.*(el|elle)

Lexpression rgulire ci-dessous vrifie si une chane de caractres est une date au format
yyyy-mm-dd entre 1900 01-01 et 2099 12-31.
(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])

Le framework .NET et les expressions rgulires


La classe System.Text.RegularExpressions.RegEx permet dutiliser les expressions rgulires
pour vrifier une rgle syntaxique sur une chane de caractres, pour modifier une chane de
caractres ou pour extraire des lments dune chane de caractres. Le programme suivant expose ces possibilits :
Exemple 16-19 :
using System ;
using System.Text.RegularExpressions ;
public class Program {
static void Main() {
// Utilisation dune regexp pour v
erifier quune chaine de
// caract`eres ne contient pas de chiffres.
bool b ;

628

Chapitre 16 : Bibliothques de classes


Regex regex1 = new Regex("^[^0-9]*$") ;
b = regex1.IsMatch("ab3de") ; // b vaut false
b = regex1.IsMatch("abcde") ; // b vaut true
// Remplacement du mot belle par le mot jolie.
Regex regex2 = new Regex("belle") ;
// Affiche Elle est jolie..
Console.WriteLine(regex2.Replace("Elle est belle.", "jolie")) ;
// Extrait chacun des mots s
epar
es par un espace.
Regex regex3 = new Regex("[ ]") ;
string[] mots=regex3.Split("la terre est bleue comme une orange") ;
foreach (string mot in mots)
Console.WriteLine(mot) ;
}
}

Optimiser lvaluation dexpressions rgulires


Nous expliquons page 255 comment optimiser certaines oprations grce la gnration dassemblages. Les expressions rgulires se prtent particulirement bien ce type doptimisation. Aussi, la classe RegExp prsente la mthode statique CompileToAssembly() qui permet
de raliser simplement une telle optimisation. Le programme produit un assemblage nomm
regexp.dll qui contient la classe CompiledExpressions.PasDeChiffres qui drive de la classe
RegEx.
Exemple 16-20 :
using System.Reflection ;
using System.Text.RegularExpressions ;
public class Program {
static void Main() {
string sExpression = "^[^0-9]*$" ;
RegexCompilationInfo reci = new RegexCompilationInfo(
sExpression,
RegexOptions.Compiled,
"PasDeChiffres",
"CompiledExpressions",
true) ;
RegexCompilationInfo[] recis = new RegexCompilationInfo[] {reci} ;
AssemblyName assemblyName = new AssemblyName() ;
assemblyName.Name = "Regex" ;
Regex.CompileToAssembly(recis, assemblyName) ;
}
}
Lexemple suivant montre comment mesurer le facteur doptimisation dune expression rgulire compile :
Exemple 16-21 :
using System ;
using System.Text.RegularExpressions ;

La console

629

using CompiledExpressions ;
using System.Diagnostics ;
public class Program {
const int NLOOP = 1000000 ;
const string str = "abcdefghijklmnopqrstuvwxyz" ;
static void Main() {
bool b ;
Regex regex1 = new Regex("^[^0-9]*$") ;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0 ; i < NLOOP ; i++)
b = regex1.IsMatch(str) ;
Console.WriteLine("Sans pr
ecompilation:{0}", sw.Elapsed );
sw = Stopwatch.StartNew();
PasDeChiffres regex2 = new PasDeChiffres() ;
for (int i = 0 ; i < NLOOP ; i++)
b = regex2.IsMatch(str) ;
Console.WriteLine("Avec pr
ecompilation:{0}", sw.Elapsed );
}
}
Nous avons eectu dirents tests avec lexpression rgulire pour vrifier quil ny a pas
de chire ("[0-9]*$") et lexpression rgulire pour vrifier une date (@"(19|20)\d\d[/.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])"). Les chanes testes taient lalphabet ("abcdefghijklmnopqrstuvwxyz") et un double alphabet ("abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz"). Voici les facteurs doptimisation obtenus :
Expression rgulire

Chane teste

Facteur doptimisation

Pas de chires

Alphabet

1,21

Pas de chires

double alphabet

1,12

Vrification de date

Alphabet

3,99

Vrification de date

double alphabet

4,03

La console
La classe System.Console permet de contrler lachage et la saisie de donnes sur la console.
Toutes les fonctionnalits de cette classe sont accessibles partir de membres statiques puisquune application a au plus une console.

Le curseur de la console
La console supporte un curseur qui reprsente lendroit o le prochain achage de donnes va
seectuer.

630

Chapitre 16 : Bibliothques de classes

Par dfaut le curseur sache aprs le dernier caractre ach mais vous pouvez fixer sa
position avec la mthode SetCursorPosition(int column,int row).

Vous pouvez obtenir ou modifier la position courante du curseur avec les proprits int
CursorTop{get;set;} et int CursorLeft{get;set;}.

Vous pouvez choisir de rendre le curseur visible ou non avec la proprit int CursorVisible{get;set;}.

Vous pouvez obtenir ou positionner la taille du curseur en utilisant la proprit int CursorSize{get ;set} qui prend ses valeurs dans lintervalle ferm [1,100]. La taille du curseur dsigne le pourcentage du remplissage du rectangle du curseur.

Achage de donnes

Vous pouvez acher une chane de caractre lendroit o le curseur est positionn en
appelant une des surcharges de la mthode Write(). La mthode WriteLine() oblige le
curseur passer une nouvelle ligne aprs avoir acher les donnes.

La mthode MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth, int


sourceHeight, int targetLeft, int targetTop) permet de copier un bloc de donnes.
Le rectangle source spcifi doit tre compltement visible. Il se peut quune partie des
donnes soient tronque si le rectangle destination nest pas strictement contenu dans la
partie visible de console.

La mthode Clear() permet deacer toutes les donnes couramment aches sur la
console et de mettre le curseur en haut gauche.

Taille et position de la console

Vous pouvez modifier la position de la console sur lcran au moyen de la mthode


SetWindowsPosition(int left, int top). Les paramtres dsignent le nombre de pixels
par rapport au coin haut gauche de lcran. Vous pouvez obtenir ou modifier la position de la console au moyen des proprits int WindowLeft{get;set;} et int WindowTop{get;set;}.

Vous pouvez modifier la taille de la console au moyen de la mthode SetWindowsSize(int


nColumn, int nRow). Vous pouvez obtenir ou modifier la taille de la console (en terme de
nombre de colonnes et de lignes) au moyen des proprits int WindowWidth{get;set;} et
int WindowHeight{get;set;}.

Vous pouvez obtenir le plus grand nombre de colonnes et de lignes possibles en fonction
de la rsolution de lcran au moyen des proprits int LargestWindowWidth{get;} et int
LargestWindowHeight{get;}.

Le nombre de colonnes et de lignes que la console contient peut dpasser la taille de la


console. Vous pouvez obtenir ou modifier ce nombre de colonnes et de lignes au moyen
des proprits int BufferWidth{get;set;} et int BufferHeight{get;set;}.

Couleur
La classe Console vous permet de modifier la couleur des donnes que vous achez en prcisant
les couleurs avec lnumration System.ConsoleColor. Dans la suite nous utiliserons le terme
couleur background pour dsigner la couleur de fond et le terme couleur foreground pour dsigner la couleur des caractres.

La console

631

Les proprits ConsoleColor ForegroundColor{get;set;} et ConsoleColor BackgroundColor{get;set;} permettent dobtenir ou de positionner les couleurs foreground et background des prochaines donnes aches.

Vous pouvez repositionner les couleurs foreground et background leurs valeurs par dfaut
en appelant la mthode ResetColor().

Saisie de donnes

Vous pouvez obtenir le prochain caractre tap sur le clavier par lutilisateur en appelant
la mthode int ReadKey(). Lappel cette mthode est bloquant jusqu ce quun caractre
soit eectivement tap sur le clavier. Ensuite le caractre correspondant la touche appuye
est ach sur la console. Vous pouvez convertir lentier retourn en une instance de char
en appelant la mthode char Convert.ToChar(int). La surcharge int ReadKey(bool) permet de ne pas acher le caractre sur la console lorsquelle prend en argument la valeur
false.

La proprit bool KeyAvailable{get;} permet de savoir si le prochain appel la mthode


ReadKey() va tre bloquant ou non.

La mthode string ReadLine() retourne la ligne qui vient dtre saisie par lutilisateur.
Cette mthode est bloquante jusqu ce que lutilisateur appuie sur la touche Entre.

La proprit bool TreatControlCAsInput{get;set;} permet de connatre ou dactiver/dsactiver le fait que lvnement Ctrl-C soit trait comme une entre normale ou
non. Si vous positionnez cette proprit false vous pouvez alors exploiter lvnement
CancelPressedKey.

Les proprits bool NumberLock{get;} et bool CapsLock{get;} permettent de savoir si les


modes du clavier NumLock et CapsLock sont couramment activs.

Redirection de flux de donnes


Vous pouvez modifier et obtenir les flux de donnes entrant, sortant et derreur de la console.
Un flux de donnes est une instance dune classe drive de la classe abstraite System.IO.Stream.
Par dfaut le flux de donnes entrant est le clavier, le flux de donnes sortant est la console et
le flux de donnes derreurs est aussi la console.
Vous pouvez obtenir un de ces flux au moyen dune des trois proprits TextReader In{get;},
TextWriter Out{get;} ou TextWriter Error{get;}.
Vous pouvez positionner un de ces flux au moyen dune des trois mthodes void SetError(TextWriter newError), void SetIn(TextReader newIn) ou void SetOut(TextWriter
newOut).

Autres fonctionnalits

La mthode Beep() permet de faire jouer un beep. Une surcharge permet de modifier la
frquence et la dure du beep.

La proprit string Title{get ;set ;} permet dobtenir ou de positionner le titre de la


console.

17
Les mcanismes dentre/sortie

Les classes de base du framework.NET permettant de raliser des entres/sorties de donnes se


trouvent pour la plupart dans lespace de noms System.IO.

Introduction aux flots de donnes


Hirarchie des classes modlisant un flot de donnes
La classe System.IO.Stream est la classe de base pour toutes les classes qui permettent deectuer
des entres sorties en modlisant un flot de donnes. La classe Stream est une classe abstraite.
Il existe trois types de manipulation dun flot de donnes qui sont :

Laccs en lecture aux donnes.

Laccs en criture aux donnes.

Laccs alatoire aux donnes (seek en anglais). Cette possibilit autorise lutilisateur dplacer le curseur en avant et en arrire dans le flot de donnes. Le curseur dfinit lendroit dans
le flot de donnes partir duquel la prochaine lecture/criture se fera. Si cette possibilit
nest pas supporte par un flot de donnes, lutilisation des proprits Length et Position
et des mthodes SetLength() et Seek() de la classe Stream provoque lenvoi de lexception
NotSupportedException. Si cette proprit nest pas supporte par un flot de donnes, on
dit que le flot de donnes est forward-only. Ce terme anglais signifie que le curseur ne peut
tre dplac quen avant.

Vous pouvez tester quelles sont les manipulations supportes par un flot de donnes en utilisant
les proprits CanRead, CanWrite et CanSeek de la classe Stream. Voici la liste des classes drivant
de la classe Stream :

System.IO.FileStream

634

Chapitre 17 : Les mcanismes dentre/sortie


Cette classe permet de modliser un flot de donnes vers un fichier. Ce type de flots de
donnes est dcrit page 637. Il peut ventuellement supporter les trois types daccs.
System.IO.IsolatedStorage.IsolatedStorageFileStream
Cette classe drive de la classe FileStream. Elle permet de raliser la fonctionnalit de stockage isol dcrit page 205, dans le chapitre relatif la scurit.
System.Net.Sockets.NetworkStream
Cette classe permet de modliser un flot de donnes vers une entit distante en utilisant des
sockets (voir page 641). Ce type de flots de donnes ne supporte jamais laccs alatoire.
System.IO.Ports.SerialPort
Cette classe permet de fournir un flot de donnes vers un port srie. Ce type de flots de
donnes ne supporte jamais laccs alatoire.

Services sur un flot de donnes


Le framework .NET propose une architecture pour permettre lajout de services des flots de
donnes. Cette architecture est dcrite en page 657. Voici la liste des classes reprsentant des
services sur les flots de donnes. Elles sont toutes drives de la classe Stream :

System.Net.Security.AuthenticatedStream
Les classes drives de cette classe sont des implmentations de protocoles de scurisations
de flot de donnes par authentification et encryptions (voir page 660).
System.Security.Cryptography.CryptoStream
Cette classe permet de crypter les donnes dun flot de donnes. Un flot qui utilise ce service
ne supporte jamais laccs alatoire (voir page 664).
System.IO.BufferedStream
Cette classe est utilise conjointement avec un flot de donnes pour lui fournir une mmoire tampon. Lutilisation de BufferedStream permet damliorer les performances dans
certains cas, comme ceux exposs page 658. BufferedStream supporte les types daccs du
flot de donnes sous-jacent.
System.IO.MemoryStream et System.IO.UnmanagedMemoryStream
Cette classe permet dobtenir une mmoire tampon partageable entre plusieurs flots de
donnes (voir page 659). La version non gre (unmanaged) permet dviter la copie des
donnes sur le tas par le CLR et est donc plus ecace.
System.IO.Compression.DeflateStream System.IO.Compression.GZipStream
Ces classes permettent de compresser les donnes dun flot (voir page 659).

Typer les donnes dun flot de donnes


En plus des classes modlisant un flot de donnes, lespace de noms System.IO prsente plusieurs couples de classes permettant de typer en entre ou en sortie les octets dun flot de donnes modlis par une des classes prsentes ci-dessus. Chaque couple contient une classe pour
la lecture et une classe pour lcriture. Voici la liste de ces couples de classes :

System.IO.BinaryWriter et System.IO.BinaryReader
Ces classes permettent de lire et dcrire des donnes codes dans un des types primitifs
.NET partir dun flot de donnes. Ces classes prsentent des mthodes telles que short
ReadInt16(), void Write(short), short ReadDouble() ou void Write(double).

Introduction aux flots de donnes

635

System.IO.TextWriter et System.IO.TextReader
Ces classes sont abstraites. Elles servent de classes de base aux classes permettant de lire et
dcrire des caractres encods dans un format particulier. La prsentation de ces formats
fait lobjet de la prochaine section. Ces classes prsentent des mthodes telles que string
ReadLine() ou void WriteLine(string).

System.IO.StringWriter et System.IO.StringReader
Ces classes sont drives respectivement de TextWriter et de TextReader. Elles sont utilises
pour crire et lire des caractres (ou des chanes de caractres) vers une (ou partir dune)
chane de caractres.

System.IO.StreamWriter et System.IO.StreamReader
Ces classes sont drives respectivement de TextWriter et de TextReader. Elles sont utilises pour crire et lire des caractres (ou des chanes de caractres) encods dans un format
particulier, vers un (ou partir dun) flot de donnes quelconque.

Encodage des chanes de caractres


La problmatique de lencodage des caractres est dassocier un caractre tel quune lettre, un
chire ou un symbole, une reprsentation binaire. Un des premiers grands standard concernant lencodage des caractres fut la norme ASCII 7 bits (American Standard Code for Information
Interchange), qui reprsentait 128 caractres tels que les 26 lettres minuscules et les 26 lettres
majuscules de notre alphabet ou encore les 10 chires. Dans la norme ASCII 7 bits, le 8ime bit
est soit positionn zro, soit utilis comme bit de parit.
La norme ASCII 7 bits tait susante pour des applications anglo-saxonnes. Rapidement, la ncessit de reprsenter les lettres accentues des alphabets europens est apparue. Chaque langue
ayant des lettres accentues direntes, on ne pouvait pas encoder tous ces caractres avec 256 valeurs. Il est alors apparu dirents jeux de 256 caractres OEM (Original Equipment Manufacturer).
Ces jeux de caractres sont appels pages de code (codepage en anglais). Il existe une page de code
pour les langues nordiques, une page de code pour le franais tel que lcrivent les qubcois
etc.
Les pages de code OEM sont satisfaisantes pour les Europens et les Amricains. Nanmoins,
elles ne peuvent absolument pas reprsenter les alphabets des langues telles que le chinois ou
larabe. On a donc invent la norme UNICODE. Cette norme associe (pratiquement) chaque
combinaison possible de 16 bits, un caractre parmi les caractres des alphabets europens, asiatiques, arabes etc, mais aussi des symboles mathmatiques, des symboles typographiques etc.
Les 256 premiers caractres de UNICODE correspondent ceux de la page de code ISO 8859-1.
UNICODE va plus loin que les formats prcdents en prenant en compte le fait que les processeurs ne grent pas les entiers de la mme manire. En eet, certains processeurs (comme ceux
dIntel) travaillent dabord avec les huit bits les moins significatifs (on dit quils sont little-endian)
alors que dautres travaillent dabord avec les huit bits les plus significatifs (on dit quils sont
big-endian). Un flot de donnes tant une suite de bits, il faut transformer certains octets pour
certains processeurs. Cette transformation se nomme UTF (Universal Transformation Format). Il
existe plusieurs normes UTF, UTF-7, UTF-8 et UTF-16.
La norme UTF-8 est la plus utilise lheure actuelle. Elle a la particularit davoir un nombre
variable doctets pour reprsenter les caractres UNICODE. Ce nombre doctets peut aller de
1 5. Les caractres reprsents par un octet sont les 128 caractres de la norme ASCII 7 bits.
Nos caractres accentus ne faisant pas partie de ces 128 premiers caractres ASCII, ils sont donc

636

Chapitre 17 : Les mcanismes dentre/sortie

cods sur plus dun octet. Si un diteur de texte supporte lencodage UTF-8, il reproduit correctement les caractres accentus. Sinon, plusieurs caractres tranges remplacent chaque caractre
accentu. Vous avez srement dj rencontr ce problme, notamment dans la lecture de vos
mails.
Lors du traitement dun flot de donnes contenant des caractres avec une des classes StreamWriter ou StreamReader, on a la possibilit de prciser le format dencodage des caractres avec
une des classes suivantes :
System.Object
System.Text.Encoding
System.Text.ASCIIEncoding
System.Text.UnicodeEncoding
System.Text.UTF7Encoding
System.Text.UTF8Encoding
La classe Encoding contient des mthodes permettant de convertir une chane de caractres dun
format un autre.

Lecture/criture des donnes dun fichier


Simple lecture/criture dans un fichier
La classe File prsente six mthodes permettant de lire ou dcrire dans un fichier trs simplement :

Les mthodes ReadAllText() et WriteAllText() permettent dcrire ou de lire une chane


de caractres partir de ou dans un fichier.

Les mthodes ReadAllLines() et WriteAllLines() permettent dcrire ou de lire un tableau de chanes de caractres partir de ou dans un fichier.

Les mthodes ReadAllBytes() et WriteAllBytes() permettent dcrire ou de lire un tableau de doctets partir de ou dans un fichier.

Les mthodes dcriture crent le fichier si celui-ci nexiste pas et crase les anciennes donnes si
le fichier existe. En outre toutes ces mthodes ferment proprement le fichier utilis. Lexemple
suivant montre comment dupliquer un fichier texte tout en lachant lcran :
Exemple 17-1 :
using System.IO ;
public class Program {
public static void Main() {
string text = File.ReadAllText(@"G:\source\Test.txt") ;
System.Console.Write(text) ;
File.WriteAllText(@"G:\source\TestCopy.txt",text) ;
}
}

Lecture/criture des donnes dun fichier

637

Lecture/criture de donnes binaires dans un fichier


Nous montrons ici comment dupliquer un fichier binaire grce aux flots de donnes. Voici les
tapes suivre :

Crer un flot de donnes sortant du fichier source, instance de la classe System.IO.


FileStream.
Crer un flot de donnes entrant dans le fichier destination, instance de la classe FileStream.
Faire transiter le contenu du fichier source vers le fichier destination. Comme vous navez
pas interprter ce contenu, vous navez pas utiliser les classes de typages dun flot de
donnes.

Pour faire transiter les donnes entre un flot de donnes entrant et un flot de donnes sortant,
on utilise en gnral une mmoire tampon (buer en anglais). Une mmoire tampon est une zone
de mmoire de taille fixe, qui est alternativement remplie par le flot de donnes entrant puis
vide dans le flot de donnes sortant. Cette opration de remplissage/vidage est eectue autant
de fois quil le faut.
Le programme suivant copie le fichier excutable de lassemblage courant, vers le fichier Copy.
exe, dans le rpertoire de lapplication. Naturellement lassemblage courant ne doit pas sappeler Copy.exe.
Exemple 17-2 :
using System.IO ;
public class Program {
static readonly int tailleTampon = 512 ;
public static void Main() {
// Le nom du domaine dapplication courant est
egal...
// ...au nom de lassemblage courant (avec lextension).
string sExe = System.AppDomain.CurrentDomain.FriendlyName ;
// Les deux fichiers sont dans le r
epertoire de lapplication.
FileStream inStream = File.OpenRead(sExe) ;
FileStream outStream = File.OpenWrite("Copy.exe") ;
// Construit une zone de m
emoire tampon.
byte[] tampon = new System.Byte[tailleTampon] ;
int nBytesRead = 0 ;
// Copie le fichier binaire.
while ((nBytesRead = inStream.Read(tampon, 0, tailleTampon)) > 0)
outStream.Write(tampon, 0, nBytesRead) ;
// Ferme les flots de donn
ees.
inStream.Close() ;
outStream.Close() ;
}
}

Lecture/criture dun fichier texte


Nous prsentons ici la copie dun fichier texte en utilisant les classes System.IO.StreamWriter
et System.IO.StreamReader. Nous allons copier ligne aprs ligne notre fichier texte. La longueur dune ligne tant de taille variable non borne, on ne peut utiliser une zone de mmoire

638

Chapitre 17 : Les mcanismes dentre/sortie

tampon de taille fixe. chaque copie de ligne nous utilisons une nouvelle chane de caractres
construite par la mthode ReadLine() de la classe StreamReader. Nous utilisons ensuite la mthode WriteLine() de StreamWriter pour copier cette chane dans le fichier destination.
Chaque ligne est ache sur la console au moyen de la mthode WriteLine() de la classe
Console. Une dirence entre ces deux mthodes WriteLine() est que celle de la classe Console
est statique alors que celle de la classe StreamWriter est dinstance.
Exemple 17-3 :
using System.IO ;
public class Program {
public static void Main() {
StreamReader inStream = File.OpenText(@"G:\source\Test.txt") ;
StreamWriter outStream = new
StreamWriter(@"G:\source\TestCopy.txt") ;
// Copie la source vers la destination.
string sTmp ;
do{
sTmp = inStream.ReadLine() ;
outStream.WriteLine(sTmp) ;
System.Console.WriteLine(sTmp) ;
}
while (sTmp != null) ;
// Ferme les flots de donn
ees.
inStream.Close() ;
outStream.Close() ;
}
}
La notion dencodage de caractres semble totalement absente de cet exemple, et pourtant cet
exemple marche quel que soit le format dencodage du fichier texte source. En fait, si lutilisateur
ne spcifie pas dencodage particulier la classe StreamReader, cette classe dtecte automatiquement le format utiliser en analysant les premiers caractres du fichier texte. Chaque type
dencodage prvoit de laisser une marque spciale sur les premiers octets dun fichier texte. Vous
pouvez obtenir cette marque avec la mthode byte[] GetPreamble() de la classe prsente par
chaque classe dencodage drive de la classe Encoding. Par exemple cette marque est les trois
octets 0xEF 0xBB 0xBF pour lencodage UTF8, et 0xFF 0xFE pour lencodage UNICODE et rien
pour lencodage ASCII. Cette marque nest pas forcment prsente dans tous les fichiers textes
et vous avez la possibilit de dsactiver lmission de cette marque lorsque vous crivez dans
un fichier texte au moyen de StreamWriter. Dans ce cas, limplmentation Microsoft utilise par
dfaut lencodage UTF-8.
La proprit Encoding CurrentEncoding{get;} de la classe StreamReader, vous permet de
connatre lencodage utilis. Cette proprit est automatiquement positionne la premire
lecture dun fichier texte. De mme la classe StreamWriter prsente la proprit Encoding
Encoding accessible en lecture seule.

Traitement asynchrone dun flot de donnes


Le traitement dun flot de donnes dpend linairement de la taille de la source de donnes. Le
dveloppeur dune application qui traite un ou plusieurs flots de donnes peut avoir un ordre

Lecture/criture des donnes dun fichier

639

de grandeur de cette taille, mais en gnral, il ne la connat pas. Le temps du traitement dun
flot de donnes est donc inconnu du dveloppeur. Si un tel traitement est eectu par le thread
principal dune application, le rsultat peut tre catastrophique puisque lapplication peut potentiellement se bloquer pendant un temps inconnu. De plus si lapplication a de nombreux
flots de donnes traiter dune manire indpendante, il serait judicieux quelles puissent effectuer ces traitements en parallle.
Le traitement asynchrone dun flot de donnes rsout ces problmes. Il permet de dlguer le
traitement sur un autre thread que celui qui dcide de lancer le traitement. Le dveloppeur na
absolument pas besoin de soccuper de cet autre thread. Ce dernier fait partie du pool de threads
dentre/sortie du CLR. Comme nous allons le voir dans le prochain exemple, il est ais de lancer
en parallle plusieurs traitements asynchrones dun flot de donnes. Une telle architecture pour
vos applications peut tre plusieurs dizaines de fois plus performante que le simple traitement
squentiel.
Lexemple suivant illustre le lancement en parallle de N traitements asynchrones. Tout dabord,
nous crons un fichier volumineux denviron 10 Mo avec la mthode CreateBlobFile(). Ensuite nTraitements lectures de ce fichier sont lances en parallle avec lappel la mthode
BeginRead() de FileStream, dans une boucle. Les threads aects ces traitements sont ceux
du pool de threads. La mthode BeginRead() permet de prciser une procdure de finalisation
qui est appele lorsque la lecture est finie. Cest le mme thread qui eectue la lecture et qui
excute la procdure de finalisation. Dans lexemple, la mthode OnLectureFinie() constitue la
procdure de finalisation. Lexemple montre comment cette mthode rcupre les donnes lues
et ventuellement des informations provenant du thread principal (comme lID du traitement)
grce la classe EtatFichier.
Contrairement aux sections prcdentes, la zone de mmoire tampon est gale la taille du
fichier lire. Nous navons donc pas nous occuper de la gestion du tampon. En fait la classe
FileStream gre son propre mcanisme de tampon en interne dune manire transparente pour
le dveloppeur. Le dveloppeur peut optionnellement fixer la taille de cette mmoire tampon
interne dans un argument du constructeur (ici nous spcifions 4096 octets).
Exemple 17-4 :
using System ;
using System.IO ;
using System.Threading ;
public class Program {
class EtatFichier {
public byte []
tampon ;
public FileStream
fs ;
public int
idTraitement ;
}
// Taille du fichier : presque 10Mo.
static readonly int nOctetsParFichier = 10000000 ;
static readonly int nTraitements = 5 ;
static string
sFileName = "blob.bin" ;
// Cree un fichier de nOctetsParFichier octets.
static void CreateBlobFile(){
byte [] Blob = new byte[nOctetsParFichier ] ;
FileStream fs = new FileStream(

640

Chapitre 17 : Les mcanismes dentre/sortie


sFileName,
FileMode.Create,FileAccess.Write,FileShare.None,
4096, false) ;
fs.Write(Blob, 0, nOctetsParFichier ) ;
fs.Flush() ;
fs.Close() ;
}
public static void Main() {
CreateBlobFile() ;
for(int i = 0 ; i< nTraitements ; i++ ){
FileStream fs = new FileStream(
sFileName,
FileMode.Open,FileAccess.Read,FileShare.Read,
// Taille du tampon interne.
4096,
true) ;
// Initialise les donn
ees `
a communiquer `
a la proc
edure
// de finalisation.
EtatFichier etat = new EtatFichier() ;
etat.idTraitement = i ;
etat.fs = fs ;
etat.tampon = new Byte[nOctetsParFichier ] ;
Console.WriteLine(
"Thread #{0} : Lance lecture asynchrone #{1}",
Thread.CurrentThread.ManagedThreadId ,i) ;
// Lance la lecture asynchrone de donn
ees.
fs.BeginRead(
etat.tampon,
0,nOctetsParFichier ,
new AsyncCallback(OnLectureFinie),
etat);
}
// Pour ne pas compliquer, nous nimpl
ementons pas de m
ecanisme
// de synchronisation pour attendre la fin des traitements
// qui sont realises sur des threads background.
Thread.Sleep(10000) ;
}
// La procedure de finalisation effectue
// un traitement sur les donn
ees lues.
static void OnLectureFinie(IAsyncResult asyncResult) {
// Recup`ere les donnees pour le traitement.
EtatFichier etat = (EtatFichier)asyncResult .AsyncState ;
byte[] data = etat.tampon ;
FileStream fs = etat.fs ;
// Finalise le flot de donn
ees qui a servi `
a lire les donn
ees.
int nOctetsLu = fs.EndRead(asyncResult ) ;
fs.Close() ;
Console.WriteLine(
"Thread #{0} : Debut traitement #{1}",

Support du protocole TCP/IP et des sockets

641

Thread.CurrentThread.ManagedThreadId ,etat.idTraitement) ;
// Simule un traitement des donn
ees dune seconde.
Thread.Sleep(1000) ;
Console.WriteLine(
"Thread #{0} : Fin traitement #{1}",
Thread.CurrentThread.ManagedThreadId ,etat.idTraitement) ;
}
}
Cet exemple ache ceci sur ma machine. On voit bien que le thread principal a pour ID #8
et que les deux threads qui ont pour IDs #6 et #10, sont impliqus dans le traitement asynchrone. Ce comportement peut varier dune excution lautre et il peut y avoir entre 1 et min
(Ntraitement, Nombre de threads IO dans le pool) threads aects au traitement de donnes.
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread

#8 : Lance lecture asynchrone


#8 : Lance lecture asynchrone
#6 : Debut traitement #0
#8 : Lance lecture asynchrone
#8 : Lance lecture asynchrone
#8 : Lance lecture asynchrone
#6 : Fin traitement #0
#6 : Debut traitement #1
#10 : Debut traitement #2
#6 : Fin traitement #1
#6 : Debut traitement #3
#10 : Fin traitement #2
#6 : Fin traitement #3
#10 : Debut traitement #4
#10 : Fin traitement #4

#0
#1
#2
#3
#4

Support du protocole TCP/IP et des sockets


Introduction aux sockets et au protocole TCP/IP
Nous montrons dans cette section que la manipulation des flots de donnes distants, cest-dire entre machines distantes et par lintermdiaire dun rseau, est trs similaire ce que nous
avons prsent plus haut. Les instances de la classe System.Net.Sockets.Socket reprsentent
des sockets. La prsentation dtaille des sockets et du protocole TCP/IP dpasse le cadre de cet
ouvrage, mais nous allons rappeler les points importants concernant ce sujet.
Le protocole IP (Internet Protocol) propose un modle dadressage dune machine. Le protocole
IP est aujourdhui globalement rpandu. Bien que ce soit une vision simpliste, on peut dire
quune adresse IP identifie une machine (ou un groupe de machines) sur le rseau mondial
Internet. Une adresse IP est un nombre cod sur quatre octets. En plus davoir une adresse IP,
chaque machine connecte au rseau Internet peut prsenter jusqu 65536 ports. Un port est
un nombre cod sur deux octets qui reprsente un point de communication sur une machine.
Chaque machine peut donc prsenter plusieurs points de communication, et un couple adresse
IP/ numro de port reprsente un point de communication unique sur le rseau internet. Pour
lutilisateur du protocole IP, tout se passe comme sil y avait eectivement jusqu 65536 flots de

642

Chapitre 17 : Les mcanismes dentre/sortie

donnes entrants et sortants. Ce sont les couches basses rseau, qui modulent et dmodulent le
flot physique de bits entrant et sortant du cble rseau. Ces couches sont conceptuellement en
dessous du protocole IP.
Une socket matrialise un point de communication sur une machine. Les sockets taient la
base, il y a 20 ans, un ensemble de fonctions bas niveau, crites en C. Elles ont t inventes
luniversit de Berkeley en Californie, pour rpondre au besoin de communication inter machines. Lensemble de ces fonctions est devenu standard et a t encapsul dans des classes. Les
sockets supportent plusieurs protocoles de communication. Notamment le protocole UDP/IP
(User Datagram Protocol) et surtout le protocole TCP/IP (Transfert Control Protocol) qui comme
son nom lindique, utilise le protocole IP et son mode dadressage.
Concrtement, un processus, nomm serveur, cre une socket et appelle une mthode bloquante (en gnral nomme Accept()) sur cet objet, pour attendre une requte. Un autre processus cre une autre socket. Ce processus est nomm client. Il existe sur la mme machine ou
sur une autre machine que celle qui contient le processus serveur. Le client fournit sa socket
le couple adresse IP/Port identifiant la socket du serveur. Grce cette information, la socket du
client se connecte la socket serveur, ce qui a pour eet de dbloquer le serveur de son attente
sur la mthode Accept() et de crer un flot de donnes entre le client et le serveur. partir de
cette tape, deux scnarios peuvent se produire sur le serveur :

Soit le serveur traite compltement les besoins du client avant de se remettre en mode dattente client en appelant Accept(). On dit que le serveur travaille dune manire synchrone,
car il sert les clients les uns aprs les autres.

Soit le serveur cre le plus rapidement possible une nouvelle socket quil passe un autre
thread pour quil traite les besoins du client. Le serveur principal rappelle donc Accept()
presque immdiatement aprs la connexion client. On dit que le serveur travaille dune
manire asynchrone.

Bien entendu, lutilisation du mode asynchrone est en gnral beaucoup plus performante,
puisquelle minimise les temps dattente globaux des clients. Nous allons prsenter ces deux
faons de travailler dans les deux prochaines sections.

Utilisation synchrone des sockets


Nous exposons ici un exemple dutilisation synchrone des sockets, avec le code du serveur et
le code du client. Ne vous tonnez pas de ne pas y voir la moindre trace de lutilisation de la
classe Socket ! Le framework .NET prsente les deux classes System.Net.Sockets.TcpListener
et System.Net.Sockets.TcpClient qui utilisent la classe Socket en interne. Lutilisation de ces
classes simplifie grandement la programmation socket avec le protocole TCP/IP. Pour les lecteurs qui connaissent dj bien la programmation avec sockets, sachez que lutilisation de ces
classes reste assez similaire. De plus rien ne vous empche dutiliser la classe Socket si vous
prfrez.

Ct serveur
Notre serveur cre un point de communication sur le port 50000 avec une instance de la classe
TcpListener. Le serveur attend la connexion dun client en appelant la mthode AcceptTcpClient(). Lorsquun client se connecte, le serveur obtient une instance de la classe System.
Net.Sockets.NetworkStream qui reprsente un flot de donnes avec le client. Dans la mthode
TraiteClient(), le serveur ouvre un fichier texte et le copie, ligne aprs ligne, dans le flot de

Support du protocole TCP/IP et des sockets

643

donnes vers le client. Une fois le fichier copi, le serveur ferme le flot de donnes vers le client
et le flot de donnes en provenance du fichier et retourne en attente dun client.
Notez que les oprations de fermeture de flot ont lieu au sein dune clause finally. En eet, ce
sont typiquement des oprations de libration de ressources critiques. Nous aurions pu opter
plutt pour lutilisation du pattern using/Dispose() puisque toutes les classes de flots de donnes
implmentent linterface IDisposable.
Exemple 17-5 :

Le code du serveur : Serveur.cs

using System ;
using System.Net.Sockets ;
using System.Net ;
using System.IO ;
class ProgServeur {
static readonly ushort port = 50000 ;
static void Main() {
IPAddress ipAddress = new IPAddress(new byte[] { 127, 0, 0, 1 }) ;
TcpListener tcpL = new TcpListener(ipAddress,port) ;
tcpL.Start() ;
// Chaque passage dans la boucle = envoi dun fichier `
a un client.
while(true){
try{
Console.WriteLine( "Attente dun client..." ) ;
// Lappel `a cette m
ethode est bloquant, c`
ad, on ne sort
// de cette methode que lorsquun client est connect
e.
TcpClient tcpC = tcpL.AcceptTcpClient() ;
Console.WriteLine( "Client connect
e." ) ;
TraiteClient( tcpC.GetStream() ) ;
Console.WriteLine( "Client d
econnect
e." ) ;
}
catch(Exception e){
Console.WriteLine(e.Message) ;
}
}
}
static void TraiteClient (NetworkStream networkStream){
// Flot de donnees denvoi sur le r
eseau.
StreamWriter streamWriter = new StreamWriter(networkStream) ;
// Flot de donnees de lecture du fichier source.
StreamReader streamReader = new StreamReader(
@"C:/Text/Fichier.txt" ) ;
// Pour chaque ligne du fichier source : envoi sur le r
eseau.
string sTmp = streamReader.ReadLine() ;
try{
while(sTmp != null ){
Console.WriteLine( "Envoi de : {0}" , sTmp ) ;
streamWriter.WriteLine(sTmp);
streamWriter.Flush();

644

Chapitre 17 : Les mcanismes dentre/sortie


sTmp = streamReader.ReadLine();
}
}
finally {
// Fermeture des flots de donn
ees
streamReader.Close() ;
streamWriter.Close() ;
networkStream.Close() ;
}
}
}

Voici lachage du serveur lorsquun client se connecte :


Exemple :

Affichage du serveur

Attente dun client...


Client connecte.
Envoi de : ####################
Envoi de : Voici le contenu...
Envoi de : ...de Fichier.txt
Envoi de : ####################
Client deconnecte.
Attente dun client...

Ct client
Le code du client est encore plus simple que celui du serveur. Le client cre une instance de
la classe TcpClient et lui fournit le couple adresse IP/Port du serveur. Lorsque le client est
connect, il obtient une instance de la classe System.Net.Sockets.NetworkStream qui reprsente un flot de donnes avec le serveur. Le client na plus qu lire les donnes partir de ce
flot. Nous savons que le serveur envoie des lignes de caractres. Aussi, le client lit les lignes les
unes aprs les autres, et les ache sur sa console.
Exemple 17-6 :

Le code du client : Client.cs

using System ;
using System.Net.Sockets ;
using System.IO ;
class ProgClient {
static readonly string host = "localhost" ;
static readonly ushort port = 50000 ;
static void Main() {
TcpClient tcpC ;
try{
// Lappel au constructeur de TcpClient envoie une
// exception si la connexion avec le serveur
echoue.
tcpC = new TcpClient(host,port);
Console.WriteLine(
"Connexion etablie avec {0}:{1}" ,host , port) ;

Support du protocole TCP/IP et des sockets

645

NetworkStream networkStream = tcpC.GetStream();


StreamReader streamReader = new StreamReader(networkStream) ;
try{
// Chaque passage dans la boucle = une ligne r
ecup
er
ee.
string sTmp = streamReader.ReadLine();
while( sTmp != null ){
Console.WriteLine( "R
eception de : {0}" ,sTmp) ;
sTmp = streamReader.ReadLine();
}
}
finally{
// Ferme les flots de donn
ees.
streamReader.Close() ;
networkStream.Close() ;
Console.WriteLine( "D
econnexion de {0}:{1}" ,host , port ) ;
}
}
catch(Exception e){
Console.WriteLine(e.Message) ;
return ;
}
}
}
Voici lachage du client :
Exemple :

Affichage du client

Connexion etablie avec localhost:50000


Reception de : ####################
Reception de : Voici le contenu...
Reception de : ...de Fichier.txt
Reception de : ####################
Deconnexion de localhost :50000
Ladresse IP est ici "localhost" car nous supposons que le serveur et le client sont sur la mme
machine. Dans un cas rel, ladresse IP du serveur serait un paramtre du programme client,
srement plac dans le fichier de configuration de lapplication ou dans les arguments en ligne
de commande. Sachez qu la place dune adresse IP vous pouvez fournir un nom de domaine
comme www.smacchia.com. La classe System.Net.Dns permet de transformer un nom de domaine en une liste dadresses IP et dobtenir un nom de domaine partir dune adresse IP. Nous
nutilisons pas cette classe ici.

Utilisation asynchrone des sockets


Ct serveur
linstar de la section prcdente, nous nutilisons pas la classe Socket mais seulement les
classes TcpListener et TcpClient. Toute limplmentation du modle asynchrone tient dans
le fait que dans le code du serveur, nous utilisons les mthodes BeginRead() et BeginWrite().
Comme nous lavons vu un peu plus haut lors du traitement asynchrone dun flot de donnes

646

Chapitre 17 : Les mcanismes dentre/sortie

avec un fichier, les mthodes BeginRead() et BeginWrite() permettent de fournir une procdure
de finalisation. Cette procdure est automatiquement appele la fin de la lecture ou de lcriture des donnes. Cette procdure est appele par le mme thread qui a eectu la lecture ou
lcriture. Vous navez pas vous occupez de ce thread. Ce dernier fait partie du pool de threads
dentre/sortie du CLR.
Contrairement lexemple du traitement asynchrone dun flot de donnes avec un fichier
(Exemple 17-4), nous nutilisons pas la possibilit de partager un objet entre le thread principal
et le thread du pool eectuant les entre/sorties. En gnral, larchitecture qui permet le
traitement asynchrone des connexions clientes distantes contient une classe dont les instances
traitent les clients (une instance de cette classe par client). Nous avons nomm cette classe
TraitementClient. Une instance de cette classe na besoin que du flot de donnes vers le client.
Ce flot de donnes est obtenu de la mme manire que dans la section prcdente, en appelant
successivement les mthodes AcceptTcpClient() puis GetStream().
Notre exemple est construit de manire ce que le serveur commence par lire les donnes du
client. Une fois rceptionnes, le serveur renvoie ces donnes au client. Le serveur eectue ces
deux oprations jusqu ce que le client ne lui envoie plus de donnes. ce moment, le serveur
arrte de traiter ce client. Bien entendu, dans un cas rel, le serveur ne renverrait pas les donnes directement au client sans traitements, mais nous avons tent de simplifier au maximum
lexemple.
Exemple 17-7 :
using
using
using
using
using

Code du serveur :Serveur.cs

System ;
System.Net.Sockets ;
System.Net ;
System.IO ;
System.Text ;

class ProgServeur {
static readonly ushort port = 50000 ;
static void Main() {
IPAddress ipAddress = new IPAddress(new byte[] { 127, 0, 0, 1 }) ;
TcpListener tcpL = new TcpListener(ipAddress,port);
tcpL.Start() ;
while(true){
try{
Console.WriteLine( "Main:Attente dun client..." ) ;
TcpClient tcpC = tcpL.AcceptTcpClient();
Console.WriteLine( "Main:Client connect
e." ) ;
TraitementClient tc =new TraitementClient(tcpC.GetStream()) ;
tc.Traite() ;
}
catch(Exception e){
Console.WriteLine(e.Message) ;
}
}
}
}
// Une instance de cette classe est cr
e
ee pour chaque connexion client.

Support du protocole TCP/IP et des sockets


class TraitementClient{
static readonly int
tailleTampon = 512 ;
private byte []
m_Tampon ;
private NetworkStream
m_NetworkStream ;
private AsyncCallback
m_ProcLecture ;
private AsyncCallback
m_ProcEcriture ;
// Le constructeur initialise :
//
- m_NetworkStream :le flot de donn
ees avec le client.
//
- m_ProcLecture : la proc
edure de finalisation dune lecture.
//
- m_ProcEcriture : la proc
edure de finalisation dune
ecriture.
//
- m_Tampon : la zone de m
emoire tampon utilis
ee pour la lecture
//
et lecriture.
public TraitementClient(NetworkStream networkStream){
m_NetworkStream = networkStream ;
m_ProcLecture
= new AsyncCallback(this.OnLectureFini) ;
m_ProcEcriture
= new AsyncCallback(this.OnEcritureFini) ;
m_Tampon
= new byte[tailleTampon] ;
}
public void Traite(){
m_NetworkStream.BeginRead(
m_Tampon, 0 , m_Tampon.Length , m_ProcLecture , null ) ;
}
// Cette methode est la proc
edure de finalisation appel
ee lorsquune
// lecture en mode asynchrone d
eclench
ee par lappel `
a la m
ethode
// BeginRead() se termine.
private void OnLectureFini( IAsyncResult asyncResult ){
int nOctets = m_NetworkStream.EndRead(asyncResult ) ;
// Renvoie au client, la chaine re
cue.
if( nOctets > 0 ){
string s = Encoding.ASCII.GetString(m_Tampon,0,nOctets) ;
Console.Write(
"Async:{0} octets recus du client : {1}" , nOctets, s ) ;
m_NetworkStream.BeginWrite(
m_Tampon, 0 , nOctets , m_ProcEcriture , null ) ;
}
// Le client na rien renvoy
e donc on ne soccupe plus de lui.
else{
Console.WriteLine( "Async:Traitement client fini." ) ;
m_NetworkStream.Close() ;
m_NetworkStream = null ;
}
}
// Cette methode est la proc
edure de finalisation appel
ee lorsquune
// ecriture en mode asynchrone d
eclench
ee par lappel `
a la m
ethode
// BeginWrite() se termine.

647

648

Chapitre 17 : Les mcanismes dentre/sortie


private void OnEcritureFini( IAsyncResult asyncResult ){
m_NetworkStream.EndWrite(asyncResult ) ;
Console.WriteLine( "Async:Ecriture finie." ) ;
m_NetworkStream.BeginRead(
m_Tampon, 0 , m_Tampon.Length , m_ProcLecture , null ) ;
}
}

Voici lachage du serveur lorsquil est connect un client dont le code est prsent ci-aprs.
Notez que le serveur se remet en attente dun autre client avant mme de recevoir des donnes
du premier client.
Affichage du serveur

Exemple :

Main:Attente dun client...


Main:Client connecte.
Main:Attente dun client...
Async:33 octets recus du client : Vous avez le bonjour du client !
Async:Ecriture finie.
Async:33 octets recus du client : Vous avez le bonjour du client !
Async:Ecriture finie.
Async:33 octets recus du client : Vous avez le bonjour du client !
Async:Ecriture finie.
Async:Traitement client fini.

Ct client
Le fait que le serveur travaille en mode asynchrone na aucun impact sur le code du client.
Le client est ainsi trs similaire au client prsent dans la section prcdente. La dirence est
quici le client ne se contente pas dattendre des donnes en provenance du serveur. Il envoie
des donnes et reoit des donnes trois fois successivement. Voici le code :
Code du Client : Client.cs

Exemple 17-8 :
using System ;
using System.Net.Sockets ;
using System.IO ;

class ProgClient {
static readonly string host = "localhost" ;
static readonly ushort port = 50000 ;
static void Main() {
TcpClient tcpC ;
try{
// Lappel au constructeur de TcpClient envoie une
// exception si la connexion avec le serveur
echoue.
tcpC = new TcpClient(host,port);
Console.WriteLine(
"Connexion etablie avec {0}:{1}",host,port) ;
// Initialise le flot de donn
ees vers le serveur

Obtenir des informations concernant le rseau

649

// accessible en ecriture et en lecture.


NetworkStream networkStream = tcpC.GetStream();
StreamWriter streamWriter = new StreamWriter(networkStream) ;
StreamReader streamReader =new StreamReader(networkStream) ;
try{
string sEnvoie = "Vous avez le bonjour du client !" ;
string sRecu ;
for(int i=0;i<3;i++)
{
// Envoi de donn
ees au serveur.
Console.WriteLine( "Client -> Serveur:" + sEnvoie ) ;
streamWriter.WriteLine(sEnvoie) ;
streamWriter.Flush() ;
// Reception de donn
ees du serveur.
sRecu = streamReader.ReadLine() ;
Console.WriteLine( "Serveur -> Client :" + sRecu ) ;
}
}
finally{
streamWriter.Close() ;
streamReader.Close() ;
networkStream.Close() ;
}
}
catch(Exception e){
Console.WriteLine(e.Message) ;
return ;
}
}
}
Voici lachage du client :
Affichage du client

Exemple :
Connexion etablie avec localhost:50000
Client -> Serveur:Vous avez le bonjour
Serveur -> Client :Vous avez le bonjour
Client -> Serveur:Vous avez le bonjour
Serveur -> Client :Vous avez le bonjour
Client -> Serveur:Vous avez le bonjour
Serveur -> Client :Vous avez le bonjour

du
du
du
du
du
du

client
client
client
client
client
client

!
!
!
!
!
!

Obtenir des informations concernant le rseau


Dcouvrir les interfaces rseaux disponibles
Les instances de la classe System.Net.NetworkInformation.NetworkInterface reprsentent
une interface rseau. La mthode statique GetAllNetworkInterfaces() de cette classe permet
de lister toutes les interfaces rseaux disponibles sur la machine. Par exemple :

650

Chapitre 17 : Les mcanismes dentre/sortie

Exemple 17-9 :
using System ;
using System.Net.NetworkInformation ;
public class Program {
public static void Main() {
NetworkInterface[] nis=NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface ni in nis)
{
Console.WriteLine("Nom : " + ni.Name) ;
Console.WriteLine("Description : " + ni.Description) ;
Console.WriteLine("Etat : " + ni.OperationalStatus.ToString()) ;
Console.WriteLine("Vitesse : {0} Kb", ni.Speed / 1024) ;
Console.WriteLine("Media Acess Control (MAC):" +
ni.GetPhysicalAddress().ToString()) ;
Console.WriteLine("---------------------------------") ;
}
}
}

Ping
Vous pouvez utiliser la classe System.Net.NetworkInformation.Ping pour dterminer si une
machine distante est accessible par le rseau. En fait cette classe est lquivalente de la commande Ping.
Exemple 17-10 :
using System ;
using System.Net.NetworkInformation ;
public class Program {
public static void Main() {
using (Ping ping = new Ping()) {
PingReply pingReply = ping.Send("www.smacchia.com", 10000) ;
System.Console.WriteLine("IP:{0} Etat:{1}",
pingReply.Address , pingReply.Status) ;
}
}
}
Cette classe implmente linterface IDispose. En outre la mthode Ping.SendAsync() vous permet de dmarrer un ping dune manire asynchrone. Il faut alors se brancher lvnement
Ping.PingCompleted pour obtenir le rsultat du ping.

Etre averti dun changement rseau


Ladresse IP aecte une interface rseau peut changer dans le temps pour de multiples raisons.
Vous pouvez tre averti le cas chant en vous abonnant lvnement NetworkAddressChanged
de la classe System.Net.NetworkInformation.NetworkChange.

Clients HTTP et FTP

651

Statistiques rseau
Vous pouvez programmatiquement avoir accs aux statistiques des protocoles IP, ICMP, TCP,
et UDP. Ces statistiques sont les mmes que celles obtenues avec les direntes options de la
commande netstat. Par exemple :
Exemple 17-11 :
using System ;
using System.Net.NetworkInformation ;
public class Program {
public static void Main() {
IPGlobalProperties ipProp =
IPGlobalProperties.GetIPGlobalProperties() ;
IPGlobalStatistics ipStat = ipProp.GetIPv4GlobalStatistics();
Console.WriteLine("Host name:" + ipProp.HostName) ;
Console.WriteLine("Domain name:" + ipProp.DomainName) ;
Console.WriteLine("IPv4 # packets recus:" +
ipStat.ReceivedPackets) ;
Console.WriteLine("IPv4 # packets envoy
es:" +
ipStat.OutputPacketRequests) ;
TcpConnectionInformation[] tcpConns =
ipProp.GetActiveTcpConnections() ;
foreach (TcpConnectionInformation tcpConn in tcpConns) {
Console.WriteLine("localhost:{0} <-> {1}:{2} state:{3}",
tcpConn.LocalEndPoint.Port,
tcpConn.RemoteEndPoint.Address.ToString(),
tcpConn.RemoteEndPoint.Port, tcpConn.State) ;
}
}
}
Vous pouvez nobtenir que les statistiques relatives une interface rseau en utilisant la mthode NetworkInterface.GetIPInterfaceProperties().

Clients HTTP et FTP


La notion dURI
Un URI (Universal Ressource Identifier) est une chane de caractres permettant de localiser une
ressource. Un URI est form de trois parties ;

Les premiers caractres dun URI reprsentent le mode daccs de lURI (scheme en anglais).
Voici les modes daccs les plus courants :

Le mode daccs file indique que la ressource est un fichier en local ou sur un intranet.

Le mode daccs http indique que la ressource est gre par un serveur web. Le mode
daccs https indique quil faut utilis le protocole HTTP scuris (i.e HTTP au dessus
du protocole SSL dcrit un peu plus bas page 661).

Le mode daccs mailto indique que la ressource est une adresse e-mail.

652

Chapitre 17 : Les mcanismes dentre/sortie

Le mode daccs ftp indique que la ressource est un fichier gr par un serveur FTP
(File Transfert Protocol) (mme remarque pour ftps que pour https).

Le nom du serveur.

Le nom de la ressource.

Voici quelques exemples dURI :


http://www.smacchia.com/ConstructeursAutomobiles.html
ftp://ftp.lip6.fr/pub/gnu/a2ps/a2ps-4.10.4.tar.gz
file://localhost/
mailto:patrick@smacchia.com
linstar des chemins, un URI peut tre relatif un autre URI. Les URI relatifs nont donc pas
de mode daccs au dbut des chanes de caractres qui les reprsentent. La classe System.Uri a
t conue pour stocker et manipuler les URI.

La classe WebClient
La classe System.Net.WebClient permet dcrire ou de rcuprer des donnes partir dun URI.
Ces donnes peuvent tre stockes sur le systme de fichier local, sur un intranet ou sur internet.
La classe WebClient est trs pratique puisque son implmentation dcide automatique du protocole de transfert de donnes utiliser en fonction de lURI (file, http, https, ftp ou ftps).
Lexemple suivant tlcharge une page HTML partir de son URI puis lache au format texte
sur la console :
Exemple 17-12 :
using System.Net ;
class Program {
static void Main() {
WebClient webClient = new WebClient() ;
string s =
webClient.DownloadString("http://www.microsoft.com/france");
System.Console.Write(s) ;
}
}
La classe WebClient prsente des mthodes telles que void DownloadFile(string uri,string
fileName) ou byte[] DownloadData(string uri) pour faciliter le tlchargement de donnes
stockes dans des fichiers ou au format binaire. Chacune de ces mthodes a une homologue
nomme UploadXXX(string uri, data) permettant dcrire des donnes un emplacement
indiqu par un URI. Notez aussi la prsence du couple de mthodes Stream OpenWrite(string
uri) et Stream OpenRead(string uri) permettant de manipuler une ressource accde par
son URI au moyen dun flot de donnes. Enfin, toutes ces mthodes (DownloadXXX() et
UpLoadXXX()) sont disponibles en une version asynchrone dont le nom est sux par Async.
Dans ce cas, lopration est ralise par un des threads du pool de threads du CLR. Vous pouvez
tre alors prvenu de la terminaison dune opration asynchrone en vous abonnant un
vnement tel que UpdloadDataCompleted de votre instance de la classe WebClient.

Clients HTTP et FTP

653

Autres classes pour manipuler les ressources partir de leurs URI


La classe WebClient est trs pratique mais elle ne sut pas lorsque vous devez exploiter certaines spcificits du protocole de transfert de donnes sous jacent (tel que les cookies du protocole HTTP). Les classes System.Net.FileWebRequest, System.Net.FtpWebRequest et System.
Net.HttpWebRequest permettent de modliser une requte de donnes en tenant compte du
protocole de transfert. Elles drivent toutes les trois de la classe WebRequest. Pour obtenir une
instance dune de ces classes il est conseill dinvoquer la mthode statique WebRequest WebRequest.Create(string uri). Vous pouvez alors personnaliser votre requte. Par exemple la
classe HttpWebRequest prsente la proprit CookieContainer permettant dassocier des cookies
votre requte.
Pour eectuer la requte il faut appeler la mthode WebResponse GetResponse() sur lobjet
reprsentant votre requte. Cette mthode retourne une instance dune des classes drives de
System.Net.WebResponse savoir System.Net.FileWebResponse, System.Net.FtpWebResponse
et System.Net.HttpWebResponse. Vous pouvez alors obtenir les donnes en appelant la mthode dinstance Stream WebResponse.GetResponseStream(). Notez que certaines mthodes
BeginGetXXX() et EndGetXXX() de la classe WebRequest permettent deectuer la requte dune
manire asynchrone. Enfin, sachez quen interne la classe WebClient utilise les classes que lon
vient de prsenter.
Lexemple suivant ache sur la console le contenu dun fichier tlcharg sur un serveur FTP :
Exemple 17-13 :
using System.Net ;
using System.Text ;
using System.IO ;
class Program{
static void Main(){
FtpWebRequest ftpReq = WebRequest.Create(
"ftp://smacchia.com/test.txt") as FtpWebRequest;
ftpReq.Method = WebRequestMethods.Ftp.UploadFile;
ftpReq.Credentials = new NetworkCredential( "login", "password" );
FtpWebResponse ftpResp = ftpReq.GetResponse() as FtpWebResponse;
StreamReader streamReader = new StreamReader(
ftpResp.GetResponseStream() , Encoding.ASCII ) ;
System.Console.WriteLine( streamReader.ReadToEnd() ) ;
streamReader.Close() ;
}
}
Vous pouvez dfinir un cache de ressources. Pour cela, il vous sut de crer une instance de
la classe System.Net.Cache.RequestCachePolicy avec une valeur de lnumration System.
Net.Cache.RequestCacheLevel prcisant le type de cache dsir. Vous pouvez alors positionner le cache au niveau global en utilisant la proprit statique RequestCachePolicy WebRequest.DefaultCachePolicy{get;set;} ou au niveau dune requte en utilisant la proprit
dinstance RequestCachePolicy WebRequest.CachePolicy{get;set;}. Plus dinformation
ce sujet sont disponibles dans la section Cache Management For Network Applications des
MSDN.

654

Chapitre 17 : Les mcanismes dentre/sortie

Serveur HTTP avec HttpListener et HTTP.SYS


La couche noyau HTTP.SYS
Les systmes dexploitation Windows XP SP2 et Windows Server 2003 contiennent une couche
noyau nomme HTTP.SYS spcialise dans le traitement du protocole HTTP. Jusqu cette innovation, les serveurs HTTP sexcutant sur les OS Windows exploitaient la couche utilisateur
Winsock (Windows Socket API) qui elle-mme consommait les services de la couche noyau TCP/IP.
Dans ce modle, chaque requte/rponse HTTP requiert une transition coteuse entre le mode
noyau et le mode utilisateur. HTTP.SYS est une couche qui sexcute entirement en mode
noyau. Notamment, HTTP.SYS prend en charge le cache de rponse. Ainsi, lorsque HTTP.SYS
retourne une page directement partir du cache il ny a pas de transition coteuse vers le mode
utilisateur. Ce modle est donc plus performant.
IIS (le serveur HTTP de Windows) exploite HTTP.SYS depuis la version 6.0. Cette couche sait
grer le routage dune requte HTTP vers le processus adquate notamment parce que IIS 6.0
lui communique ses informations de routage chaque dmarrage. HTTP.SYS sait aussi grer le
stockage des requtes HTTP en attendant le dmarrage du processus qui saura les traiter.

La classe System.Net.HttpListener
Le framework .NET 2.0 prsente la classe System.Net.HttpListener qui permet dexploiter la
couche HTTP.SYS pour dvelopper un serveur HTTP. Bien entendu, vous ne pouvez pas utiliser
cette classe lorsque votre application sexcute sur un OS qui ne supporte pas HTTP.SYS. Le
programme suivant montre comment utiliser cette classe afin de retourner une page HTML
qui contient la date et lheure courante ainsi que lURL demande. Une telle page HTML est retourne pour chaque requte HTTP entrante par le port 8008 destination du chemin /hello :
Exemple 17-14 :
using System.Net ;
using System.IO ;
class Program {
static void Main() {
HttpListener httpListener = new HttpListener() ;
string uri = string.Format("http://localhost:8008/hello/") ;
httpListener.Prefixes.Add(uri);
httpListener.Start();
while (true) {
// Ne pas effectuer la requ
ete si le client
HttpListenerContext ctx = httpListener.GetContext() ;
ctx.Response.ContentType = "text/html" ;
TextWriter writer = new StreamWriter(
ctx.Response.OutputStream, System.Text.Encoding.Unicode) ;
writer.WriteLine(
"<html><body>Page demandee `a {0}<br/>URL:{1}</body></html>",
System.DateTime.Now, ctx.Request.Url) ;
writer.Flush() ;
writer.Close() ;
}

Serveur HTTP avec HttpListener et HTTP.SYS

655

}
}
Le dmarrage du serveur HTTP se fait lappel de la mthode Start(). Il faut au pralable
enregistrer toutes les URLs que votre instance de HttpListener va traiter laide de la proprit
Prefixes. HTTP.SYS supporte le protocole HTTPS. En consquence, vous pouvez prciser des
URLs ayant le mode daccs https://. Si une URL est dj traite par HTTP.SYS, une exception
est leve lors de lajout. Une erreur classique est de ne pas prciser de numro de port dans vos
URLs. Le port 80 est alors pris par dfaut mais en gnral ce port est dj utilis par le serveur
IIS si celui-ci est prsent sur la machine en production. Une exception est alors leve.
Une fois le serveur web dmarr, on entre dans une boucle sans fin. Chaque passage dans la
boucle reprsente le traitement dune requte HTTP. La mthode HttpListener.GetContext()
est bloquante jusqu la rception dune requte. Le traitement de la requte est alors matrialis
par une instance de System.Web.HttpListenerContext. On construit alors notre page HTML
que lon inclut dans la rponse HTTP laide du flot de donnes Stream HttpListenerContext.
Response.OutputStream.
En plus des proprits Request et Response, la classe HttpListenerContext prsente la proprit
IPrincipal User{get;} qui prcise lutilisateur Windows responsable de la requte si celui-ci
a t authentifi. HTTP.SYS supporte les quatre modes dauthentification : Connexion Anonyme, Authentification Digest, Authentification de base et Authentification intgre Windows.
Ces modes sont dcrits en page 957.

Traitement asynchrone des requtes


Dans lExemple 17-14, toutes les requtes HTTP sont traites les unes la suite des autres par
le mme thread. Clairement cette faon de faire nest pas ecace. Aussi, la classe HttpListener
prsente les deux mthodes BeginGetContext() et EndGetContext() qui permettent de traiter
plusieurs requtes simultanment sur les threads du pool du CLR. Voici notre exemple rcrit
avec cette possibilit (notez qu chaque excution de la boucle nous attendons une seconde
pour viter que le thread principal ne monopolise le processeur puisque la mthode BeginGetContext() nest pas bloquante) :
Exemple 17-15 :
using System ;
using System.Net ;
using System.IO ;
class Program {
static void Main() {
HttpListener httpListener = new HttpListener() ;
string uri = string.Format("http://localhost:8008/hello/") ;
httpListener.Prefixes.Add(uri) ;
httpListener.Start() ;
while (true) {
IAsyncResult result =
httpListener.BeginGetContext(ProcessResponse, httpListener);
System.Threading.Thread.Sleep(1000) ;
}
}

656

Chapitre 17 : Les mcanismes dentre/sortie


private static void ProcessResponse(IAsyncResult result) {
HttpListener httpListener = result.AsyncState as HttpListener;
HttpListenerContext ctx = httpListener.EndGetContext(result);
ctx.Response.ContentType = "text/html" ;
TextWriter writer = new StreamWriter(
ctx.Response.OutputStream, System.Text.Encoding.Unicode) ;
writer.WriteLine(
"<html><body>Page demandee `a {0}<br/>URL:{1}</body></html>",
System.DateTime.Now, ctx.Request.Url ) ;
writer.Flush() ;
writer.Close() ;
}
}

Support des protocoles denvoi de mails SMTP et MIME


En .NET 2.0, les classes de lespace de noms System.Web.Mail sont maintenant obsoltes. Pour
envoyer des mails, il faut utiliser les classes de lespace de noms System.Net.Mail. Notamment,
une instance de la classe SmtpClient reprsente un point daccs vers un serveur SMTP. Voici
un exemple qui se sert de cette classe pour envoyer un mail :
Exemple 17-16 :
using System.Net ;
using System.Net.Mail ;
class Program {
static void Main() {
SmtpClient client = new SmtpClient("smtp.myserver.fr",25);
client.Credentials = new
NetworkCredential("patrick.smacchia@myserver.fr", "mypwd") ;
client.Send("patrick@smacchia.com", "destinataire@xyz.com",
"my subject", "my body") ;
}
}
La classe SmtpClient prvoit aussi une mthode SendAsync() et un vnement SendCompleted
pour envoyer un mail dune manire asynchrone, sans bloquer le thread courant. Des facilits
sont aussi proposes pour communiquer avec un serveur SMTP en SLL.
Une instance de la classe MailMessage reprsente un mail, une instance de la classe MailAddress
reprsente une adresse mail et une instance de la classe Attachment reprsente un attachement
un mail. Reprenons notre programme avec ces classes pour envoyer notre mail en lui attachant
le fichier C:\file.txt :
Exemple 17-17 :
using System.Net ;
using System.Net.Mail ;
class Program {
static void Main() {
SmtpClient client = new SmtpClient("smtp.myserver.fr",25) ;

Burisation et compression dun flot de donnes

657

client.Credentials = new
NetworkCredential("patrick.smacchia@myserver.fr", "mypwd") ;
MailMessage msg = new MailMessage("patrick@smacchia.com",
"destinataire@xyz.com", "my subject", "my body");
msg.Attachments.Add(new Attachment(@"C:\file.txt"));
client.Send(msg) ;
}
}
Lespace de noms System.Net.Mime contient des classes pour le support de la norme Multipurpose Internet Mail Exchange (MIME). Cette norme dcrit le formatage denttes dans le corps dun
mail. Ces enttes permettent de dcrire la manire dont le corps du mail ainsi que ses attachements doivent tres achs. Par exemple, certains mails sont au format HTML et contiennent
des images.

Burisation et compression dun flot de donnes


Cette section montre comment vous pouvez intervenir sur la faon dont les donnes dun
flot sont lues et crites laide de services. Ces services sont matrialiss par certaines classes
du framework qui permettent par exemple de mettre en mmoire tampon ou de compresser/dcompresser les donnes dun flot. Nous verrons dans la section suivante que dautres
classes permettent aussi la scurisation (i.e lauthentification et/ou lencryptions) des donnes
dun flot.

Le design pattern dcorateur et la mise en sries de services sur un


flot de donnes
Le framework .NET se base sur le design pattern Gof nomm dcorateur pour raliser larchitecture qui permet de mettre en srie des services sur un flot de donnes. Lide est simple : chaque
classe reprsentant un flot de donnes et chaque classe reprsentant un service sur un flot drive de la classe abstraite Stream. Une instance dune classe reprsentant un service possde une
rfrence de type Stream vers le flot de donnes sur lequel elle agit. Le client de cette instance la
manipule par exemple en appelant les mthodes Read([out]buffer) et Write([in]buffer). Le
client na pas ncessairement conscience quil manipule en fait un service qui encapsule laccs
un flot puisquil peut trs bien se contenter dune rfrence de type Stream. Lextrait de code
suivant illustre une utilisation de cette architecture o trois services sont mis en sries sur un
flot de donnes rseau :
...
NetworkStream networkStream = new NetworkStream(...) ;
BufferedStream bufferedStream = new BufferedStream(networkStream,...) ;
CryptoStream
cryptoStream = new CryptoStream(bufferedStream,...) ;
GZipStream
gzipStream = new GZipStream(cryptoStream,...) ;
Stream
clientStream = gzipStream as Stream ;
...
clientStream.Read(...) ;
clientStream.Write(...) ;
...

658

Chapitre 17 : Les mcanismes dentre/sortie

Dans cet exemple, si le code de cration du flot et de ses trois services est isol, le fait que les donnes du flot soient compresses/dcompresses, encryptes/dcryptes puis mises en mmoire
tampon est compltement transparent du point de vue du code qui appelle les mthodes Read()
et Write(). Voici un diagramme de classe UML pour formaliser cette architecture :
Stream
Read([out]buffer)
Write([in]buffer)

NetworkStream
Read([out]buffer)

stream

stream

stream

GZipStream

BufferedStream

CryptoStream

Read([out]buffer)
Write([in]buffer)

Read([out]buffer)
Write([in]buffer)

Read([out]buffer)
Write([in]buffer)

stream.Read(bufferTmp)
//dcompresse les donnes
//de bufferTmp et crit le
//rsultat dans buffer

stream.Read(bufferTmp)
//dcrypte les donnes
//de bufferTmp et crit le
//rsultat dans buffer

//crit les donnes de


//buffer dans bufferTmp
if (bufferTmp est plein)
stream.Write(buffer)

Figure 17 -1 : Le design pattern dcorateur et les services sur les flots de donnes

Burisation dun flot de donnes


La classe NetworkStream ne gre pas de zone de mmoire tampon. Concrtement, lorsque vous
appelez une des mthodes Write() ou WriteByte(), les donnes sont directement envoyes aux
clients. Un gros problme de performance peut apparatre si vous envoyez beaucoup de donnes
sous la forme de petits paquets. En eet, chaque criture sur le rseau consomme un volume
incompressible de ressources, indpendant du nombre doctets envoys. Si vous envoyez beaucoup de donnes sous la forme de petits paquets, sans avoir attendre de rponses du client,
il serait judicieux denvoyer tous ces petits paquets en une seule fois. Cest--dire quil serait
judicieux que plusieurs appels la mthode Write() ou la mthode WriteByte() ne gnre
quun accs rseau. Il faut donc que vous griez une zone de mmoire tampon pour accueillir
ces paquets au fur et mesure de leur production par le serveur. Le contenu de cette zone de
mmoire tampon sera vid en une seule fois au moment opportun.
La classe System.IO.BufferedStream a t conue spcialement dans ce but. Cette classe est extrmement simple dutilisation. Lors de la cration dune instance, vous fournissez au constructeur une rfrence vers linstance de NetworkStream laquelle vous souhaitez associer une zone
de mmoire tampon. La classe BufferedStream drive de la classe Stream. Ceci implique que
toutes les mthodes prsentes pour utiliser un flot de donnes sont accessibles. Linstance de
BufferedStream transmet les donnes envoyer linstance de NetworkStream que lorsque la
zone de mmoire de tampon est pleine. Vous pouvez aussi forcer lenvoi de donnes vers linstance de NetworkStream, en appelant la mthode Flush() sur linstance de BufferedStream.
Lors de la cration dune instance de BufferedStream, vous avez la possibilit de spcifier une
taille pour la zone de mmoire tampon. Cette taille est le paramtre sur lequel vous devez jouer

Burisation et compression dun flot de donnes

659

pour obtenir les meilleures performances possibles. Empiriquement, la taille de 4Ko constitue
un bon choix, mais nous vous conseillons de faire plusieurs tests avec plusieurs valeurs.
La classe BufferedStream peut tre aussi utilise pour lire les donnes. Notez quil est conseill
de ne pas utiliser une instance de BufferedStream la fois pour lire et crire des donnes. Si
vous utilisez une instance de BufferedStream avec des donnes toujours plus grandes que la
taille interne de la zone de mmoire tampon, il se peut que la zone de mmoire tampon ne
soit jamais alloue.
Une instance de la classe System.IO.MemoryStream peut tre utilise dans le mme esprit que
BufferedStream pour amliorer les performances dun flot de donnes rseau. La dirence
dutilisation est que vous avez une marge de manuvre plus large avec MemoryStream. Une
instance de MemoryStream nest pas explicitement lie avec une instance de NetworkStream. Vous
ne prcisez le flot de donnes sous-jacent que lors de lappel la mthode WriteTo(). Ainsi, vous
pouvez par exemple envoyer les mmes donnes contenues dans la mme zone de mmoire
tampon, plusieurs clients.
La version non gre System.IO.UnmanagedMemoryStream de la classe MemoryStream permet
dviter la copie des donnes sur le tas des objets du CLR. Elle est donc plus ecace.
Notez enfin quil nest pas ncessaire dutiliser la classe BufferedStream pour utiliser une zone
de mmoire tampon avec une instance de FileStream. En eet, comme nous lavons vu, la classe
FileStream gre nativement en interne une zone de mmoire tampon.

Compression dun flot de donnes


Le nouvel espace de noms System.IO.Compression contient les deux classes GZipStream et
DeflateStream permettant de compresser de dcompresser un flot de donnes en utilisant
les algorithmes GZip (spcification RFC 1952 : GZIP 4.3) et Deflate (spcification RFC 1951 :
DEFLATE 1.3). Lexemple suivant montre comment compresser un fichier :
Exemple 17-18 :
using System.IO ;
using System.IO.Compression ;
public class Program {
public static void Main() {
string sFileIn = @"C:\Test.txt" ;
string sFileOut = @"C:\Test.bin" ;
byte[] contenu = File.ReadAllBytes(sFileIn) ;
FileStream fileStream = new FileStream(sFileOut,
FileMode.Create, FileAccess.Write);
GZipStream gzipStream = new GZipStream(fileStream,
CompressionMode.Compress);
gzipStream.Write(contenu, 0, contenu.Length) ;
gzipStream.Close() ;
fileStream.Close() ;
FileInfo fileOut = new FileInfo(sFileOut) ;
System.Console.WriteLine("Avant {0} octets", contenu.Length) ;
System.Console.WriteLine("Apr`
es {0} octets", fileOut.Length) ;
}
}

660

Chapitre 17 : Les mcanismes dentre/sortie

Il est regrettable que lon ne puisse pas utiliser simplement la classe GZipStream pour manipuler les fichiers dextension .zip. Aussi, voici un lien vers la bibliothque gratuite et opensource SharpZipLib qui permet deectuer ceci : http://www.icsharpcode.net/OpenSource/
SharpZipLib/Default.aspx

Lecture/criture des donnes sur un port srie


La classe System.IO.Ports.SerialPort permet dexploiter un port srie dune manire synchrone ou par vnement.
Une instance de cette classe nest pas un flot de donnes puisque celle-ci ne drive pas de la classe
Stream. En revanche, la classe SerialPort prsente la proprit Stream BaseStream{get;} qui
fournit un flot de donnes en lecture et en criture vers le port srie sous-jacent. Bien entendu,
laccs alatoire nest pas support par un tel flot de donnes.
La proprit string PortName{get;set;} spcifie le nom du port srie sous-jacent. La mthode
statique string[] GetPortNames() permet dobtenir tous les ports sries disponibles sur la machine courante.
Les mthodes ReadLine() et WriteLine() sont prvues pour lenvoie et la rception de chanes
de caractres. La proprit Encoding SerialPort.Encoding{get;set;} permet de positionner
lencodage de ces chanes de caractres.
La classe SerialPort permet de travailler un niveau bas grce aux nombreuses proprits permettant de manipuler les bits du port srie sous-jacent.

Support des protocoles SSL, NTLM et Kerberos


de scurisation des donnes changes
Prsentation des protocoles
Le protocole SSL (Secure Socket Layer) a t dvelopp lorigine par Netscape pour scuriser
lchange de donnes sur internet. Ses dernires versions sont nomms TLS (Transport Layer
Security). Ce protocole fournit trois services : lauthentification du serveur, lauthentification
optionnelle du client et lchange scuris de donnes. Ce protocole nest pas li un protocole
internet particulier. Il peut se placer en dessous des protocoles HTTP, TCP, FTP, TELNET etc.
Les protocoles NTLM (NT Lan Manager) et Kerberos sont les protocoles dauthentification et de
scurisation de donnes changes entre plateformes Windows. NTLM est support par tous les
systmes Windows depuis Windows 95/98 tandis que Kerberos est support par les versions plus
rcentes de Windows. Si au moins un des deux partis dune communication ne supporte pas
Kerberos, le protocole NTLM est utilis par dfaut.

Implmentations des protocoles dans System.Net.Security


Le nouvel espace de noms System.Net.Security prsentent des classes permettant dutiliser
les protocoles SSL, NTLM et Kerberos pour scuriser les donnes dun flot. Ces classes encapsulent lAPI win32 relatives ces protocoles qui est connue sous lacronyme SSPI (Security
Support Provider Interface). Les classes NegociateSteam et SslStream drivent de la classe
AuthenticatedStream.

Support des protocoles SSL, NTLM et Kerberos de scurisation des donnes changes

661

La classe SslStream
La classe SslStream est utilise pour exploiter le protocole SSL. Reprenons le code du client/serveur
TCP de la page 642 afin dillustrer la scurisation dun flot de donnes TCP avec le protocole
SSL :
Exemple 17-19 :

Serveur.cs

...
private static X509Certificate getServerCert() {
X509Store store = new X509Store(StoreName.My,
StoreLocation.CurrentUser) ;
store.Open(OpenFlags.ReadOnly) ;
X509CertificateCollection certs = store.Certificates.Find(
X509FindType.FindBySubjectName, "CN=SslSvrCertif", true) ;
return certs[0] ;
}
static void TraiteClient(NetworkStream networkStream) {
SslStream streamSsl = new SslStream( networkStream );
streamSsl.AuthenticateAsServer( getServerCert() );
StreamWriter streamWriter = new StreamWriter( streamSsl ) ;
StreamReader streamReader = new StreamReader(
@"C:/Text/Fichier.txt") ;
string sTmp = streamReader.ReadLine() ;
try {
while (sTmp != null) {
Console.WriteLine("Envoi de : {0}", sTmp) ;
streamWriter.WriteLine(sTmp) ;
streamWriter.Flush() ;
sTmp = streamReader.ReadLine() ;
}
} finally {
streamReader.Close() ;
streamWriter.Close() ;
streamSsl.Close();
networkStream.Close() ;
}
}
...
On voit que du cot serveur, un nouveau flot de donnes de type SslStream se place entre le
flot de donnes rseau networkStream et le flot de donnes streamWriter utilis pour envoyer
des donnes.
Pour crer ce flot de donnes SSL, nous rcuprons un certificat X509 nomm SslSvrCertif
stock dans le rpertoire Personnel de lutilisateur actuel. Le fait de passer ce certificat la
mthode AuthenticateAsServer() permet lauthentification SSL du serveur avec ce certificat.
Voici le code du client :

662

Chapitre 17 : Les mcanismes dentre/sortie

Exemple 17-20 :

Client.cs

...
NetworkStream networkStream = tcpC.GetStream() ;
SslStream streamSsl = new SslStream (
networkStream,false, ValidateSvrCertificateCallback );
streamSsl.AuthenticateAsClient( "SslSvrCertif" );
StreamReader streamReader = new StreamReader( streamSsl );
try {
string sTmp = streamReader.ReadLine() ;
while (sTmp != null) {
Console.WriteLine("Reception de : {0}", sTmp) ;
sTmp = streamReader.ReadLine() ;
}
} finally {
streamReader.Close() ;
streamSsl.Close();
networkStream.Close() ;
Console.WriteLine("Deconnexion de {0}:{1}", host, port) ;
}
...
static bool ValidateSvrCertificateCallback(object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors) {
if ( sslPolicyErrors != SslPolicyErrors.None ) {
Console.WriteLine(
"Erreur dans la validation du SSL Certificate !") ;
Console.WriteLine( sslPolicyErrors.ToString() ) ;
return false ;
} else return true ;
}
...
Ici aussi un flot de donnes de type SslStream se place entre le flot de donnes rseau
networkStream et le flot de donnes streamWriter utilis pour recevoir des donnes du serveur.
La mthode ValidateSvrCertificateCallback() est appele lors de lauthentification du certificat du serveur. Si cette mthode retourne true, le serveur est considr comme authentifi.
Cette mthode est connue de notre instance de SslStream car elle est rfrence par un dlgu
pass son constructeur.
On passe le nom du certificat du serveur la mthode AuthenticateAsClient(). Une autre
surcharge de cette mthode permet de passer un certificat spcifique au client pour son authentification. Lauthentification du client est optionnelle avec le protocole SSL et comprenez bien
quici, nous ny avons pas recours.

La classe NegociateStream
La classe NegociateStream est utilise pour exploiter les protocoles Windows NTLM et Kerberos.
Reprenons nos exemples afin dillustrer lutilisation de ces protocoles sur un flot de donnes
TCP. Le niveau de protection le plus lev, savoir EncryptAndSign, est choisi :

Support des protocoles SSL, NTLM et Kerberos de scurisation des donnes changes
Exemple 17-21 :

663

Serveur.cs

...
static void TraiteClient(NetworkStream networkStream) {
NegotiateStream streamAuth = new NegotiateStream(networkStream);
streamAuth.AuthenticateAsServer(
CredentialCache.DefaultNetworkCredentials,
ProtectionLevel.EncryptAndSign,
TokenImpersonationLevel.Impersonation);
WindowsPrincipal principal = new WindowsPrincipal(
streamAuth.RemoteIdentity as WindowsIdentity);
// Ne pas effectuer la requ
ete si le client
// nest pas un administrateur.
if( !principal.IsInRole( @"BUILTIN\Administrators" ) ){
networkStream.Close();
return;
}
StreamWriter streamWriter = new StreamWriter(streamAuth) ;
StreamReader streamReader = new StreamReader(
@"C:/Text/Fichier.txt") ;
string sTmp = streamReader.ReadLine() ;
try {
while (sTmp != null) {
Console.WriteLine("Envoi de : {0}", sTmp) ;
streamWriter.WriteLine(sTmp) ;
streamWriter.Flush() ;
sTmp = streamReader.ReadLine() ;
}
} finally {
streamReader.Close() ;
streamWriter.Close() ;
streamAuth.Close();
networkStream.Close() ;
}
}
...
On observe quun flot de donnes de type NegociateStream vient sintercaler entre le flot de
donnes rseau networkStream et le flot de donnes streamWriter utilis pour envoyer les donnes. Nous rcuprons lutilisateur Windows utilis par le client avec la proprit IIdentity
NegociateStream.RemoteIdentity{get;}. Lidentit de cet utilisateur a t achemine automatiquement par le protocole sous-jacent (NTLM ou Kerberos). Si cet utilisateur nest pas un
administrateur, nous choisissons de ne pas satisfaire sa requte. Une autre alternative aurait t
par exemple daecter cet utilisateur au thread courant durant la dure de la requte (impersonification) afin que les vrifications des permissions soient ralises implicitement. Ici, seule la
permission de lire le fichier C:/Text/Fichier.txt aurait t requise.
Voici le code du client. On remarque que cest lidentit de lutilisateur sous lequel ce code sexcute qui est envoye au serveur par limplmentation de NegociateStream :

664

Chapitre 17 : Les mcanismes dentre/sortie

Exemple 17-22 :

Client.cs

...
NetworkStream networkStream = tcpC.GetStream() ;
NegotiateStream streamAuth=new NegotiateStream(networkStream);
streamAuth.AuthenticateAsClient(
CredentialCache.DefaultNetworkCredentials,
WindowsIdentity.GetCurrent().Name,
ProtectionLevel.EncryptAndSign,
TokenImpersonationLevel.Impersonation);
StreamReader streamReader = new StreamReader( streamAuth ) ;
try {
// Chaque passage dans la boucle = une ligne r
ecup
er
ee.
string sTmp = streamReader.ReadLine() ;
while (sTmp != null) {
Console.WriteLine("Reception de : {0}", sTmp) ;
sTmp = streamReader.ReadLine() ;
}
} finally {
// Ferme les flots de donn
ees.
streamReader.Close() ;
streamAuth.Close();
networkStream.Close() ;
Console.WriteLine("Deconnexion de {0}:{1}", host, port) ;
}
...

Cryptage dun flot de donnes


La classe System.Security.Cryptography.CryptoStream permet de crypter et de dcrypter
un flot de donnes, sans authentification daucun des deux partis. Le constructeur de la classe
CryptoStream permet de prciser lalgorithme utiliser pour lencryption ou la dcryption
en passant une instance dune classe qui implmente linterface ICryptoTransform. Les algorithmes de cryptographie sont exposs en page 219.

18
Les applications fentres
(Windows Forms)

Les applications fentres


sous les systmes dexploitation Windows
Comme le nom lindique, les applications avec fentres jouent un rle prpondrant dans les
systmes dexploitation Windows. Microsoft a toujours tenu ce que le dveloppement dapplications avec fentres sous ses systmes dexploitation, soit la fois simple et standard. Dans ce but,
Microsoft distribue la plupart des techniques quil utilise en interne pour dvelopper ses propres
interfaces homme/machine. Ceci explique la cohrence dans le style des fentres qui contribue
grandement la convivialit qui ressort de lutilisation des systmes dexploitation Windows.

Console Application et Windows Application


Avant les systmes dexploitation Windows, il y avait le systme dexploitation DOS. Sous les systmes dexploitation Windows il y a deux types dapplications :

Les applications en mode console : on les excute dans une fentre de commande, style
DOS.

Les applications avec fentres : on peut les excuter partir dune ligne de commande ou
partir dune icone, dans un explorateur.

Cette dualit applications en mode console/applications avec fentre existe encore sous .NET.

Le compilateur C  csc.exe a besoin de savoir si lexcutable quil produit est une application en mode console (option /target:exe) ou une application avec fentre (option /target:winexe).

666

Chapitre 18 : Les applications fentres (Windows Forms)


Lenvironnement de dveloppement Visual Studio vous demande ds la cration dun projet
C  , sil reprsente une application en mode console (Console Application) ou une application
avec fentres (Windows Application).

Notion de messages Windows


Le passage de DOS aux systmes dexploitation Windows a vu natre la notion de messages Windows. chaque vnement (mouvement de la souris, touche clavier enfonce ou relche etc),
le systme dexploitation Windows fabrique un message quil envoie lapplication graphique
concerne. Chaque message contient :

Un identifiant message qui indique le type dvnement (clic droit souris, touche clavier
enfonce, etc).

Des paramtres dont les types et le nombre varient en fonction de lentre (position souris
sur lcran, code touche clavier etc).

chaque type dvnement correspond une procdure de rappel (callback procedure). Pour une
fentre donne, le dveloppeur a la possibilit dcrire, ou de rcrire, une procdure de rappel
pour un vnement particulier (par exemple un clic gauche sur un bouton).
Dans les applications avec fentres, chaque fentre a un thread et un seul qui attend la rception de messages dans une file dattente propre au thread. Un tel thread peut grer plusieurs
fentres. Lorsquun message arrive, le thread excute la procdure de rappel adquate. Le code
principal dun tel thread est donc constitu dune boucle qui est excute chaque rception
dun message. Cette boucle contient entre autres, un gigantesque switch (plusieurs centaines de
cas) qui associe les procdures de rappel (les callbacks) aux vnements.

volution du dveloppement des applications avec fentres


Lvolution des techniques de dveloppement dapplications avec fentres sous Windows a toujours converge vers la simplification de la maintenance de ce gigantesque switch. Lintroduction des MFC (Microsoft Fundation Classes) est alle dans ce sens. Le dveloppeur navait plus qu
dfinir les associations vnements/procdures de rappel qui le concernait. Une table dassociation tait dfinie avec des macros. Elle pouvait ressembler ceci :
BEGIN_MESSAGE_MAP(CDlgWizard, CDialog)
ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTips )

// Tool Tips

ON_WM_SHOWWINDOW()

//
//
//

Evenement fen
etre vue ou cach
ee.
Correspond `
a la proc
edure de rappel
standard OnShowWindow().

ON_WM_MOUSEMOVE()

//
//
//

Evenement La souris bouge


Correspond `
a la proc
edure de rappel
standard OnMouseMove().

ON_WM_PAINT()

//
//
//

Evenement La r
egion visible de la fen
etre est
modifi
ee. Correspond `
a la proc
edure de rappel
standard OnPaint().

Les applications fentres sous les systmes dexploitation Windows

667

ON_BN_CLICKED(IDC_BN_VALIDATE, OnBnClickedValidate)
// Evenement Le bouton dID IDC_BN_VALIDATE est cliqu
e.
// Correspond `a la proc
edure de rappel propri
etaire
// OnBnClickedValidate().
END_MESSAGE_MAP()
LIDE Visual Studio simplifiait cette gestion en permettant de placer les contrles de la fentre
laide dun diteur WYSIWYG (What You See Is What You Get). Cet diteur ajoutait automatiquement les associations vnements/procdures de rappel dans la table. Cet diteur avait cependant lnorme dfaut de ne pas pouvoir modifier ou supprimer les associations quil produisait. Il fallait une certaine exprience pour raliser ces oprations la main. De plus lditeur
se servait dun fichier unique pour dcrire toutes les fentres dune application. De nombreux
conflits daccs en criture ce fichier survenaient entre les dveloppeurs de lapplication.
Cette section est crite au pass, car pour le dveloppeur .NET, tout ceci est presque du pass.
Le travail du dveloppeur dapplications avec fentres est grandement simplifi sous .NET grce
la technologie Windows Forms. Comme nous allons lexposer dans la prochaine section, la
description du contenu des fentres ainsi que les associations vnements/procdures de rappel
sont trs intuitives, et ne font pas appels des fichiers ou du code superflu. Ceci implique
notamment quune application avec fentres peut tout fait tre dveloppe sans diteur particulier. Un simple diteur de texte comme le bloc-notes sut.

Lespace de noms System.Windows.Forms


Lespace de noms System.Windows.Forms contient de nombreuses classes utilises pour crer des
applications graphiques Windows. Ces classes peuvent tre rparties en cinq groupes :

Les formulaires :
Ce sont les classes qui contiennent les comportements de base des formulaires. Il vous sut
de construire une classe qui hrite dune de ces classes formulaires pour crer votre propre
formulaire. On peut citer la classe System.Windows.Forms.Form, qui reprsente la classe de
base pour tous les formulaires.

Les contrles :
Un contrle est un lment dun formulaire. Une trentaine de types de contrles classiques
sont fournis par le framework .NET, comme le bouton ou la zone ddition de texte. Ils ont
tous la particularit de driver de la classe System.Windows.Forms.Control qui dcrit le
comportement de base dun contrle. La section suivante liste toutes les classes de contrles
standard et explique comment vous pouvez aussi crer vos propres classes de contrles.

Les composants :
Dans le domaine des applications graphiques, les composants permettent de rajouter des
fonctionnalits vos formulaires Par exemple la classe System.Windows.Forms.ToolTip
permet dajouter la fonctionnalit de description par tooltip des contrles, un formulaire.
On peut aussi citer la fonctionnalit de menu et daide la demande de lutilisateur.
La classe de base de tous les composants est la classe System.ComponentModel.Component.
Les classes Control et Form drivent de la classe Component. La notion de composant est donc
plus globale que les notions de contrle et de formulaire. De plus, la notion de composant
nest pas cantonne au domaine des applications graphiques. Plus de dtails sur la notion
de composant sont disponibles dans larticle Class vs. Component vs. Control des MSDN.

668

Chapitre 18 : Les applications fentres (Windows Forms)


Concrtement un composant est un champ dun formulaire. Lide de renfermer le
comportement global du formulaire dans une classe formulaire, tout en dfinissant les
contrles du formulaire comme des champs de cette classe, est en fait un design pattern (Gof)
appel mdiateur. Lavantage de ce design pattern est que les composants nont absolument
pas besoin de se connatre pour interagir entre eux. Ceci implique que les classes des
composants sont totalement dcouples.

Les fentres de dialogues usuels :


De nombreux dialogues usuels sont directement encapsuls dans des classes de base du framework .NET. On peut citer le dialogue de choix dun fichier pour le charger, le dialogue
de choix de fontes, le dialogue de choix dune couleur ou le dialogue de configuration dun
dossier imprimer.

Les classes daide au dveloppement de formulaires :


De nombreux types sont ncessaires pour dvelopper des formulaires. Vous avez par
exemple des dlgations qui reprsentent la signature des procdures de rappel ou des
numrations dont les valeurs anent le style des contrles ou des formulaires.

Introduction aux formulaires Windows Forms


Nous allons crer un formulaire de conversion francs/euros bas sur le taux de conversion 1 euro
= 6.55957 francs. Pour cela nous allons utiliser Visual Studio. la fin de cette section nous allons
montrer que nous aurions pu utiliser un simple diteur de texte et le compilateur csc.exe pour
arriver au mme rsultat.
Il faut dabord crer un nouveau projet de type Visual C   Windows Application avec lditeur
Visual Studio. Vous arrivez immdiatement sur lditeur de formulaire. Notez quune classe C 
associe au formulaire est aussi cre.
Lditeur de formulaire permet dajouter des contrles vos formulaires. Vous navez qu choisir le type de contrle dans la boite outil (toolbox) puis placer et dimensionner le contrle sur
votre formulaire. En utilisant deux contrles de type Button, deux contrles de type TextBox et
deux contrles de type Label nous avons pu crer le formulaire de la Figure 18-1.
Vous pouvez maintenant modifier les proprits Text des six contrles et du formulaire de faon
arriver au rsultat de la figure prcdente.
Pour avoir accs aux proprits dun contrle, il sut de cliquer droit sur le contrle dans lditeur et de choisir le menu Properties ou de slectionner le contrle et de taper F4. Une fentre
similaire celle de la Figure 18-2 apparat, et permet dditer les proprits du contrle. Lensemble des proprits est propre chaque type de contrle (mis part le sous-ensemble des
proprits de la classe Control).
Chaque contrle du formulaire reprsente un champ dans la classe du formulaire. Lditeur a
ajout ces champs automatiquement dans la classe du formulaire.
...
public class Form1 : System.Windows.Forms.Form {
private System.Windows.Forms.Button
Francs2Euros ;
private System.Windows.Forms.Button
Euros2Francs ;
private System.Windows.Forms.TextBox textBoxEuros ;
private System.Windows.Forms.Label
label1 ;
private System.Windows.Forms.Label
label2 ;

Introduction aux formulaires Windows Forms

669

Figure 18 -1 : Lditeur de formulaire Visual Studio .NET

Figure 18 -2 : Edition des proprits dun contrle

private System.Windows.Forms.TextBox
...

textBoxFrancs ;

Avec Visual Studio 2003, un formulaire Windows Forms tait compltement dfini sur un mme
fichier source C  . Le code gnr par cet IDE tait spar de votre propre code au moyen de
rgions. Visual Studio .2005 exploite la notion de classe partielle de C  2.0 au niveau de la gestion
des formulaires. Par dfaut, chaque formulaire nomm FormXXX correspond deux fichiers :
FormXXX.cs qui contient votre code et FormXXX.Designer.cs qui contient le code gnr par
Visual Studio 2005. Par souci de simplicit, les exemples de code des formulaires du prsent ouvrage tiennent chacun sur un seul fichier source C  .

670

Chapitre 18 : Les applications fentres (Windows Forms)

Les noms des champs du formulaire rfrenant les contrles, ont t modifis aussi dans la
fentre des proprits de la Figure 18-2. Dans la mthode InitializeComponent() de la classe
du formulaire, lditeur a ajout automatiquement du code qui permet de crer les contrles,
de positionner les contrles et de configurer la taille et les champs des contrles. Par exemple
le code ajout pour un bouton est :
...
//
// Euros2Francs
//
this.Euros2Francs.Location
this.Euros2Francs.Name
this.Euros2Francs.Size
this.Euros2Francs.TabIndex
this.Euros2Francs.Text

=
=
=
=
=

new System.Drawing.Point(160, 56) ;


"Euros2Francs" ;
new System.Drawing.Size(96, 32) ;
1 ;
"Euros -> Francs" ;

...
Comme vous le voyez, contrairement la gestion des fentres avec les MFC, il ny a aucune ligne
de code superflue et tout ce qui concerne un formulaire se trouve dans la classe de ce formulaire.

Ajout dvnements
Chaque type de contrle prsente un ensemble dvnements. On parle bien du mme concept
dvnement que celui prsent par C  car on parle dvnements de la classe du contrle.
chaque vnement dun contrle, vous pouvez associer une mthode de la classe du formulaire
contenant le contrle. Cette mthode constitue alors la procdure de rappel de lvnement.
Par exemple le type de contrle Button prsente lvnement Click qui dclenche lappel la
procdure de rappel associe, lorsque le bouton est cliqu. En double-cliquant sur le bouton de
conversion Euros vers Francs dans lditeur, Visual Studio ajoute automatiquement la mthode
suivante la classe formulaire :
...
private void Euros2Francs_Click(object sender, System.EventArgs e) {
// `a remplir avec votre propre code.
}
...
Lditeur associe cette mthode lvnement Click avec le code suivant plac dans la mthode
InitializeComponent() aprs linitialisation des champs du contrle Euros2Francs :
...
this.Euros2Francs.Click += new
System.EventHandler(this.Euros2Francs_Click) ;
...
Plutt que de double-cliquer le bouton dans lditeur, on peut facilement associer les vnements dun contrle aux mthodes dun formulaire, partir des proprits du contrle comme
le montre la Figure 18-3. Notez que pour accder ce sous-menu des proprits, il faut cliquer
licone qui ressemble un clair.
Notez enfin que le prototype des mthodes qui servent de procdures de rappel dun vnement
est immuable. Il doit tre gal au prototype de la dlgation System.EventHandler.

Introduction aux formulaires Windows Forms

671

Figure 18 -3 : Edition des vnements dun contrle

Remplissage des mthodes


Il ne nous reste plus qu remplir les deux mthodes Euros2Francs_Click et Francs2Euros_Click avec le code suivant :
...
const double Taux = 6.55957;
private void Francs2Euros_Click(object sender, System.EventArgs e) {
double sommeF;
if( double.TryParse(textBoxFrancs.Text, out sommeF) )
textBoxEuros.Text = (sommeF / Taux).ToString();
}
private void Euros2Francs_Click(object sender, System.EventArgs e) {
double sommeE;
if ( double.TryParse(textBoxEuros.Text, out sommeE) )
textBoxFrancs.Text = (sommeE * Taux).ToString();
}
...
Notez la possibilit dutiliser la mthode double.TryParse() pour viter quune exception ne
survienne pour dans le cas dune saisie dun double invalide.

Comment se passer de Visual Studio ?


Nous aurions pu compltement nous passer de Visual Studio pour crer cette application fentre. Il sut dcrire le code suivant dans un fichier source C  (par exemple Convertisseur.cs)
puis de compiler ce fichier avec le compilateur csc.exe comme ceci :
>csc.exe /target:winexe Convertisseur.cs

672

Chapitre 18 : Les applications fentres (Windows Forms)

Si lon avait choisi /target:exe, le compilateur naurait pas produit derreur. La dirence est
que lexcutable gnr aurait besoin dune console pour sexcuter. Si nous le lancions partir
dun explorateur, cet excutable se crerait sa propre console.
Il est vident que lditeur de formulaire de Visual Studio est surtout utile pour pr visualiser et
rgler laspect du formulaire. La majorit du code suivant est constitu par linitialisation des
contrles. Seul le code en gras a eectivement t crit la main.
Convertisseur.cs

Exemple 18-1 :
using System.Drawing ;
using System.ComponentModel ;
using System.Windows.Forms ;
namespace WindowsFrancEuro {
public class MyForm : Form {
private Button Francs2Euros ;
private Button Euros2Francs ;
private TextBox textBoxEuros ;
private Label label1 ;
private Label label2 ;
private TextBox textBoxFrancs ;
public MyForm() { InitializeComponent() ; }
private void InitializeComponent() {
this.Francs2Euros = new Button() ;
this.Euros2Francs = new Button() ;
this.label1 = new Label() ;
this.label2 = new Label() ;
this.textBoxEuros = new TextBox() ;
this.textBoxFrancs = new TextBox() ;
this.SuspendLayout() ;

// Francs2Euros
this.Francs2Euros.Location = new Point(160, 16) ;
this.Francs2Euros.Name = "Francs2Euros" ;
this.Francs2Euros.Size = new System.Drawing.Size(96, 32) ;
this.Francs2Euros.TabIndex = 0 ;
this.Francs2Euros.Text = "Francs -> Euros" ;
this.Francs2Euros.Click += this.Francs2Euros_Click ;
// Euros2Francs
this.Euros2Francs.Location = new Point(160, 56) ;
this.Euros2Francs.Name = "Euros2Francs" ;
this.Euros2Francs.Size = new Size(96, 32) ;
this.Euros2Francs.TabIndex = 1 ;
this.Euros2Francs.Text = "Euros -> Francs" ;
this.Euros2Francs.Click += this.Euros2Francs_Click ;
// label1

Introduction aux formulaires Windows Forms


this.label1.Location = new Point(104, 24) ;
this.label1.Name = "label1" ;
this.label1.Size = new Size(40, 16) ;
this.label1.TabIndex = 3 ;
this.label1.Text = "Francs" ;
// label2
this.label2.Location = new Point(104, 56) ;
this.label2.Name = "label2" ;
this.label2.Size = new Size(40, 16) ;
this.label2.TabIndex = 5 ;
this.label2.Text = "Euros" ;
// textBoxEuros
this.textBoxEuros.Location = new Point(8, 56) ;
this.textBoxEuros.Name = "textBoxEuros" ;
this.textBoxEuros.Size = new Size(88, 20) ;
this.textBoxEuros.TabIndex = 4 ;
this.textBoxEuros.Text = "0" ;
// textBoxFrancs
this.textBoxFrancs.Location = new Point(8, 24) ;
this.textBoxFrancs.Name = "textBoxFrancs" ;
this.textBoxFrancs.Size = new Size(88, 20) ;
this.textBoxFrancs.TabIndex = 6 ;
this.textBoxFrancs.Text = "0" ;
// Form1
this.AutoScaleDimensions = new SizeF(5, 13) ;
this.ClientSize = new Size(264, 102) ;
this.Controls.AddRange(new Control[] {
this.textBoxFrancs,
this.label2,
this.textBoxEuros,
this.label1,
this.Euros2Francs,
this.Francs2Euros}) ;
this.Name = "Form1" ;
this.Text = "Convertisseur Francs/Euros" ;
this.ResumeLayout(false) ;
}
[System.STAThread]
static void Main() { Application.Run(new MyForm()) ; }
const double TAUX = 6.55957;
private void Francs2Euros_Click(object sender,System.EventArgs e){
double sommeF;
if( double.TryParse(textBoxFrancs.Text, out sommeF) )
textBoxEuros.Text = (sommeF / TAUX).ToString();
}

673

674

Chapitre 18 : Les applications fentres (Windows Forms)


private void Euros2Francs_Click(object sender,System.EventArgs e){
double sommeE;
if ( double.TryParse(textBoxEuros.Text, out sommeE) )
textBoxFrancs.Text = (sommeE * TAUX).ToString();
}
}
}

Notion de fentres modales


Dans une interface graphique, il est souvent judicieux de faire apparatre une nouvelle fentre
pour saisir ou prsenter des donnes spcifiques. Il y a deux types de scnario lorsque lapparition dune nouvelle fentre fille est une consquence dune action faite sur la fentre mre :

Soit la fentre mre reste en arrire-plan et est gele, jusqu ce que la fentre fille disparaisse. Dans ce cas on dit que la fentre fille est modale. Pour crer un formulaire modal il
faut utiliser la mthode ShowDialog() de la classe Form :
...
MaClasseDeFormulaire UnFormulaire = new MaClasseDeFormulaire()
// La methode ShowDialog() ne retourne que lorsque le formulaire
// UnFormulaire disparat.
UnFormulaire.ShowDialog() ;
...

Soit on peut continuer utiliser la fentre mre, mme si la fentre fille est encore prsente.
Dans ce cas on dit que la fentre fille est non modale (modeless en anglais). Pour crer un
formulaire non modal il faut utiliser la mthode Show() de la classe Form :
...
MaClasseDeFormulaire UnFormulaire = new MaClasseDeFormulaire()
// La methode Show() retourne imm
ediatement apr`
es la cr
eation du
// formulaire.
UnFormulaire.Show() ;
...

Il est plus courant dutiliser des fentres modales que des fentres non modales. En eet, les
fentres modales sont trs adaptes la saisie de donnes de faon squentielle. Cependant, dans
certains scnarios o plusieurs fentres sont ncessaires lexploitation de lapplication, on ne
peut quutiliser des fentres non modales. Par exemple Visual Studio utilise de trs nombreuses
fentres non modales, une pour la vue des fichiers, une pour la vue du projet, une pour acher
les messages du compilateurs etc. Mais comprenez bien quune application avec trop de fentres
non modales est souvent trs confuse pour lutilisateur (rappelez-vous votre raction lors de la
premire ouverture dun IDE).

Facilits pour dvelopper des formulaires

675

Facilits pour dvelopper des formulaires


vnements clavier et souris
Vous avez la possibilit dintercepter les vnements produits par les priphriques dentres
standard que sont le clavier et la souris. Tous les vnements prsents existent aussi dans la
classe Form.
Un contrle intercepte les vnements clavier partir du moment o il a le focus du clavier. Un
contrle obtient le focus :

Soit parce que lutilisateur a cliqu sur le contrle.

Soit parce que lutilisateur fait dfiler le focus dun contrle lautre en appuyant sur la
touche TAB ou MAJ+TAB. Vous pouvez modifier lordre de dfilement des contrles en
aectant des valeurs croissantes aux proprits TabIndex des contrles.

La classe System.Windows.Forms.Control, dont hrite toutes les classes de contrles standard


Windows Forms, prsente notamment les vnements KeyDown, KeyUp, et KeyPress qui provoquent lappel de leurs procdures de rappel respectivement : lorsquune touche est enfonce,
relche ou lorsquune touche correspondant un caractre est enfonce. Bien videmment,
lvnement nest dclench que si le contrle a le focus. Le paramtre de cet vnement est le
code de la touche clavier enfonce.
Un contrle intercepte les vnements souris partir du moment o la souris se trouve sur la
zone du contrle. Vous pouvez nanmoins dcider de capturer les vnements souris mme
lorsque la souris est hors de la zone du contrle en positionnant la proprit capture true.
Plusieurs vnements souris sont disponibles et il est important de comprendre quand ils sont
dclenchs. Chacun de ces vnements est paramtr, notamment par le bouton souris sur lequel le clic est fait (sil y a clic) et par la position de la souris.
vnement

Description

MouseDown

Un bouton de la souris est enfonc.

MouseUp

Un bouton de la souris est relch.

Click

Un bouton de la souris est cliqu.

DoubleClick

Un bouton de la souris est double-cliqu (il vaut mieux ne pas implmenter cet vnement en mme temps que lvnement Click. Dans
ce cas Windows appelle les deux procdures de rappel lors dun doubleclic).

MouseMove

La souris se dplace au dessus du contrle (ou ailleurs si la proprit


capture est positionne true).

MouseEnter

La souris entre dans la zone du contrle.

MouseLeave

La souris quitte la zone du contrle.

MouseHover

La souris marque un temps darrt dans le contrle.

676

Chapitre 18 : Les applications fentres (Windows Forms)

MouseWheel

La molette de la souris est actionne.

Lvnement Paint
Lvnement Paint, avec sa procdure de rappel OnPaint(), est trs important dans les applications graphiques sous les systmes dexploitation Windows. En eet, les achages des fentres
ne sont pas persistants. Cela veut dire que si une fentre (ou une partie dune fentre) devient
visible alors quelle ne ltait pas, il est ncessaire que le thread responsable de cette fentre
reconstruise cette partie. Lorsquune fentre (ou une partie dune fentre) devient visible, les
systmes dexploitation Windows envoient le message WM_PAINT. Sous .NET cela se traduit par le
dclenchement de lvnement Paint du formulaire concern et par lappel de la procdure de
rappel OnPaint(). Parmi les arguments de cet vnement, il y a notamment les coordonnes
du rectangle dessiner. La plupart du temps, vous ne vous occuperez pas de cet vnement
car par dfaut, lvnement est automatiquement transmis aux contrles qui doivent se racher. Nanmoins, lorsque vous utilisez la bibliothque GDI+ ou lorsque vous faites vos propres
contrles, il faut rcrire la mthode OnPaint() avec votre propre code.

Oprations asynchrones
Dans une application Windows Forms, lorsque le thread qui traite les messages Windows est bloqu par lexcution dune opration longue, lutilisateur est en gnral exaspr de voir sa fentre gele. Pour pallier ce problme, on dlgue en gnral lopration longue soit un thread
du pool soit un thread cr spcialement pour loccasion.
Windows Forms 2.0 prsente la classe BackgroundWorker qui permet de standardiser le dveloppement dopration asynchrone au sein dun formulaire. Cette classe prsente notamment des facilits pour signaler priodiquement lavancement et pour annuler lopration. Son utilisation est
illustre par lexemple suivant qui dlgue le calcul de nombres premiers au moyen dune instance de la classe System.ComponentModel.BackgroundWorker. Lavancement du traitement est
signal par une barre de progression et vous avez la possibilit dannuler lopration au moyen
dun bouton. Dans cet exemple, seules les mthodes DoWork() et IsPrime() sont excute par
un thread background du pool :

Figure 18 -4 : Formulaire de calcul de nombres premiers


Exemple 18-2 :
...
public class PrimeForm : Form {
public PrimeForm() {
InitializeComponent() ;
InitializeBackgoundWorker() ;

Facilits pour dvelopper des formulaires


}
private void InitializeBackgoundWorker() {
backgroundWorker.DoWork += DoWork;
backgroundWorker.RunWorkerCompleted += Complete;
backgroundWorker.ProgressChanged += ProgressChanged;
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
}
private void DoWork(object sender,DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker ;
e.Result = IsPrime((int)e.Argument, worker, e) ;
}
private void ProgressChanged(object sender,
ProgressChangedEventArgs e) {
progressBar.Value = e.ProgressPercentage;
}
private void Complete(object sender, RunWorkerCompletedEventArgs e) {
textBoxInput.Enabled = true ;
buttonStart.Enabled = true ;
buttonCancel.Enabled = false ;
if ( e.Error != null )
// Cas ou une exception a
et
e lanc
ee :
MessageBox.Show(e.Error.Message) ;
else if ( e.Cancelled ) // Cas ou annulation :
textBoxResult.Text = "Op
eration annul
ee !" ;
else
// Op
eration r
eussie :
textBoxResult.Text = e.Result.ToString() ;
}
private void buttonStart_Click(object sender, EventArgs e) {
int number = 0 ;
if (int.TryParse(textBoxInput.Text, out number)) {
textBoxResult.Text = String.Empty ;
textBoxInput.Enabled = false ;
buttonStart.Enabled = false ;
buttonCancel.Enabled = true ;
progressBar.Value = 0 ;
backgroundWorker.RunWorkerAsync(number);
} else textBoxResult.Text = "entr
ee invalide !" ;
}
private void buttonCancel_Click(object sender, EventArgs e) {
backgroundWorker.CancelAsync();
buttonCancel.Enabled = false ;
}
private string IsPrime(int number, BackgroundWorker worker,
DoWorkEventArgs e) {
int racine = ((int)System.Math.Sqrt(number))+1 ;
int highestPercentageReached = 0 ;
for (int i = 2 ; i < racine ; i++) {
if ( worker.CancellationPending ) {
e.Cancel = true ;

677

678

Chapitre 18 : Les applications fentres (Windows Forms)


return String.Empty ;
} else {
if (number % i == 0)
return "est divisible par " + i.ToString() ;
int percentComplete =
(int)((float)i / (float)racine * 100) ;
if (percentComplete > highestPercentageReached) {
highestPercentageReached = percentComplete ;
worker.ReportProgress(percentComplete);
}
}
}
return "est premier" ;
}
...

Autres possibilits des formulaires


La cration dapplications graphiques avec Windows Forms prsente un grand nombre dautres
possibilits que celles qui viennent dtre expliques. Nous avons essay ci-dessus, de slectionner quelques possibilits la fois non triviales et souvent sollicites. Voici dautres possibilits
dont lutilisation est dtaille dans les MSDN :

La particularit de la classe Component est dimplmenter linterface System.Compo\-nent\


-Model.IComponent. Cette interface permet un composant dtre encapsul dans une
instance dune classe qui implmente linterface System.ComponentModel.IContainer. Un
conteneur (container en anglais) est un ensemble de composants et chaque composant la
connaissance de son conteneur. Cet ensemble dinterfaces propose un modle pour limplmentation du design pattern Mediator. Une consquence de ce modle est la possibilit
dditer vos formulaires dune manire WYSIWYG avec Visual Studio. Pour nommer cette
possibilit ddition composant/conteneur durant la phase de conception/dveloppement
dun projet, les documentations Microsoft utilisent le terme dition durant le designtime . Plus de dtails propos de ce sujet sont disponibles dans larticle Design-Time
Architecture des MSDN.

La classe System.Windows.Forms.MessageBox permet dacher trs simplement une fentre avec du texte et les combinaisons de boutons classiques :
OK ;
OK/Cancel ;
Abort/Retry/Ignore ;
Retry/Cancel ;
Yes/No ;
Yes/No/Cancel.
Lachage se fait grce la mthode statique Show() de cette classe qui est surcharges en
de nombreuse versions.

La gestion du drag & drop se fait grce aux vnements System.Windows.Forms.Control.


DragDrop, DragEnter et DragOver. Chacun de ces vnements accepte un argument de type
System.Windows.Forms.DragEnventArgs.

Les contrles standards

679

Vous pouvez utiliser dirents types de menus grce la classe System.Windows.Forms.


Menu et ses classes drives, ContextMenu, MainMenu et MenuItem.

Vous pouvez utiliser les classes de dialogue standard suivantes :


System.Windows.Forms.ColorDialog
System.Windows.Forms.FileDialog
System.Windows.Forms.FontDialog
System.Windows.Forms.OpenFileDialog
System.Windows.Forms.PageSetupDialog
System.Windows.Forms.PrintControllerWithStatusDialog
System.Windows.Forms.PrintDialog
System.Windows.Forms.PrintPreviewDialog
System.Windows.Forms.SaveFileDialog
La classe System.Windows.Forms.Timer permet dutiliser simplement un timer, cest--dire
un objet qui dclenche un vnement intervalle de temps rgulier. Plus de dtails ce
sujet sont disponibles en page 169.

La classe System.Windows.Forms.ToolTips permet dacher une fentre daide donnant


des informations sur lutilisation dun contrle. Pour obtenir cette aide, lutilisateur na
qu laisser la souris quun petit instant sur le contrle. Laide disparat automatiquement
lorsque la souris bouge ou aprs un certain moment. Tous ces dlais sont configurables par
des proprits de la classe.

La classe System.Windows.Forms.Help permet dacher laide complte de lapplication au


format HTML dans un navigateur. Cette aide est beaucoup plus gnrale que celle dispense
par les tooltips. Vous pouvez acher laide (respectivement lindex de laide) en utilisant la
mthode statique ShowHelp() (respectivement ShowHelpIndex()). Vous pouvez tlcharger
loutil HTML Help WorkShop sur le site de Microsoft. Cet outil permet de crer des fichiers
daide au format .chm ou .htm.

La classe System.Windows.Forms.ClipBoard permet de placer ou de rcuprer des informations sur le clipboard. Nutilisez jamais cette technique pour communiquer des informations entre processus. Le clipboard doit tre strictement rserv aux donnes utilisateurs.

La classe System.Windows.Forms.NotifyIcon permet de dvelopper des applications type


tray icon. Ces applications ont pour principe dtre rsidente en mmoire et de prsenter
un icone dans la barre de Windows qui indique leur tat courant. Lapplication Windows
Messenger est un exemple dapplication tray icon.

En appelant la mthode statique Application.EnableVisualStyles() au dmarrage dune


application Windows Forms, vous fait en sorte que vos contrles ayant leur proprit
FlatStyle positionne Standard ou System auront lapparence dfinie par le systme
dexploitation sous-jacent

Les contrles standards


Hirarchie des contrles standard disponibles
La hirarchie des contrles standard est prsente ci-dessous. Les contrles dont le nom est crit
en gras sont nouveaux par rapport Windows Forms 1.x :

680

Chapitre 18 : Les applications fentres (Windows Forms)


System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Windows.Forms.Control
Microsoft.WindowsCE.Forms.DocumentList
System.ComponentModel.Design.ByteViewer
System.Windows.Forms.AxHost
System.Windows.Forms.ButtonBase
System.Windows.Forms.Button
System.Windows.Forms.CheckBox
System.Windows.Forms.RadioButton
System.Windows.Forms.DataGrid
System.Windows.Forms.DataGridView
System.Windows.Forms.DateTimePicker
System.Windows.Forms.GroupBox
System.Windows.Forms.Label
System.Windows.Forms.LinkLabel
System.Windows.Forms.ListControl
System.Windows.Forms.ComboBox
System.Windows.Forms.ListBox
System.Windows.Forms.ListView
System.Windows.Forms.MdiClient
System.Windows.Forms.MonthCalendar
System.Windows.Forms.PictureBox
System.Windows.Forms.PrintPreviewControl
System.Windows.Forms.ProgressBar
System.Windows.Forms.ScrollableControl
System.Windows.Forms.ContainerControl
System.Windows.Forms.Form
System.Windows.Forms.PropertyGrid
System.Windows.Forms.SplitContainer
System.Windows.Forms.ToolStripContainer
System.Windows.Forms.ToolStripPanel
System.Windows.Forms.UpDownBase
System.Windows.Forms.DomainUpDown
System.Windows.Forms.NumericUpDown
System.Windows.Forms.UserControl
System.Windows.Forms.Design.ComponentTray
System.Windows.Forms.Panel
System.Windows.Forms.FlowLayoutPanel
System.Windows.Forms.SplitterPanel
System.Windows.Forms.TableLayoutPanel
System.Windows.Forms.TabPage
System.Windows.Forms.ToolStripContentPanel
System.Windows.Forms.ToolStrip
System.Windows.Forms.BindingNavigator
System.Windows.Forms.DataNavigator
System.Windows.Forms.MenuStrip
System.Windows.Forms.StatusSTrip

Les contrles standards

681

System.Windows.Forms.ToolStripDropDown
System.Windows.Forms.ToolStripDropDownMenu
System.Windows.Forms.ContextMenuStrip
System.Windows.Forms.ScrollBar
System.Windows.Forms.HScrollBar
System.Windows.Forms.VScrollBar
System.Windows.Forms.Splitter
System.Windows.Forms.StatusBar
System.Windows.Forms.TabControl
System.Windows.Forms.TextBoxBase
System.Windows.Forms.RichTextBox
System.Windows.Forms.TextBox
System.Windows.Forms.MaskedTextBox
System.Windows.Forms.ToolBar
System.Windows.Forms.TrackBar
System.Windows.Forms.TreeView
System.Windows.Forms.WebBrowserBase
System.Windows.Forms.WebBrowser
Nous vous invitons consulter les MSDN et faire vos propres essais pour avoir plus de dtails
sur chacune de ces classes.

Les nouveaux contrles


Voici un rapide descriptif de chacun des nouveaux contrles :

Les contrles ToolStrip, MenuStrip, StatusStrip et ContextMenuStrip :


Ces contrles remplacent respectivement les contrles ToolBar, MainMenu, StatusBar et
ContextMenu (ces derniers sont toujours prsents pour prserver la compatibilit ascendante). En plus dun aspect visuel plus travaill, ces nouveaux contrles se rvlent tre
particulirement pratiques manipuler au moment du design dune fentre, notamment
grce une API cohrente. Des nouvelles fonctionnalits ont t ajoutes telles que la
possibilit de partager un rendu entre contrles, le support des GIF animes, lopacit,
la transparence et des facilits pour sauver ltat courant (position, taille etc) dans le fichier de configuration. La hirarchie de classes drives de la classe System.Windows.Forms.
ToolStripItem constitue autant dlments insrables dans ce type de contrle. Plus dinformations ce sujet sont disponibles dans les articles ToolStrip Technology Summary et
ToolStrip Control Overview (Windows Forms) des MSDN.

Les contrles DataGridView et BindingNavigator :


Ces contrles font partie dun nouveau framework pour dvelopper des fentres de prsentation et ddition de donnes. Ce framework fait lobjet de la section 18 Prsentation et
dition des donnes un peu plus loin dans ce chapitre. Sachez quil est maintenant prfrable davoir recours un contrle DataGridView pour tout achage de donnes type table
ou liste dobjets.

Les contrles FlowLayoutPanel et TableLayout :


Ces contrles permettent dagencer le positionnement des contrles enfants quils contiennent lorsque lutilisateur en modifie taille. La philosophie dagencement du contrle FlowLayoutPanel est de lister les contrles enfants horizontalement ou verticalement de ma-

682

Chapitre 18 : Les applications fentres (Windows Forms)


nire ce quils soient dplacs lors du redimensionnement. Cette philosophie est comparable ce que lon voit lorsque lon redimensionne un document HTML ach par un
navigateur. La philosophie dagencement du contrle TablePanel est comparable au mcanisme dancrage (anchoring en anglais) ou les contrles enfants sont redimensionns selon
la taille du contrle parent. Cependant, ici les contrles enfant se trouvent dans les cellules
dune table.

Les contrles SplitterPanel et SplitContainer :


Lutilisation conjugue de ces contrles permet dimplmenter plus simplement le partitionnement dune fentre dune manire redimensionnable que ce que lon avait en 1.1
avec le contrle Splitter.

Le contrle WebBrowser :
Ce contrle permet dinsrer un navigateur web directement dans vos fentres Windows
Forms.

Le contrle MaskedTextBox :
Ce contrle ache une TextBox dans laquelle le format du texte insrer est contraint.
Plusieurs types de masques sont prsents par dfaut tel que la contrainte dentrer une date
ou un numro de tlphone au format US. Naturellement, vous pouvez fournir vos propres
masques.

Les contrles SoundPlayer et SystemSounds :


La classe SoundPlayer permet de jouer des sons au format .wav tandis que la classe
SystemSounds permet de rcuprer les sons systmes associs lutilisateur courant du
systme dexploitation.

Crer vos propres contrles


Vous avez la possibilit de crer trs facilement vos propres contrles graphiques. Tous ceux qui
ont eu crer leurs propres contrles ActiveX vont tre trs agrablement surpris par la simplicit
de la cration dun contrle graphique avec .NET. En gnral, un contrle graphique est rutilisable. Aussi, il est judicieux de crer un assemblage partag pour contenir le code dun contrle
(ou un ensemble de contrles). Vous avez la possibilit dinsrer dans vos propres contrles :

Des contrles standard .NET.

Dautres contrles propritaires.

Des tracs graphiques grce lutilisation de la bibliothque GDI+, qui fait lobjet de la
dernire section du prsent chapitre.

Un contrle propritaire drive ncessairement de la classe System.Windows.Forms.UserControl.


Nous vous proposons un tutorial qui montre comment crer un contrle graphique dachage
de pourcentages avec des colonnes. Le contrle cr contient des contrles standard et des tracs
avec la bibliothque GDI+. Pour plus de simplicit nous crons le contrle et le formulaire qui
lutilise dans le mme assemblage, mais il vaut mieux crer ses contrles dans des assemblages
partags. Pour simplifier notre expos, nous utilisons Visual Studio. Cependant un diteur de
texte aurait su pour crer ce contrle.
Crez un nouveau projet de type Windows Application. Ajoutez-y un nouveau contrle utilisateur (cliquer droit sur le projet Add  Add New Item...  User Control) que vous pouvez appeler ControlPourCent . Lide est que le contrle ressemble celui de la Figure 18-5. Un client

Crer vos propres contrles

683

du contrle peut changer la valeur du pourcentage lexcution. Un dveloppeur client du


contrle peut choisir sil souhaite acher la grille et/ou le contour, lors du dveloppement de
lapplication (design-time) et lexcution.

Figure 18 -5 : Aspect de notre contrle propritaire


Voici le code. Notez que dans la mthode Dessine() nous utilisons des classes de la bibliothque
GDI+ que nous dtaillerons dans la prochaine section :
Exemple 18-3 :
using System.ComponentModel ;
using System.Drawing ;
using System.Windows.Forms ;
public class ControlPourCent : UserControl {
private TextBox PourCentLeft ;
private TextBox PourCentRight ;
// Propriete pour regler la valeur du pourcentage (entre 0.0 et 1.0).
private byte m_Valeur = 30 ;
public double Valeur {
set {
if (value < 0.0) m_Valeur = 0 ;
else if (value > 1.0) m_Valeur = 100 ;
else m_Valeur = (byte)(100.0 * value) ;
Dessine() ;
}
}
// Propriete pour decider si la grille doit
etre dessin
ee.
private bool m_bGrille = true ;
[Category("Appearance")]
public bool bGrille {
set { m_bGrille = value ; Dessine() ; }
get { return m_bGrille ; }
}
// Propriete pour decider si le contour doit
etre dessin
e.
private bool m_bContour = true ;
[Category("Appearance")]
public bool bContour {
set { m_bContour = value ; Dessine() ; }

684

Chapitre 18 : Les applications fentres (Windows Forms)


get { return m_bContour ; }
}
// Constructeur.
public ControlPourCent() { InitializeComponent() ; }
// Code dinitialisation du contr
ole.
#region Component Designer generated code
private void InitializeComponent() {
this.PourCentLeft = new TextBox() ;
this.PourCentRight = new TextBox() ;
this.SuspendLayout() ;
// PourCentLeft
this.PourCentLeft.Location = new Point(16, 8) ;
this.PourCentLeft.Name = "PourCentLeft" ;
this.PourCentLeft.ReadOnly = true ;
this.PourCentLeft.Size = new Size(24, 20) ;
this.PourCentLeft.TabIndex = 0 ;
this.PourCentLeft.Text = "" ;
this.PourCentLeft.TextAlign = HorizontalAlignment.Center ;
// PourCentRight
this.PourCentRight.Location = new Point(64, 8) ;
this.PourCentRight.Name = "PourCentRight" ;
this.PourCentRight.ReadOnly = true ;
this.PourCentRight.Size = new Size(24, 20) ;
this.PourCentRight.TabIndex = 1 ;
this.PourCentRight.Text = "" ;
this.PourCentRight.TextAlign = HorizontalAlignment.Center ;
// ControlPourCent
this.Controls.AddRange(new Control[] {
this.PourCentRight,
this.PourCentLeft}) ;
this.Name = "ControlPourCent" ;
this.Size = new Size(104, 168) ;
// Affecte une procedure de rappel `
a l
ev
enement Paint.
this.Paint += new PaintEventHandler(this.ControlPourCent_Paint) ;
this.ResumeLayout(false) ;
}
#endregion
private void Dessine() {
// Remplit les botes de texte.
PourCentLeft.Text = m_Valeur.ToString() ;
PourCentRight.Text = (100 - m_Valeur).ToString() ;
// Il faut que la methode Dispose() de toute instance de la

Crer vos propres contrles

685

// classe Graphics soit appel


e d`
es que possible.
using (Graphics g = CreateGraphics()) {
// Remplit un rectangle de blanc
// pour effacer laffichage pr
ec
edent.
g.FillRectangle(Brushes.White, 3, 39, 100, 101) ;
// Si m_bContour dessine le contour.
if (m_bContour)
g.DrawRectangle(new Pen(Color.Black), 2, 39, 100, 101) ;
// Si m_bGrille dessine la grille.
if (m_bGrille) {
Pen p = new Pen(Color.Gray) ;
for (int i = 1 ; i < 10 ; i++)
g.DrawLine(p, 3, 39 + i * 10, 102, 39 + i * 10) ;
}
// Dessine les deux rectangles de pourcentage.
g.FillRectangle(Brushes.Blue, 17, 40, 22, m_Valeur) ;
g.FillRectangle(Brushes.Red, 64, 40, 22, 100 - m_Valeur) ;
}
}
// Procedure de rappel de l
ev
enement Paint
// Pour simplifier, le controle est enti`
erement redessin
e `
a chaque
// reception dun evenement Paint.
private void ControlPourCent_Paint(object sender,PaintEventArgs e) {
Dessine() ;
}
}
Lutilisation de lattribut CategoryAttribute sur les proprits bGrille et bContour permet au client du contrle, de configurer ces proprits laide de Visual Studio comme le
montre la Figure 18 -6. Notez que vous pouvez crer vos propres catgories avec lattribut
CategoryAttribute. Vous pouvez aussi utiliser la douzaine de catgories proposes par Visual
Studio. Ces catgories sont exposes dans larticle CategoryAttribute Class des MSDN.
Notez enfin que vous pouvez prvoir des facilits pour stocker directement ltat dun contrle
dans les paramtres de lapplication. Ainsi, il gardera le mme aspect chaque excution de
lapplication. Cette possibilit fait lobjet de larticle Application Settings for Custom Controls
des MSDN.

Utilisation du contrle
Notre contrle sutilise exactement de la mme manire quun contrle standard. Il faut juste
rfrencer lassemblage dans lequel est dfini notre contrle (dans notre exemple, le contrle
est dans le mme assemblage que le code qui lutilise mais ce nest pas le cas en gnral).
Le formulaire client de notre contrle est montr la Figure 18-7. Un clic sur le bouton Nouveau tirage produit un pourcentage alatoire qui est aect aux trois instances du contrle.

686

Chapitre 18 : Les applications fentres (Windows Forms)

Figure 18 -6 : Configuration des proprits dun contrle propritaire avec Visual Studio .NET
Nous avons utilis trois instances pour illustrer le fait que vous pouvez choisir de dessiner la
grille et/ou le contour.

Figure 18 -7 : Formulaire client de notre contrle propritaire


Voici le code :
Exemple 18-4 :
using System.Drawing ;
using System.ComponentModel ;
using System.Windows.Forms ;
public class MyForm : Form
private Button Tirage ;
private ControlPourCent
private ControlPourCent
private ControlPourCent

{
controlPourCent1 ;
controlPourCent2 ;
controlPourCent3 ;

public MyForm() { InitializeComponent() ; }


#region Windows Form Designer generated code

Crer vos propres contrles


private void InitializeComponent() {
this.controlPourCent1 = new ControlPourCent() ;
this.Tirage = new Button() ;
this.controlPourCent2 = new ControlPourCent() ;
this.controlPourCent3 = new ControlPourCent() ;
this.SuspendLayout() ;
// Tirage
this.Tirage.Location = new Point(112, 8) ;
this.Tirage.Name = "Tirage" ;
this.Tirage.Size = new Size(120, 32) ;
this.Tirage.TabIndex = 0 ;
this.Tirage.Text = "Nouveau tirage" ;
this.Tirage.Click += new System.EventHandler(this.Tirage_Click) ;
// controlPourCent1
this.controlPourCent1.bContour = true;
this.controlPourCent1.bGrille = true;
this.controlPourCent1.Location = new Point(8, 56) ;
this.controlPourCent1.Name = "controlPourCent1" ;
this.controlPourCent1.Size = new Size(104, 152) ;
this.controlPourCent1.TabIndex = 1 ;
// controlPourCent2
this.controlPourCent2.bContour = false;
this.controlPourCent2.bGrille = true;
this.controlPourCent2.Location = new Point(120, 56) ;
this.controlPourCent2.Name = "controlPourCent2" ;
this.controlPourCent2.Size = new Size(104, 144) ;
this.controlPourCent2.TabIndex = 2 ;
// controlPourCent3
this.controlPourCent3.bContour = true;
this.controlPourCent3.bGrille = false;
this.controlPourCent3.Location = new Point(232, 56) ;
this.controlPourCent3.Name = "controlPourCent3" ;
this.controlPourCent3.Size = new Size(104, 144) ;
this.controlPourCent3.TabIndex = 3 ;
// Form1
this.AutoScaleDimensions = new SizeF(5, 13) ;
this.ClientSize = new Size(352, 214) ;
this.Controls.AddRange(new Control[] {
this.controlPourCent3,
this.controlPourCent2,
this.controlPourCent1,
this.Tirage}) ;
this.Name = "MyForm" ;
this.Text = "MyForm" ;

687

688

Chapitre 18 : Les applications fentres (Windows Forms)


this.ResumeLayout(false) ;
}
#endregion
[System.STAThread]
static void Main() { Application.Run(new MyForm()) ; }
private void Tirage_Click(object sender, System.EventArgs e) {
// Calcul dun valeur aleatoire entre 0.0 et 1.0.
System.Random random = new System.Random() ;
double d = random.NextDouble() ;
controlPourCent1.Valeur = d;
controlPourCent2.Valeur = d;
controlPourCent3.Valeur = d;
}
}

Prsentation et dition des donnes


Crer rapidement une fentre de prsentation et ddition
de donnes avec Visual Studio 2005
Grce certains outils de Visual Studio 2005, il est possible de dvelopper une fentre ddition
et de prsentation des donnes volue telle que celle de la Figure 18-8 en deux minutes montre
en main. Cest ce que lon appelle le RAD (Rapid Application Development).

Figure 18 -8 : Edition et prsentation dune table


Voici un petit tutorial dutilisation de Visual Studio 2005 qui explique comment dvelopper cette
fentre. Il faut au pralable avoir insr le DataSet typ prsent en page dans votre projet (il
est dailleurs ncessaire davoir assimil cette notion de DataSet typ pour aborder la prsente
section). Prvoyez aussi une fentre vierge nomme FormEmployes dans votre projet :
Rendre visible la vue Data Source (menu Data  Show Data Sources)  Dpliez la source de donnes ORGANISATIONDataSet  Slectionnez le nud EMPLOYES  Dpliez la combobox qui apparat  Choisissez loption DataGridView  Glissez/Dposez le nud EMPLOYES sur la fentre
FormEmployes en cours ddition  Visual Studio 2005 a ajout plusieurs entits votre fentre :

Prsentation et dition des donnes

689

Un DataSet typ de type ORGANISATIONDataSet qui reprsente la source de donnes ainsi


quun TableAdapter de type EMPLOYESTableAdapter.

Un contrle non visuel de type BindingSource qui reprsente la liaison entre notre source
de donnes (i.e le DataSet typ) et les contrles visuels de prsentation et ddition des
donnes.

Un contrle visuel de type DataGridView qui prsente la liste des employs et qui permet
aussi dditer chaque cellule.

Un contrle visuel de type BindingNavigator qui contient des boutons style VCR pour
naviguer dans la liste des employs. Ce contrle prsente aussi un bouton pour ajouter
un nouvel employ, un bouton pour dtruire lemploy couramment slectionn et un
bouton pour sauver les changements raliss sur la liste des employs.

En plus de tous le code ncessaire la cration et linitialisation de ces contrles et objets,


Visual Studio a gnr deux mthodes qui se trouvent dans le fichier source FormEmployes.cs :
...
private void FormEmployes_Load(object sender, EventArgs e) {
this.eMPLOYESTableAdapter.Fill(this.oRGANISATIONDataSet.EMPLOYES);
}
private void bindingNavigatorSaveItem_Click(object sender, EventArgs e){
if (this.Validate()) {
this.eMPLOYESBindingSource.EndEdit();
this.eMPLOYESTableAdapter.Update(this.oRGANISATIONDataSet.EMPLOYES);
} else {
System.Windows.Forms.MessageBox.Show(this,
"Validation errors occurred.", "Save",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Warning) ;
}
}
...
Il est clair que la mthode FormEmployes_Load() appele lors du chargement de la fentre
contient le code qui permet remplir la table EMPLOYES de notre DataSet typ partir de la base
de donnes. Comme expliqu en page , ceci se fait laide du TableAdapter associ la table
EMPLOYES.
Il est clair la mthode bindingNavigatorSaveItem_Click() appele lors du click du bouton de
sauvegarde du contrle BindingNavigator, contient le code qui permet de sauver dans la base
les modifications eectus. Ici aussi, cette opration se fait laide du TableAdapter associ la
table EMPLOYES.
Si vous vous intressez au fichier FormEmployes.Designer.cs vous vous apercevrez que tout est
configurable et notamment lensemble des boutons contenus dans le contrle BindingNavigator
et lensemble des colonnes du contrle DataGridView.
Pour obtenir la fentre de la Figure 18-8 il faut maintenant ajouter les cinq contrles de type
Label et les cinq contrles de type EditBox qui permettent de prsenter une vue dtaille des
cinq champs de lemploy couramment slectionn. Slectionnez le nud EMPLOYES dans la
source de donnes ORGANISATIONDataSet  Dpliez la combobox qui apparat  Choisissez
loption Details  Glissez/Dposez le nud EMPLOYES sur la fentre FormEmployes en cours

690

Chapitre 18 : Les applications fentres (Windows Forms)

ddition  Visual Studio 2005 a ajout les dix contrles. On obtient ainsi une vue matresse
dtaille trs pratique pour ldition et linsertion des donnes des lignes dune table. Notez
quen page 942 nous exposons comment obtenir ce type de vue dans le cadre dune page web.
Il est intressant danalyser les lignes de code ddies la cration des liaisons. Elles se trouvent
toutes dans la mthode gnr InitializeComponent() du fichier FormEmployes.Designer.cs.
Nous avons ajout des commentaires explicatifs :
private void InitializeComponent() {
...
// Cree le BindingSource.
this.eMPLOYESBindingSource =
new System.Windows.Forms.BindingSource(this.components) ;
...
((System.ComponentModel.ISupportInitialize)
(this.eMPLOYESBindingSource)).BeginInit() ;
...
// Lie le BindingSource avec la table EMPLOYES du DataSet typ
e.
this.eMPLOYESBindingSource.DataMember = "EMPLOYES" ;
this.eMPLOYESBindingSource.DataSource = this.oRGANISATIONDataSet ;
...
// Lie le BindingNavigator avec le BindingSource.
this.eMPLOYESBindingNavigator.BindingSource =
this.eMPLOYESBindingSource ;
...
// Lie la DataGridView avec le BindingSource.
this.eMPLOYESDataGridView.DataSource = this.eMPLOYESBindingSource ;
...
// Pour chaque colonne de la DataGridView, indique
// la colonne associee dans la table des employ
es.
this.dataGridViewTextBoxColumn1.DataPropertyName =
...
this.dataGridViewTextBoxColumn2.DataPropertyName =
...
this.dataGridViewTextBoxColumn3.DataPropertyName =
...
this.dataGridViewTextBoxColumn4.DataPropertyName =
...
this.dataGridViewTextBoxColumn5.DataPropertyName =
...

le nom de
"EmployeID" ;
"DepID" ;
"Nom" ;
"Pr
enom" ;
"T
el" ;

// Cree les liaisons entre les TextBox et le DataSet typ


es.
this.employeIDTextBox.DataBindings.Add(
new System.Windows.Forms.Binding("Text",
this.eMPLOYESBindingSource, "EmployeID", true)) ;
...
this.depIDTextBox.DataBindings.Add(
new System.Windows.Forms.Binding("Text",
this.eMPLOYESBindingSource, "DepID", true)) ;

Prsentation et dition des donnes

691

...
this.nomTextBox.DataBindings.Add(
new System.Windows.Forms.Binding("Text",
this.eMPLOYESBindingSource, "Nom", true)) ;
...
this.prenomTextBox.DataBindings.Add(
new System.Windows.Forms.Binding("Text",
this.eMPLOYESBindingSource, "Pr
enom", true)) ;
...
this.telTextBox.DataBindings.Add(
new System.Windows.Forms.Binding("Text",
this.eMPLOYESBindingSource, "T
el", true)) ;
...
((System.ComponentModel.ISupportInitialize)
(this.eMPLOYESBindingSource)).EndInit() ;
...
}

Utilit du contrle BindingSource


Vous pouvez vous passer dun contrle de type BindingSource pour lier un contrle de type
DataGridView directement une source de donnes tel quune DataTable. Lexemple suivant
montre une fentre avec une DataGridView remplie avec le contenu de la table EMPLOYES et une
DataGridView rempli avec le contenu de la table DEPARTEMENTS :
Exemple 18-5 :
...
public class MyForm : Form {
...
private void Form1_Load(System.Object sender, System.EventArgs e) {
depDataGridView.DataSource = dSet.DEPARTEMENTS;
empDataGridView.DataSource = dSet.EMPLOYES;
depTableAdapter.Fill(dSet.DEPARTEMENTS) ;
empTableAdapter.Fill(dSet.EMPLOYES) ;
}
...
private DataGridView empDataGridView ;
private DataGridView depDataGridView ;
private DEPARTEMENTSTableAdapter depTableAdapter ;
private EMPLOYESTableAdapter empTableAdapter ;
private ORGANISATIONDataSet dSet ;
...
Bien que lon puisse sen passer, les objets de type BindingSource peuvent se rvler utiles pour
plusieurs raisons :

By design, ils reprsentent un niveau dindirection entre les contrles de prsentation et


ddition de donnes et les sources de donns. Cette indirection permet de changer simplement de source de donnes sans toucher au code dune fentre.

692

Chapitre 18 : Les applications fentres (Windows Forms)

Comme nous lavons vu, les contrles BindingNavigator, DataGridView et Binding\


-Source ont t conus de manire collaborer facilement.

Comme nous lavons vu, on peut se servir dun contrle BindingSource pour lier des donnes aux contrles de prsentation au moment du design de la fentre avec Visual Studio
2005.

Les contrles de type BindingSource vous permettent de fournir votre propre logique de
cration dun nouvel lment. Plus de dtails ce sujet sont disponibles dans larticle How
to: Customize Item Addition with the Windows Forms BindingSource des MSDN.

Grce aux proprits Filter et Sort de la classe BindingSource, vous pouvez filtrer ou trier
les donnes dune source avant de les prsenter.

Les contrles de type BindingSource permettent dexploiter simplement une relation one
to many . Considrons la relation nomme Est_Employ
e_Par entre les tables EMPLOYES et
DEPARTEMENTS de notre DataSet typ (cette relation est dfinie en page 710). Crons une
fentre avec deux DataGridView et deux BindingSource. La premire BindingSource est
lie avec la table DEPARTEMENT. La seconde est lie avec la premire avec pour valeur de la
proprit DataMember le nom de la relation. La seconde BindingSource est alors capable de
dtecter les changements de slection de dpartement sur la premire. Le cas chant, elle
applique la relation et ne retourne que les employs appartenant au dpartement slectionn :

Figure 18 -9 : BindingSource et relations

Exemple 18-6 :
...
private void Form1_Load(System.Object sender, System.EventArgs e) {
depDataGridView.DataSource = depBindingSource;
empDataGridView.DataSource = empBindingSource;
depTableAdapter.Fill(dSet.DEPARTEMENTS) ;
empTableAdapter.Fill(dSet.EMPLOYES) ;
depBindingSource.DataSource = dSet ;
depBindingSource.DataMember = "DEPARTEMENTS" ;
empBindingSource.DataSource = depBindingSource;
empBindingSource.DataMember = "Est_Employ
e_Par";
}
...

Prsentation et dition des donnes

693

Liaisons entre BindingSource et une source de donnes


Un avantage utiliser des instances de la classe BindingSource est la possibilit quils orent
pour sabstraire des types de source de donnes. Quelque soit la source de donnes sous jacente, le consommateur dune instance de BindingSource peut toujours exploiter les donnes
au moyen de linterface IBindingList. Cette interface implmente les interfaces IEnumerables,
IList et ICollection et elle est implmente par la classe BindingSource. En plus des mthodes
daccs aux donnes des interfaces implmentes, linterface IBindingList prsente des proprits pour renseigner un client sur les possibilits supportes par la source (trie, dition, insertion
etc), des mthodes telles que AddNew() pour ajouter un lment ou Find() pour rechercher
un lment et un vnement ListChanged dclench chaque modification des donnes ou
lorsque la source elle-mme est modifie.
Comme nous lavons vu, on peut aecter une source de donnes de type DataTable un objet
de type BindingSource par lintermdiaire de la proprit object DataSource{get;set;}. On
peut aussi aecter un DataSet cette proprit condition de prciser le nom de la table qui fait
oce de source de donnes par lintermdiaire de la proprit string DataMember{get;set;}.
On peut aussi aecter cette proprit :

Nimporte quel objet qui implmente linterface IEnumerable.

Nimporte quel objet qui reprsente un tableau (i.e dont la classe drive de la classe System.
Array).

Une instance de la clases System.Type pour prciser le type des objets qui peuvent tre
ajouts.

Nimporte quel objet. Le type dun tel objet constituera alors le type des objets qui peuvent
tre ajouts.

Avoir une liste dobjets pour source de donnes


Nous venons de voir que nous ne sommes pas contraint de nous lier avec un objet ADO.NET
tel quun DataSet ou une DataTable. Nimporte quelle liste dobjets de donnes peut faire
oce de source de donnes. Cette particularit est essentielle pour dvelopper des architectures 3-Tiers ou N-Tiers o les donnes subissent un traitement entre la couche de persistance
et la couche de prsentation. Lexemple suivant illustre ceci. Nous avons une liste dobjets de
type Employe que nous souhaitons pouvoir prsenter et diter dans une DataGridView. Cette
liste drive du type BindingList<Employe> aussi, nous pouvons nous passer dun objet de type
BindingSource pour la lier avec la DataGridView. Remarquez que nous implmentons la mthode BindingList<>.AddNewCore(). Elle est appele automatiquement par la DataGridView
lors de lajout dun employ. Nous implmentons aussi lvnement UserDeletingRow pour
dtecter la suppression dun employ. Enfin, puisque notre classe Employe implmente linterface IEditableObject, la DataGridView nous informe de la slection dun employ avec BeginEdit(), de la fin de ldition dun Employe avec EndEdit() ou de lannulation de ldition
dun employ avec CancelEdit(). Lannulation de ldition se fait lorsque lutilisateur appuie
sur la touche ESC. Lors de lappel de EndEdit(), les proprits Nom et Prenom de lobjet employ
concern ont dj t positionnes aux nouvelles valeurs saisies par lutilisateur.
Exemple 18-7 :
using System ;
using System.ComponentModel ;

694

Chapitre 18 : Les applications fentres (Windows Forms)


using System.Windows.Forms ;
public partial class MyForm : Form {
private DataGridView dataGridView ;
EmployeList list = new EmployeList() ;
public MyForm() {
InitializeComponent() ;
list.Add(new Employe("Lafleur", "L
eon")) ;
list.Add(new Employe("Dupont", "Anne")) ;
dataGridView.DataSource = list ;
}
private void InitializeComponent() {
this.dataGridView = new System.Windows.Forms.DataGridView() ;
dataGridView.Dock = DockStyle.Fill ;
dataGridView.AutoGenerateColumns = true ;
dataGridView.UserDeletingRow += UserDeletingRowHandler;
this.Controls.Add(this.dataGridView) ;
}
protected virtual void UserDeletingRowHandler(object s,
DataGridViewRowCancelEventArgs e) {
if ( MessageBox.Show("Etes vous sur(e) ?", string.Empty,
MessageBoxButtons.YesNo) == DialogResult.Yes)
((Employe)e.Row.DataBoundItem).Deleting() ;
}
[STAThread]
static void Main() { Application.Run(new MyForm()) ; }
}
class EmployeList : BindingList<Employe> {
public EmployeList() { this.AllowNew = true ; }
protected override object AddNewCore() {
Employe emp = new Employe("-", "-") ;
Add(emp) ;
return emp ;
}
}
public class Employe : IEditableObject {
private string m_Nom ;
private string m_Prenom ;
public Employe(string nom, string prenom) {
m_Nom = nom ; m_Prenom = prenom ;
}
public string Nom
{ get{return m_Nom;}
set{m_Nom = value;} }
public string Prenom { get{return m_Prenom;} set{m_Prenom = value;} }
void IEditableObject.BeginEdit() { }
void IEditableObject.CancelEdit() { }
void IEditableObject.EndEdit() { }
public void Deleting() { }
}

Internationaliser les fentres

695

Internationaliser les fentres


Avons daborder la prsente section, il faut avoir assimil les concepts dinternationalisation
dune application .NET qui font lobjet de la section 2 Internationalisation et assemblages
satellites , page 34.
Dans le cas dune application Windows Forms, Visual Studio 2005 prsente des facilits pour diter
plusieurs versions localises dune mme fentre. Dans les proprits dune fentre, vous trouverez dans la catgorie Design les proprits Localizable qui vaut false par dfaut et Language
qui vaut (Default) par dfaut. Si vous souhaitez diter plusieurs versions dune mme fentre
XXX.cs relatives plusieurs cultures, il faut dabord positionner la proprit Localizable true.
Un fichier XXX.resx est alors automatiquement associ la fentre. Si vous slectionner une
culture yy-ZZ pour la proprit Language de votre fentre, Visual Studio se met automatiquement en mode dition pour cette culture. Lors du premier changement sur un contrle, un
fichier XXX.yy-ZZ.resx est alors automatiquement associ la fentre. Il contient bien entendu
les valeurs des ressources spcifiques la culture yy-ZZ. Pour ajouter des contrles votre fentre il faut revenir la valeur (Default) pour la proprit Language de la fentre.
Il est intressant de remarquer que le code dinitialisation des contrles (i.e dans la mthode InitializeComponents()) est dirent selon quune fentre est localise ou pas.
Dans le cas localis, vous remarquerez des appels une mthode ComponentResourceManager.ApplyRessource() qui soccupent de charger les bonnes versions des ressources lexcution
selon la culture courante.

La bibliothque GDI+
La bibliothque GDI+ (GDI pour Graphical Device Interface) contient de nombreuses classes qui
permettent deectuer toutes sortes de tracs : des tracs de lignes, des tracs de courbes, des
dgrads, acher des images etc. GDI+ remplace lancienne bibliothque GDI utilises par les
dveloppeurs sous Windows. En plus de bnficier de toute la convivialit des langages .NET,
cette bibliothque prsente de nouvelles fonctionnalits, notamment en ce qui concerne les
formats des fichiers images et lachage de dgrads. La bibliothque GDI+ supporte les formats
JPG, PNG, GIF et BMP alors que la bibliothque GDI ne supportait que le format BMP ( moins
dutiliser dautres librairies non standard).

La classe System.Drawing.Graphics
La bibliothque GDI+ permet de dessiner, cest--dire de faire toutes sortes de formes gomtriques tel que des rectangles, des lignes et mme des courbes de Bzier. Dans les mthodes de
chaque contrle ou formulaire, on peut se procurer une instance de la classe System.Drawing.
Graphics en appelant la mthode CreateGraphics() de la classe Control ou Form. Les instances
de cette classe constituent le support sur lequel on peut dessiner, de la mme faon quune
feuille de papier est le support du dessinateur. Pour ceux qui ont dj travaill avec GDI, les
instances de cette classe sont les quivalentes des instances Device Context (DC).

Il est important dappeler la mthode Dispose() ds que possible, sur toutes les instances
de la classe Graphics obtenues par lappel une mthode CreateGraphics().

696

Chapitre 18 : Les applications fentres (Windows Forms)

La classe System.Drawing.Pen
De la mme manire quun dessinateur a besoin dun crayon pour tracer une courbe, les mthodes de la classe Graphics pour le trac de lignes, de courbes ou de contour de figures, ont
besoin dune instance de la classe System.Drawing.Pen.

Les constructeurs de la classe Pen acceptent soit une instance de la structure System.
Drawing.Color pour spcifier la couleur du trait, soit une instance de la classe System.
Drawing.Brush pour spcifier le type de remplissage dun trait pais.

Vous pouvez indiquer lpaisseur, en pixel, du trait avec la proprit Width de la classe Pen.

Vous pouvez indiquer si vous souhaitez un trac plein, en tir ou en pointill avec la proprit DashStyle de la classe Pen.

Vous pouvez indiquer quel type de dessin doit tre plac aux extrmits du trait avec la
proprit StartCap et EndCap de la classe Pen.

Par exemple le code suivant ache la courbe de Bzier de la Figure 18-10 :


Exemple 18-8 :
...
using (Graphics g = CreateGraphics()) {
Pen pen = new Pen( Color.Black ) ;
pen.Width = 5;
pen.DashStyle = DashStyle.Dash;
pen.StartCap = LineCap.RoundAnchor;
pen.EndCap = LineCap.ArrowAnchor;
g.DrawBezier(pen,
new Point(10, 30),
new Point(30, 200),
new Point(50, -100),
new Point(70, 100)) ;
}
...

Figure 18 -10 : Achage dune courbe de Bzier avec GDI+

La classe System.Drawing.Brush
De la mme manire quun dessinateur a besoin dun pinceau pour remplir une surface, les
mthodes de la classe Graphics pour le remplissage dune surface, ont besoin dune instance
dune classe drive de la classe System.Drawing.Brush. Ces classes drives sont les suivantes
(elles sont toutes dans lespace de noms System.Drawing.Drawing2D) :

La bibliothque GDI+

697

SolidBrush : Brosse utiliser pour un remplissage uniforme.


HatchBrush : Brosse utiliser pour un remplissage avec hachures.
TextureBrush : Brosse utiliser pour un remplissage avec une image en arrire-plan.
LinearGradientBrush : Brosse utiliser pour un remplissage avec un dgrad de couleur.
PathGradientBrush : Brosse utiliser pour un remplissage avec un dgrad de couleur (plus
labor quavec LinearGradientBrush).

Par exemple le code suivant ache le pentagone de la Figure 18-11, rempli dune manire alterne par un croisement de diagonales :
Exemple 18-9 :
...
using (Graphics g = CreateGraphics()) {
Brush brush = new HatchBrush( HatchStyle.DiagonalCross,
Color.White, Color.Black) ;
Point[] pts = new Point[5] ;
pts[0] = new Point(50, 3) ;
pts[1] = new Point(30, 100) ;
pts[2] = new Point(80, 30) ;
pts[3] = new Point(4, 35) ;
pts[4] = new Point(70, 100) ;
g.FillClosedCurve(brush, pts, FillMode.Alternate) ;
}
...

Figure 18 -11 : Remplissage dune courbe avec GDI+

Acher du texte dans vos dessins


La mthode DrawString() de la classe Graphics permet dacher du texte dans vos dessins. Par
exemple le code suivant ache le hi de la Figure 18 -12 :
Exemple 18-10 :
...
using (Graphics g = CreateGraphics()) {
Brush brush = new HatchBrush( HatchStyle.DiagonalBrick ,
Color.White , Color.Black) ;
g.DrawString("hi", new Font("Times", 70), brush,
new Point(5, 5));
}
...

698

Chapitre 18 : Les applications fentres (Windows Forms)

Figure 18 -12 : Ache de texte avec GDI+

Grer des images avec GDI+


La classe abstraite System.Drawing.Image sert de classe de base aux classes : System.Drawing.
Bitmap et System.Drawing.Imaging.Metafile.

Images dfinies par leurs pixels


La classe Image sert charger partir dun fichier, modifier, acher et sauvegarder dans
des fichiers, des images bitmap (i.e dcrites exhaustivement par les tats de leurs pixels). Les
formats dimage supports sont dfinis par les membres statiques de System.Drawing.Imaging.
ImageFormat, parmi lesquels on trouve :
Proprits statiques de
ImageFormat

Format associ

Bmp

Le format dimage bitmap. Prcisons que ce format ne compresse


pas limage.

Gif

Le format GIF (Graphic Interchange Format). Prcisons quune des


caractristiques de ce format est de rduire le nombre de couleurs
de limage pour la compresser (en gnral 256 couleurs). De plus,
ce format permet de produire des animations.

Jpeg

Le format JPEG (Joint Photo Expert Group). Prcisons que ce format


compresse limage avec une perte dinformation. De plus, le taux
de compression est paramtrable.

Png

Le format W3C PNG (Portable Network Graphics). Prcisons que la


particularit de ce format est de compresser limage sans perte dinformation. Il est particulirement adapt aux copies dcran.

Tiff

Le format TIFF (Tag Image File Format).

La proprit PixelFormat de la classe Image dtermine le nombre de bits par pixel (bpp) dans
limage et prend ses valeurs dans lnumration System.Drawing.Imaging.PixelFormat. Dans
le cas des formats de pixel o la valeur attribue chaque pixel dtermine un index dans un
tableau de couleur (appel palette de limage), vous devez utiliser la proprit Palette de la
classe Image.
Pour acher une image dans un formulaire ou dans un contrle, il sut dutiliser une des
versions surcharges de la mthode DrawImage() de la classe Graphics (prsente un peu plus
haut).
Les transformations que vous pouvez appliquer une image sont limites aux flip (i.e retournements verticaux et horizontaux) et aux rotations dangle droit. On verra dans la prochaine
section comment traiter plus srieusement une image.
Il est conseill dintgrer les images dans des fichiers de ressources intgrs lassemblage courant ou un assemblage satellite.

La bibliothque GDI+

699

Images dfinies par des oprations


La classe System.Drawing.Imaging.Metafile drive de la classe Image. Elle prsente la possibilit de charger partir dun fichier, de construire ou de sauver dans un fichier, des images
dfinies partir doprations simples comme le trac dellipse ou de traits etc.
La bibliothque GDI supporte le format EMF (Enhanced Meta File) qui peut stocker des oprations. La bibliothque GDI+ supporte le format EMF+ qui peut servir stocker toutes oprations
de EMF, plus un certain nombre doprations propres EMF+. Il y a donc une compatibilit
ascendante entre les formats EMF et EMF+. Voici un exemple dutilisation de la classe Metafile :
Exemple 18-11 :
using System.Drawing.Imaging ;
...
public class Form1 : System.Windows.Forms.Form {
private Metafile m_Metafile ;
private void OnClick1(object sender, System.EventArgs e) {
using (Graphics g = CreateGraphics()) {
System.IntPtr hDC = g.GetHdc();
m_Metafile = new Metafile(hDC, EmfType.EmfPlusOnly);
using (Graphics metafilegraphic =
Graphics.FromImage(m_Metafile))
using (Brush brush = new SolidBrush(Color.Black)) {
metafilegraphic.FillEllipse(brush, 10, 10, 50, 50);
metafilegraphic.FillRectangle(brush, 5, 5, 10, 10);
}
// Il faut toujours lib
erer les hDC sinon
// une exception est lanc
ee.
g.ReleaseHdc(hDC);
}
}
public void OnClick2(object sender, System.EventArgs e) {
if( m_Metafile != null)
using (Graphics g = CreateGraphics()) {
g.DrawImage(m_Metafile, 10, 10);
}
}
...
}
...

Optimisation des traitements dimages


Le traitement dimage consiste modifier les couleurs des pixels de limage selon certaines oprations mathmatiques. Chaque pixel est cod par trois valeurs entires, une pour le vert, une
pour le rouge, une pour le bleu. Lintervalle de valeurs possibles pour ces valeurs entires dpend du nombre de bits par pixel. La meilleure qualit dimage possible est obtenue avec 24 bits
par pixel, soit une valeur entre 0 et 255 pour chaque composante, soit un peu plus de 16 millions
de couleurs. Lil humain ne peut distinguer plus de 16 millions de couleurs.

700

Chapitre 18 : Les applications fentres (Windows Forms)

Nous allons montrer le traitement dimage qui consiste inverser les couleurs. Supposons que
le nombre de bits par pixel est de 24. Linversion des couleurs consiste aecter le complment
255 pour chacune des trois composantes, pour chacun des pixels de limage. La Figure 18-13
montre lapplication de ce traitement une image. Pour lanecdote sachez que cette image, souvent utilise pour tester les traitements dimage, est la photographie de la playmate sudoise
Lena Soderberg, extraite du magazine Playboy en 1972. Par la suite, elle fut invite certaines
confrences sur le traitement dimage.

Figure 18 -13 : Lena et le traitement dimage


Avec le framework .NET, il y a deux faons de procder :

Soit nous utilisons les mthodes SetPixel() et GetPixel() de la classe Bitmap. Voici lextrait pertinent du code source :

Exemple 18-12 :
...
using (Graphics g = CreateGraphics()) {
Bitmap m_Bmp = new Bitmap("Lena.jpg") ;
g.DrawImage(m_Bmp, new Point(5, 5)) ;
// Attend 1 seconde...
System.Threading.Thread.Sleep(1000) ;
int width = m_Bmp.Width ;
int height = m_Bmp.Height ;
Color cSrc, cDest ;
for (int y = 0 ; y < height ; y++)
for (int x = 0 ; x < width ; x++) {
cSrc = m_Bmp.GetPixel(x, y) ;
cDest = Color.FromArgb(255 - cSrc.R, 255 - cSrc.G,
255 - cSrc.B) ;
m_Bmp.SetPixel(x, y, cDest) ;
}
g.DrawImage(m_Bmp, new Point(5, 5)) ;
}
...

La bibliothque GDI+

701

Soit nous passons directement par des pointeurs pour accder aux pixels de limage. Cette
technique optimise le code par rapport la technique prcdente dun facteur 20
100 selon le traitement appliquer ! Cette technique est un peu plus dlicate implmenter, mais un tel facteur doptimisation vaut bien ces eorts. Il faut notamment tenir
compte des points suivants :

La mthode (ou la partie du code) qui ralise le traitement doit tre qualifie avec le
mot-cl unsafe pour pouvoir manipuler des pointeurs (voir page 501).
Il faut verrouiller les accs la zone de mmoire du bitmap en utilisant les mthodes
LockBits()/UnlockBits() de la classe Bitmap.

Voici lextrait pertinent du code source :


Exemple 18-13 :
...
public struct StructPixel {
public byte R ; public byte G ; public byte B ;
}
...
using (Graphics g = CreateGraphics()) {
Bitmap m_Bmp = new Bitmap("Lena.jpg") ;
g.DrawImage(m_Bmp, new Point(5, 5)) ;
// Attend 1 seconde...
System.Threading.Thread.Sleep(1000) ;
unsafe {
int width = m_Bmp.Width ;
int height = m_Bmp.Height ;
BitmapData BmpData = m_Bmp.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite,
m_Bmp.PixelFormat) ;
StructPixel* pCurrent = null ;
StructPixel* pBmp = (StructPixel*)BmpData.Scan0 ;
for (int y = 0 ; y < width ; y++) {
pCurrent = pBmp + y * height ;
for (int x = 0 ; x < height ; x++) {
pCurrent->R = (byte)(255 - pCurrent->R) ;
pCurrent->G = (byte)(255 - pCurrent->G) ;
pCurrent->B = (byte)(255 - pCurrent->B) ;
pCurrent++ ;
}
}
m_Bmp.UnlockBits(BmpData) ;
}
g.DrawImage(m_Bmp, new Point(5, 5)) ;
}
...
Dans ce cas prcis, le facteur doptimisation obtenu selon des tests sur une machine de rfrence,
est denviron 97.

702

Chapitre 18 : Les applications fentres (Windows Forms)

Animation et double buering


Il est ais de crer une animation en enchanant lachage dimages hauteur de plusieurs
dizaines de fois par seconde. Pour raliser ceci, on utilise en gnral une instance de la classe
System.Windows.Forms.Timer qui se charge de dclencher intervalle rgulier lappel une mthode par le thread de la fentre. Lexemple suivant montre comment produire une animation
reprsentant un carr qui tourne au milieu dune fentre :
Exemple 18-14 :
using System.Drawing ;
using System.Windows.Forms ;
using System.Drawing.Drawing2D ;
public partial class AnimForm : Form {
private float angle ;
private Timer timer = new Timer() ;
public AnimForm() {
timer.Enabled = true;
timer.Tick += OnTimer;
timer.Interval = 20 ; // 20 milliseconds => 50 images par seconde.
timer.Start();
}
private void OnTimer(object sender, System.EventArgs e) {
angle ++ ;
if (angle > 359)
angle = 0 ;
Refresh();
}
protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics ;
Matrix matrix = new Matrix() ;
matrix.Rotate(angle, MatrixOrder.Append) ;
matrix.Translate(this.ClientSize.Width / 2,
this.ClientSize.Height/ 2, MatrixOrder.Append) ;
g.Transform = matrix ;
g.FillRectangle(Brushes.Azure, -100, -100, 200, 200) ;
}
[System.STAThread]
public static void Main() {
Application.Run(new AnimForm()) ;
}
}
Si vous excutez cet exemple, vous vous apercevrez que lanimation nest pas parfaite. En eet,
vous observerez des scintillements dans lachage. Ce dfaut mineur mais perceptible est du
la non synchronisation entre la frquence dachage de votre cran et la frquence de production des images. Concrtement, il arrive que votre carr soit ach en plein milieu de sa
construction.
Pour pallier ce problme, on utilise la technique dite du double buering. Cette technique
consiste maintenir en mmoire deux buers graphiques. chaque instant, un de ces buers

La bibliothque GDI+

703

contient la dernire image cre tandis que lautre contient limage en cours de construction.
Ds quune image est produite, le rle des buers est invers. Il est particulirement ais dutiliser cette technique sur vos propres formulaires. Il sut dappeler la mthode SetStyle() avec
les bons arguments dans le constructeur de votre formulaire aprs linitialisation des composants. Ainsi pour ne plus avoir ce problme lors de la rotation de notre carr il sut de rcrire
notre exemple comme ceci :
Exemple 18-15 :
...
timer.Start() ;
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer,true) ;
}
private void OnTimer(object sender, System.EventArgs e) {
...
Il se peut que ce mcanisme de double buering ne soit pas adapt vos animations. Dans ce
cas, vous pouvez avoir recours aux classes BufferedGraphicsContext et BufferedGraphics afin
grer vous-mme les buers. Une instance de BufferedGraphics sobtient partir de la mthode
BufferedGraphicsContext.Allocate(). Une telle instance gre un buer en interne. Vous pouvez avoir accs ce buer au moyen de la proprit BufferedGraphics.Graphics{get;}. Une
fois que vous avez dessin sur ce buer, il faut appeler la mthode BufferedGraphics.Render()
afin dacher son contenu lcran. Voici notre exemple rcrit avec ces classes :
Exemple 18-16 :
using System.Drawing ;
using System.Windows.Forms ;
using System.Drawing.Drawing2D ;
public partial class AnimForm : Form {
private float angle ;
private Timer timer = new Timer() ;
private BufferedGraphics bufferedGraphics;
public AnimForm() {
BufferedGraphicsContext context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
bufferedGraphics = context.Allocate(this.CreateGraphics(),
new Rectangle(0, 0, this.Width, this.Height));
timer.Enabled = true ;
timer.Tick += OnTimer ;
timer.Interval = 20 ; // 50 d
eclenchements par seconde.
timer.Start() ;
}
private void OnTimer(object sender, System.EventArgs e) {
angle ++ ;
if (angle > 359)
angle = 0 ;

704

Chapitre 18 : Les applications fentres (Windows Forms)


Graphics g = bufferedGraphics.Graphics;
g.Clear(Color.Black) ;
Matrix matrix = new Matrix() ;
matrix.Rotate(angle, MatrixOrder.Append) ;
matrix.Translate(this.ClientSize.Width / 2,
this.ClientSize.Height/ 2, MatrixOrder.Append) ;
g.Transform = matrix ;
g.FillRectangle(Brushes.Azure, -100, -100, 200, 200) ;
bufferedGraphics.Render( Graphics.FromHwnd(this.Handle) );
}
[System.STAThread]
public static void Main() {
Application.Run(new AnimForm()) ;
}
}

19
ADO.NET 2.0

Introduction aux bases de donnes


Notion de SGBD
Pratiquement tous les logiciels utilisent un systme de persistance au sens large du terme. Par
exemple la base des registres de Windows ou mme un fichier .ini peuvent tre vus comme
des systmes de persistance. Pour grer un grand volume dinformations, les logiciels utilisent
des Systmes de Gestion de Base de Donnes (SGBD) dvelopps en gnral par des entreprises
tierces comme Oracle ou Microsoft. On peut citer les SGBD SQL Server, Access, MySql. Grer des
donnes ne se limite pas qu stocker des donnes. Les SGBD fournissent un grand nombre de
fonctionnalits comme la recherche de donnes partir de critres ou la protection des donnes.
Certains dveloppeurs dveloppent leurs propres SGBD, souvent pour des raisons de performances voire par mconnaissance du modle relationnel, mais cette pratique reste marginale
et contestable.

Le modle relationnel
Les premiers SGBD ont fait leur apparition la fin des annes 60, dans le cadre des programmes
spatiaux amricains. Un progrs dcisif a t ralis dans les annes 70 avec linvention du modle
relationnel. Le modle relationnel est bas sur un modle mathmatique qui permet de prsenter les donnes dune manire simple dans des tables.
La notion de table est assez proche de celle de relation. Une colonne dune table sappelle un attribut de la relation et est dfinie par un nom. Les lments dun attribut prennent leurs valeurs
dans un domaine (en gnral un type). Il faut prciser pour chaque colonne sil est obligatoire
que toutes les lignes contiennent un lment valide (i.e dans le domaine). Lensemble de la
description des colonnes dune table (i.e des attributs dune relation) est appel schma de la
relation. Les lignes dune table sont aussi appeles tuples ou enregistrements en franais ou rows

706

Chapitre 19 : ADO.NET 2.0

ou records en anglais. Une cl primaire (primary key en anglais) est lensemble des colonnes dont
la connaissance de valeurs permet didentifier une ligne unique de la table considre. Souvent,
la cl primaire nest constitue que dune colonne.
Le modle relationnel permet dviter les redondances dinformations qui menacent lintgrit
de la table et qui consomment des ressources. Cette fonctionnalit importante est possible grce
aux cls trangres (foreign key en anglais). Au lieu de disperser la mme donne dans plusieurs
endroits de la base, on utilise des cls trangres qui permettent de rfrencer la donne. La
donne nest donc pas duplique et reste accessible. Grce ce systme, on peut stocker des structures complexes dans une base de donnes relationnelle, comme une arborescence de donnes.
Un autre avantage du modle relationnel est de pouvoir assurer lintgrit des donnes de la
base, grce la possibilit de dcrire des contraintes dintgrit. Par exemple si vous avez une table
dont les lignes dcrivent des voitures et une autre table dont les lignes dcrivent des marques de
voitures, vous pouvez garantir la contrainte suivante : chaque voiture correspond une marque.
Lalgbre relationnelle contient six oprations de base, qui agissent sur des relations et produisent
des relations. Ces oprations sont lunion, la dirence, le produit cartsien, la projection, la
restriction et la jointure. Au moyen de ces oprations, lutilisateur dune base de donnes relationnelle peut accder aux donnes dune base et les modifier.

Le langage SQL
Le langage non procdural et standardis nomm SQL (Structured Query Langage) a t dvelopp
pour accder aux donnes dune base relationnelle grce des requtes. Ces requtes modlisent
des oprations de lalgbre relationnelle. Le langage SQL permet de raliser des requtes trs
complexes en quelques mots. Cet ensemble doprations sur les donnes du langage SQL est
nomm DML pour Data Manipulation Langage. On se sert aussi de requtes SQL pour construire
et modifier la structure dune base de donnes (insertion de table, assignation de droits aux
utilisateurs etc). Cette partie du langage est nomme DDL pour Data Definition Langage.
La plupart des SGBD lheure actuelle sont bass sur le modle relationnel et supportent un
langage driv de SQL. Par exemple, la gamme de SGBD SQL Server dit par Microsoft supporte
le langage T-SQL (Transact SQL) qui en plus des possibilits de base de SQL permet de dclarer
des variables, de contrler des transactions, de grer des exceptions, de grer le format XML etc.
La partie du langage relative ces possibilits est nomme DCL pour Data Control Langage.

Ncessit dune architecture distribue


Les applications avec interfaces utilisateurs qui accdent une base de donnes sont de deux
types :

Il y a les applications monolithiques, qui encapsulent dans un mme excutable linterface


utilisateur et le code pour accder la base.

Les applications distribues, dont seule la partie serveur est habilite accder la base. Le
serveur peut tre pris en charge par ASP.NET, utiliser les facilits de COM+ ou tre crit
compltement. De nombreux types de middleware peuvent tre utiliss entre les clients et
le serveur (HTTP/SOAP, .NET Remoting etc). Les clients peuvent tre lgers (navigateur
web) ou riches (excutables Windows Forms par exemple).

Le choix dune de ces deux architectures pour vos applications est fondamental. Le premier
choix nest judicieux que dans le cas de petites applications qui volueront peu.

Introduction ADO.NET

707

Le choix du type darchitecture doit se faire tt dans le cycle de vie du projet.

Introduction ADO.NET
Lappellation ADO.NET englobe la fois lensemble des classes du framework .NET utilises pour
manipuler les donnes contenues dans des SGBD relationnels, et la philosophie dutilisation de
ces classes.
Avant ADO.NET, la technologie ADO (ActiveX Data Object) constituait lensemble des classes
quil fallait utiliser pour accder aux bases de donnes dans les environnements Microsoft. Malgr son nom, ADO.NET est beaucoup plus quun successeur de ADO. Bien que ces deux technologies aient une finalit commune, de profondes dirences les sparent, notamment parce
quADO.NET est beaucoup plus complet.

Mode connect et mode dconnect


Les notions de mode dconnect et de mode connect dcrivent la faon dont une application travaille avec une base de donnes. Il faut dabord introduire la notion de connexion avec une base
de donnes. Une connexion est une ressource qui est initialise et utilise par une application
pour travailler avec une base de donnes. Une connexion avec une base de donnes peut prendre
les tats ouvert et ferm. Si une application dtient une connexion ouverte avec une base de
donnes, on dit quelle est connecte la base. Une connexion est en gnral initialise partir
dune chane de caractres qui contient des informations sur le type de SGBD supportant la base
de donnes et/ou sur la localisation physique de la base de donnes. Tout ceci est dtaill un peu
plus loin.
Lorsquune application travaille avec ADO .NET, de nombreux cas de figures sont possibles :

Une application travaille en mode connect si elle charge des donnes de la base au fur et
mesure de ses besoins. Lapplication reste alors connecte la base. Lapplication envoie
ses modifications sur les donnes la base au fur et mesure quelles surviennent.

Une application travaille en mode dconnect dans les cas suivants :

Si lapplication charge des donnes de la base, quelle se dconnecte de la base, quelle


exploite et modifie les donnes, quelle se reconnecte la base pour envoyer les modifications sur les donnes.

Si lapplication charge des donnes de la base, quelle se dconnecte de la base et quelle


exploite les donnes. Dans ce cas, on dit que lapplication accde la base en lecture
seule.

Si lapplication rcupre des donnes partir dune autre source que la base de donnes
(par exemple partir dune saisie manuelle dun utilisateur ou dun document XML)
puis quelle se connecte la base pour y stocker les nouvelles donnes.

Si lapplication nutilise pas la base de donnes. Prciser ce cas un sens, car une application peut travailler avec des classes de ADO.NET sans pour autant utiliser une base
de donnes. Par exemple, on verra que les classes de ADO.NET sont particulirement
adaptes pour la prsentation de donnes au format XML.

708

Chapitre 19 : ADO.NET 2.0

La philosophie dADO tait de travailler en mode connect. Il est trs dicile, voire impossible,
de crer des serveurs performants en terme de monte en charge (scalable), lorsque lon travaille
en mode connect. Notez quavec ADO, on peut travailler en mode dconnect au prix de beaucoup deorts.
Prcisons la signification du mot scalable. On dit dune application quelle est scalable, si, lorsque
vous ajoutez du hardware pour lexcuter (processeur, RAM, PCs etc) vous obtenez un gain de
performance commensurable la quantit de hardware ajoute. En pratique, on observe que
lajout de hardware pour excuter une application ne provoque pas forcment un gain de performance. En eet, de nombreux goulets dtranglement subsistent toujours dirents niveaux
(locking des donnes, middleware...). Seule larchitecture de lapplication peut minimiser les effets nfastes de ces goulets dtranglement.
Avec ADO.NET, on peut travailler soit en mode dconnect soit en mode connect. De nombreuses fonctionnalits intressantes, que nous dtaillons plus loin, sont disponibles pour chacun des deux modes.
La faiblesse du mode connect est quil gnre de trs nombreux accs la base de donnes et
plus gnralement, il gnre de nombreux accs rseau si la base de donnes est spare physiquement du reste de lapplication. La faiblesse du mode dconnect est quil amne consommer beaucoup de mmoire, puisquune partie de la base est rcupre en mmoire vive pour
chaque appel client. Cette faiblesse peut tre prohibitive pour un serveur devant grer un grand
nombre de clients, chaque client obligeant le serveur manipuler beaucoup de donnes en
mmoire.
Bien quune pr-analyse soit toujours ncessaire, il est en gnral plus ecace de travailler en
mode dconnect avec ADO.NET.

Les fournisseurs de donnes (Providers)


Un fournisseur de donnes (provider en anglais) est une couche logicielle permettant de communiquer avec un SGBD relationnel spcifique. Voici les quatre fournisseurs de donnes supports
par dfaut par le framework .NET :

Le SGBD SQL Server a son propre fournisseur de donnes. Les classes de ce fournisseur
de donnes se trouvent dans lespace de noms System.Data.SqlClient. Ce fournisseur de
donnes fonctionne avec les versions 7.0 2000 et 2005 de SQL Server. Bien videmment, les
fonctionalits spcifiques une version de SQL Server ne peuvent pas tre exploites partir
de ce fournisseur de donnes si vous travaillez avec une version antrieure de ce produit.

Un autre fournisseur de donnes permet de communiquer avec les fournisseurs de donnes qui supportent lAPI OleDB. OleDB est une API permettant daccder aux donnes
dun SGBD, avec la technologie COM. Les classes de ce fournisseur de donnes se trouvent
dans lespace de noms System.Data.OleDbClient. Notez quil faut utiliser ce fournisseur de
donnes si vous travaillez avec des versions antrieures 7.0 de SQL Server.

Il existe un fournisseur de donnes .NET qui se place au dessus du protocole ODBC (Open
DataBase Connectivity). Ce fournisseur de donnes gr permet dexploiter les fournisseurs
de donnes non grs qui supportent lAPI ODBC (mais pas la totalit, voir le site de Microsoft pour plus de dtails). Les classes de ce fournisseur daccs sont disponibles dans lespace
de noms System.Data.Odbc.

Introduction ADO.NET

709

Il existe un fournisseur de donnes .NET qui est spcialis pour lutilisation de bases de
donnes Oracle. Les classes de ce fournisseur de donnes sont disponibles dans lespace de
noms System.Data.OracleClient.

lexception du fournisseur de donnes Oracle qui se trouve dans la DLL System.Data.


OracleClient.dll les trois autres fournisseurs de donnes se trouvent dans la DLL System.
Data.dll.

Schma gnral de ADO.NET


Voici un schma gnral (mais simplifi) de larchitecture ADO.NET :
XML

DataSet
DataTableCollection
DataTable
DataRowCollection

Application utilisant ADO.NET

Travail
en mode
dconnect

DataColumnCollection
ConstraintCollection

DataRelationCollection
DataRelation

Fournisseur de donnes .NET


Classe drive de DbDataAdapter

Travail
en mode
connect

Classe drive de DbConnection

Base de
donnes
(SGBD)

Classe drive de DbDataReader

Figure 19 -1 : Schma gnral de ADO.NET


Ce schma illustre plusieurs principes cls dADO.NET :

Tous les accs au SGBD se font par lintermdiaire dune connexion dont limplmentation
fait partie du fournisseur de donnes.

Les classes DataSet et DataTable permettent de travailler en mode dconnect. Dans ce


mode, tout chargement ou toute sauvegarde de donnes se fait par lintermdiaire dun
objet adaptateur. Un DataSet est un cache de donnes relationnelles hberg lexcution
dans le domaine dapplication courant. Vous pouvez remplir un DataSet avec des donnes
provenant dune base de donnes, vous dconnecter, consommer les donnes du DataSet
et ventuellement les modifier, puis enfin vous reconnecter la base pour ventuellement
sauver les changements eectus sur les donnes.

La classe DataReader permet de travailler en mode connect.

710

Chapitre 19 : ADO.NET 2.0


Comme nous le verrons dans le chapitre suivant consacr XML plusieurs passerelles
existent entre le monde du stockage des donnes au format XML et le monde du stockage
relationnel des donnes.

La BD utilise pour illustrer les exemples


Dans tous les exemples de ce chapitre nous allons utiliser une base de donnes nomme
ORGANISATION gre par le SGBD SQL Server. Les exemples sont compatibles avec les versions
2000 et 2005 sauf indication contraire. Nous utiliserons bien entendu le fournisseur de donnes
SQL Client spcifique SQL Server. En dernire section, nous dcrirons des fonctionnalits
propres ce fournisseur de donnes.
Notre base de donnes ORGANISATION contient les tables DEPARTEMENTS et EMPLOYES qui sont
censes modliser lorganisation interne dune entreprise en supposant que chaque employ
appartient exactement un dpartement. Les tables ainsi que les contraintes peuvent tre cres
avec le script T-SQL suivant :
Exemple 19-1 :
CREATE TABLE [dbo].[EMPLOYES] (
[EmployeID] [int] IDENTITY (1, 1) ,
[DepID] [char] (3) NOT NULL ,
[Nom] [nvarchar] (30) NOT NULL ,
[Prenom] [nvarchar] (30) NOT NULL ,
[Tel] [nvarchar] (20) NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DEPARTEMENTS] (
[DepID] [char] (3) NOT NULL,
[Departement] [nvarchar] (30) NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE EMPLOYES ADD CONSTRAINT ID_Primaire
PRIMARY KEY (EmployeID)
GO
ALTER TABLE DEPARTEMENTS ADD CONSTRAINT DepID_Primaire
PRIMARY KEY (DepID)
GO
ALTER TABLE EMPLOYES ADD CONSTRAINT Est_Employ
e_Par
FOREIGN KEY (DepID) REFERENCES DEPARTEMENTS(DepID)
GO
La cl primaire de la table EMPLOYES est constitue par la colonne EmployeID. La syntaxe IDENTITY (1, 1) signifie que la valeur entire de cette colonne est aecte automatiquement par
SQL Server qui se sert dun compteur interne, selon lalgorithme suivant : le compteur interne
est positionn 1 lorsquune ligne est insre pour la premire fois dans cette table ; chaque
insertion dune ligne le compteur interne est incrment de 1. chaque insertion dune ligne
dans la table EMPLOYES, la valeur EmployeID de la nouvelle ligne prend la valeur du compteur
interne.

Introduction ADO.NET

711

La cl primaire de la table DEPARTEMENTS est constitue par la colonne DepID. Elle doit tre positionne par lutilisateur chaque ajout dune ligne dans la table. Voici le diagramme de notre
base de donnes :

Figure 19 -2 : Diagramme de notre base de donnes


Nous supposerons lors de lutilisation de nos exemples, que ltat initial de la base de donnes
est celui-ci :
Table DEPARTEMENTS
DepID

Departement

DEV

Dveloppement

FIN

Financier

MKT

Marketing

Table EMPLOYES
EmployeID

DepID

Nom

Prnom

Tl

MKT

Lafleur

Lon

0497112233

DEV

Dupont

Anne

0497112235

DEV

Schmol

Jean

FIN

Gripsou

Nol

0497112237

Voici le script T-SQL permettant de remplir la base. Notez que lon dlgue au SGBD le calcul
des valeurs de la colonne EmployeID :
Exemple 19-2 :
INSERT INTO DEPARTEMENTS VALUES (DEV,D
eveloppement)
INSERT INTO DEPARTEMENTS VALUES (FIN,Financier)
INSERT INTO DEPARTEMENTS VALUES (MKT,Marketing)
GO
SET IDENTITY_INSERT EMPLOYES OFF

712

Chapitre 19 : ADO.NET 2.0


GO
INSERT
INSERT
INSERT
INSERT
GO

INTO
INTO
INTO
INTO

EMPLOYES
EMPLOYES
EMPLOYES
EMPLOYES

VALUES (MKT,Lafleur,L
eon,0497112233)
VALUES (DEV,Dupont,Anne,0497112235)
(DepID,Nom,Pr
enom) VALUES (DEV,Schmol,Jean)
VALUES (FIN,Gripsou,No
el,0497112237)

La chane de connexion que nous utilisons dans nos exemples pour accder cette base est la
suivante :
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
Pensez configurer le mode dauthentification SQL Server and Windows pour excuter les
exemples avec cette chane de connexion.

Connexion et fournisseurs de donnes


Dcoupler une application dun fournisseur de donnes
Un grand nombre de paradigmes se retrouvent dans la plupart des SGBD relationnels : Ils sont
accessibles au travers dune connexion, on peut manipuler les donnes laide de requtes SQL
etc. Bien entendu chaque couple SGBD/fournisseur de donnes associ a ses spcificits. Par
exemple seul le fournisseur de donnes de SQL Server permet dobtenir les statistiques dune
connexion.
Dans le nouvel espace de noms System.Data.Common ADO.NET2 prsente plusieurs classes abstraites qui dfinissent un modle pour lensemble des fonctionnalits partages par les SGBD.
Par exemple la classe DbConnection dfinit la notion de connexion vers une base de donnes
tandis que la classe DbCommand dfinit la notion de commande vers une base de donnes. Notez
quADO.NET 1.x reprsentait cet ensemble de fonctionnalits partages au moyen dinterfaces
telles que IDbConnection ou IDbCommand. Ces interfaces sont toujours prsentes par le framework et les nouvelles classes abstraites correspondantes les implmentent. Cependant, les nouvelles fonctionnalits partages supportes par ADO.NET2 ne sont disponibles quau travers des
classes abstraites aussi, il est conseill de les prfrer aux interfaces.
Lorsquune application exploite des donnes stockes dans un SGBD il est intressant de chercher rendre votre code le plus indpendant possible (le plus abstrait possible) dun type de
SGBD. Lintrt de cette dmarche est quil est courant de ne pas matriser le type de SGBD
qui sera utilis en production. Un client peut vouloir utiliser votre application avec un SGDB
Oracle parce quil ne veut pas changer ses habitudes tandis quun autre client prfrera un SGBD
gratuit tel que MySQL. Cette indpendance du type de SGBD est aisment obtenue si vous nexploitez que les fonctionnalits communes aux SGBD. En eet, il sut alors de ne manipuler les
donnes quau travers des classes abstraites de lespace de noms System.Data.Common dont nous
venons de parler. Nanmoins, un problme se pose : un endroit de votre code, il faut crer
les objets ADO.NET que vous manipulez. Cette opration implique de prciser explicitement
la classe utiliser. Par exemple :
Exemple 19-3 :
using System.Data.Common ;
using System.Data.SqlClient ;

Connexion et fournisseurs de donnes

713

class Program {
static void Main() {
DbConnection cnx ;
DbCommand
cmd ;
if( /*test si on travaille avec SQL Server*/ true ){
cnx = new SqlConnection();// Ici on couple notre application...
cmd = new SqlCommand() ; // ...avec le provider SqlClient.
}
// Ici on exploite cnx et cmd ind
ependamment du SGBD sous-jacent.
}
}
Pour limiter les eets de ce problme chaque fournisseur de donnes ADO.NET2 prsente une
classe drive de la classe abstraite System.Data.Common.DbProviderFactory. Cette classe prsente les mthodes suivantes :
DbConnection CreateConnection() ;
DbCommand CreateCommand() ;
DbCommandBuilder CreateCommandBuilder() ;
DbConnection CreateConnection() ;
DbConnectionStringBuilder CreateConnectionStringBuilder() ;
DbDataAdapter CreateDataAdapter() ;
DbDataSourceEnumerator CreateDataSourceEnumerator() ;
DbParameter CreateParameter() ;
DbPermission CreatePermission() ;
premire vue, il faudrait rcrire lexemple comme ceci :
Exemple 19-4 :
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void Main() {
DbProviderFactory fabrique ;
// Tester si lon travaille avec un SGBD type SQL Server.
if (true)
fabrique= new SqlClientFactory();
// `A partir dici le code est ind
ependent du provider sous-jacent.
DbConnection cnx = fabrique.CreateConnection() ;
DbCommand
cmd = fabrique.CreateCommand() ;
}
}
Cependant cet exemple ne compile pas car la classe SqlClientFactory na pas de constructeur
public. La seule faon de construire une instance dune classe drive de DbProviderFactory est
dutiliser la mthode statique CreateFactory() de la classe System.Data.Common.DbProviderFactories. Cette dernire classe est en fait une fabrique de fabrique. La mthode CreateFactory() retourne une fabrique dobjets ADO.NET pour un fournisseur de donnes si vous lui
passez en argument lespace de nom du fournisseur de donnes sous forme dune chane de
caractres. Notre exemple se rcrit donc comme ceci :

714

Chapitre 19 : ADO.NET 2.0

Exemple 19-5 :
using System.Data.Common ;
// Nous navons plus besoin du namespace System.Data.SqlClient !!
class Program {
static void Main() {
DbProviderFactory fabrique =
DbProviderFactories.GetFactory("System.Data.SqlClient");
// `A partir dici le code est ind
ependent du provider sous-jacent.
DbConnection cnx = fabrique.CreateConnection() ;
DbCommand
cmd = fabrique.CreateCommand() ;
}
}
Pour que notre exemple soit 100% indpendant dun fournisseur de donnes il ne nous reste
plus qu obtenir la chane de caractres contenant lespace de noms partir dun fichier de
configuration. Ceci est prvu par lattribut XML providerName dun lment XML add de llment XML connectionStrings du fichier de configuration de votre application. Par exemple le
fichier de configuration de votre application peut ressembler ceci :
Exemple 19-6 :

app.exe.config

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<connectionStrings>
<add name="Ma DB" providerName="System.Data.SqlClient"
connectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"/>
</connectionStrings>
</configuration>
Le code de votre application ressemblera alors ceci. Il est bien 100% indpendant de tous les
fournisseurs de donnes :
Exemple 19-7 :
using System.Data.Common ;
using System.Configuration ;
class Program {
static void Main() {
ConnectionStringSettings cfg =
ConfigurationManager.ConnectionStrings["Ma DB"];
DbProviderFactory fabrique =
DbProviderFactories.GetFactory(cfg.ProviderName) ;
DbConnection cnx ;
DbCommand
cmd ;
cnx = fabrique.CreateConnection() ;
cmd = fabrique.CreateCommand() ;
cnx.ConnectionString = cfg.ConnectionString;
cmd.Connection = cnx ;
}
}

app.cs

Connexion et fournisseurs de donnes

715

Larchitecture prsente par cet ensemble de classes est plus connue sous la forme du design
pattern nomm fabrique abstraite (abstract factory en anglais). Cette architecture ne vous empche pas de ponctuellement tenter dutiliser les services propres un fournisseur de donnes
particulier. Par exemple le programme suivant montre comment obtenir les statistiques dune
connexion condition que lon utilise le fournisseur de donnes de SQL Server (seul ce fournisseur de donnes expose cette possibilit) :
app.cs

Exemple 19-8 :
using System.Data.Common ;
using System.Configuration ;
using System.Collections ; // Pour IDictionary.
using System.Data.SqlClient ;
class Program {
static void Main() {
ConnectionStringSettings cfg =
ConfigurationManager.ConnectionStrings["Ma DB"] ;
DbProviderFactory fabrique =
DbProviderFactories.GetFactory(cfg.ProviderName) ;
DbConnection cnx = fabrique.CreateConnection() ;
IDictionary cnxStats = EventuallyGetStats(cnx) ;
}
public static IDictionary EventuallyGetStats(DbConnection cnx) {
if ( cnx is SqlConnection )
return ( cnx as SqlConnection ).RetrieveStatistics();
return null ;
}
}

La liste des fournisseurs de donnes est extensible. Aussi, chaque fournisseur de donnes disponible sur une machine doit se dclarer dans le fichier machine.config comme ceci :
Exemple 19-9 :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
...
<system.data>
<DbProviderFactories>
...
<add name="OleDb Data Provider"
invariant="System.Data.OleDb"
support="BF"
description=".Net Framework Data Provider for OleDb"
type="System.Data.OleDb.OleDbFactory, System.Data,
Version=2.0.3600.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
...
</DbProviderFactories>
</system.data>
</configuration >

app.exe.config

716

Chapitre 19 : ADO.NET 2.0

Notez quun fournisseur de donnes spcifique une application web peut tre dclar dans le
fichier web.config correspondant.
La chane de caractres spcifie par lattribut invariant est la mme que celle spcifie en argument de la mthode DbProviderFactory.GetFactory(). Lattribut support dfinit un masque
de 8 bits relatif lindicateur binaire System.Data.Common.DbProviderSupportedClasses spcifiant les possibilits prsentes par le fournisseur de donnes. Enfin lattribut type indique
le nom de la classe fabrique drivant de DbProviderFactory ainsi que lassemblage qui la
contient. Cet assemblage doit tre soit dans le rpertoire de lapplication soit dans le rpertoire
GAC.

Chanes de connexion
Une chane de connexion est une chane de caractres qui contient les informations ncessaires
pour localiser et pour se connecter une base de donnes. Une chane de connexion est constitue de plusieurs couples cl/valeur, spars par des points virgules. Par exemple :
string s1 ="Data Source=Server;Database=MyDB;User ID=FOOID;Password=FOOPWD;" ;
string s2 ="Data Source=Server ; " +
"Initial Catalog=pubs;Integrated Security=SSPI;" ;
string s3 ="SERVER = localhost ; UID=sa ; PWD = ; database = ORGANISATION" ;
Certaines donnes sont ncessairement reprsentes dans une chane de connexion. On peut
citer le nom du serveur hbergeant la base de donnes ou le login de lutilisateur DB sous lequel on souhaite se connecter. Les cls associes ces donnes dirent selon le fournisseur de
donnes. Par exemple, le fournisseur de donnes OleDb se sert des cls "Data Source" et "User
ID" tandis que le fournisseur de donnes Odbc se sert des cls "Server" et "UID". Le fournisseur de donnes SqlClient supporte ces deux syntaxes. Certains fournisseurs de donnes prsentent la possibilit de construire une chane de connexion au moyen dune classe drive de
System.Data.Common.DbConnectionStringBuilder. Cette possibilit est illustre par lexemple
suivant. Cet exemple exploite aussi le fait que le fournisseur de donnes SqlClient permet dnumrer les sources de donnes auxquelles il peut accder :
Exemple 19-10 :
using System.Data ;
using System.Data.Common ;
using System.Configuration ;
class Program {
static void Main() {
Configuration cfg = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None) ;
ConnectionStringSettings cfg_cs =
cfg.ConnectionStrings.ConnectionStrings["Ma DB"] ;
DbProviderFactory fabrique =
DbProviderFactories.GetFactory(cfg_cs.ProviderName) ;
DbDataSourceEnumerator e=fabrique.CreateDataSourceEnumerator();
DataTable tbl = e.GetDataSources();
if (tbl.Rows.Count > 0) {
// Laisser lutilisateur choisir un serveur.

app.cs

Connexion et fournisseurs de donnes

717

int choixUtilisateur = 0 ;
DataRow row = tbl.Rows[choixUtilisateur] ;
string dataSource = row[ "ServerName" ].ToString() ;
string instanceName = row[ "InstanceName" ].ToString() ;
string isClustered = row[ "IsClustered" ].ToString() ;
string version = row[ "Version" ].ToString();
// Met `a jour la chane de cnx dans le fichier de config
// independament du fournisseur de donn
ees sous-jacent.
DbConnectionStringBuilder csBuilder =
fabrique.CreateConnectionStringBuilder();
csBuilder.ConnectionString = cfg_cs.ConnectionString;
if( csBuilder.ContainsKey("Server"))
csBuilder.Remove("Server");
csBuilder.Add("Server", dataSource) ;
cfg_cs.ConnectionString = csBuilder.ConnectionString ;
cfg.Save() ;
} // end choix de la ligne.
} // end methode Main()
} // end class Program.
Cet exemple est particulirement pertinent si vous utilisez la cl "Data Source" dans votre
chane de connexion dans votre fichier de configuration. En eet, la valeur associe sera correctement mise jour bien que nous utilisions la chane de caractres "Server" pour la rfrencer
dans notre programme.
Si vous ne souhaitez pas utiliser cette possibilit de construction dynamique de chanes de
connexion nous vous conseillons de vous rfrer au site http://www.connectionstrings.com/.
Il runit un grand nombre dexemples de chanes de connexion prsents en fonction des
sources de donnes disponibles sur le march des SGBD.
Lorsque vous utilisez le fournisseur de donnes ODBC il est conseill dexploiter la notion de
DSN (Data Source Name) qui vite aux dveloppeurs de manipuler les donnes contenues dans la
chane de connexion. Un DSN est une information stocke dans le systme dexploitation de la
machine qui excute lapplication. Cette information associe une base de donnes une chane
de caractres. Par exemple une chane de connexion utilisant un DSN peut ressembler ceci :
string s1 = "DSN=MonAppliDSN" ;
En plus de sa simplicit dutilisation, un DSN prsente lavantage de constituer une indirection
vers une base de donnes paramtrable aprs compilation de lapplication. Cette indirection se
fait indpendamment du SGBD sous-jacent et indpendamment de lemplacement physique
de la base, puisque ces donnes sont des paramtres du DSN. En gnral on configure les DSN
prsents dans un systme dexploitation Microsoft avec le menu Panneau de configuration  Outil
dadministration  Sources de donnes (ODBC).

Stocker les chanes de connexion


Les chanes de connexion ont la possibilit dtre stockes dans un fichier de configuration. Cette
notion de fichier de configuration fait lobjet de la section page 58.

718

Chapitre 19 : ADO.NET 2.0

LExemple 19-6 illustre un fichier de configuration qui dfinit une chaine de connexion dans son
lment <connexionStrings>. LExemple 19-7 montre comment du code C  dune application
peut avoir accs aux chanes de connexion dfinies dans un fichier de configuration.
Les chanes de connexion sont parfois considres comme des donnes confidentielles du fait
quelles contiennent souvent un mot de passe. Le framework .NET 2.0 vous permet de sauver une
version encrypte dune chaine de connexion dans un fichier de configuration. Ceci est expliqu
en page .

Pool de connexions
Ceux qui ont dj crit des serveurs accdant des bases de donnes savent que lutilisation dun
pool de connexions peut augmenter dune manire significative les performances. Un pool de
connexions est une collection de connexions quivalentes. Cest--dire que les connexions sont
connectes la mme base et que chaque demande dun client se sert dune de ces connexions
pour accder aux donnes. Les connexions sont des objets complexes, coteux initialiser et
dtruire. Lide sous-jacente la notion de pool de connexions est de recycler les connexions
dj ouvertes de faon diminuer globalement les cots de crations, dinitialisation et de destruction des connexions.
La bonne nouvelle est que ADO.NET fait en sorte que ce mcanisme de pooling de connexions
soit compltement transparent pour lutilisateur. Un pool de connexions est automatiquement
cr en interne pour chaque chane de connexion utilise. Par exemple :
...
string sCnx = "server=localhost ;uid=sa ;pwd =;database = ORGANISATION";
SqlConnection cnx1 = new SqlConnection(sCnx) ;
cnx1.Open() ;
// Ici le pool de connexion associ
e `
a la chaine sCnx ne contient pas de
// connexion mais le thread courant en utilise une.
cnx1.Close() ;
// La connexion nest pas detruite mais est plac
ee dans le pool associ
e
// `a la chaine sCnx.
...
La gestion en interne du pool de connexions dpend du fournisseur de donnes sous-jacent.
Aussi nous vous conseillons de vous rfrez la documentation ocielle du fournisseur si vous
souhaitez paramtrer cette gestion. Sachez que le fournisseur de donnes SqlClient prsente
des possibilits pour permettre programmatiquement un certain contrle sur la faon dont le
pooling de connexions deectue. Plus dinformations ce sujet sont disponibles dans larticle
Connection Pooling for the .NET Framework Data Provider for SQL Server des MSDN.

Obtenir les mtadonnes dune base de donnes


ADO.NET 2.0 prsente un framework permettant de naviguer programmatiquement dans le
schma dun SGBD. Par schma, nous entendons lensemble des mtadonnes du SGBD telles
que les ensembles des tables, des colonnes, des vues, des utilisateurs, des cls trangres, des
index etc. Typiquement, un tel framework permet de dvelopper des outils qui prsentent la
structure dun SGBD. Visual Studio 2005 contient un tel outil illustr par le panneau gauche de
la Figure 19 -3 (page 732). On y voit la structure du SGBD expose sous une forme hirarchique.

Connexion et fournisseurs de donnes

719

Toute la problmatique dun tel framework est quil est gnrique dans le sens o il est commun
tous les types de SGBD et donc tous les fournisseurs de donnes. Or, les dirents types de
SGBD nont pas exactement la mme structure. Bien entendu, on y retrouve dans chacun les
concepts classiques tels que les notions de tables ou dindex. Mais les dtails sont en gnral diffrents. Par exemple, il existe trois types de filtre (aussi nomm restriction) qui peuvent sappliquer une requte sur lensemble des tables dun SGBD type Oracle (OWNER, TABLE_NAME, et TYPE)
tandis quil en existe quatre pour les SGBD de type SQL Server (table_catalog, table_schema,
table_name et table_type).
La rponse cette problmatique est forcment un aaiblissement du typage. Les requtes sur la
structure dun SGBD se font dune manire non types laide dune seule mthode, la mthode
DataTable GetSchema() dfinie dans la classe de base DbConnection. Lensemble des lments
structuraux du SGBD satisfaisant une requte est contenu dans une DataTable. Si vous utilisez
cette mthode sans paramtre vous obtiendrez ce que lon nomme lensemble des collections
de mtadonnes. Pour un SGBD de type SQL Server cet ensemble est : MetaDataCollections,
DataSourceInformation, DataTypes, Restrictions, ReservedWords, Users, Databases, Tables,
Columns, Views, ViewColumns, ProcedureParameters, Procedures, ForeignKeys, IndexColumns,
Indexes, et UserDefinedTypes. Si vous utilisez cette mthode avec un paramtre de type chane
de caractres gale une de ces collections, vous obtiendrez lensemble des lments de cette
collection. Lexemple suivant illustre ceci en numrant lensemble des tables dune SGBD :
Exemple 19-11 :
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Data ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd= ; database = ORGANISATION" ;
using (DbConnection cnx = new SqlConnection(sCnx)) {
cnx.Open() ;
DataTable tbl = cnx.GetSchema("tables");
foreach (DataRow row in tbl.Rows) {
foreach (DataColumn col in tbl.Columns)
System.Console.WriteLine(col.ToString() + " = " +
row[col].ToString()) ;
System.Console.WriteLine() ;
}
} // end using cnx.
}
}
Voici un extrait de lachage de cet exemple :
...
TABLE_CATALOG = ORGANISATION
TABLE_SCHEMA = dbo
TABLE_NAME = EMPLOYES
TABLE_TYPE = BASE TABLE

720

Chapitre 19 : ADO.NET 2.0


TABLE_CATALOG = ORGANISATION
TABLE_SCHEMA = dbo
TABLE_NAME = DEPARTEMENTS
TABLE_TYPE = BASE TABLE
...

Enfin, une troisime surcharge de la mthode GetSchema() existe. Elle vous permet de filtrer le
rsultat de la requte.
Plus dinformations ce sujet sont disponibles dans larticle Schemas in ADO.NET 2.0 des
MSDN rdig par Bob Beauchemin. La notion de restriction y est dtaille ainsi que les impacts
de ce framework au niveau de la conception dun fournisseur de donnes.

Travailler en mode connect avec des DataReader


Utiliser une implmentation de DataReader
pour obtenir des donnes
Voici un petit programme en mode console, utilisant la classe SqlReader pour rcuprer le
contenu de la table EMPLOYES. Les lignes sont obtenues partir de la base, une une par lappel
la mthode Read(). La connexion nest ferme que lorsque les donnes ont t traites :
Exemple 19-12 :
using System ;
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void Main() {
// Chaine de connexion.
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
// Requete SQL pour recup
erer des donn
ees.
string sCmd = "SELECT * FROM EMPLOYES" ;
// Creation dun objet connexion.
using( DbConnection cnx = new SqlConnection(sCnx) ) {
// Creation dune commande.
using(DbCommand cmd=new SqlCommand(sCmd,cnx as SqlConnection)){
// Ouverture de la connexion.
cnx.Open();
// Execution de la commande.
using( DbDataReader rdr = cmd.ExecuteReader() ) {
// Affichage des colonnes 2 et 3 de la table EMPLOYES.
while( rdr.Read() )
Console.WriteLine(rdr.GetString(2) + " " +
rdr.GetString(3));
} // end using rdr.
} // end using cmd.
} // end using cnx.

Travailler en mode connect avec des DataReader

721

}
}
Cet exemple ache sur la console :
Lafleur Leon
Dupont Anne
Schmol Jean
Gripsou Noel
La plupart des classes de ADO.NET implmentent linterface IDispose et doivent donc soit tre
instancies au sein dune clause using soit tre disposes manuellement.
La classe DbProviderFactory ne prsente pas la mthode CreateDbDataReader() bien que
chaque fournisseur de donnes ait une classe drive de DbDataReader. La raison est que
les nouvelles instances de ces classes sont ncessairement obtenues en retour de lappel la
mthode ExecuteReader() sur une commande.

Obtenir une valeur partir dune base de donnes


Il arrive quune application ait besoin dune simple valeur calcule partir des informations
contenues dans une base de donnes. Par exemple on peut souhaiter compter le nombre de
lignes dune table (COUNT), calculer la somme (SUM), la moyenne (AVG), le minimum (MIN), le
maximum (MAX), la variance (VAR, VARP) ou lcart type (STDEV, STDEVP pour standard deviation
en anglais) des valeurs dune ou de plusieurs colonnes, dune ou de plusieurs tables. Ce serait
un gros gaspillage de ressources que de rcuprer la table entire ou mme la colonne entire,
afin deectuer le calcul au niveau de votre code. Aussi, le langage SQL peut raliser ces calculs
directement au niveau du SGBD grce aux mots-cls cits, utiliss dans une requte. Voici un
exemple dutilisation du mot-cl COUNT, qui permet de rcuprer le nombre de lignes de la table
EMPLOYES :
Exemple 19-13 :
using System ;
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void Main() {
// Requete SQL pour recup
erer le scalaire.
string sCmd = "SELECT COUNT(*) FROM EMPLOYES";
int nEmployes ;
using( DbConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
cnx.Open() ;
using( DbCommand cmd =
new SqlCommand(sCmd,cnx as SqlConnection) ) {
nEmployes = (int) cmd.ExecuteScalar();
} // end using cmd.
} // end using cnx.
Console.WriteLine("Nombre demploy
es:{0}", nEmployes) ;
}
}

722

Chapitre 19 : ADO.NET 2.0

Utiliser des requtes SQL pour obtenir et modifier des donnes


Les commandes que lon peut excuter sur une base de donnes sont de trois types.

Une commande/requte SQL.

Une procdure stocke qui associe un nom et des paramtres une requte SQL stocke directement par le SGBD. Comprenez bien quarchitecturalement parlant, utiliser des procdures stockes permet de dplacer de la complexit de votre code source vers le SGBD.
La proximit dun traitement dans une procdure stocke avec les donnes elles-mmes
est un avantage. Cependant, lutilisation de procdures stockes complexifie en gnral la
maintenance globale dune application, notamment parce que plusieurs langages de programmation sont alors utiliss (T-SQL, C  etc).

Une commande ne prenant en compte que le nom dune ou de plusieurs tables. Dans ce
cas, le contenu entier des tables est rcupr par la commande.

Ces types de commandes peuvent tre dfinis par la proprit CommandType de linterface
IDbCommand. La premire solution est choisie par dfaut. Il existe principalement quatre types
de commandes/requtes SQL :

SELECT : permet dobtenir des donnes. Ce type de requte est gnralement utilis pour
remplir un DataSet partir dune commande et dun DataAdapter, comme vu prcdemment.

INSERT : permet dinsrer une ligne dans une table, par exemple :
INSERT INTO DEPARTEMENTS VALUES (COM,Communication)

UPDATE : permet de modifier les valeurs dune ou de plusieurs lignes dans une table, par
exemple :
UPDATE DEPARTEMENTS SET Departement=Comm WHERE DepID = COM

DELETE : permet de supprimer une ou plusieurs lignes dans une table, par exemple :
DELETE FROM DEPARTEMENTS WHERE DepID = COM

Pour excuter une telle commande il sut dutiliser la mthode ExecuteNonQuery() de linterface IDbCommand (notez que non query signifie que lon excute pas une requte qui va nous
retourner des informations) :
Exemple 19-14 :
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void Main() {
// Requete SQL pour inserer un enregistrement.
string sCmd1 =
"INSERT INTO DEPARTEMENTS VALUES (COM,Communication)" ;
// Requete SQL pour modifier un enregistrement.
string sCmd2 =

Travailler en mode dconnect avec des DataSet

723

"UPDATE DEPARTEMENTS SET Departement=Comm WHERE DepID = COM" ;


// Requete SQL pour supprimer un enregistrement.
string sCmd3 = "DELETE FROM DEPARTEMENTS WHERE DepID = COM" ;
using ( DbConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
cnx.Open() ;
DbCommand cmd = new SqlCommand(sCmd1, (SqlConnection)cnx) ;
int nLignesAffectees = (int)cmd.ExecuteNonQuery() ;
cmd.CommandText = sCmd2 ;
nLignesAffectees = (int)cmd.ExecuteNonQuery() ;
cmd.CommandText = sCmd3 ;
nLignesAffectees = (int)cmd.ExecuteNonQuery() ;
} // end using cnx.
}
}
Un peu plus loin dans ce chapitre nous insrerons un employ dans la table EMPLOYES. La commande SQL sera celle-ci :
INSERT INTO EMPLOYES VALUES (COM,Smith,Adam,0497112239)
Nous ninitialisons pas le champ EmployeID de la ligne insre dans la table EMPLOYES, puisque
la valeur de ce champ est calcule par le SGBD. La technique utilise pour rcuprer cette valeur
aprs une insertion dpend du fournisseur de donnes. Cette technique est dcrite dans larticle
Retrieving Identity or Autonumber Values des MSDN.

Travailler en mode dconnect avec des DataSet


Les exemples de cette section utilisent la base de donnes prsente page 710.

Remplir un cache avec des donnes dune base


Voici un petit programme en mode console, utilisant les classes SqlDataAdapter et DataSet
pour rcuprer le contenu de la table EMPLOYES. On voit que le DataAdapter reprsente un ensemble de commandes couples avec une connexion qui est utilis pour remplir le DataSet. Les
donnes sont traites alors que lapplication nest plus connecte la base. Le DataSet fait alors
oce de cache dconnect de donnes. Le traitement des donnes consiste en lachage des
couples nom/prnom sur la console :
Exemple 19-15 :
using System ;
using System.Data ;
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void FillWithData(DataSet dSet) {
// Creation dun DataAdapter pour acc
eder `
a la base de donn
ees.

724

Chapitre 19 : ADO.NET 2.0


// On lui fournit la requ
ete SQL et la chane de connexion.
using ( DbDataAdapter dAdapter = new SqlDataAdapter(
"SELECT * FROM EMPLOYES",
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
// Cet appel `a la methode Fill() provoque :
// - Louverture dune connexion avec la base de donn
ees.
// - Lexecution de la requ
ete SQL.
// - La creation dune table EMPLOYES dans le DataSet.
// - Le remplissage de la table EMPLOYES du DataSet
//
avec les donnees r
ecup
er
ees par la requ
ete SQL.
dAdapter.Fill(dSet);
} // end using dAdapter, provoque la d
econnection.
}
static void Main() {
DataSet dSet = new DataSet() ;
FillWithData(dSet) ;
// Affichage des colonnes Nom et Pr
enom de la table EMPLOYES.
// Lindex de la table est forc
ement 0 puisque nous navons
// recupere quune seule table.
DataTable dTable = dSet.Tables[0];
foreach (DataRow dRow in dTable.Rows)
Console.WriteLine(dRow["Nom"] + " " + dRow["Pr
enom"]);
}
}

Cet exemple ache sur la console :


Lafleur Leon
Dupont Anne
Schmol Jean
Gripsou Noel

Travailler avec des relations entre les tables dun DataSet


Une instance de la classe DataSet peut contenir une ou plusieurs tables. Vous avez la possibilit
dajouter des relations entre les tables contenues dans un mme DataSet. Une relation se fait
entre une colonne dune table et une colonne dune autre table. Une telle relation est utilise
en gnral pour modliser une relation one to many entre les lignes dune table parent (i.e
la table dont la colonne dans la relation constitue sa cl primaire) et les lignes dune table fille
(i.e la table dont la colonne dans la relation constitue une cl trangre). Dans lexemple suivant, on met en relation la colonne DepID de la table DEPARTEMENTS et la colonne DepID de la
table EMPLOYES. En eet, il y a bien une relation one to many entre un dpartement et les
employs du dpartement puisque chaque employ a un seul dpartement et un dpartement
peut contenir zro, un ou plusieurs employs. La cration dune telle relation dans le DataSet
permet de parcourir logiquement les donnes :

Travailler en mode dconnect avec des DataSet

725

Exemple 19-16 :
using System ;
using System.Data ;
using System.Data.SqlClient ;
class Program {
static void Main() {
DataSet dSet = new DataSet() ;
using ( SqlConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
using (SqlDataAdapter dAdapter = new SqlDataAdapter()) {
dAdapter.SelectCommand = new SqlCommand(
"SELECT * FROM EMPLOYES", cnx) ;
dAdapter.Fill(dSet, "EMPLOYES") ;
dAdapter.SelectCommand = new SqlCommand(
"SELECT * FROM DEPARTEMENTS", cnx) ;
dAdapter.Fill(dSet, "DEPARTEMENTS") ;
} // end using cnx.
} // end using dAdapter.
// Cree une relation dans le DataSet.
// La relation se fait entre la colonne DepID de la table
// DEPARTEMENTS et la colonne DepID de la table EMPLOYES.
DataColumn dCol1 = dSet.Tables["DEPARTEMENTS"].Columns["DepID"] ;
DataColumn dCol2 = dSet.Tables["EMPLOYES"].Columns["DepID"] ;
DataRelation dRelation = dSet.Relations.Add(
"Employes du departement", dCol1, dCol2);
// Navigue logiquement dans la relation one to many
// dun departement vers ses employ
es.
foreach (DataRow dRow1 in dSet.Tables["DEPARTEMENTS"].Rows){
Console.WriteLine("Departement : {0}", dRow1["Departement"]) ;
foreach (DataRow dRow2 in dRow1.GetChildRows(dRelation) )
Console.WriteLine(" {0} {1}",dRow2["Nom"],dRow2["Pr
enom"]) ;
}
}
}
Ce petit programme ache :
Departement : Developpement
Dupont Anne
Schmol Jean
Departement : Financier
Gripsou Noel
Departement : Marketing
Lafleur Leon

Sauver les donnes modifies dun cache


Lorsque vous modifiez les donnes dans une DataTable (contenue dans un DataSet ou non)
les donnes originales sont conserves. Ainsi, en interne, une DataTable maintient les don-

726

Chapitre 19 : ADO.NET 2.0

nes courantes et les donnes originales. En tant que client dune DataTable vous navez accs
quaux donnes courantes. Cependant chaque ligne dune table (i.e chaque instance de la classe
DataRow) prsente la proprit DataRowState RowState{get;} qui indique si ses donnes ont
subi des changements :
Valeur de lnumration
DataRowState

Description

Unchanged

La ligne na pas t modifie.

Added

La ligne a t ajoute la table.

Deleted

La ligne a t supprime de la table.

Modified

Les donnes contenues dans la ligne ont t modifies.

Detached

La ligne vient dtre cre mais ne fait pas encore partie dune
table.

Il serait plus juste de rajouter pour chacune de ces descriptions, depuis le dernier appel la mthode AcceptChanges() ou RejectChanges() sur la DataTable contenant la ligne . En eet, si vous
appelez lune ou lautre de ces mthodes au nom loquent, ltat de chaque ligne est positionn
Unchanged. De plus, si lon appelle AcceptChanges(), les lignes qui taient dans ltat Deleted
sont eectivement dtruites. Si lon appelle RejectChanges(), les lignes qui taient dans ltat
Added sont dtruites et les donnes qui ont t modifies sont remises leurs tats initiaux.
Lappel la mthode AcceptChanges() sur un DataSet ou une DataTable ne signifie aucunement quil y a un accs la base de donnes. Noubliez pas quun DataSet ou une DataTable est
un cache de donnes dconnect. Cependant, si un cache contient des donnes extraites dune
base, avant daccepter les changements, il faut en gnral mettre jour les donnes dans la base.
Nous allons voir que les oprations de mise jour de la base de donnes et dacceptation des
changements dans un cache sont deux oprations distinctes, qui ncessitent chacune du code.
Pour mettre jour dans une base les donnes modifies dans un cache il faut se reconnecter la
base puis eectuer une requte SQL pour chaque modification. Pour vous vitez davoir coder
cette opration fastidieuse le fournisseur de donne sous-jacent a la possibilit de prsenter une
classe qui est capable de fabriquer ces requtes SQL en les dduisant dune requte SELECT.
Cette classe doit driver de System.Data.Common.DbCommandBuilder. Tout ceci est illustr par
lexemple suivant :
Exemple 19-17 :
using System.Data ;
using System.Data.SqlClient ;
class Program {
static string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
static string sCmd = "SELECT * FROM DEPARTEMENTS" ;
static void Main() {
DataTable dTable = new DataTable() ;

Travailler en mode dconnect avec des DataSet

727

FillTableFromDB(dTable);
ChangeDataInTable(dTable);
SynchronizeChangesWithDB(dTable);
}
static void ChangeDataInTable(DataTable dTable) {
// Ajoute un nouveau departement.
dTable.Rows.Add("COM","Communication");
}
static void FillTableFromDB(DataTable dTable) {
using ( SqlDataAdapter dAdapter = new SqlDataAdapter(sCmd, sCnx)){
dAdapter.Fill(dTable) ;
} // end using dAdapter.
}
static void SynchronizeChangesWithDB(DataTable dTable) {
using ( SqlDataAdapter dAdapter = new SqlDataAdapter(sCmd, sCnx)){
// Construit les commandes de mise `
a jour dans dAdapter...
// ...`a partir de la commande Select.
SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(dAdapter);
try {
// Met `a jour les donn
ees dans la base.
dAdapter.Update(dTable);
}
catch {
// Ici, traiter lerreur de maj.
return ;
}
// Accepte les changements dans le cache.
dTable.AcceptChanges();
} // end using dAdapter.
}
}
Sachez que ce principe de conservation en interne des donnes originales et des donnes courantes afin de faciliter la mise jour se retrouve dans un design pattern de Martin Fowler nomm
Unit Of Work.
Tel que ce programme est crit, lappel la mthode Update() provoque un accs la base pour
chaque requte SQL. Cette technique est peu ecace si vous avez sauver un grand nombre de
changements aussi, depuis ADO.NET2, les classes drives de DbDataAdapter ont la possibilit
de mettre jour en un seul accs la base tout un lot de changements. Pour cela, il sut de positionner la proprit int DbDataAdapter.UpdateBatchSize{get;set;} qui spcifie le nombre
de requtes SQL contenues dans un lot.
Enfin, notez que lopration de mise jour est susceptible de lancer une exception. La raison est
quADO.NET utilise une gestion optimiste des accs concurrents aux donnes dune base.

Mode dconnect et gestion optimiste des accs concurrents


Lorsque plusieurs utilisateurs travaillent simultanment en mode dconnect avec les mmes
bases de donnes, il y a possibilit de conflit lors de la modification des donnes. Par exemple

728

Chapitre 19 : ADO.NET 2.0

supposons que lutilisateur A rcupre le numro de tlphone de Anne Dupont 8h00. Supposons que lutilisateur B modifie le numro de tlphone de Anne Dupont 8h05. Enfin,
supposons que lutilisateur A dcide de changer aussi le numro de tlphone de Anne Dupont
8h10, partir du numro quil a rcupr 10 minutes plus tt. Il y a clairement un conflit de
mise jour du numro de tlphone de Anne Dupont. Quel est finalement le numro de Anne
Dupont : celui que lutilisateur B a sauv 8h05 ou celui que lutilisateur A a sauv 8h10 ?
ADO.NET gre ces conflits dune manire optimiste. Cela signifie quaucune mesure nest mise
en uvre pour viter ces conflits, mais si un conflit survient, il est dtect et par dfaut la donne
nest pas modifie. Dans notre exemple, le numro de tlphone garderait donc sa valeur aecte 8h05 et lutilisateur A serait averti du conflit 8h10. La gestion est optimiste dans le sens o
on espre que les conflits ne surviennent que trs rarement. Lorsquun conflit est dtect, une
exception de type System.Data.DBConcurrenyException est leve lors de lappel la mthode
Update() sur ladaptateur.
Thoriquement il existe une technique pour prvenir les conflits. Cette technique est appele
gestion pessimiste des conflits. Cette technique utilise des systmes de verrous sur les lignes (voire
sur les tables) afin de synchroniser les accs. La gestion est pessimiste dans le sens o lon prvoit
que les conflits arrivent susamment souvent pour nuire au bon droulement de lapplication.
Ce systme de verrous implique des temps dattentes pour les clients qui souhaitent accder aux
donnes, et donc une baisse significative des performances gnrales de lapplication.
Il est conseill aux dveloppeurs utilisant ADO.NET de faire en sorte que les architectures de
leurs bases de donnes et de leurs applications soient adaptes une gestion optimiste. Cette
contrainte est assez faible dans la mesure o il sut de minimiser les cas o plusieurs utilisateurs accdent en criture la mme donne. Dans la plupart des systmes distribus rels, cette
contrainte est satisfaite de facto. En eet, en gnral le volume des donnes traiter est immense
par rapport aux nombres dutilisateurs simultans. Concrtement, si des dizaines dutilisateurs
utilisent simultanment des dizaines de millions de lignes, les conflits ne surviennent que trs
rarement.

Ajouter des contraintes une table dun DataSet


Vous pouvez associer des contraintes une table contenue dans un DataSet. Ces contraintes
servent de garde-fou . Concrtement, la prsence de telles contraintes sur une table provoque
lenvoi dune exception lorsquune modification ne respecte pas une des contraintes. Pour que
lexception soit eectivement lance, il faut que la proprit EnforceConstraints du DataSet
contenant la DataTable soit positionne true.
Chaque contrainte est reprsente par une instance dune classe drive de System.Data.
Constraint. Deux classes drivent de la classe Constraint :

System.Data.UniqueConstraint
Une contrainte de ce type impose que la table que lon obtiendrait en projetant la table
courante sur certaines de ses colonnes nait que des lignes direntes deux deux. Les colonnes sont prcises dans la contrainte. Ce type de contraintes est particulirement utile
pour vrifier que lon nutilise pas une valeur dj utilise pour cl primaire, lorsque lon
ajoute une nouvelle ligne dans une table.

System.Data.ForeignKeyConstraint
Cette contrainte sutilise sur une table parent lorsquune relation one to many sur
des cls primaires/cls trangres existe entre une table parent et une table fille. Une telle

Travailler en mode dconnect avec des DataSet

729

contrainte permet de prciser le comportement adopter lorsquune ligne de la table


parent voit sa cl primaire modifie ou lorsquune ligne dune table parent est dtruite.
Pour cela, les proprits DeleteRule et UpdateRule dune telle contrainte prennent leurs
valeurs dans lnumration System.Data.Rule.

Cascade : les lignes concernes de la table fille sont soit mises jour avec la nouvelle
valeur de la cl primaire de la table parent, soit supprimes si la ligne de la table parent
est supprime. Cest le comportement adopt par dfaut.

None : rien nest fait sur les lignes concernes de la table fille.

SetDefault : chaque valeur dune ligne concerne de la table fille prend la valeur par
dfaut prcise dans sa colonne (proprit DefaultValue de la classe Column).

SetNull : les valeurs correspondantes des lignes concernes de la table fille sont positionnes nulle.

Vue des donnes avec la classe DataView


La classe System.Data.DataView vous permet de slectionner partiellement le contenu dune
DataTable selon dirents critres :

Vous pouvez dcider de ne visualiser que les lignes qui sont dans un certain tat en positionnant la proprit DataRowViewState RowStateFilter{get;set;}. Par exemple la valeur DataRowViewState.ModifiedCurrent vous permet de visualiser le contenu courant de
toutes les lignes qui ont t modifies tandis que la valeur DataRowViewState.Deleted vous
permet de visualiser le contenu original de toutes les lignes qui ont t supprimes.

Vous pouvez dcider dappliquer un filtre sur le contenu de certaines colonnes en positionnant la proprit string RowFilter{get;set;}. Le formatage de la chane de caractres
reprsentant le filtre est similaire au formatage dune clause WHERE en SQL. Plus de dtails
sur ce formatage sont disponibles dans lentre La proprit DataColumn.Expression des
MSDN.

Vous pouvez dcider de trier les lignes filtres selon lordre croissant du contenu des
colonnes reprsentant la cl primaire en positionnant la proprit bool ApplyDefaultSort{get;set;}.

Vous pouvez dcider de trier les lignes filtres selon le contenu de une ou plusieurs colonnes
en utilisant la proprit string Sort{get;set;}. Le tri se fait par ordre croissant par dfaut. Vous devez sparer le nom des colonnes avec une virgule et utiliser les expressions ASC
ou DESC pour spcifier un tri par ordre croissant ou dcroissant.

Le contenu dune DataView est dynamique. Autrement dit, il reflte en temps rel les changements apports sur les donnes de la DataTable sous-jacente. Cette particularit est illustre par
le programme suivant :
Exemple 19-18 :
using System ;
using System.Data ;
using System.Data.SqlClient ;
class Program {
static string sCnx =

730

Chapitre 19 : ADO.NET 2.0


"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
static string sCmd = "SELECT * FROM EMPLOYES" ;
static void Main() {
DataTable dTable = new DataTable() ;
using (SqlDataAdapter dAdapter = new SqlDataAdapter(sCmd, sCnx)) {
dAdapter.Fill(dTable) ;
} // end using dAdapter.
DataView dView = new DataView(dTable);
// Filtre les employes du d
epartement d
eveloppement.
dView.RowFilter = "DepID=DEV" ;
Console.WriteLine("--> Data Table:") ;
foreach (DataRow dRow in dTable.Rows)
Console.WriteLine(dRow["DepID"] + " " + dRow["Nom"]) ;
Console.WriteLine("--> Data View avant mutation de Gripsou:") ;
for (int i = 0 ; i < dView.Count ; i++)
Console.WriteLine(dView[i]["DepID"] + " " + dView[i]["Nom"]) ;
// Mutation de Gripsou au d
epartement d
eveloppement.
foreach (DataRow dRow in dTable.Rows)
if (((string)dRow["Nom"]) == "Gripsou")
dRow["DepID"] = "DEV" ;
Console.WriteLine("--> Data View apr
es mutation de Gripsou:") ;
for (int i = 0 ; i < dView.Count ; i++)
Console.WriteLine(dView[i]["DepID"] + " " + dView[i]["Nom"]) ;
}
}

Ce programme ache ceci :


-->
MKT
DEV
DEV
FIN
-->
DEV
DEV
-->
DEV
DEV
DEV

Data Table:
Lafleur
Dupont
Schmol
Gripsou
Data View avant mutation de Gripsou:
Dupont
Schmol
Data View apres mutation de Gripsou:
Dupont
Schmol
Gripsou

la dirence du systme de vues fourni par les dirents SGBD, la classe DataView ne vous
permet pas de crer de vue sur une jointure de tables. Elle ne permet pas non plus dexclure ou
dajouter des colonnes.

DataSet typs

731

La mthode DataTable DataView.ToTable() vous permet de fabriquer une nouvelle DataTable


avec une copie du contenu prsent par la DataView.
Vous pouvez ajouter des lignes une DataView si la proprit bool AllowNew{get;set;} est
positionne. Vous pouvez aussi modifier le contenu des lignes existantes si la proprit bool
AllowEdit{get;set;} est positionne. Ces modifications ne seront reportes dans la DataTable
sous-jacente que si la mthode EndEdit() de la DataRowView concerne est appele. Plus de dtails ce sujet sont disponibles dans larticle Modifying Data Using a DataView des MSDN.
Si vous exploitez la possibilit de tri sur une ou plusieurs colonnes vous pouvez utilisez les mthodes Find() et FindRows() pour rcuprer une ou plusieurs lignes dont le vecteur des valeurs
des colonnes utilises pour le tri est gal un vecteur de valeurs spcifi.
La mthode DataView DataRowView.CreateChildView(DataRelation) permet dobtenir une
DataView sur les lignes de la DataTable enfant lorsque vous disposez dune relation entre tables
dans un DataSet. Bien videmment il faut pour cela que la DataTable sous-jacente la DataView
contenant la ligne source soit la table parent de la relation.
La classe System.Data.DataViewManager permet de grer plusieurs vues sur plusieurs tables
dun mme DataSet.

DataSet typs
Maintenant que nous avons prsent la notion de DataSet il est temps de montrer que lon
utilise en fait que rarement la classe DataSet. En eet, on prfre utiliser ce que lon nomme
des DataSet typs. Loutil xsd.exe fourni avec le framework permet de construire une classe C 
ou VB.NET drive de la classe DataSet. Cette construction se fait partir dun schma XSD
ou partir des tables dune base de donnes. Cette classe gnre prsente des sous classes qui
permettent daccder aux donnes du cache dune manire fortement type. Pour bien illustrer
cette notion de typage fort, voici lExemple 19-16 rcrit en exploitant la classe de Dataset typs
ConsoleApplication1.ORGANISATIONDataSet :
Exemple 19-19 :
using
using
using
using
using

System ;
System.Data ;
System.Data.Common ;
System.Data.SqlClient ;
ConsoleApplication1 ; // Espace de noms contenant la classe
// ORGANISATIONDataSet de DataSet typ
e.
class Program {
static void Main() {
ORGANISATIONDataSet dSet = new ORGANISATIONDataSet();
using (SqlConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
using (SqlDataAdapter dAdapter = new SqlDataAdapter()) {
dAdapter.SelectCommand = new SqlCommand(
"SELECT * FROM EMPLOYES", cnx) ;
dAdapter.Fill(dSet, "EMPLOYES") ;
dAdapter.SelectCommand = new SqlCommand(
"SELECT * FROM DEPARTEMENTS", cnx) ;

732

Chapitre 19 : ADO.NET 2.0


dAdapter.Fill(dSet, "DEPARTEMENTS") ;
} // end using dAdapter.
} // end using cnx.
foreach (ORGANISATIONDataSet.DEPARTEMENTSRow dRow in
dSet.DEPARTEMENTS) {
Console.WriteLine("Departement : " + dRow.Departement) ;
foreach (ORGANISATIONDataSet.EMPLOYESRow eRow in
dRow.GetEMPLOYESRows())
Console.WriteLine("
" + eRow.Nom
+ " " + eRow.DEPARTEMENTSRow.Departement) ;
}
}
}

Cet exemple ache ceci :


Departement : Developpement
Dupont Developpement
Schmol Developpement
Departement : Financier
Gripsou Financier
Departement : Marketing
Lafleur Marketing
On observe quune instance de la classe ORGANISATIONDataSet se manipule comme un DataSet.
Ceci est normal puisque cest un DataSet car la classe ORGANISATIONDataSet drive de la classe
DataSet. On voit aussi que la classe ORGANISATIONDataSet contient les classes encapsules
EMPLOYESRow et DEPARTEMENTSRow qui sont trs pratiques. Non seulement elles prsentent une
proprit correctement type pour chaque colonne de la table sous jacente, mais en plus,
elles ont des membres permettant de naviguer dans la relation Est_Employ
e_Par. Enfin, on
voit que la classe ORGANISATIONDataSet prsente les proprits EMPLOYES et DEPARTEMENTS qui
permettent daccder directement aux tables.

Cration dune classe de DataSet typs


Comme nous lavons expliqu, la classe ORGANISATIONDataSet ainsi que ses classes encapsules
ont t gnres par loutil xsd.exe. Pour viter de se servir de cet outil en ligne de commande
Visual Studio prsente des menus pour crer des classes de DataSet typs :

Soit vous faite : Menu Data  Add New Data Source  Database  puis vous slectionnez
une base de donnes ainsi que ses tables qui vont tre prises en compte dans votre classe de
DataSet typ.

Soit vous faite : Click droit sur votre projet  Add  New Item...  DataSet  puis vous slectionnez une base de donnes ainsi que ses tables qui vont tre prises en compte dans votre
classe de DataSet typ au moyen de la fentre Server Explorer.

Dans les deux cas, votre projet contient un nouveau fichier dextension .xsd (en loccurrence
ORGANISATIONDataSet.xsd). Un fichier de code source (en loccurrence ORGANISATIONDataSet.
Designer.cs) est associ ce dernier. Cest ce fichier qui contient le code gnr de la classe
ORGANISATIONDataSet et de ses classes encapsules :

DataSet typs

733

Figure 19 -3 : Visual Studio et les DataSet typs


Si vous souhaitez ajouter des fonctionnalits une de ces classes gnres, il faut cliquer droit
sur le fichier ORGANISATIONDataSet.xsd et slectionner View Code. Cette action aura pour eet
de crer un nouveau fichier associ nomm ORGANISATIONDataSet.cs. Ce fichier contient une
dclaration partielle de la classe ORGANISATIONDataSet. Vous pouvez alors vous servir de cette
dclaration partielle pour ajouter de nouveaux membres la classe ORGANISATIONDataSet ou
ses classes encapsules. En eet, toutes ces classes gnres sont dclares dune manire partielle. Ainsi, cette technique vous permet de sparer le code gnr et votre propre code dans
deux fichiers distincts. Ceci a pour but dviter des problmes de perte de code cause des mises
jour.

TableAdapter et requtes SQL types


Lors de la gnration dun DataSet typ, Visual Studio gnre aussi des classes de DataAdapter typs dans un espace de noms ddi (en loccurrence ConsoleApplication1.ORGANISATIONDataSetTableAdapters). On nomme ces classes des TableAdapter. Rappelons quune instance de
DataAdapter est un ensemble de commandes coupl avec une connexion permettant de remplir
les tables dun DataSet. Rcrivons notre Exemple 19-19 laide des classes EMPLOYESTableAdapter
et DEPARTEMENTSTableAdapter qui ont t gnres :
Exemple 19-20 :
...
// Espace de noms contenant les TableAdapters.
using ConsoleApplication1.ORGANISATIONDataSetTableAdapters;
class Program {
static void Main() {
ORGANISATIONDataSet dSet = new ORGANISATIONDataSet() ;
using (SqlConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
using (EMPLOYESTableAdapter eAdapter = new
EMPLOYESTableAdapter()) {

734

Chapitre 19 : ADO.NET 2.0


eAdapter.Connection = cnx;
eAdapter.Fill(dSet.EMPLOYES);
} // end using eAdapter.
using (DEPARTEMENTSTableAdapter dAdapter = new
DEPARTEMENTSTableAdapter()) {
dAdapter.Connection = cnx;
dAdapter.Fill(dSet.DEPARTEMENTS);
} // end using dAdapter.
} // end using cnx.
...

On remarque lutilisation des mthodes Fill(EMPLOYESDataTable) et Fill(DEPARTEMENTSDataTable). Ces mthodes nous vitent davoir crire les requtes SQL de slection globale des
lignes dune table. Nous aurions pu aussi utiliser les mthodes EMPLOYESDataTable GetData()
et DEPARTEMENTSDataTable GetData() qui jouent le mme rle mis part quelles vous vitent
davoir crer la table.
En fait, lintrt majeur des TableAdapter rside dans le fait quils nous vitent davoir crire
toutes sortes de requtes SQL (i.e des requtes de type SELECT, INSERT, UPDATE et DELETE). Lide
est de remplacer chaque requte SQL par une mthode dun TableAdapter. Lavantage est que
les paramtres entrant et sortant de ces mthodes sont typs. Un wizard de cration de telle
mthode existe. Voici comment sen servir pour crer une mthode EMPLOYESDataTable GetEmployesByDepID(string DepID) qui retourne une table contenant les employs dun dpartement :
Click droit de ORGANISATIONDataSet.xsd  View designer  Click droit sur la zone EMPLOYESTableAdapter  Add Query  Use SQL Statements  Next  SELECT which returns Rows ( ce stade
vous pouvez aussi choisir de crer une requte de type INSERT, UPDATE, DELETE)  Next
 Query Builder  Crer la requte : SELECT EmployeID, DepID, Nom, Pr
enom, T
el FROM EMPLOYES WHERE DepID = @DepID  OK  Next  Return a DataTable avec pour nom de mthode
GetEmployesByDepID()  Finish
Voici un exemple dun programme qui utilise cette mthode :
Exemple 19-21 :
using System.Data.SqlClient ;
using ConsoleApplication1 ;
using ConsoleApplication1.ORGANISATIONDataSetTableAdapters ;
class Program {
static void Main() {
ORGANISATIONDataSet.EMPLOYESDataTable table ;
using (SqlConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
using (EMPLOYESTableAdapter eAdapter = new
EMPLOYESTableAdapter()) {
table = eAdapter.GetEmployesByDepID("DEV");
} // end using eAdapter.
} // end using cnx.
foreach (ORGANISATIONDataSet.EMPLOYESRow eRow in table)
System.Console.WriteLine("
" + eRow.Nom) ;

Pont entre le mode connect et le mode dconnect

735

}
}

Pont entre le mode connect et le mode dconnect


ADO.NET2 prsente un pont entre le mode connect et le mode non connect. Concrtement,
vous pouvez remplir un DataSet ou une DataTable partir dun DataReader. loppos vous
pouvez parcourir les donnes contenues dans un DataSet ou dans une DataTable laide
dun DataReader. Ces deux possibilits sont illustres par lexemple suivant qui remplit une
DataTable avec les donnes de la table EMPLOYES par lintermdiaire dun DataReader connect
la base. Dans un second temps les donnes de la DataTable sont lues laide dun autre
DataReader non connect la base. Tout se passe alors comme si la DataTable tait une base
de donnes laquelle tait connect ce deuxime DataReader :
Exemple 19-22 :
using System ;
using System.Data ;
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void Main() {
DataTable dTable = new DataTable() ;
using ( DbConnection cnx = new SqlConnection (
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
using ( DbCommand cmd = new SqlCommand (
"SELECT * FROM EMPLOYES", (SqlConnection) cnx) ) {
cnx.Open() ;
using (DbDataReader rdrCmd = cmd.ExecuteReader()) {
// DataReader connect
e -> DataTable
dTable.Load(rdrCmd, LoadOption.OverwriteChanges);
} // end using rdrCmd.
} // end using cmd.
} // end using cnx.
// DataTable -> DataReader non connect
e.
using ( DbDataReader rdrTbl = dTable.CreateDataReader() ) {
while (rdrTbl.Read())
Console.WriteLine(rdrTbl.GetString(2) + " " +
rdrTbl.GetString(3)) ;
} // end using rdrTbl.
}
}
La mthode Load() de la classe DataTable (qui est aussi prsente par la classe DataSet) prend
en second argument une valeur de lnumration LoadOption. Cette information indique comment rsoudre les conflits de mise jour entre les donnes originales et courantes contenues
dans le DataSet et les donnes en provenance du DataReader. Lnumration LoadOption prsente trois valeurs :

736

Chapitre 19 : ADO.NET 2.0

PreserveChanges : (valeur par dfaut) Ecrase seulement les donnes originales du DataSet
avec les donnes en provenance du DataReader.

OverwriteChanges : Ecrase les donnes originales et courantes du DataSet. avec les donnes
en provenance du DataReader.

Upsert : Ecrase seulement les donnes courantes du DataSet avec les donnes en provenance du DataReader.

Ponts entre lobjet et le relationnel


Le modle relationnel a atteint ses limites avec lavnement des langages objets. En eet, le
modle relationnel nest pas adapt la sauvegarde des tats des objets des programmes crits
en langages objets, comme C  , C++ ou Java.

La problmatique structurelle
Lorsque lon planifie dutiliser un modle de persistance relationnel partir dun langage objet,
le premier problme que lon se pose est celui-ci : il nexiste pas de faon simple pour stocker
ltat dun objet dont la classe est drive dune autre classe :

Soit on cre une seule table pour toute une hirarchie de classes (design pattern de Martin
Fowler nomm Single Table Inheritance). Bien que pour une petite hirarchie de classes
cette solution soit ecace, elle devient rapidement impraticable lorsque le nombre de
classes et de champs augmente.

Soit on cre une table par classe non abstraite (design pattern de Martin Fowler nomm
Concrete Table Inheritance). Cette solution a le gros dsavantage dtre dicilement maintenable. Si une classe de base change, il faut changer galement la structure de toutes les
tables reprsentant les classes drives.

Soit on cre une table pour chaque classe en utilisant un systme de cl trangre (design
patterns de Martin Fowler nomm Class Table Inheritance). Une table ne contient que les
champs dfinis dans la classe correspondante. Cette solution rsout le problme de la maintenance, mais introduit de la complexit dans laccs aux donnes.

Un autre problme dordre structurel est d la faon de stocker les relations one to many et
many to many entre objets. Dans les langages objets, les collections sont physiquement gres
soit par un systme de positionnement dobjet (types valeur) soit par un systme de rfrencement dobjet (types rfrence). Aucun de ces deux systmes nest applicable dans une base de
donnes relationnelle. Pour rsoudre les cas de relations one to many , on utilise des cls trangres. Pour rsoudre les cas de relations many to many on utilise une table dassociation. Il
faut bien comprendre que toute la dicult du stockage des relations vient de la gestion du passage du systme cl trangre/table dassociation au systme rfrence/positionnement et
inversement.

La problmatique comportementale
Un autre problme rencontr, lorsque lon utilise un modle de persistance relationnel partir
dun langage objet, est comportemental. Le problme comportemental est relatif la faon
dont les objets sont chargs et sauvegards. En gnral, vous chargez plusieurs tats dans des objets, vous faites des modifications puis vous sauvez les nouveaux tats de ces objets. Il est souvent

Ponts entre lobjet et le relationnel

737

ecace de ne charger que partiellement ltat dun objet bien que cela ncessite plus danalyse et
de conception (design pattern de Martin Fowler nomm Lazy Load). De plus, durant le traitement
des donnes par lapplication, il est assez dicile de garder la trace de chaque changement. Pour
cette tche prcise, la technologie ADO.NET propose une solution ecace qui fait lobjet de la
section page 725.

Trois grands types de solutions


Il existe trois approches direntes pour traiter ces problmatiques : Les bases de donnes objet,
la gnration de code et la rflexion sur une table dassociation :

Une nouvelle gnration de bases de donnes, permettant de stocker simplement les tats
des objets, a vu le jour il y a quelques annes. Lide est simplement de stocker ltat dun
objet soit en le srialisant, soit en tablissant une correspondance avec les donnes relationnelles pour bnficier de lecacit du modle de requte relationnel. Lutilisation de ces
bases de donnes dans lindustrie est jusquici reste marginale. Un tournant vient dtre
pris par Microsoft dans SQL Serveur 2005. En eet, lexcution, le processus de ce SGBD
hberge le CLR de faon permettre une certaine intgration entre les types grs .NET
et les donnes des bases. Par exemple, sous certaines conditions, un type .NET peut typer
directement les donnes dune colonne dune table (cette fonctionnalit est nomme User
Defined Type ou UDT). En outre, les procdures stockes peuvent tre rdiges en code gr
.NET.
Si vous souhaitez rester dans une approche plus traditionnelle, vous pouvez minimiser les
consquences des problmes de maintenances et de complexits inhrentes au paradigme
objet/relationnel avec la gnration automatique de code (design pattern de Martin Fowler
nomm Metadata Mapping avec gnration de code). Dans cette mthode, le code SQL
ncessaire pour construire et exploiter une base de donnes et la couche logicielle qui attaque la base de donnes sont construits partir dune mme description. Il y a moins de
problmes de maintenance, et une grande partie de la complexit peut tre encapsule dans
le code gnr.
Une autre faon que la gnration de code pour tablir un lien entre les objets et les donnes
relationnelles est de prvoir une table de correspondance (i.e de mapping) entre les champs
des classes et les colonnes de tables. lexcution, on se sert dun moteur de mapping pour
parcourir une telle table et tablir la correspondance entre les tats des objets et les donnes
relationnelles. Cest le design pattern de Fowler nomm Metadata Mapping rflexif.

Un dbat sur les pros et cons des deux variantes du design pattern Metadata Mapping est disponible lURL http://www.theserverside.net/news/thread.tss?thread_id=29071.
Enfin, prcisons quune des priorit des concepteurs de la version 3.0 de C  , est dunifier au
niveau langage le monde des donnes (XML et SQL) et le monde des objets. A priori, leurs travaux se basent sur le langage exprimental Cw (prononcez C omega) prsent lURL http:
//research.microsoft.com/Comega/.

Les outils de mapping objet/relationnel


Il existe plus dune quarantaine doutils de mapping objet/relationnel (mapping OR) ciblant la
plateforme .NET. La plupart se basent sur une ou lautre version du design pattern Metadata
Mapping. Certains sont gratuits et open-source alors que dautres sont payants. Parmi les outils
les plus populaires nous pouvons citer :

738

Chapitre 19 : ADO.NET 2.0

NHibernate : http://wiki.nhibernate.org/display/NH/Home

Data Tier Modeler (DTM) : http://www.evaluant.com/en/solutions/dtm/default.aspx

OlyMars (pour Olympique de Marseille ) http://www.microsoft.com/france/msdn/


technologies/outils/olymars/default.asp

Object Broker http://sourceforge.net/projects/objectbroker/

Une liste plus complte est disponible sur le site http://sharptoolbox.com dans la catgorie
Object-Relational mapping.
Microsoft souhaitait fournir un outil de mapping OR avec .NET 2.0 nomm Object Space. La
livraison de cet outil a t repousse, priori parce que le projet a gagn en envergure et sera
notamment intgr au futur moteur de gestion de donnes nomm WinFS.
Vous pouvez consulter cet article de Fabrice Marguerie qui prsente un certain nombre de
critres prendre en compte lors du choix dun outil de mapping OR http://madgeek.com/
Articles/ORMapping/FR/mapping.htm.

Fonctionalits spcifiques au fournisseur de donnes de


SQL Server
Requtes asynchrones
Le fournisseur de donnes SqlClient de ADO.NET2 permet deectuer des commandes dune
manire asynchrone. La nouveaut par rapport .NET v1.x est que pendant lexcution asynchrone dune commande, aucun thread nattend le rsultat de la base. Cette faon de procder
est dirente et plus performante que lutilisation dun thread du pool laide dun dlgu
asynchrone. En interne cette nouvelle possibilit utilise un mcanisme win32 dentre/sortie
asynchrone fournit depuis Windows 2000. Elle est disponible sur toutes les versions de SQL Server
supportes par le fournisseur de donnes SqlClient savoir 7/2000/2005.
Au niveau framework, cette possibilit est exploitable par six nouvelles mthodes de la classe
SqlCommand :
BeginExecuteNonQuery() /
BeginExecuteReader()
/
BeginExecuteXmlReader() /

EndExecuteNonQuery()
EndExecuteReader()
EndExecuteXmlReader()

Bien quen interne le fonctionnement soit dirent, ces mthodes sutilisent pareillement quun
dlgu asynchrone, savoir, en utilisant un objet accessible par linterface IAsyncResult pour
matrialiser lappel asynchrone. Lexemple suivant obtient deux DataReader sur notre base
ORGANISATION dune manire asynchrone. Notez quune connexion ne peut supporter simultanment plusieurs appels asynchrones aussi nous sommes obligs dutiliser deux connexions
quivalentes. En outre, notez que vous devez spcifier async=true dans la chane de connexion
des connexions susceptibles dtre utilises pour eectuer des appels asynchrones. En eet, le
support de cette possibilit un lger impact ngatif sur lexcution synchrone des commandes
do lintrt du drapeau async.

Fonctionalits spcifiques au fournisseur de donnes de SQL Server

739

Exemple 19-23 :
using System.Data ;
using System.Data.SqlClient ;
class Program {
static string sCnx = "server = localhost ; uid=sa ; pwd = ; " +
"database = ORGANISATION ; async=true" ;
static string sCmd1 = "SELECT * FROM EMPLOYES" ;
static string sCmd2 = "SELECT * FROM DEPARTEMENTS" ;
static void Main() {
using (SqlConnection cnx1 = new SqlConnection(sCnx))
using (SqlConnection cnx2 = new SqlConnection(sCnx)) {
cnx1.Open() ;
SqlCommand cmd1 = new SqlCommand(sCmd1, cnx1) ;
System.IAsyncResult ar1 = cmd1.BeginExecuteReader() ;
cnx2.Open() ;
SqlCommand cmd2 = new SqlCommand(sCmd2, cnx2) ;
System.IAsyncResult ar2 = cmd2.BeginExecuteReader() ;
// Ici vous pouvez faire du travail avec le thread courant.
SqlDataReader rdr1 = cmd1.EndExecuteReader(ar1);
SqlDataReader rdr2 = cmd2.EndExecuteReader(ar2);
// Ici utilisation de rdr1 et rdr2
} // end using cnx1 et cnx2.
}
}
Notez enfin que des vrifications pouvant entraner une exception lors de lappel dune mthode BeginXxx() sont eectues sur les paramtres entrants. En outre lexcution peut aussi
connatre des checs qui provoqueront lenvoi dun exception lors de lappel dune mthode
EndXxx().

Copie de donnes en masse (bulk copy)


Loutil bcp.exe (Bulk Copy Program, programme de copie en masse en franais) livr avec SQL Server
permet de copier ou dobtenir un gros volume de donnes partir dun serveur de donnes
SQL Server. Le fournisseur SqlClient de ADO.NET2 permet dutiliser les services de cet outil
grce la nouvelle classe System.Data.SqlClient.SqlBulkCopy. Cette classe permet la copie en
masse de donnes partir dune DataTable, dun DataSet ou dun DataReader vers une base SQL
Server. Lexemple suivant montre comment copier en masse le contenu dune table DEPARTEMENT
dune base ORGANISATION vers une table DEPARTEMENT dune autre base ORGANISATION2 :
Exemple 19-24 :
using System.Data ;
using System.Data.SqlClient ;
class Program {
static string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
static string sCnx2 =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION2" ;
static void Main() {

740

Chapitre 19 : ADO.NET 2.0


using (SqlConnection cnx = new SqlConnection(sCnx)) {
SqlCommand cmd = new SqlCommand("SELECT * FROM DEPARTEMENTS ",
cnx) ;
cnx.Open() ;
SqlDataReader rdr = cmd.ExecuteReader() ;
// Copie massive des N_ROWS lignes.
using (SqlBulkCopy bulkCopier = new SqlBulkCopy(sCnx2)) {
bulkCopier.DestinationTableName = "DEPARTEMENTS";
bulkCopier.WriteToServer(rdr);
} // end using bulkCopier ;
} // end using cnx.
}
}

La classe SqlBulkCopy prsente aussi la possibilit dtre notifie sur ltat courant de la copie,
la possibilit de rgler un time out sur lopration de copie et la possibilit de dfinir une correspondance entre les colonnes sources et destinations au cas o les schmas des tables impliques
ne seraient pas strictement identiques.

Obtenir les statistiques dune connexion


LExemple 19-8 en page 715 montre comment obtenir les statistiques concernant lactivit dune
connexion vers un SGBD de type SQL Server. Lobjet retourn est un dictionnaire dont les cls
sont les noms des statistiques. Ce dictionnaire contient une vingtaine de statistiques telles que
le nombre doctets envoys ou reus. En plus de la mthode RetrieveStatistics(), la classe
SqlConnection prsente la mthode ResetStatistics() qui permet de remettre zro les statistiques dune connexion.

SQL Server 2005 Express Edition


Bien que ce produit ne fasse pas partie intgrante ni du framework .NET 2.0 ni de la plateforme
.NET 2.0, nous le mentionnons car il va faire certainement rencontr un grand succs auprs
des dveloppeurs .NET 2.0. SQL Server 2005 Express Edition est le successeur des SGBD gratuits
et librement distribuables que sont Microsoft SQL Server Desktop Engine (MSDE) et Jet. Il prsente
plusieurs avantages par rapport ces produits :

Une intgration pousse avec le CLR. SQL Server Express est une version simplifie du SGBD
phare (mais payant) de Microsoft, SQL Server 2005.
Dploiement dapplication XCopy possible. Une base de donne peut se rsumer un fichier .mdf prsent dans larborescence de rpertoires dune application .NET dploye.
Linstallation de SQL Server 2005 Express Edition peut faire partie des prrequis du dploiement ClickOnce dune application .NET. Pas besoin de linstallation de MDAC.
Des outils graphiques facilitent lutilisation du produit.
Une intgration pousse avec Visual Studio 2005. En outre, SQL Server Express Edition est
automatiquement install lors de linstallation de Visual Studio 2005.
Support pouss du XML. Possibilit de typer une colonne dune table en XML.
Meilleure performance et meilleure gestion de la scurit.

Plus dinformation sur ce produit peuvent tre trouves sur le site de Microsoft.

20
Les transactions

Introduction la notion de transaction


Une succession de commandes sur des donnes est dite transactionnelle, si la fin de ces
modifications tous les changements sont valids (commit) ou aucun des changements nest
valid (rollback). Lexemple standard utilisant une transaction est le transfert dune somme
dun compte bancaire A vers un compte bancaire B. Cette opration est le rsultat de deux
commandes :

Le dbit du compte A de la somme.

Le crdit du compte B de la somme.

Si une des deux commandes choue, lautre commande doit absolument chouer. Les deux
commandes doivent donc tre excutes au sein dune mme transaction.

Les transactions servent garantir lintgrit dun ensemble de donnes aprs une phase
de mises jour.
Cet exemple illustre aussi le fait que les commandes excutes au sein dune transaction peuvent
se faire sur une mme base de donnes ou sur des bases de donnes direntes. En eet, on peut
supposer que les deux comptes appartiennent la mme banque, auquel cas les commandes se
font srement sur la mme base de donnes. Si les deux comptes appartiennent des banques
direntes, les commandes se font sur des bases de donnes distinctes. On dit que la transaction
est distribue.
La thorie des transactions est assez complexe. Un milieu transactionnel supporte idalement
quatre proprits, dites proprits ACID :

742

Chapitre 20 : Les transactions

Atomicity

Toutes les actions seectuent ou aucune.

Consistency

La transaction laisse les donnes dans un tat cohrent.

Isolation

Les transactions seectuent dune manire isole les unes des autres.

Durable

Les rsultats sont stocks de manire permanente.

Nous allons voir dans cette section quelles sont les possibilits prsentes par le framework pour
eectuer des transactions locales ou distribues. Nous verrons que lensemble des proprits
ACID peut dans certains cas tre amoindri pour ne pas trop pnaliser les performances.

Moteur transactionnel (TM), Resource Manager (RM) et sources de


donnes
Les bases de donnes ne reprsentent quun cas dutilisation des transactions. En eet, cette ide
de coordination de mise jour de donnes peut se rvler utile pour dautres sources de donnes
telles que des queues de messages, des services web, des systmes de fichiers ou mme des reprsentations en mmoire de donnes. Toutes ces sources de donnes peuvent participer dans une
transaction grce une couche logicielle spcifique chaque source en gnral nomme avec
lacronyme RM (pour Resource Manager) que nous reprendrons par la suite.
Dans les applications modernes la gestion des transactions est dlgue une couche logicielle
nomme moteur transactionnel (transaction manager en anglais ou TM). Vis--vis de son moteur
transactionnel, une application qui a recours des transactions a les responsabilits suivantes :

Lui demander de crer une transaction.

Lui communiquer quelles sont les sources de donnes impliques dans la transaction. Durant cette tape, le RM dune source lenregistre auprs du moteur. On parle alors denrlement dune source de donnes au sein dune transaction (enlistement en anglais).

Eectuer les mises jour des donnes de chaque source durant la transaction.

Informer le moteur de la terminaison des mises jour.

Le moteur transactionnel est responsable de la coordination des travaux des RM. Cette coordination a pour but de garantir lintgrit des donnes : soit elles sont toutes t mises jour
correctement, soit le droulement de la transaction a rencontr un problme et aucune modification de donnes na eu lieu. Pour grer une telle coordination, du point de vue dun moteur
transactionnel les RM supportent tous une mme API qui permet de les faire participer au sein
dune transaction. Toute cette architecture est expose par la figure suivante :

Introduction aux transactions distribues et aux algorithmes 2PC


Une transaction distribue consiste mettre jour des donnes de plusieurs RM en respectant
les proprits ACID. cette fin, les moteurs transactionnels utilisent en gnral des algorithmes
de type validation en 2 phases (2 Phase Commit en anglais, ou plus simplement 2PC que nous
utiliserons par la suite) :

Introduction la notion de transaction

743

ResourceManager
firmware
(RM)

Source de donnes
Requtes et
mises jour
des donnes
durant la
transaction

Enrlement
des sources et
coordination
des travaux
des RM par
le moteur

ResourceManager
firmware
(RM)

Source de donnes

Moteur transactionnel (TM)

Application effectuant une transaction

Informe le moteur quil doit crer une transaction

Informe le moteur quil ny aura plus de mise jour des donnes


Le moteur en dduit quil doit finaliser la transaction

Figure 20 -1 : Interaction entre application, moteur transactionnel, RM et sources de donnes

Dans la premire phase lapplication enrle chaque source de donnes et eectue les requtes et mises jour de donnes. Le moteur informe chacun des RM quil participe une
transaction distribue. Chaque RM eectue alors une transaction locale sur ses donnes.
Il contacte le moteur transactionnel juste avant de valider sa transaction locale si celle-ci a
russi ou ds quun chec est constat. On parle de vote de la part des RM. Chaque RM vote
Succs ou Echec.
Si au moins un RM a chou dans sa transaction locale, le moteur informe tous les RM
participants que la transaction distribue a chou. Chaque RM doit alors annuler sa transaction locale en remettant ses donnes dans leur tat initial. Si le succs a t vot lunanimit, la transaction distribue est en passe dtre russie et le moteur informe chaque RM
quil doit valider sa transaction locale.

Une variante de cet algorithme existe. Dans la premire phase, lorsquun RM constate que sa
transaction locale a russi, il peut la terminer eectivement. Si la transaction distribue russit
aussi, le RM na alors rien faire durant la seconde phase. Dans le cas contraire le RM doit refaire
une transaction locale pour annuler les eets de la premire transaction. On parle dalgorithme
de recouvrement (recovery en anglais) de mise jour des donnes.
Tel quel, lalgorithme 2PC nest pas fiable 100%. Une panne rseau entre le moteur et un RM
qui survient au milieu de la deuxime phase laisse ce RM dans un tat inconsistant. Une telle
situation est qualifie de douteuse (in-doubt en anglais). Les consquences ngatives de ces cas
rares mais invitables sont rpares grce un service de recouvrement qui vrifie priodiquement
si une transaction distribue a potentiellement corrompu les donnes en se terminant dune
manire douteuse. Ce service peut tenter de rparer automatiquement les donnes corrompues
grce des informations sauves par la source de donnes lors de la premire phase. Sil choue,
le service de recouvrement peut, en dernier recours, informer un administrateur.
Il existe des protocoles permettant un moteur de coordonner une transaction distribue entre
les RM. On peut citer les protocoles OleDB, XA ou WS-AtomicTransaction.
Au vu de la description de lalgorithme 2PC, il est clair que les transactions distribues sont
des oprations coteuses qui ncessitent de nombreuses communications entre les dirents
participants. Il est donc toujours prfrable dutiliser une transaction locale si possible.

744

Chapitre 20 : Les transactions

Transaction locale sur une connexion


Dans lexplication de lalgorithme 2PC nous avons parl de transaction locale. La plupart
des SGBD prsentent nativement la possibilit deectuer des transactions locales. La classe
DbConnection (dont drivent toutes les classes de connexion des fournisseurs de donnes ADO.
NET) expose la mthode BeginTransaction() qui retourne un objet dont la classe drive de
DbTransaction. La plupart des fournisseurs de donnes encapsulent le support transactionnel
du SGBD sous-jacent en rcrivant la mthode BeginTransaction() et en spcialisant la classe
DbTransaction. Lexemple suivant exploite la base de donnes relationnelle dcrite en page
710. Il montre comment eectuer une transaction locale avec le fournisseur SqlClient. Nous
y eectuons deux commandes sur notre base de donnes :

On ajoute le dpartement Communication la base de donnes ORGANISATION.

On ajoute un employ ce dpartement.

Ces commandes doivent tre excutes au sein dune transaction. En eet, si lajout du dpartement choue et si lajout de lemploy russit, la base de donnes se retrouve dans un tat incohrent car un employ appartient un dpartement qui nexiste pas (notez que dans ce cas prcis
la condition dintgrit rfrentielle FOREIGN KEY (DepID) REFERENCES DEPARTEMENTS(DepID)
empche ce type de corruption de donnes).
Exemple 20-1 :
using System.Data.Common ;
using System.Data.SqlClient ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
string sCmd1 =
"INSERT INTO DEPARTEMENTS VALUES (COM,Communication)" ;
string sCmd2 =
"INSERT INTO EMPLOYES VALUES (COM,Smith,Adam,0497112239)" ;
try {
using (SqlConnection cnx = new SqlConnection(sCnx)) {
cnx.Open() ;
DbTransaction tx = cnx.BeginTransaction();
DbCommand cmd = new SqlCommand(sCmd1, cnx) ;
cmd.Transaction = tx ;
cmd.ExecuteNonQuery() ;
cmd.CommandText = sCmd2 ;
cmd.ExecuteNonQuery() ;
tx.Commit();
// Ici les deux commandes se sont correctement ex
ecut
ees
// et la transaction a
et
e effectu
ee.
} // end using cnx.
} catch {
// Si une exception a
et
e lanc
ee avant ou pendant lappel `
a
// tx.Commit() les donn
ees de la base nont pas
et
e modifi
ees.
}

Introduction la notion de transaction

745

}
}
Dans le cas particulier du SGBD SQL Server, le langage T-SQL ore la possibilit deectuer des
transactions (do le T pour Transact). Par exemple le code T-SQL suivant permet de raliser
nos deux oprations dune manire transactionnelle. Notez la ncessit de tester aprs chaque
opration si une erreur sest produite :
BEGIN TRANSACTION
INSERT INTO DEPARTEMENTS VALUES (COM,Communication)
If @@error <> 0 GOTO ERR_HANDLER
INSERT INTO EMPLOYES VALUES (COM,Smith,Adam,0497112239)
If @@error <> 0 GOTO ERR_HANDLER
COMMIT TRANSACTION
RETURN
ERR_HANDLER:
ROLLBACK TRANSACTION
RETURN
Dans notre programme C  le fournisseur de donnes SqlClient exploite T-SQL pour reproduire ce comportement transactionnel. Lutilisation directe de T-SQL est donc ncessairement
plus performante pour raliser des transactions locales. En revanche, lutilisation du fournisseur de donnes se prte mieux la rdaction de transactions complexes impliquant un grand
nombre de lignes. Prcisons quil vaut mieux ne pas mettre jour une trop grosse quantit
de donnes lors dune mme transaction locale. Un ordre de grandeur ne pas dpasser est le
millier de lignes. En eet, dans le cas dune transaction massive qui se termine en chec ou en
situation douteuse, le temps pris pour finaliser la transaction devient prohibitif. Si vos besoins
dpassent cette limite, il faut trononner vos mises jour en transactions de taille raisonnable.

Le moteur transactionnel de Windows : le DTC


Microsoft livre un moteur transactionnel avec ses OS. Ce moteur se nomme le DTC (Distributed
Transaction Coordinator parfois nomm MSDTC). Il supporte lheure actuelle les protocoles
OleDB et XA. La version Windows Vista de DTC supportera aussi les protocoles transactionnels
relatifs aux Web Services, WS-Coordination, WS-AtomicTransaction et WS-BusinessActivity. Windows
Vista fournira aussi des RM permettant de faire des transactions sur des sources de donnes
Windows telles que des fichiers ou la base de registre.
Le DTC nest pas forcment accessible ou install selon votre version de Windows. Vous pouvez
vrifier sa prsence en eectuant la manipulation suivante : Dmarrer  Panneau de configuration
 Outils dadministration  Services de composants  Services de composants  Ordinateur  Poste
de travail  Distributed Transaction Coordinator.
Sous Windows XP SP2 le DTC est install par dfaut mais toutes ses options sont dsactives par
dfaut. Vous pouvez avoir accs la fentre de paramtrage du DTC comme ceci : (...) Services
de composants  Ordinateur  Poste de travail  Click droit Proprits  Onglet MSDTC. Activez
alors les paramtres de scurit dont vous avez besoin.
Sous Windows 2003 le DTC nest pas install par dfaut. Pour linstaller, il vous sut deectuer cette manipulation : Dmarrer  Panneau de configuration  Ajout/Suppression de programmes

746

Chapitre 20 : Les transactions

ou Ajouter/Supprimer des composants Windows  Serveur dapplication. Installez alors COM+ puis
MSDTC.
Si le DTC est en cours dexcution, il est possible de lister les transactions courantes avec longlet
Liste des transactions et dobtenir des statistiques sur les transactions eectues par le DTC avec
longlet Statistiques de transactions.
Depuis plusieurs annes le moteur DTC est exploitable partir de la technologie COM+. Depuis
les versions 1.x de .NET les dveloppeurs peuvent raliser des transactions distribues avec le
DTC. En eet la partie du framework .NET nomme Service dEntreprise (qui fait lobjet de la
section page 295) permet dencapsuler les services de la technologie COM+ au moyen dune
API .NET. Ainsi, avec les versions 1.x du framework il faut utiliser les services dentreprises pour
raliser des transactions distribues et les fournisseurs de donnes ADO.NET pour raliser des
transactions locales.

Le framework System.Transactions
La version 2 du framework .NET prsente le nouvel espace de noms System.Transactions qui
propose un modle de programmation transactionnelle unifi. Tout assemblage qui souhaite
utiliser les services de ce framework doit rfrencer lassemblage Systems.Transactions.dll.

LTM, RM durables et RM volatiles


Le framework System.Transactions contient un moteur transactionnel nomm LTM (Lightweight
Transaction Manager) spcialis dans la gestion de RM volatiles. La caractristique dun RM
volatile est quil na pas besoin de service de recouvrement lorsquil participe une transaction
2PC qui donne lieu une situation douteuse. En consquence, les donnes gres par un RM
volatile ne sont souvent stockes quen mmoire, l o une situation douteuse ne risque pas
de les corrompre durablement. Tt ou tard le processus contenant les donnes potentiellement
corrompues tombera.
Le concept de RM volatile soppose celui de RM durable. Un RM durable est donc un RM qui
a besoin du service de recouvrement lorsquil participe une transaction 2PC qui donne lieu
une situation douteuse. Les donnes gres par un RM durable sont sauves sur un disque
dur, dune manire durable do le nom. Avant la fin de la premire phase, un RM durable qui
participe une transaction 2PC doit stocker lancienne et la nouvelle version des donnes. Si la
transaction donne lieu une situation douteuse, le service de recouvrement contactera le RM
durable pour lui indiquer quelle version des donnes il doit garder. Le laps de temps entre la fin
de la transaction dans un cas douteux et le contact du service de recouvrement peut se chirer
en minutes voire en heures. Plus de dtails ce sujet sont disponibles dans larticle Performing
Recovery des MSDN.
Toute transaction cre avec le framework System.Transactions est dabord gre par le LTM.
Lutilisation du LTM est bien plus performante que celle du DTC puisque toutes les communications entre le LTM et ses RM sont ralises lintrieur du mme domaine dapplication.
Une transaction gre par le LTM peut impliquer plusieurs RM volatiles et au plus un RM
durable qui sait accomplir sa tche en une phase ou en deux phases selon lalgorithme utilis
par le moteur. On qualifie un tel RM durable de Promotable Single Phase Enlistment (PSPE). Le
framework System.Transactions sait dlguer automatiquement une transaction du LTM vers
le DTC ds quune des conditions suivantes survient :

Le framework System.Transactions

747

Un RM durable non PSPE enrle sa source de donnes dans la transaction.

Un RM durable PSPE participe dj la transaction lorsquun second RM durable (PSPE


ou non) enrle sa source de donnes. ce moment prcis, le premier RM durable PSPE
doit sauver les donnes pour rsoudre un ventuel cas douteux. Il passe du mode une phase
au mode deux phases. On dit quil a t promu. La documentation anglo saxonne parle de
promotable enlistment.

La transaction est srialise pour tre utilise dans un autre domaine dapplication.

Toutes les connexions un SGBD sont des RM durables. Cependant, seules les connexions vers
le SGBD SQL Server 2005 sont des RM durables PSPE (pour linstant).
La documentation anglo saxonne dit quune transaction est escalated lorsquelle est dlgue du
LTM vers le DTC (on pourrait traduire le terme escalated par promue ou alourdie en franais).
Cette escalation dune transaction se fait dune manire compltement transparente du point de
vue du code client de System.Transactions. Ce sont les mmes classes et mthodes de System.
Transactions qui sont utilises indpendamment du fait qu lexcution le LTM ou le DTC est
impliqu. Loptimisation est implicite mais il est prfrable que vous ayez conscience de tout
ceci si les performances constituent pour vous un enjeu important. Prcisons quen interne, la
dlgation dune transaction vers le DTC nlimine pas compltement le LTM de la partie. Ce
dernier collabore en fait avec le DTC pour continuer grer les RM volatiles.
Nous mentionnons un malentendu classique. Comprenez bien que le LTM et le DTC sont
tous deux des moteurs transactionnels distribus. Leur dirence ne se situe pas au niveau du
nombre de RM quils peuvent grer. Seule la qualit durable/durable PSPE/volatile des RM participant une transaction permet au framework System.Transactions de dcider dimpliquer
ou non le DTC.

Transaction implicite avec System.Transactions


Rcrivons lExemple 20-1 en utilisant les types de lespace de noms System.Transactions:
Exemple 20-2 :
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Transactions ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
string sCmd1 =
"INSERT INTO DEPARTEMENTS VALUES (COM,Communication)" ;
string sCmd2 =
"INSERT INTO EMPLOYES VALUES (COM,Smith,Adam,0497112239)" ;
try {
using (TransactionScope txScope = new TransactionScope()) {
using (SqlConnection cnx = new SqlConnection(sCnx)) {
cnx.Open();
DbCommand cmd = new SqlCommand(sCmd1, cnx) ;
cmd.ExecuteNonQuery() ;

748

Chapitre 20 : Les transactions


cmd.CommandText = sCmd2 ;
cmd.ExecuteNonQuery() ;
txScope.Complete();
} // end using cnx.
} // end using txScope, la transaction seffectue ici.
} catch {
// Si une exception a
et
e lanc
ee avant ou pendant lappel `
a
// txScope.Complete() les donn
ees de la base nont pas
et
e
// modifiees.
}
}
}

Cet exemple illustre le fait que toute connexion ouverte pendant lexistence dun objet de type
System.Transactions.TransactionScope est automatiquement et implicitement enrle dans
la transaction sous-jacente. Si vous excutez lexemple prcdent sur un autre SGBD que SQL
Server 2005 (par exemple avec SQL Server 7 ou 2000) vous constaterez quune transaction DTC
est cre dans longlet Liste des transactions du DTC. Dans ce cas prcis de transaction locale
un SGBD de type autre que SQL Server 2005 il est plus ecace dutiliser le modle de programmation transactionnel ADO.NET 1.x propos par le fournisseur de donnes. Jim Johnson
prsente dans une entre de son blog disponible lURL http://pluralsight.com/blogs/
jimjohn/archive/2005/09/13/14795.aspx une possibilit de contourner cette limitation en
rendant PSPE un RM durable non PSPE.
Le programme suivant illustre une transaction distribue qui porte sur deux bases de donnes.
Lide est ici de muter un employ de la base ORGANISATION vers la base ORGANISATIOSN2. Grce
au modle de programmation unifi de System.Transactions, le code de cet exemple est similaire celui de lexemple prcdent:
Exemple 20-3 :
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Transactions ;
class Program {
static void Main() {
string sCnx1 =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
string sCnx2 =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION2" ;
string sCmd1 =
"DELETE FROM EMPLOYES WHERE Nom=Smith AND Pr
enom=Adam" ;
string sCmd2 =
"INSERT INTO EMPLOYES VALUES (DEV,Smith,Adam,0497112239)" ;
try {
using ( TransactionScope txScope = new TransactionScope() ) {
using ( SqlConnection cnx1 = new SqlConnection(sCnx1) ) {
cnx1.Open();
DbCommand cmd = new SqlCommand(sCmd1, cnx1) ;
cmd.ExecuteNonQuery() ;
} // end using cnx1.

Le framework System.Transactions

749

using ( SqlConnection cnx2 = new SqlConnection(sCnx2) ) {


cnx2.Open();
DbCommand cmd = new SqlCommand(sCmd2, cnx2) ;
cmd.ExecuteNonQuery() ;
} // end using cnx2.
txScope.Complete() ;
} // end using txScope, la transaction seffectue ici.
} catch { }
}
}
Dans ce cas le DTC est exploit quelquesoit les types des deux SGBD sous jacents.

Evnements dclenchs lors dune transaction


Vous avec la possibilit dtre averti quand une transaction est termine au moyen de lvnement Transaction.TransactionCompleted. Vous avec aussi la possibilit dtre averti quand
une transaction se met tre gre par le DTC au moyen de lvnement TransactionManager.
DistributedTransactionStarted. Rcrivons lExemple 20-3 en nous abonnant ces vnements :
Exemple 20-4 :
...
class Program {
static void Main() {
...
try {
using ( TransactionScope txScope = new TransactionScope() ) {
Transaction.Current.TransactionCompleted += OnTxCompleted;
TransactionManager.DistributedTransactionStarted +=
OnDistributedTxStarted;
using ( SqlConnection cnx1 = new SqlConnection(sCnx1) ) {
...
}
static void OnTxCompleted(object sender, TransactionEventArgs e) {
Transaction tx = e.Transaction;
System.Console.WriteLine("Completed! Status:" +
tx.TransactionInformation.Status.ToString());
}
static void OnDistributedTxStarted(object sender,
TransactionEventArgs e) {
Transaction tx = e.Transaction;
System.Console.WriteLine("Distributed tx started!");
}
}

Dtails internes du fonctionnement de System.Transactions


La magie de lenrlement implicite et automatique dune connexion dans une transaction se
fait en interne grce la proprit statique ITransaction Transaction.Current{get;set;}.

750

Chapitre 20 : Les transactions

Linterface ITransaction prsente des mthodes permettant un RM denrler sa source de


donnes dans la transaction sous-jacente. Le code de lactivation dun RM (par exemple louverture dune connexion) na alors qu senrler si la proprit Transaction.Current retourne
une transaction. Bien entendu, le fait de crer une instance de TransactionScope entrane la
cration en interne de la transaction qui est retourne par la proprit Transaction.Current.
Rappelons quune connexion nest marque comme disponible au niveau de son pool de
connexions que lorsquelle est ferme (avec la mthode Close() ou Dispose()). Cette rgle
est modifie lorsquune connexion est enrle dans une transaction. Elle nest alors marque
comme disponible au niveau de son pool que lorsque la transaction est acheve.

Paramtrer le niveau disolation entre transactions (TIL)


La classe TransactionScope prsente de nombreux constructeurs. Certains prennent en paramtre une instance de la structure TransactionOptions. Cette instance permet de paramtrer
le time out de la transaction (qui est dune minute par dfaut) ainsi que son niveau disolation
par rapport aux autres transactions qui seectuent sur les mmes RM.
Ce niveau disolation est plus connu sous lacronyme TIL pour Transaction Isolation Level. Naturellement plus le TIL est haut, plus les performances sont dgrades du fait dun verrouillage
plus strict des donnes. Selon le niveau disolation choisi, trois sortes de problmes peuvent ou
non survenir :

Lecture sale (Dirty read en anglais) : Une transaction T1 en cours peut avoir accs en lecture
aux donnes modifies par une autre transaction T2 en cours. Cela pose un problme si T2
ne valide pas ses changements.

Lecture non rptable (Non repeatable read en anglais) : Lorsquune transaction en cours eectue une seconde lecture dune certaine donne, il se peut que la valeur obtenue ait chang
du fait dune transaction qui sest termine durant le laps de temps entre les deux lectures.

Lecture fantme (Phantom read en anglais) : Lorsquune transaction en cours eectue une
seconde fois une requte quelle a dj eectue, il se peut que lensemble des lignes retournes la seconde fois soit dirent de lensemble des lignes retournes la premire fois. Cela
est alors du des modifications eectues et valides entre temps par une autre transaction.

Voici les niveaux disolations proposs par lnumration IsolationLevel : du plus permissif
au plus strict et donc, du moins coteux en terme de performance au plus pnalisant :
TIL

Lecture sale

Lecture non rptable

Lecture fantme

ReadUncommited

possible

possible

Possible

ReadCommited

impossible

possible

Possible

RepeatableRead

impossible

impossible

Possible

Snapshot

impossible

impossible

impossible

Serialize

impossible

impossible

impossible

Le framework System.Transactions

751

La dirence entre les TIL Snapshot et Serialize tient dans le fait que lors de lutilisation du
TIL Snapshot, une exception peut tre leve (et par consquent une transaction peut choue)
si un des trois cas problmatiques est constat. Si vous choisissez le TIL Serialize, sachez quen
interne un systme de verrouillage des donnes est mis en place. Ce systme est coteux car il
gne lexcution des autres transactions puisquelles doivent attendre leur tour pour avoir accs
aux donnes verrouilles. On qualifie lapproche Snaphot doptimiste (dans le sens o lon est
confiant que lchec des transactions survient rarement) tandis que lapproche Serialize est
qualifie de pessimiste.
vous de choisir le niveau disolation de vos transactions en soupesant limportance de leur
succs face votre besoin de performance. Une boutade dit que seules les oprations manipulant de largent ont besoin du niveau disolation Serialize.

Paramtrer le comportement adopter


lors de transactions imbriques
Certains constructeurs de la classe TransactionScope acceptent une valeur de lnumration
TransactionScopeOption qui permet de dterminer comment se comporte le nouveau scope
sil est imbriqu dans un autre scope transactionnel. Les valeurs de cette numration sont :

Mandatory : Une transaction doit exister au moment o le nouveau scope est cr. Dans
le cas contraire une exception est leve.

NotSupported : Ce nouveau scope nentrane pas de cration dune nouvelle transaction et


ne modifie pas une ventuelle transaction courante au moment ou il est cr.

Required : Ce nouveau scope besoin dune transaction. Il utilise la transaction courante


si elle existe, sinon il en cre une nouvelle. La valeur Required est la valeur prise par dfaut.

RequiresNew : Ce nouveau scope entrane la cration dune nouvelle transaction indpendamment du fait quune transaction courante existe ou non. Le cas chant, la transaction
cache redeviendra la transaction courante la fermeture de ce nouveau scope.

Supported : Ce nouveau scope nentrane pas de cration dune nouvelle transaction mais
prend en compte une ventuelle transaction courante au moment ou il est cr.

Transaction explicite avec System.Transactions


Lespace de noms System.Transactions permet dexploiter des transactions en se passant dune
instance de la classe TransactionScope. Voici lExemple 20-2 rcrit avec cette technique :
Exemple 20-5 :
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Transactions ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
string sCmd1 =
"INSERT INTO DEPARTEMENTS VALUES (COM,Communication)" ;

752

Chapitre 20 : Les transactions


string sCmd2 =
"INSERT INTO EMPLOYES VALUES (COM,Smith,Adam,0497112239)" ;
try {
CommittableTransaction tx = new CommittableTransaction();
using (SqlConnection cnx = new SqlConnection(sCnx)) {
cnx.Open() ;
// Ici la connexion est enr
ol
ee.
cnx.EnlistTransaction(tx);
DbCommand cmd = new SqlCommand(sCmd1, cnx) ;
cmd.ExecuteNonQuery() ;
cmd.CommandText = sCmd2 ;
cmd.ExecuteNonQuery() ;
} // end using cnx.
tx.Commit();
} catch { /*...*/ }
}
}

Nous allons voir dans la prochaine section quil est parfois ncessaire davoir recours une cette
syntaxe explicite dans certains contextes asynchrones. Mis part ce cas, cette pratique est en
gnral viter puisque nous voyons quelle ncessite plus de code (puisquil faut explicitement
enrler les sources de donnes) sans apporter davantages.

Utilisation avances de System.Transactions


Traitement dune transaction sur plusieurs threads
Dans une application multithreads la transaction courante dfinie par la proprit statique
Transaction.Current nest valide que pour le thread qui la cre. Cela implique que pendant
lexcution dune transaction par un thread A, un autre thread B obtiendrait une valeur nulle
partir de cette proprit. Si vous dsirez faire participer plusieurs threads dans la mme transaction, il est ncessaire de fournir aux autres threads des transactions dpendantes de la transaction originale. Une transaction dpendante est obtenue avec la mthode IDependantTransaction ITransaction.DependentClone(bool). Linterface IDependantTransaction qui tend
linterface ITransaction na que la mthode Complete() en plus. Cette mthode sert indiquer
la transaction mre quune transaction dpendante a correctement termin son travail. Si
toutes les transactions dpendantes appellent Complete() et si la transaction mre a aussi russi,
vous pouvez alors appeler la mthode Commit() sur cette dernire.
Pour vous vitez davoir grer la synchronisation entre les appels Complete() et lappel Commit() vous pouvez crer les transactions dpendantes en passant la valeur DependentCloneOption.BlockCommitUntilComplete la mthode DependentClone(DependentCloneOption). En
procdant comme ceci, lappel Commit() est bloquant jusqu ce que toutes les transactions dpendantes aient votes. Si vous passez la valeur DependentCloneOption.RollbackIfNotComplete
lors de la cration dune transaction dpendante, il faut imprativement que le thread responsable de cette transaction dpendante appelle la mthode Complete() avant que la mthode
Commit() soit appele sur la transaction mre. Dans le cas contraire toute la transaction choue.
Naturellement, il sut quune seule transaction (mre ou dpendante) choue pour que la

Utilisation avances de System.Transactions

753

transaction mre et toutes ses transactions dpendantes chouent. Voici un exemple exposant
la syntaxe dutilisation des transactions dpendantes :
Exemple 20-6 :
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Transactions ;
using System.Threading ;
class Program {
static string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
static string sCmd1 =
"INSERT INTO DEPARTEMENTS VALUES (COM,Communication)" ;
static string sCmd2 =
"INSERT INTO EMPLOYES VALUES (COM,Smith,Adam,0497112239)" ;
static void Main() {
try {
using (TransactionScope txScope = new TransactionScope()) {
using (SqlConnection cnx = new SqlConnection(sCnx)) {
cnx.Open() ;
DbCommand cmd = new SqlCommand(sCmd1, cnx) ;
cmd.ExecuteNonQuery() ;
DependentTransaction depTx =
Transaction.Current.DependentClone (
DependentCloneOption.BlockCommitUntilComplete );
ThreadPool.QueueUserWorkItem(AsyncProc, depTx);
txScope.Complete();
} // end using cnx.
} // end using txScope, lappel `
a ITransaction.Commit()
// seffectue ici.
} catch { }
}
static void AsyncProc(object state) {
DependentTransaction depTx = state as DependentTransaction;
try {
using (SqlConnection cnx = new SqlConnection(sCnx)) {
cnx.Open() ;
cnx.EnlistTransaction(depTx) ;
DbCommand cmd = new SqlCommand(sCmd2, cnx) ;
cmd.ExecuteNonQuery() ;
depTx.Complete();
} // end using cnx.
} catch {
depTx.Rollback() ;
}
}
}

754

Chapitre 20 : Les transactions

Gestion asynchrone de la compltion dune transaction


Vous pouvez grer dune manire asynchrone la compltion dune transaction. Cette possibilit
est utile car les blocages des threads durant lattente de la compltion dune transaction a un
impact ngatif sur les performances. Pour exploiter cette possibilit, il faut grer explicitement
vos transactions comme dans lExemple 20-5 et appeler la mthode BeginCommit() au lieu de
Commit(). Cette mthode accepte en paramtre un dlgu reprsentant la mthode appeler
lorsque la transaction est acheve. Au sein de cette mthode, il ne vous reste plus qu appeler
la mthode EndCommit(). Cette dernire mthode lvera une exception si la transaction na pas
russie. Vous avez alors une chance de fournir le code excuter en cas de non succs lors du
rattrapage de lexception.
Exemple 20-7 :
class Program {
static void Main() {
...
CommittableTransaction tx = new CommittableTransaction() ;
try {
using (SqlConnection cnx = new SqlConnection(sCnx)) {
cnx.Open() ;
cnx.EnlistTransaction(tx) ;
...
tx.BeginCommit(OnCommited,null);
} // end using cnx.
} catch { /*...*/ }
// Ici, le code peut sexecuter alors que la transaction tx
// nest pas encore commitee.
}
static void OnCommited(System.IAsyncResult asyncResult) {
CommittableTransaction tx = asyncResult as CommittableTransaction;
try {
using (tx) {
tx.EndCommit(asyncResult) ;
}
} catch (TransactionException e) {
// Ici, mettre le code a d
eclencher quand
// la transaction a echou
e !
}
}
}

System.Transactions et CAS
Il est intressant de remarquer que le code qui implique le recours au DTC pour grer
une transaction doit avoir la permission CAS System.Transactions.DistributedTransactionPermission. Dans le cas de la promotion dune RM lors de la dlgation dune transaction du LTM vers le DTC, cest le code qui a dclench la promotion qui doit avoir la permission
et non le code responsable de lenrlement.

Introduction la cration dun RM transactionnel

755

Introduction la cration dun RM transactionnel


Nous allons prsenter ici la conception dun RM transactionnel au moyen dune classe gnrique nomme TxList<T>. Cette classe constitue limplmentation dun type de liste o lajout
dun lment au moyen de la mthode TxAdd(T) est un changement qui peut faire partie dune
transaction. Vous pouvez ajouter plusieurs lments durant une transaction. Ils seront tous
ajouts si la transaction est valide ou aucun deux ne sera ajout si la transaction est avorte.
Pour rendre possible ce comportement, une instance de TxList<T> maintient en interne la liste
dataToCommit qui reprsente les lments en attente dtre ajouts. Les deux mthodes prives
OnCommit() et OnRollback() contiennent les implmentations des oprations commit et rollback.
Pour que ses instances soient manipulables par System.Transactions, il faut que la classe
TxList<T> implmente linterface System.Transactions.ISinglePhaseNotification. Cette
interface prsente la mthode SinglePhaseCommit() qui est appele par le moteur transactionnel lorsque la liste concerne est lunique RM dune transaction qui est sur le point dtre valide. Cette interface implmente linterface System.Transactions.IEnlistmentNotification.
IEnlistmentNotification prsente les mthodes Prepare(), Commit(), InDoubt() et Rollback() qui sont appeles par le gestionnaire de transactions lorsque la liste concerne est
enrle dans une transaction distribue sur plusieurs RM. Dans ce cas, le moteur transactionnel
sous-jacent (LTM ou DTC) gre automatiquement la transaction en deux phases (algorithme
2PC).
Lorsquun lment est ajout une instance de TxList<T>, nous testons si elle est enrle dans
une transaction. Si ce nest pas le cas, nous appelons la mthode Enlist() qui enrle la liste dans
la transaction courante si nous sommes dans un contexte transactionnel. Pour cela, le code de
cette mthode apple la mthode Transaction.EnlistVolatile() sur la transaction courante.
Cela indique au moteur transactionnel quil a faire un RM volatile.
Exemple 20-8 :
using System ;
using System.Collections.Generic ;
using System.Transactions ;
public class TxList<T> : List<T>, ISinglePhaseNotification {
private Transaction m_Tx;
private List<T> dataToCommit;
private string m_Name ;
public TxList(string name) {
m_Name = name ;
dataToCommit = new List<T>() ;
}
public void TxAdd(T t) {
Console.WriteLine(m_Name + ".TxAdd(" + t.ToString() +")") ;
if ( m_Tx == null )
Enlist();
dataToCommit.Add(t);
}
private void OnCommit() {
Console.WriteLine("
"+m_Name+".OnCommit()") ;
foreach (T t in dataToCommit)

756

Chapitre 20 : Les transactions


base.Add(t);
dataToCommit.Clear();
}
private void OnRollback() {
dataToCommit.Clear();
}
private void Enlist() {
m_Tx = Transaction.Current;
if (m_Tx != null) {
Console.WriteLine("
" + m_Name + ".EnlistVolatile()") ;
m_Tx.EnlistVolatile(this, EnlistmentOptions.None);
}
}
public void DisplayContent() {
Console.Write("--> Contenu de " + m_Name + " : ") ;
foreach (T t in this)
Console.Write( t.ToString() + ";") ;
Console.Write("
dataToCommit: " ) ;
foreach (T t in dataToCommit)
Console.Write(t.ToString() + ";") ;
Console.WriteLine() ;
}
#region IEnlistmentNotification Members
public void Prepare(PreparingEnlistment preparingEnlistment) {
Console.WriteLine(m_Name + ".Prepare()") ;
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment) {
Console.WriteLine(m_Name + ".Commit()") ;
OnCommit();
enlistment.Done();
}
public void InDoubt(Enlistment enlistment) {
Console.WriteLine(m_Name + ".InDoubt()") ;
throw new NotImplementedException();
}
public void Rollback(Enlistment enlistment) {
Console.WriteLine(m_Name + ".Rollback()") ;
OnRollback();
enlistment.Done();
}
#endregion
#region ISinglePhaseNotification Members
public void SinglePhaseCommit(
SinglePhaseEnlistment singlePhaseEnlistment) {
Console.WriteLine(m_Name + ".SinglePhaseCommit()") ;
OnCommit();

Introduction la cration dun RM transactionnel

757

singlePhaseEnlistment.Committed();
}
#endregion
}
Voici un petit programme qui exploite une seule instance de txList<string> dans une transaction :
Exemple 20-9 :
using System.Transactions ;
class Program {
static void Main() {
TxList<string> txList = new TxList<string>("List") ;
using ( TransactionScope txScope = new TransactionScope() ) {
txList.TxAdd("A") ; txList.TxAdd("B") ;
txScope.Complete();
txList.DisplayContent() ;
}
txList.DisplayContent() ;
}
}
On peut facilement suivre le droulement des oprations grce lachage du programme :
List.TxAdd(A)
List.EnlistVolatile()
List.TxAdd(B)
--> Contenu de List :
dataToCommit: A;B;
List.SinglePhaseCommit()
List.OnCommit()
--> Contenu de List : A;B ;
dataToCommit:
Si lon ne valide pas la transaction en mettant en commentaire lappel txScope.Complete(),
la transaction est automatiquement avorte et le programme ache ceci :
List.TxAdd(A)
List.EnlistVolatile()
List.TxAdd(B)
--> Contenu de List :
dataToCommit: A;B;
List.Rollback()
--> Contenu de List :
dataToCommit:
Rcrivons notre programme de faon ce quil gre deux listes transactionnelles au sein dune
mme transaction :
Exemple 20-10 :
using System.Transactions ;
class Program {
static void Main() {
TxList<string> txList1 = new TxList<string>("List1") ;

758

Chapitre 20 : Les transactions


TxList<string> txList2 = new TxList<string>("List2") ;
using ( TransactionScope txScope = new TransactionScope() ) {
txList1.TxAdd("A") ; txList1.TxAdd("B") ;
txList2.TxAdd("C") ; txList2.TxAdd("D") ;
txScope.Complete();
txList1.DisplayContent() ; txList2.DisplayContent() ;
}
txList1.DisplayContent() ; txList2.DisplayContent() ;
}
}

Le moteur transactionnel sous jacent (LTM en loccurrence) passe automatiquement en 2PC


avec une phase de prparation et une phase de validation. Ceci devient clair avec lachage du
programme :
List1.TxAdd(A)
List1.EnlistVolatile()
List1.TxAdd(B)
List2.TxAdd(C)
List2.EnlistVolatile()
List2.TxAdd(D)
--> Contenu de List1 :
dataToCommit: A;B;
--> Contenu de List2 :
dataToCommit: C;D;
List1.Prepare()
List2.Prepare()
List1.Commit()
List1.OnCommit()
List2.Commit()
List2.OnCommit()
--> Contenu de List1 : A;B ; dataToCommit:
--> Contenu de List2 : C;D ; dataToCommit:
Vous pouvez aussi dclarer au moteur transactionnel que votre RM est durable. Pour cela, il suffit dappeler la mthode Transaction.EnlistDurable() au lieu de Transaction.EnlistVolatile() au moment de lenrlement. La mthode EnlistDurable() prend en argument lidentifiant unique du RM que le service de recouvrement devra appeler si la transaction donne lieu
une situation douteuse.
Vous pouvez aussi dvelopper un RM durable PSPE en implmentant linterface System.
Transaction.IPromotableSinglePhaseNotification plutt que linterface ISinglePhaseNotification. Plus dinformations ce sujet et plus gnralement sur le dveloppement de RM
sont disponibles dans les articles Optimization using Single Phase Commit and Promotable
Single Phase Notification et Implementing a Resource Manager des MSDN.

21
XML

Introduction XML
Les problmes rsolus par XML
XML est lacronyme de eXtensible Markup Langage. XML est la rponse au besoin de standardisation du formatage des donnes changes entre systmes htrognes. Avant XML, les donnes
taient changes dans des formats propritaires. Les applications devaient connatre ce format
propritaire pour exploiter les donnes et cela posait les trois problmes suivants.

Les documents XML sont crits avec du texte :


Ces formats propritaires sappuyaient sur les formats des types primitifs des systmes sousjacents. Mais la reprsentation dun double ou dune chane de caractres peut tre fort
dirente dun systme lautre. Par exemple, le formatage des types primitifs .NET est
standardis mais son utilisation reste limite .NET.
En XML, un paquet dinformation sappelle un document XML. Un document XML est un
fichier texte, lisible par nimporte quel diteur de texte sur nimporte quelle plateforme. Ce
problme est rsolu car chaque donne est code sous forme dune chane de caractres a
priori non type. On verra que lon peut nanmoins typer les donnes avec des types spcifiques XML, qui sont indpendants du systme sous-jacent.
Les documents XML ont la capacit dauto-dcrire leur structure :
Un autre gros problme de ces formats propritaires concerne la structure dans laquelle
sont stockes les informations. Il est normal quune application qui reoit un numro de
commande sache interprter et utiliser ce numro de commande dans sa logique mtier.
Cependant, il est regrettable que lapplication doive aussi savoir o ce numro est stock
dans le paquet dinformation reu. On dit du paquet dinformation reu par lapplication
quil nest pas auto-descriptif.
Ce problme est rsolu grce un systme de balises extensibles (do les mots Extensible
Markup). Grce aux schmas, qui dcrivent la structure dun document, le chemin daccs

760

Chapitre 21 : XML
une donne spcifique lintrieur du document XML peut tre connu par lapplication
en mme temps que linstance du document. On peut voir ces schmas comme des types
de documents XML.

Les documents XML sont semi structurs :


Un dernier problme de ces formats propritaires est que, bien souvent, ils manquent de
flexibilit dans la structuration des donnes. Concrtement, si la plupart des commandes
contiennent un numro de commande, un identifiant client et une description, il est difficile denvoyer une commande sans description. En gnral, on na pas dautres choix que
dadopter une convention du type : si la description vaut N/A alors cela signifie quil ny a
pas de description pour cette commande.
Ce problme est rsolu car les documents XML sont semi structurs. Concrtement, chaque
donne est encadre par des balises, et une donne peut tre constitue de plusieurs autres
donnes. Si une donne commande na pas de balise description, lapplication considre
que la commande na pas de description. Autrement dit, lapplication obtient une information (i.e pas de description pour cette commande) partir dun manque dinformation
(i.e pas de balise description pour cette commande).

Unification du monde des documents et du monde des donnes


Le constat suivant peut tre fait : la plupart des donnes sont stockes dans des documents
exploitables par des humains. En eet, la plupart des informations se trouvent dans des documents textuels (.html, .doc, .txt etc).
Un casse-tte classique pour les dveloppeurs est dautomatiser lextraction de ces informations de ces documents textuels, afin de pouvoir les traiter avec des logiciels. Quel dveloppeur
na jamais t tent dextraire un numro de commande dun document HTML en se basant
sur le fait que cest la seule partie du document qui est crite en bleue avec la police Verdana de
taille 14 ?
Parce que XML est flexible et extensible, les diteurs de documents XML peuvent la fois prvoir des balises pour la prsentation des donnes et des balises pour les donnes elles-mmes.
Il devient alors ais de produire le document et dextraire les donnes. Cette vision devient de
plus en plus une ralit. Par exemple, la plupart des logiciels Microsoft de la gamme Oce 2003
(Word, Excel etc) produisent des documents dans des formats XML.

Structure dun document XML


Le systme de balises XML est similaire au systme de balises HTML, mis part que vous pouvez crer autant de balises que vous souhaitez, do la notion dextensibilit. Nous exposons cidessous le contenu dun fichier XML reprsentant un extrait de linventaire dune bibliothque
fictive (ce fichier est extrait de la documentation du framework):
Exemple 21-1 :

books.xml

<?xml version="1.0" encoding="utf-8" ?>


<bookstore>
<book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0">
<title>The Autobiography of Benjamin Franklin</title>
<author>
<first-name>Benjamin</first-name>

Introduction XML

761

<last-name>Franklin</last-name>
</author>
<price>8.99</price>
</book>
<book genre="novel" publicationdate="1967" ISBN="0-201-63361-2">
<title>The Confidence Man</title>
<author>
<first-name>Herman</first-name>
<last-name>Melville</last-name>
</author>
<price>11.99</price>
</book>
<book genre="philosophy" publicationdate="1991" ISBN="1-861001-57-6">
<title>The Gorgias</title>
<author>
<name>Plato</name>
</author>
<price>9.99</price>
</book>
</bookstore>
Plusieurs remarques simposent :

Il y a autant de balises que de types de donnes. Il y a une balise pour la notion de bibliothque <bookstore>, une balise pour la notion de livre <book> une balise pour la notion
de titre de livre <title> etc. Un groupe <balise>donn
ees</balise> est appel lment
XML. On voit quun lment XML peut contenir soit une donne soit (exclusif) dautres
lments XML.

Un lment qui contient dautres lments peut galement contenir des donnes grce
la notion dattribut XML (attention, cela na rien voir avec la notion dattribut .NET). Par
exemple, le genre dun livre est un attribut nomm genre de llment <book>.

Le fait quun lment XML puisse en contenir dautres permet de prsenter les donnes
sous une forme hirarchique. Une application qui veut obtenir les titres des livres parus en
1967 doit dabord trouver les lments <book> ayant un attribut publicationdate gal
1967 puis numrer les lments <title> correspondant. Peu importe lordre dans lequel
les livres sont stocks dans le document XML, lapplication saura toujours retrouver linformation dont elle a besoin.

On peut insrer des commentaires dans un fichier XML avec la syntaxe <!-- commentaire
-->.

Lentte dun document XML peut spcifier le type dencodage du texte avec lattribut
encoding. On voit que le fichier texte books.xml est cod au format UTF-8.

Bien que cet exemple ne le montre pas, certains attributs particuliers peuvent dfinir
des espaces de noms XML dans un lment XML. On exploite les espaces de nom XML
pour viter quil y ait collision de noms des lments ou des attributs lorsque plusieurs
langages XML sont utiliss simultanment. On associe en gnral une URI (tel que
http://www.smacchia.com) un espace de noms XML pour tre certain de lunicit du nom
de lespace de noms. Modifions notre exemple pour illustrer la syntaxe :

762

Chapitre 21 : XML
...
<bookstore xmlns:SMA="http://www.smacchia.com" >
<SMA:book genre="autobiography" publicationdate="1981"
ISBN="1-861003-11-0">
...
</SMA:book>
...

Introduction XSD, XPath, XSLT et XQuery


Typage des donnes, schma et XSD
A priori, les donnes dun document XML ne sont pas types. Lapplication peut dcider de garder le nombre demploys dans une chane de caractres. Elle peut aussi dcider de convertir
cette chane en un nombre entier. Dans ce cas lapplication ne sait pas forcment que ce nombre
ne peut pas tre ngatif.
Pour imposer des contraintes, il faut pouvoir dcrire le schma des donnes, cest--dire dcrire
le type de chaque lment (chane de caractres, entier etc) et les contraintes sur chaque lment
(entier positifs, infrieur un million etc). Il existe plusieurs techniques pour dcrire le schma
dun document XML, et la technique XSD (XML Schema Definition) est celle qui a t retenue
pour .NET. Lide est dassocier un document XML un schma XSD dcrivant les lments.
Par exemple, on aurait pu associer le schma XSD suivant notre document books.xml :
Exemple 21-2 :
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="bookstore">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="book">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string" />
<xs:element name="author">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="name"
type="xs:string" />
<xs:element minOccurs="0" name="first-name"
type="xs:string" />
<xs:element minOccurs="0" name="last-name"
type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>

books.xsd

Introduction XSD, XPath, XSLT et XQuery

763

<xs:element name="price" type="xs:decimal" />


</xs:sequence>
<xs:attribute name="genre" type="xs:string" use="required"/>
<xs:attribute name="publicationdate" type="xs:unsignedShort"
use="required" />
<xs:attribute name="ISBN" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
On voit que lon peut typer chaque lment dun document XML, mais ce nest pas une obligation. Les types disponibles et la conversion entre ces types et les types .NET sont prsents dans
larticle Data Type Support between XML Schema (XSD) Types and .NET Framework Types
des MSDN.

XPath
XPath est un langage permettant de rdiger des requtes pour slectionner des nuds dans un
document XML. Certains lont surnomm SQL pour XML car la finalit de XPath vis vis
des donnes reprsentes au format XML est similaire la finalit de SQL vis vis des donnes
reprsentes dans une base de donnes relationnelle.
XPath permet de slectionner des noeuds dans un document XML avec une syntaxe comparable
lcriture du chemin dun fichier. Voici des exemples dexpressions XPath (en gras) appliques
sur le document books.xml suivies par la liste des nuds slectionns :
/bookstore/book/author/first-name
<first-name>Benjamin</first-name>
<first-name>Herman</first-name>
/bookstore/book/author/*
<first-name>Benjamin</first-name>
<last-name>Franklin</last-name>
<first-name>Herman</first-name>
<last-name>Melville</last-name>
<name>Plato</name>
/bookstore/book[@publicationdate>1980]/title
<title>The Autobiography of Benjamin Franklin</title>
<title>The Gorgias</title>

XSLT
Lacronyme XSLT veut dire eXtensible Stylesheet Language Transformation. XSLT est un langage
permettant dexploiter les donnes contenues dans un document XML afin dobtenir un autre
document. Le document produit peut tre, par exemple, au format HTML, dans un autre format XML ou simplement au format texte.

764

Chapitre 21 : XML

Un programme crit en XSLT (i.e un stylesheet) est un document XML. XSLT est construit sur
un systme de template. lexcution, XSLT slectionne les nuds du document source qui
correspondent aux nuds du stylesheet puis excute le corps du template pour chaque noeud
slectionn. La slection se fait par lintermdiaire dune expression XPath.
Le stylesheet suivant appliqu au document books.xml illustre ceci. Notez la prsence de lespace
de noms caractristique xsl. Les expressions en gras sont rdiges avec XPath.
Exemple 21-3 :

books.xslt

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match= "/bookstore" >
<xsl:for-each select= "book[@publicationdate>1980]" >
Titre : <xsl:value-of select= "title" />
Publie il y a <xsl:value-of select= "2005 - (@publicationdate)" /> ans
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Le document produit est celui-ci :
Titre : The Autobiography of Benjamin Franklin
Publie il y a 24 ans
Titre : The Gorgias
Publie il y a 14 ans
Pour une prsentation complte des technologies XPath et XSLT nous vous conseillons de
consulter louvrage suivant :
Comprendre XSLT
OReilly 2002
Bernd Amann, Philippe Rigaud
ISBN : 2-84177-148-2

XQuery
Le langage XQuery a la mme finalit que XSLT : transformer un document XML en un autre
document texte (qui nest pas ncessairement un document XML). Remarquez que cette finalit
peut tre aussi apprhende comme une faon de faire des requtes sur un document XML, do
le nom de XQuery.
XQuery est considr comme plus convivial que XSLT notamment parce quil nest pas luimme un langage XML. La raison principale de cet engouement pour XQuery rside cependant
dans le fait que le flot dexcution des instructions est comparable celui des langages impratifs
bien connus tels que C  ou Java.
linstar dXSLT, XQuery exploite le langage XPath pour la slection de nud. Voici un programme XQuery qui transforme notre document books.xml en un fichier texte qui numre
dans lordre de leurs dates de publication les titres des livres autobiographiques :

Les approches pour parcourir et diter un document XML

765

for $b in document("books.xml")/book where


$b/genre="autobiography" return $b/title" sortby(publicationdate)
Clairement cette logique de slection est trs proche de celle propose par le langage SQL.
XQuery 1.0 ayant t normalis quelques mois aprs la sortie du framework .NET 2.0, ce dernier
ne supporte malheureusement pas cette technologie. On peut cependant signaler que SQL Server 2005 supporte un sous-ensemble de XQuery pour interroger les donnes XML stockes dans
une base de donnes.

Les approches pour parcourir et diter


un document XML
Le parcours dun document XML (i.e la consommation par un programme des informations
contenues dans un document XML) peut se faire selon deux approches :

Lapproche type curseur : Le document XML est parcouru laide dun curseur. Le curseur
peut tre vu comme la position dans le document XML de linformation en cours de lecture. Cette position peut dsigner un lment XML ou un attribut XML. Le programme
est responsable de dplacer le curseur partir de la position courante vers la position des
prochaines informations consommer (aller vers mon prochain lment frre , aller
vers mon premier lment enfant etc).

Lapproche type arbre : Lors dune phase de prchargement du document XML un arbre
dobjets est cr. Chaque objet dans larbre reprsente une entit du document XML. On
peut alors exploiter les informations contenues dans le document en parcourant larbre des
objets.

Lapproche type curseur a plusieurs avantages sur lapproche type arbre. Elle ne ncessite pas une
phase de prchargement et la mmoire ncessaire pour exploiter le document XML ne dpend
pas de la taille du document. Il est clair que lapproche type curseur est plus ecace en termes
de performance. Cependant, la programmation objet fait quil est parfois plus ais dexploiter
des informations contenues dans un arbre dobjets. Les deux modles ont donc chacun leurs
raisons dtre implmenter par le framework .NET.
Les oprations de gnration et de modification dun document XML peuvent aussi se faire
selon ces deux approches. Les avantages et les inconvnients sont les mmes mis part que la
phase de prchargement du modle arbre est remplac par une phase de post gnration lorsque
lon souhaite finaliser les changements.
Enfin, nous allons expliquer que les deux approches ne sont pas incompatibles. Par exemple, il
est possible de parcourir un arbre charg en mmoire avec un curseur. Notamment, nous verrons que lon peut se servir de requtes XPath sur larbre afin dobtenir un curseur sur lensemble
des nuds slectionn. Tout lavantage de ces pratiques rside dans le fait que lon peut crire du
code de manipulation de donnes indpendant du mode de stockage XML. Les donnes XML
sont consommes par une approche type curseur que le document soit pr charg en mmoire
ou charg au fur et mesure partir dune source telle quun fichier.
Si le framework standard System.Xml que nous allons prsenter ne vous satisfait pas sur certains
points, vous pouvez envisager davoir recours au framework gratuit et open-source Mvp.Xml. Plus
dinformation sont disponibles lURL http://sourceforge.net/projects/mvp-xml/.

766

Chapitre 21 : XML

Parcours et dition dun document XML avec un curseur


(XmlReader et XmlWriter)
Lecture des donne avec XmlReader
Les classes abstraites System.Xml.XmlReader et System.Xml.XmlWriter permettent respectivement de parcourir et de modifier des donnes stockes dans un format XML avec une approche
type curseur forward only. Un curseur forward only est un curseur qui ne peut se dplacer quen
avant.
Ces classes sont abstraites pour saranchir de la faon dont sont stockes les donnes en interne. Les classes concrtes qui drivent de XmlReader et XmlWriter ont la responsabilit de grer
le flot des donnes XML en lecture et en criture. La paire de classes concrtes System.Xml.
XmlTextReader et System.Xml.XmlTextWriter permettent de grer un flot textuel de donnes
stockes au format XML respectivement en lecture et en criture.
La classe concrte System.Xml.XmlNodeReader permet de lire des donnes XML qui sont stockes dans un arbre avec une approche type curseur. Lutilisation de cette classe cumule les inconvnients de lapproche type curseur (complexit dans la rdaction du code) et de lapproche
type arbre (pnalit au chargement des donnes et totalit des donnes stocke en mmoire).
En revanche cette classe vous permet dcrire un algorithme de lecture de donnes XML indpendamment de la source de donnes.
Il est conseill de ne pas instancier directement les classes qui drivent de XmlReader. En eet,
les nombreuses surcharges de la mthode statique XmlReader XmlReader.Create() retourne
lobjet adquate selon vos besoin.
Daprs cette description, les lecteurs connaissant dj XML peuvent penser que XmlReader est
une implmentation du protocole SAX de lecture dun document XML. Cette assertion est fausse
et vous pouvez vous rfrer larticle Comparing XmlReader to SAX Reader des MSDN pour
comprendre les dirences.
Voici un programme qui lit le document books.xml avec une instance dune classe drive de
XmlReader. Notez la gestion dun mcanisme dindentation pour exposer la structure hirarchique lors de lachage :
Exemple 21-4 :
using System ;
using System.Xml ;
class Program {
static void Main() {
XmlReader xtr = XmlReader.Create(@"C:\books.xml") ;
string sIndent = string.Empty ;
string sElem = string.Empty ;
while ( xtr.Read() ) {
if ( xtr.NodeType == XmlNodeType.Element ) {
sIndent = string.Empty ;
for (int i = 0 ; i < xtr.Depth ; i++) sIndent += "
sElem = xtr.Name ;
if ( xtr.MoveToFirstAttribute() )

" ;

Parcours et dition dun document XML avec un curseur (XmlReader et XmlWriter)

767

do
Console.WriteLine("{0}{1} Attr:{2}" ,
sIndent , sElem , xtr.Value ) ;
while ( xtr.MoveToNextAttribute() ) ;
}
else if (xtr.NodeType == XmlNodeType.Text)
Console.WriteLine("{0}{1} Val:{2}",sIndent,sElem,xtr.Value) ;
}
}
}
Cet exemple ache ceci :
bookstore Attr:http://www.contoso.com/books
book Attr:autobiography
book Attr:1981
book Attr:1-861003-11-0
title Val:The Autobiography of Benjamin Franklin
first-name Val:Benjamin
last-name Val:Franklin
price Val:8.99
book Attr:novel
book Attr:1967
book Attr:0-201-63361-2
title Val:The Confidence Man
...
La classe XmlReader prsente de nombreuses facilits pour la lecture des donnes telles que le
typage des valeurs rcupres, la prise en compte des espaces de noms, viter les espaces et les
commentaires etc. Ceci est possible grce de multiples mthodes telles que MoveToContent(),
ReadContentAsBoolean(), ReadStartElement() etc.

Validation des donnes durant la lecture


Pour valider les donnes durant leur lecture, il faut avoir recours une surcharge de la mthode
XmlReader.Create() qui accepte dans sa liste darguments une instance de la classe System.Xml.
XmlReaderSettings. Cette instance permet de prciser les vrifications qui doivent tre faites
lors de la lecture des donnes. Reprenons lExemple 21-4 en validant les donnes XML en entre
auprs de du schma books.xsd (celui de lExemple 21-2). Notez la ncessit de fournir une
mthode callback (en loccurrence la mthode ValidatingProblemHandler()) pour rcuprer
les problmes de validation :
Exemple 21-5 :
...
using System.Xml.Schema ;
class Program {
static void Main() {
XmlReaderSettings settings = new XmlReaderSettings() ;
settings.Schemas.Add(String.Empty,
@"C:\books.xsd") ;

768

Chapitre 21 : XML
settings.Schemas.Compile();
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += ValidatingProblemHandler;
settings.ValidationFlags =
XmlSchemaValidationFlags.ReportValidationWarnings;
XmlReader xtr = XmlReader.Create(@"C:\books.xml",settings) ;
...
}
static void ValidatingProblemHandler(object sender,
ValidationEventArgs e) {
if ( e.Severity == XmlSeverityType.Warning ) {
Console.Write("WARNING: ") ; Console.WriteLine(e.Message) ;
} else if ( e.Severity == XmlSeverityType.Error ) {
Console.Write("ERROR: ") ;
Console.WriteLine(e.Message) ;
}
}
}

Ecriture des donnes avec XmlWriter


Dans le mme esprit que la classe XmlReader, la classe System.Xml.XmlTextWriter permet de
crer un fichier XML en prcisant les lments les uns aprs les autres. Voici un petit exemple qui
cre un fichier XML (nous avons volontairement indent le code pour en faciliter la lecture) :
Exemple 21-6 :
using System.Xml ;
using System.Text ;
class Program {
static void Main() {
XmlTextWriter xtw = new
XmlTextWriter(@"C:\book2.xml", Encoding.UTF8) ;
xtw.Formatting = Formatting.Indented;
xtw.WriteStartDocument(true) ;
xtw.WriteStartElement("books") ;
xtw.WriteStartElement("book") ;
xtw.WriteAttributeString("ISBN", "2-84177-245-4") ;
xtw.WriteElementString("title", "Pratique de .NET et C#") ;
xtw.WriteEndElement() ;
xtw.WriteEndElement() ;
xtw.WriteEndDocument() ;
xtw.Flush() ;
xtw.Close() ;
}
}
Voici le fichier XML cr :

Parcours et dition dun document XML avec DOM (XmlDocument)


Exemple :

769
books2.xml

<?xml version="1.0" encoding="utf-8" standalone="yes"?>


<books>
<book ISBN="2-84177-245-4">
<title>Pratique de .NET et C#</title>
</book>
</books>

Parcours et dition dun document XML avec DOM


(XmlDocument)
Chargement et parcours dun document avec XmlDocument
La classe System.Xml.XmlDocument reprsente limplmentation de la norme W3C DOM
(Document Object Model). Cette norme dcrit la reprsentation dun document au format XML
en mmoire sous la forme dun arbre dobjets. Cette arborescence est constitue de nuds
instances de System.Xml.XmlNode (pour les feuilles) et System.Xml.XmlNodeList (pour les
composites). La classe XmlDocument drive de la classe XmlNode tant donne quon peut toujours
voir un document XML comme son nud racine.
Contrairement la classe XmlTextReader, larborescence est compltement construite lors
du chargement du document XML par lappel une mthode Load() sur une instance de
XmlDocument. Il vaut mieux avoir une ide de la taille du document charger, car dans le
cas dun fichier volumineux le chargement complet en mmoire peut tre prohibitif. Si le
document XML source a au moins une erreur de syntaxe, une exception est lance. Aussi, il est
recommand dappeler la mthode Load() au sein dun bloc try/catch.
Voici un programme qui parcourt rcursivement la structure dun XmlDocument initialis au
pralable avec le document books.xml. Cet exemple ache le nom, la valeur et les attributs
de chaque nud avec une indentation pour montrer la structure hirarchique :
Exemple 21-7 :
using System ;
using System.Xml ;
public class Program {
static void DisplayNode(XmlNode xNode, string sIndent) {
Console.WriteLine("{0}Node: {1}({2})",
sIndent, xNode.Name, xNode.Value) ;
if (xNode.Attributes != null)
foreach (XmlAttribute xAtt in xNode.Attributes)
Console.WriteLine("{0} Attribute: {1}",
sIndent, xAtt.Value) ;
if (xNode.HasChildNodes)
foreach (XmlNode _xNode in xNode.ChildNodes)
DisplayNode(_xNode, sIndent + " ") ;
}
static public void Main() {

770

Chapitre 21 : XML
XmlDocument xDoc = new XmlDocument() ;
try { xDoc.Load(@"C:\books.xml") ; }
catch { }
foreach (XmlNode xNode in xDoc.ChildNodes)
DisplayNode(xNode, string.Empty) ;
}
}

Voici un extrait de lachage :


Node: xml(version="1.0" encoding="utf-8")
Node: #comment( This file represents a fragment of a book store
inventory database )
Node: bookstore()
Node: book()
Attribute: autobiography
Attribute: 1981
Attribute: 1-861003-11-0
Node: title()
Node: #text(The Autobiography of Benjamin Franklin)
Node: author()
Node: first-name()
Node: #text(Benjamin)
Node: last-name()
Node: #text(Franklin)
Node: price()
Node: #text(8.99)
...

Edition et sauvegarde des donnes avec XmlDocument


La classe XmlDocument prsente des facilits ddition avec des mthodes telles que InsertAfter(), InsertBefore(), AppendChild(), CreateAttribute() etc. Vous pouvez tous moment
rcuprer une chane de caractres contenant le document XML avec la proprit string InnerXml{get;set;}. Vous pouvez aussi sauvegarder le document XML avec les dirente surcharges de la mthode Save() (qui acceptent en entre dirents types de flots de donnes ou
le nom dun fichier cible).

Validation dun arbre DOM


La mthode XmlDocument.Validate() permet de valider un arbre DOM auprs dun schma
XSD. Par exemple :
Exemple 21-8 :
using System ;
using System.Xml ;
using System.Xml.Schema ;
using System.Xml.XPath ;
public class Program {

Parcours et dition dun document XML avec XPath

771

static public void Main() {


XmlDocument xDoc = new XmlDocument() ;
try { xDoc.Load(@"C:\books.xml") ; } catch { }
xDoc.Schemas.Add( String.Empty, @"C:\books.xsd") ;
xDoc.Schemas.Compile() ;
ValidationEventHandler validator = ValidatingProblemHandler ;
xDoc.Validate(validator) ;
}
static void ValidatingProblemHandler(object sender,
ValidationEventArgs e) {
if (e.Severity == XmlSeverityType.Warning) {
Console.Write("WARNING: ") ; Console.WriteLine(e.Message) ;
} else if (e.Severity == XmlSeverityType.Error) {
Console.Write("ERROR: ") ; Console.WriteLine(e.Message) ;
}
}
}
Une version surcharge de la mthode XmlDocument.Validate() accepte en argument un nud
de larbre afin de procder une validation partielle de larbre.

Evnements de la classe XmlDocument


La classe XmlDocument prsente les vnements suivants qui vous permettent de dclencher une
action lors dun changement sur larbre DOM :
public
public
public
public
public
public

event
event
event
event
event
event

XmlNodeChangedEventHandler
XmlNodeChangedEventHandler
XmlNodeChangedEventHandler
XmlNodeChangedEventHandler
XmlNodeChangedEventHandler
XmlNodeChangedEventHandler

NodeChanged ;
NodeChanging ;
NodeInserted ;
NodeInserting ;
NodeRemoved ;
NodeRemoving ;

Parcours et dition dun document XML avec XPath


Appliquer une expression XPath sur un arbre DOM
Grce la mthode XmlNode.SelectNodes() il est ais de slectionner un ensemble de noeuds
avec une expression XPath. Ainsi, lexemple suivant slectionne tous les prnoms des auteurs
contenus dans le document books.xml :
Exemple 21-9 :
using System.Xml ;
public class Program {
static public void Main() {
XmlDocument xDoc = new XmlDocument() ;
try { xDoc.Load(@"C:\books.xml") ; } catch { }
XmlNodeList books = xDoc.SelectNodes(

772

Chapitre 21 : XML
@"/bookstore/book/author/first-name");
foreach (XmlNode book in books)
System.Console.WriteLine(book.OuterXml) ;
}
}

Cet exemple ache ceci :


<first-name>Benjamin</first-name>
<first-name>Herman</first-name>

Parcours dun document XPathDocument avec XPathNavigator


Les instances de la classe System.Xml.XPath.XPathNavigator permettent de parcourir (et ventuellement de modifier) un arbre DOM charg en mmoire au moyen dexpressions XPath.
Vous pouvez rcuprer un tel objet partir de la mthode XPathNavigator CreateNavigator()
de linterface IXPathNavigable. Les classes XmlDocument et System.Xml.XPath.XPathDocument
implmentent cette interface.
La classe XPathDocument est comparable la classe XmlDocument car ses instances permettent
de stocker un arbre DOM reprsentant un document XML. En revanche, un arbre DOM stock dans une instance de XPathDocument est accessible en lecture seule. Aussi, une instance de
XPathNavigator peut modifier un arbre DOM que si elle agit sur un document charg avec
une instance de XmlDocument. Limplmentation de XPathNavigator est cependant plus adapte
dans certaines situations car elle apporte en moyenne un gain de performance significatif.
Lexemple suivant montre comment parcourir rcursivement laide dune instance de XPathNavigator, un document XML charg dans une instance de XPathDocument :
Exemple 21-10 :
using System ;
using System.Xml ;
using System.Xml.XPath ;
class Program {
static void Main() {
XPathDocument doc = new XPathDocument(@"C:\books.xml") ;
XPathNavigator navigator = doc.CreateNavigator();
navigator.MoveToRoot() ;
DisplayRecursive(navigator, string.Empty) ;
}
static public void DisplayRecursive(XPathNavigator navigator,
string indent) {
if ( navigator.HasChildren ) {
navigator.MoveToFirstChild() ;
DisplayNode(navigator,indent+"
") ;
DisplayRecursive(navigator, indent + " ") ;
navigator.MoveToParent() ;
}
while (navigator.MoveToNext()) {
DisplayNode(navigator, indent) ;

Parcours et dition dun document XML avec XPath

773

DisplayRecursive(navigator, indent) ;
}
}
static private void DisplayNode(XPathNavigator navigator,
string indent) {
if (navigator.NodeType == XPathNodeType.Text)
Console.WriteLine(indent+navigator.Value) ;
else if (navigator.Name != String.Empty)
Console.WriteLine(indent + "<" + navigator.Name + ">") ;
}
}
Cet exemple ache ceci :
<bookstore>
<book>
<title>
The Autobiography of Benjamin Franklin
<author>
...

Traverse dune slection XPath avec XPathNodeIterator


Une alternative la mthode XmlNode.SelectNodes() pour slectionner un ensemble de
noeuds avec une expression XPath est dutiliser une instance de XPathNodeIterator obtenue partir de la mthode XPathNodeIterator XPathNavigator.Select(<< XPathExpression >>). Une instance de cette classe permet dnumrer les lments slectionns partir
dune expression XPath. Pour chaque lment, la proprit XPathNavigator XPathNodeIterator.Current{get;} vous renvoie un navigateur positionn llment courant. Tout ceci est
expos par lexemple suivant :
Exemple 21-11 :
using System.Xml.XPath ;
class Program {
static void Main() {
XPathDocument document = new XPathDocument(@"C:\books.xml") ;
XPathNavigator navigator = document.CreateNavigator() ;
XPathNodeIterator iterator =
navigator.Select(@"/bookstore/book/author/first-name") ;
while( iterator.MoveNext() )
System.Console.WriteLine("<" + iterator.Current.Name + ">" +
iterator.Current.Value) ;
}
}
Cet exemple ache ceci :
<first-name>Benjamin
<first-name>Herman

774

Chapitre 21 : XML

Edition dun arbre DOM XmlDocument avec XPathNavigator


Lexemple suivant montre comment utiliser une instance de XPathNavigator afin de modifier
un arbre DOM stock dans une instance de XmlDocument. En loccurrence, nous insrons un
nouvel lment <book> dans notre document XmlDocument :
Exemple 21-12 :
using System ;
using System.Xml ;
using System.Xml.XPath ;
class Program {
static void Main() {
XmlDocument xDoc = new XmlDocument() ;
try { xDoc.Load(@"C:\books.xml") ; } catch { }
XPathNavigator navigator = xDoc.CreateNavigator() ;
navigator.MoveToRoot() ;
// S
electionne la racine.
if (navigator.MoveToFirstChild())
// S
electionne <bookstore>.
if (navigator.MoveToFirstChild()) // S
electionne
// <book>Autobiography...
navigator.InsertElementBefore( string.Empty, "book",
string.Empty, "Pratique de .NET et C#");
xDoc.Save(@"C:\new_books.xml") ;
}
}
Voici un aperu du fichier books.xml modifi :
Exemple :

Books.xml

<?xml version="1.0" encoding="utf-8" ?>


<bookstore>
<book>Pratique de .NET et C#</book>
<book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0">
<title>The Autobiography of Benjamin Franklin</title>
...

Transformer un document XML avec XSLT


La classe System.Xml.Xsl.XslCompiledTransform peut tre utilise pour transformer un document XML au moyen dun programme rdig avec XSLT 1.0. Cette nouvelle classe du framework .NET 2.0 remplace la classe XslTransform maintenant devenue obsolte. Comme son
nom lindique, son atout principal et de compiler les programmes XSLT en code MSIL avant
dappliquer une transformation. Le cot initial de la compilation est rapidement amorti aprs
quelques transformations.
Lexemple suivant applique le programme books.xslt (Exemple 21-3) sur le document books.
xml (Exemple 21-1) et ache le rsultat sur la console :

Ponts entre le relationnel et XML

775

Exemple 21-13 :
using System.Xml.Xsl ;
class Program {
static void Main() {
System.Xml.XmlDocument xDoc = new System.Xml.XmlDocument() ;
xDoc.Load(@"C:\books.xml") ;
XslCompiledTransform xTrans = new XslCompiledTransform();
xTrans.Load(@"C:\books.xslt");
xTrans.Transform(xDoc, null, System.Console.Out);
}
}
Dans la dernire section du prsent chapitre, nous exposons les facilits proposes par Visual
Studio pour ldition et le dbogage dun programme XSLT.

Ponts entre le relationnel et XML


Obtenir un document XML partir dun DataSet
Vous pouvez aisment obtenir un document XML dcrivant les donnes contenues dans les
tables dun DataSet. Pour cela, il sut dutiliser la mthode WriteXml() de la classe DataSet.
Vous pouvez aussi obtenir un schma XSD dcrivant les schmas des tables contenues dans un
DataSet avec la mthode WriteXmlSchema() de la classe DataSet. Voici un exemple qui remplit
un DataSet partir dune base de donnes (celle dcrite page 710) puis ache les donnes et le
schma sur la console, aux formats XML et XSD :
Exemple 21-14 :
using System.Data ;
using System.Data.SqlClient ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
using( SqlConnection cnx = new SqlConnection(sCnx) ) {
using( SqlDataAdapter dataAdapter = new SqlDataAdapter() ) {
DataSet dataSet = new DataSet() ;
string sCmd = "SELECT * FROM EMPLOYES" ;
dataAdapter.SelectCommand = new SqlCommand(sCmd, cnx) ;
dataAdapter.Fill(dataSet, "EMPLOYES") ;
dataSet.WriteXml( System.Console.Out );
System.Console.WriteLine("-------------------------------") ;
dataSet.WriteXmlSchema( System.Console.Out );
} // end using SqlDataAdapter
} // end using SqlConnection
}
}

776

Chapitre 21 : XML

Voici un extrait de lachage de ce programme :


<NewDataSet>
<EMPLOYES>
<EmployeID>1</EmployeID>
<DepID>MKT</DepID>
<Nom>Lafleur</Nom>
<Prenom>Leon</Prenom>
<Tel>0497112233</Tel>
</EMPLOYES>
...
</NewDataSet>------------------------------<?xml version="1.0" encoding="ibm850"?>
<xs:schema id="NewDataSet" xmlns=""
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true"
msdata:Locale="fr-FR">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="EMPLOYES">
<xs:complexType>
<xs:sequence>
<xs:element name="EmployeID" type="xs:int" minOccurs="0" />
<xs:element name="DepID" type="xs:string" minOccurs="0" />
<xs:element name="Nom" type="xs:string" minOccurs="0" />
<xs:element name="Pr
enom" type="xs:string" minOccurs="0" />
<xs:element name="T
el" type="xs:string" minOccurs="0" />
...

Remplir un DataSet partir dun document XML


Il est possible dinsrer des lignes dans une table dun DataSet partir dun fichier XML. Dans
lexemple suivant, nous ajoutons les donnes sur de nouveaux employs dans la base de donnes dcrite page 710. Les donnes sources sur ces employs sont contenues dans le fichier DataFile.xml :
Exemple 21-15 :
using System.Data ;
using System.Data.SqlClient ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
using( SqlConnection cnx = new SqlConnection(sCnx) ) {
using( SqlDataAdapter dataAdapter = new SqlDataAdapter() ) {
DataSet dataSet = new DataSet() ;
// Gen`ere automatiquement les commandes pour

Ponts entre le relationnel et XML

777

// mettre a` jour la base.


string sCmd = "SELECT * FROM EMPLOYES WHERE EmployeID=-1" ;
dataAdapter.SelectCommand = new SqlCommand(sCmd, cnx) ;
SqlCommandBuilder cmdBuilder =
new SqlCommandBuilder(dataAdapter) ;
// Construction de la table EMPLOYES et insertion des
// lignes `a partir du fichier DataFile.xml.
dataSet.ReadXml(@"C:/DataFile.xml");
// Met `a jour la base.
dataAdapter.Update(dataSet, "EMPLOYES") ;
} // end using SqlDataAdapter
} // end using SqlConnection
}
}
Voici le fichier DataFile.xml. Il nest pas ncessaire de prciser une valeur pour le champ
EmployeID puisque celle-ci est dtermine par la base de donnes.
Exemple 21-16 :

DataFile.xml

<?xml version="1.0" encoding="utf-8" ?>


<NewDataSet>
<EMPLOYES>
<DepID>MKT</DepID>
<Nom>Pointcarre</Nom>
<Prenom>Paul</Prenom>
<Tel>0497112283</Tel>
</EMPLOYES>
<EMPLOYES>
<DepID>DEV</DepID>
<Nom>Plom</Nom>
<Prenom>Olivier</Prenom>
<Tel>0497112285</Tel>
</EMPLOYES>
</NewDataSet>

La classe System.Xml.XmlDataDocument
Nous avons vu dans les deux sections prcdentes comment transfrer des donnes au format
XML dans les tables dun DataSet et vice-versa. La classe System.Xml.XmlDataDocument (qui
drive de la classe XmlDocument) a t spcialement conue cet eet. Concrtement, on lie
une instance de XmlDataDocument avec un DataSet, puis on manipule les donnes par lintermdiaire du XmlDataDocument ou par lintermdiaire du DataSet. Il faut bien comprendre quen
interne, ce sont les mmes donnes qui sont manipules. Les changements faits par lintermdiaire du DataSet sont immdiatement visibles par lintermdiaire du XmlDataDocument et vice
versa.
Il est lgitime de se demander pourquoi utiliser la classe XmlDataDocument alors que les deux
sections prcdentes ont montr que la classe DataSet pouvait grer le format XML. Nous pouvons identifier les trois raisons suivantes :

778

Chapitre 21 : XML
Lutilisation de la classe XmlDataDocument permet de faire des requtes XPath sur les donnes.
Lutilisation de la classe XmlDataDocument permet de rester fidle un document XML
source. Si vous chargez un document XML dans un DataSet puis que vous le sauvez, il peut
y avoir des dirences de formatage entre les documents XML sources et destinations. Avec
lutilisation de la classe XmlDataDocument le document XML destination sera strictement
identique au document XML source (par exemple les espaces, lordre des lments ou les
commentaires nauront pas t modifis).
Lutilisation de la classe XmlDataDocument permet de tenir compte des relations entre les
tables du DataSet lors du formatage XML. Concrtement, dans une relation one to many ,
les lments enfants dun lment parent seront physiquement inclus dans llment parent dans le document XML. La notion de relation entre tables dun DataSet est prsente
page 728. Avec cette technique, une relation est prise en compte seulement si sa proprit
Nested est positionne true, ce qui nest pas le cas par dfaut.

XML et SQL Server


Depuis la version 2000, le SGBD SQL Server prsente un composant nomm SQLXML qui permet de formater les requtes SQL ainsi que les rponses au format XML. SQLXML se retrouve
aussi dans le serveur IIS. Ainsi, il est possible denvoyer des requtes XML et de recevoir des
rponses XML par HTTP. Autrement dit, cette infrastructure permet de concevoir des services
web basiques dinterrogation de bases de donnes.
Le fournisseur de donnes SqlClient de ADO.NET permet dexploiter SQLXML. Lexemple suivant permet de lister les lignes de la table EMPLOYES au format XML. Notez lajout de lexpression
FOR XML AUTO la fin de notre requte SQL. Cette syntaxe constitue une extension du langage
T-SQL et permet de prciser que lon souhaite avoir le rsultat de la requte dans un format
XML :
Exemple 21-17 :
using System.Data.Common ;
using System.Data.SqlClient ;
using System.Xml ;
class Program {
static void Main() {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
string sCmd = "SELECT * FROM EMPLOYES FOR XML AUTO" ;
using (DbConnection cnx = new SqlConnection(sCnx)) {
using (SqlCommand cmd = new SqlCommand(sCmd,
cnx as SqlConnection)) {
cnx.Open() ;
System.Xml.XmlReader reader = cmd.ExecuteXmlReader() ;
while ( reader.Read() )
System.Console.WriteLine( reader.ReadOuterXml() ) ;
reader.Close() ;
} // end using cmd.
} // end using cnx.
System.Console.Read() ;

Ponts entre lobjet et XML (srialisation XML)

779

}
}
Cet exemple ache ceci :
<EMPLOYES EmployeID="1" DepID="MKT" Nom="Lafleur" Pr
enom="L
eon"
Tel="0497112233"/>
<EMPLOYES EmployeID="3" DepID="DEV" Nom="Schmol" Pr
enom="Jean" />
...
La version 2005 de SQL Server apporte des nouvelles facilits quant lintgration de donnes
XML dans une base de donnes relationnelle. Notamment, une colonne dune table peut tre
type en XML. Chaque lment dune telle colonne est un document XML. Le type du framework .NET correspondant une telle colonne est System.Data.SqlTypes.SqlXml. Il y a aussi
possibilit de typer fortement une colonne XML par un schma XSD et vous pouvez eectuer
des requtes XQuery sur les lments dune telle colonne. Une prsentation dtaille de ces fonctionnalits de SQL Server 2005 dpasse le cadre du prsent ouvrage.

Ponts entre lobjet et XML (srialisation XML)


La classe System.Xml.XmlSerializer
Grce la classe System.Xml.XmlSerializer vous avez la possibilit de srialiser au format XML
nimporte quel objet .NET. Ce mcanisme de srialisation admet cependant quelques limitations :

XmlSerializer ne srialise que les champs et attributs publics contrairement la srialisation dobjets binaire dcrite page 790.

XmlSerializer ne tient pas compte de lattribut [Serializable] ou de linterface ISerializable.

XmlSerializer ne peut srialiser un graphe dobjets qui admet au moins une rfrence circulaire.

Voici un programme qui illustre lutilisation de la classe XmlSerializer en sauvant ltat dune
instance de la classe Entreprise dans un fichier XML, et en chargeant cet tat dans une autre
instance de la classe Entreprise.
Exemple 21-18 :
using System.Xml ;
using System.Xml.Serialization ;
using System.IO ;
public class book {
public string genre { get { return m_genre ; }
set { m_genre = value ; } }
private string m_genre ;
public string title { get { return m_title ; }
set { m_title = value ; } }
private string m_title ;
public decimal price { get { return m_price ; }

780

Chapitre 21 : XML
set { m_price = value ; } }
private decimal m_price ;
}
public class Program {
static public void Main() {
book b1 = new book() ;
b1.genre = "autobiography" ;
b1.title = "The Autobiography of Benjamin Franklin" ;
b1.price = 8.99M ;
//Sauve une instance de la classe book dans le fichier book.xml.
FileStream fs1 = File.OpenWrite("book.xml");
XmlSerializer xmls = new XmlSerializer(typeof(book));
xmls.Serialize(fs1, b1);
fs1.Close() ;
// Cree une instance de la classe book...
// ...`a partir du fichier book.xml.
FileStream fs2 = File.OpenRead("book.xml");
book b2 = (book)xmls.Deserialize(fs2);
fs2.Close() ;
}
}

Le fichier XML cr est celui-ci :


Exemple :

book.xml

<?xml version="1.0" encoding="utf-8"?>


<book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<genre>autobiography</genre>
<title>The Autobiography of Benjamin Franklin</title>
<price>8.99</price>
</book>
Vous avez la possibilit de srialiser une information de type nullable dans un document XML.
Dans ce cas l, la valeur nulle sera prise si llment ou lattribut est absent dans le document
XML ou si llment est prsent mais contient un attribut xsi:nil="true". Dans le schma
XSD, le fait quun lment ou un attribut peut tre nul se traduit par la prsence de lattribut
nil="true" dans llment xs:element correspondant
Vous pouvez srialiser un type gnrique. Cela ne pose aucun problme lors de la srialisation
mais il faut faire attention de spcifier les types paramtres adquats lors de la construction dun
XmlSerializer destin la dsrialisation.

Attributs spcialiss pour la srialisation XML


Ne confondez pas la notion dattribut .NET et la notion dattribut XML qui sont direntes.
Vous avez la possibilit dutiliser des attributs .NET pour modifier le comportement par dfaut
de la srialisation dans un fichier XML. En gnral il faut se plier un schma XSD lorsque

Ponts entre lobjet et XML (srialisation XML)

781

lon srialise un objet au format XML. Par exemple vous pourriez souhaiter ne pas srialiser la
proprit price et faire en sorte que la proprit genre soit un attribut XML et non un lment.
Voici les attributs .NET que vous pouvez utiliser :

XmlRoot : Permet didentifier une classe ou une structure comme le nud racine. En gnral on utilise cet attribut pour assigner un nom dirent du nom de la classe llment
XML correspondant.

XmlElement : Spcifie quun champ ou une proprit publique doit tre srialis comme
un lment. Comme il sagit du comportement par dfaut, on utilise en gnral cet attribut
pour assigner un nom dirent du nom du champ ou de la proprit, llment XML
correspondant.

XmlAttribute : Spcifie quun champ ou une proprit publique doit tre srialis comme
un attribut XML et non comme un lment, ce qui est le comportement par dfaut. On
peut en profiter pour assigner un nom dirent du nom du champ ou de la proprit,
llment XML correspondant.

XmlArray : Spcifie quun champ ou une proprit publique doit tre srialis comme un
tableau. En gnral on utilise cet attribut pour faire en sorte quun tableau dobjets soit
srialis.

XmlArrayItem : Spcifie que les instances dun type peuvent tre srialises dans un tableau.

XmlIgnore : Indique quil ne faut pas srialiser un champ ou une proprit publique.

Modifions la classe book avec certains de ces attributs :


Exemple 21-19 :
...
[XmlRoot(ElementName = "livre")]
public class book {
[XmlAttribute(AttributeName = "genre")]
public string genre { get { return m_genre ; }
set { m_genre = value ; } }
private string m_genre ;
[XmlElement(ElementName = "titre")]
public string title { get { return m_title ; }
set { m_title = value ; } }
private string m_title ;
[XmlIgnore]
public decimal price { get { return m_price ; }
set { m_price = value ; } }
private decimal m_price ;
}
...
Le fichier XML produit est maintenant celui-ci :

782

Chapitre 21 : XML

Exemple :

book.xml

<?xml version="1.0" encoding="utf-8"?>


<livre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
genre="autobiography">
<titre>The Autobiography of Benjamin Franklin</titre>
</livre>

Loutil sgen.exe
Lorsque vous srialisez au format XML un objet dont le type na pas encore eu dinstances srialises, la classe XmlSerializer cre en interne une nouvelle classe spcialise dans la srialisation des instances de ce type. Vous pouvez amliorer les performances en pr-gnrant ces
classes spcialises dans la srialisation XML grce loutil sgen.exe (XML Serializer Generation
Tool). Cet outil accepte un assemblage XXX en entre (avec option /a) et cre un nouvel assemblage XXX.XmlSerializers.dll qui contient ces classes spcialises dans la srialisation XML.
Dans le cas de notre class book de lExemple 21-19, une classe Microsoft.Xml.Serialization.
GeneratedAssembly.bookSerializer est cre et vous pouvez lutiliser comme ceci :
Exemple 21-20 :
using Microsoft.Xml.Serialization.GeneratedAssembly;
...
public class Program {
static public void Main() {
...
FileStream fs1 = File.OpenWrite("book.xml") ;
bookSerializer bookS = new bookSerializer();
bookS.Serialize(fs1, b1);
...
book b2 = (book)bookS.Deserialize(fs2) ;
fs2.Close() ;
}
}
...

Loutil xsd.exe
Nous avons montr que lutilisation de certains attributs .NET permet de se plier un schma
XSD lors de la srialisation dun objet au format XML. Loutil xsd.exe fourni avec le framework
.NET va plus loin et permet les oprations suivantes :

xsd.exe peut gnrer le code dune classe C  (drivant ventuellement de la classe DataSet)
partir dun schma XSD. Notamment, en page 731 nous expliquons comment cet outil
permet de crer des DataSet typs.
xsd.exe permet de gnrer un schma XSD partir dun fichier XML ou partir de types
contenus dans un assemblage.

Lutilisation de loutil xsd.exe est dcrite dans larticle XML Schema Definition Tool (Xsd.exe)
des MSDN.

Visual Studio .NET et XML

783

Visual Studio .NET et XML


Visualisation et dition des documents XML et XSD
Visual Studio permet dditer des documents XML sous la forme de documents textes. Il sait
dtecter les erreurs lexicales et syntaxiques XML. Lorsque de telles erreurs sont dtectes, elles
sont soulignes dans les documents et reportes dans la liste des erreurs. Il est possible dinfrer
un schma XSD partir du document XML couramment visualis. Il sut pour cela daller dans
le menu XML  Create Schema.
Les schmas XSD sont quant eux visualisables et ditables par lintermdiaire dun designer
graphique expos par la Figure 21 -1 :

Figure 21 -1 : designer de schmas XSD


Visual Studio cre un fichier dextension .xsx pour chaque schma XSD visualis par cet diteur.
Ce fichier contient les informations exploites par lditeur pour organiser la vue du document
(coordonnes des tables, niveau de zoom etc). Notez quil est toujours possible de retourner
une vue textuelle dun schma XSD.

Validation dun document XML auprs dun schma XSD


Pour que Visual Studio puisse eectuer une telle validation, il faut dabord quil ait accs au
schma XSD concern. Visual Studio tient compte des schmas XSD contenus dans le projet courant et des schmas XSD stocks dans les rpertoire [Rep dinstallation de VS]\Xml\Schemas
et [Rep dinstallation de VS]\Common7\IDE\Policy\Schemas. Vous pouvez associer un
schma XSD connu par Visual Studio un document XML dune manire naturelle en prcisant
lespace de nom cible du schma XSD dans le document XML. Revoyons nos fichiers exemples
books.xml et books.xsd :
Exemple 21-21 :

books.xml

<?xml version="1.0" encoding="utf-8" ?>


<bookstore xmlns ="http://www.contoso.com/books">
<book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0">
...
Exemple 21-22 :
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"

books.xsd

784

Chapitre 21 : XML
elementFormDefault="qualified"
targetNamespace="http://www.contoso.com/books"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="bookstore">
...

Vous pouvez aussi associer un schma XSD un document XML par lintermdiaire du menu
proprit  Schemas du document XML. Ce menu propose une fentre daide au choix du
schma XSD parmi tous ceux connus de Visual Studio.
Lorsquun schma XSD est associ un document XML, Visual Studio ralise non seulement la
validation en dtectant les erreurs et en les mettant en vidence, mais il active aussi le mcanisme trs pratique dintellisense pour faciliter ldition du document XML.

dition et dbogage dun programme XSLT


Comme pour tous documents XML, Visual Studio vrifie les erreurs lexicales et syntaxiques
lorsque vous ditez un programme XSLT. En outre, Lditeur XML peut valider en temps rel
votre document auprs du fichier xslt.xsd qui dfinit le schma de ce langage. Il sut pour
cela dinclure lespace de noms http://www.w3.org/1999/XSL/Transform dans llment racine.
Visual Studio vous permet aussi de dboguer vos programmes XSLT de deux faons :

Durant le dbogage dun programme .NET lors de lappel la mthode XslCompiledTransform.Transform() le dbogueur va directement au point dentre du programme
XSLT. Ceci est possible seulement si vous avez utiliser le constructeur XslCompiledTransform(bool enableDebug) avec la valeur true pour construire votre instance.

Directement partir de Visual Studio en utilisant le menu XML  Debug XSLT qui est disponible lors de ldition dun programme XSLT. Pour exploiter cette possibilit, il faut au
pralable avoir fourni un fichier XML en entre et un fichier texte en sortie (qui accueillera
le produit de la transformation XSLT) dans les sous menu input et output du menu proprit
du document XSLT.

22
.NET Remoting

Introduction
Quest ce que .NET Remoting ?
Nous avons dfini la notion de domaine dapplication page 87 comme tant un conteneur
assemblage durant lexcution. Nous rappelons quun processus contient un ou plusieurs domaines dapplications, isols les uns des autres par le CLR. Lassemblage mscorlib contenant les
types de bases de la plateforme .NET est physiquement charg dans le processus hors de tout
domaine dapplication. Lisolation entre les domaines dapplication se fait principalement au
niveau des types, de la scurit et de la gestion des exceptions. Cette isolation ne se fait pas au
niveau des threads.
Si vous avez assimil cette notion de domaine dapplication, vous pouvez comprendre la dfinition de .NET Remoting qui est :

.NET Remoting est linfrastructure de la plateforme .NET qui permet des objets situs
dans des domaines dapplications dirents, de pouvoir se connatre et de pouvoir communiquer entre eux. Lobjet appelant est nomm client, lobjet appel est nomm serveur
ou objet serveur.
Deux domaines dapplications dirents peuvent se trouver :

dans le mme processus ;

dans deux processus dirents sur la mme machine ;

dans deux processus dirents sur deux machines direntes.

786

Chapitre 22 : .NET Remoting

.NET Remoting masque au dveloppeur ces trois aspects du problme. La localisation dun objet
se fait en gnral aprs la compilation des sources, pendant linstallation de lapplication. Plus
prcisment, linformation tel objet est accessible sur telle machine peut ne pas faire partie
du code source. Dans ce cas cette information se situe dans un fichier de configuration accessible
par le client de lobjet.
Nous prcisons, lattention des dveloppeurs ayant dj utilis les technologies Microsoft que
.NET Remoting peut tre vu comme le successeur de DCOM.

FAQ
Q : Peut-on choisir le protocole de communication sous-jacent la communication entre
deux objets ?
R : Oui. Avec .NET Remoting chaque protocole de communication est encapsul dans un objet
appel canal (channel en anglais). Les canaux encapsulant les protocoles HTTP, TCP ainsi quun
protocole de communication entre processus dune mme machine (IPC pour Inter Processus
Communication) sont implments nativement. Vous pouvez nanmoins dvelopper vos propres
canaux pour dautres protocoles.
Q : Sous quelle forme les donnes ncessaires un appel de mthode transitent-elles sur
le rseau ?
R : Les donnes ncessaires un appel de mthode sont principalement les valeurs des paramtres entrants, un identificateur de la mthode, un identificateur de lobjet avant lappel et les
valeurs des paramtres sortants au retour de lappel. En .NET Remoting les objets responsables
de lemballage de ces donnes sont appels formateurs (formaters en anglais). Les formateurs
emballant les donnes dans un format binaire ou dans un format XML nomm SOAP sont
implments nativement. Vous pouvez nanmoins dvelopper vos propres formateurs pour vos
besoins spcifiques (cryptage des donnes, limination de redondances etc).
Q : Larchitecture utilisant ces objets formateurs et canaux est-elle aussi utilise lorsque
le client et lobjet serveur sont dans le mme domaine dapplication ou dans le mme
processus ?
Dans ces deux cas, il nest videmment pas ncessaire dutiliser un canal qui utilise le rseau, et
.NET Remoting adopte automatiquement ce comportement. Il existe donc un canal spcifique
aux appels au sein du mme espace dadressage.
Q : Comment le code source du client, qui nest pas dans le mme assemblage que la classe
reprsentant lobjet serveur, peut-il connatre la classe de cet objet ?
R : Avec .NET Remoting, au moins trois techniques sont envisageables. Soit lassemblage client
rfrence lassemblage serveur la compilation. Dans ce cas lassemblage serveur doit tre install avec lassemblage client. Cette technique est donc trs contraignante. On lui prfre lencapsulation dinterfaces dans un assemblage tierce, qui est rfrenc la fois par le serveur et le
client. Une 3me technique permet de construire automatiquement cet assemblage tierce partir de lassemblage du serveur, mme si les classes de celui-ci nimplmentent pas dinterfaces.
Cette 3me technique ncessite lutilisation dun outil du framework .NET.
Q : Ne serait-il pas plus simple de rcuprer une copie de lobjet serveur dans le domaine
dapplication du client, et de travailler sur cette copie, plutt que dutiliser le rseau pour
chaque appel de mthode ?

Marshaling By Reference (MBR)

787

R : .NET Remoting ore cette possibilit et la rponse la question est : a dpend. Ceci implique que lassemblage contenant la classe de lobjet serveur est accessible par le client et ce nest
en gnral pas souhaitable. En outre, beaucoup dobjets serveurs ne peuvent pas tre dplacs
dans un autre domaine dapplication. La raison classique est que ces objets serveurs contiennent
des rfrences vers dautres objets du domaine, qui sont inamovibles. Par exemple une instance
de la classe Thread peut tre considre comme un objet inamovible de son processus. Enfin,
dans le cas de la rcupration de lobjet par le client, on se prive de la possibilit de pouvoir
utiliser un objet dune manire concurrente entre plusieurs clients.
Q : Qui est responsable de la cration dun objet serveur ? Le client ou le serveur luimme ?
R : Les deux cas de figure sont envisageables avec .NET Remoting. Dans le cas o le serveur est
responsable de la cration dun objet, cet objet est identifi par un URI. Le client contacte cet
objet en utilisant cet URI, un peu comme vous contactez un proche en utilisant son numro de
tlphone. Dans ce cas, lobjet est eectivement cr lors du premier appel du client.
Q : Qui est responsable de la destruction dun objet serveur ? Le client ou le serveur luimme ?
R : Malgr un mcanisme de ping volu, DCOM a montr quil ne faut pas faire confiance au
client quant la destruction dun objet serveur. DCOM a aussi montr quun tel mcanisme
de ping nuit gravement aux performances. Avec .NET Remoting, les objets serveurs sont automatiquement dtruits par le serveur aprs une certaine dure durant laquelle lobjet na pas t
utilis. Un client qui essaye de contacter un objet qui nexiste plus rcupre une exception.
Q : .NET Remoting supporte-t-il les appels asynchrones ? (Reformul : Y a t-il un mcanisme pour quun client ne soit pas oblig dattendre que lappel dune mthode par .NET
Remoting se termine avant de continuer ?)
R : Oui. .NET Remoting supporte le mcanisme dappel asynchrone dcrit page 171. Lors dun
appel asynchrone, vous pouvez choisir ou non dtre averti par le serveur lorsque celui-ci a excut votre appel. Vous pouvez ainsi rcuprer des informations relatives votre traitement par
le serveur, dune manire asynchrone.

Marshaling By Reference (MBR)


.NET Remoting prsente deux solutions architecturalement trs direntes, pour permettre
un client dappeler une mthode sur un objet distant. Ces deux solutions portent les noms de
Marshalling By Value (MBV) et Marshalling By Reference (MBR). Par dfaut, les instances dune
classe ne sont pas utilisables dune manire distante.
Le Marshalling By Reference (MBR) consiste obtenir un nouvel objet appel proxy transparent
(transparent proxy en anglais) dans le domaine dapplication du client. Pour le code du client,
tout se passe comme si un proxy transparent tait une rfrence classique vers un objet, do
la notion de transparence. En fait mme le compilateur csc.exe ne sait pas que certaines rfrences seront en fait des proxys transparents lexcution.
Une condition ncessaire pour pouvoir utiliser dans un assemblage des proxys transparents la
place des rfrences dun type, est que les mtadonnes de ce type soient accessibles la compilation de lassemblage. Nous exposerons plusieurs faons de raliser cette condition.
ce stade, les lecteurs curieux doivent srement se poser des questions comme :

788

Chapitre 22 : .NET Remoting

Qui est responsable de la cration de lobjet distant ?

Comment rcupre-t-on un proxy transparent ?

Comment la classe proxy fait-elle pour faire transiter les donnes entrantes et sortantes des
appels sur un rseau ?

Toutes les rponses ces questions se trouvent dans ce chapitre. Lanalyse du programme suivant
va apporter quelques lments de rponse. Nous nous intressons au cas o le client et la classe
de lobjet distant sont dans le mme assemblage et o les domaines dapplication du client et
de lobjet distant sont dans le mme processus.
Exemple 22-1 :
using
using
using
using

MBRTest.cs

System ;
System.Runtime.Remoting.Contexts ;
System.Runtime.Remoting ;
System.Threading ;

public class Foo : MarshalByRefObject {


public void AfficheInfo(string s) {
Console.WriteLine(s) ;
Console.WriteLine(" Nom du domaine : " +
AppDomain.CurrentDomain.FriendlyName) ;
Console.WriteLine(" ThreadID
: " +
Thread.CurrentThread.ManagedThreadId) ;
}
}
public class Program {
static void Main() {
// obj1
Foo obj1 = new Foo() ;
obj1.AfficheInfo("obj1:") ;
Console.WriteLine(" IsObjectOutOfAppDomain(obj1)=" +
RemotingServices.IsObjectOutOfAppDomain(obj1)) ;
Console.WriteLine(" IsTransparentProxy(obj1)=" +
RemotingServices.IsTransparentProxy(obj1)) ;
// obj2
AppDomain appDomain = AppDomain.CreateDomain("Autre domaine.") ;
Foo obj2 = (Foo)appDomain.CreateInstanceAndUnwrap(
"MBRTest", // Nom de lasm contenant le type.
"Foo") ;
// Nom du type.
obj2.AfficheInfo("obj2:") ; // <- Ici, le code client ne sait pas
// quil manipule un proxy transparent.
Console.WriteLine(" IsObjectOutOfAppDomain(obj2)=" +
RemotingServices.IsObjectOutOfAppDomain(obj2)) ;
Console.WriteLine(" IsTransparentProxy(obj2)=" +
RemotingServices.IsTransparentProxy(obj2)) ;

Marshaling By Reference (MBR)

789

}
}
Ce programme ache :
obj1:
Nom du domaine : MBRTest.exe
ThreadID
: 6116
IsObjectOutOfAppDomain(obj1)=False
IsTransparentProxy(obj1)=False
obj2:
Nom du domaine : Autre domaine.
ThreadID
: 6116
IsObjectOutOfAppDomain(obj2)=True
IsTransparentProxy(obj2)=True
Notez lutilisation des mthodes statiques IsObjectOutOfAppDomain() et IsTransparentProxy() de la classe RemotingServices.
La Figure 22 -1 illustre larchitecture mise en place par le CLR pour excuter ce programme. La
notion de contexte est explique un peu plus loin dans le prsent chapitre :
Processus
Domaine dapplication par dfaut
FriendlyName = "MBRTest.exe"
Assemblage MBRTest
Contexte
Client (mthode statique Main())
...
Obj1.AfficheInfo(...)

Obj1

...
Obj2.AfficheInfo(...)

Proxy vers Obj2

FriendlyName = "Autre domaine"

Contexte

Obj2

Thread

Assemblage MBRTest

ThreadID=6116

Second domaine dapplication

Figure 22 -1 : Exemple limit utilisant le Marshalling By Reference


Dans le programme prcdent, vous avez certainement remarqu que la classe Foo drive de la
classe System.MarshalByRefObject. Lorsque le compilateur JIT rencontre une rfrence type
par une classe qui drive de MarshalByRefObject, il sait que cette rfrence peut tre ventuellement un proxy transparent. Une consquence est que le compilateur JIT sinterdit de mettre
en ligne les mthodes appeles partir dune telle rfrence. Une telle pratique supposerait

790

Chapitre 22 : .NET Remoting

que lobjet rfrenc nest pas distant, ce qui peut tre mis en dfaut. Cela revient dire que
faire driver une classe de la classe MarshalByRefObject permet dindiquer au compilateur JIT
quune instance de cette classe peut potentiellement tre utilise dune manire distante grce
un proxy transparent. Dans le cas o cette instance et son client sont dans le mme domaine
dapplication, le client travaille avec une rfrence vers linstance et non avec un proxy transparent.

Marshalling By Value (MBV) et serialisation binaire


Le Marshalling By Value (MBV) consiste fabriquer un clone de lobjet distant, dans le mme domaine dapplication que le client. Le CLR fait en sorte que ce clone ait exactement le mme tat
que lobjet distant. Prcisons que lon dsigne par tat dun objet un moment donn, lensemble
des valeurs prises par les champs de types valeur de lobjet. Selon lapplication, ltat dun objet
contient aussi lensemble des tats pris par les objets rfrencs par les champs de types rfrence
de lobjet.
Le clone nest pas un objet distant. Le client na pas besoin dun proxy transparent pour y accder. Notez quaucun constructeur nest appel sur le clone. Ce comportement est logique
puisque le clone doit tre exactement comme lobjet original, sur lequel un constructeur a dj
t appel.
Une condition ncessaire pour utiliser la technique MBV est que le domaine dapplication du
client doit pouvoir charger lassemblage contenant la classe de lobjet distant original. Eventuellement, le client et cette classe peuvent faire partie du mme assemblage. Une autre condition
ncessaire est que lobjet distant original ne doit pas contenir des rfrences vers des objets qui
ne sont pas clonables. On entend par l quil ny a aucun intrt cloner certains objets. Par
exemple, pourquoi cloner une instance de la classe Thread hors du processus qui contient physiquement le thread auquel fait rfrence cette instance ?
En interne, le CLR reflte ltat de lobjet distant dans un stream binaire puis envoie ce
stream binaire au domaine dapplication du client. Du ct client, le CLR reoit ce stream
binaire et reconstitue ltat dans le clone. Ces oprations sont appeles respectivement srialisation et dsrialisation de lobjet. En .NET, pour quun objet soit srialisable il faut que
sa classe ait lattribut System.Serializable ou (exclusif) que sa classe tende linterface
System.Runtime.Serialization.ISerializable. Dans le premier cas vous signalez au CLR
dutiliser le mcanisme de srialisation standard, dans le second cas vous avez la possibilit
dimplmenter votre propre mcanisme de srialisation. Par dfaut tous les types primitifs sont
srialisables.
Une fois lobjet clon dans le domaine dapplication du client, les tats du clone et de lobjet
distant original sont indpendants. Les changements faits sur lun ne seront pas reflts sur
lautre. Le terme MBValue prend alors tout son sens puisque ce comportement est similaire au
comportement dun argument de type valeur pass dans une mthode. Les changements faits
sur un tel argument dans la mthode ne se refltent pas sur la variable initiale dclare dans
lappelant. La Figure 22-2 illustre ceci.
Dans lExemple 22-1, il est intressant de constater quen modifiant comme ceci la classe Foo de
faon en faire une classe MBV :

Marshalling By Value (MBV) et serialisation binaire

Domaine dapplication du client


Client

791

Domaine dapplication
contenant lobjet distant
original

...
obj1.Champs1 = 200
...

Clone de lobjet distant

Objet distant original

Champs 1 : 200
Champs 2 : 456

Champs 1 : 123
Champs 2 : 456

Figure 22 -2 : Marshalling By Value


MBVTest.cs

Exemple 22-2 :
...
[Serializable]
public class Foo{
...

// : MarshalByRefObject

<- en commentaire

...on obtient lachage suivant :


obj1:
Nom du domaine : MBVTest.exe
ThreadID
: 3620
IsObjectOutOfAppDomain(obj1)=False
IsTransparentProxy(obj1)=False
obj2:
Nom du domaine : MBVTest.exe
ThreadID
: 3620
IsObjectOutOfAppDomain(obj2)=False
IsTransparentProxy(obj2)=False
Ceci prouve que lobjet distant a bien t clon dans un nouvel objet situ dans le domaine
dapplication du client.
Dans le chapitre consacr la gnricit, en page 499 nous expliquons que la srialisation binaire sait traiter les types gnriques.

Srialisation tolrante aux changement de version


Lorsque lon exploite la srialisation dobjet, on constate quun problme courant est du
lvolution des classes srialiser et notamment, lajout de nouveaux champs. En eet,
aprs mise jour de lapplication, lorsque lon tente de dsrialiser un objet une exception
est lance car les valeurs des nouveaux champs de la classe concerne ne sont pas fournies. Pour viter ce problme vous pouvez marquer les nouveaux champs avec lattribut
System.Runtime.Serialization.OptionalFieldAttribute. Les champs concerns prendront
la valeur par dfaut de leur type.
Lespace de noms System.Runtime.Serialization contient aussi les quatre champs suivants qui
permettent de marquer une mthode afin quelle soit invoque durant une certaine tape de

792

Chapitre 22 : .NET Remoting

la srialisation/dsrialisation. Vous pouvez ainsi profiter de telles mthodes pour fournir une
valeur aux nouveaux champs :

OnDeserializingAttribute : La mthode est appele avant que ltat de lobjet dsrialis


ait t rcupr et positionn.

OnDeserializedAttribute : La mthode est appele aprs que ltat de lobjet dsrialis


ait t rcupr et positionn.

OnSerializingAttribute : La mthode est appele avant que ltat de lobjet ait t srialis.

OnSerializedAttribute : La mthode est appele aprs que ltat de lobjet ait t srialis.

La classe ObjectHandle
Dans le code de lexemple de la section de prsentation MBR, la place dutiliser la mthode
CreateInstanceAndUnwrap() on aurait pu crire :
...
ObjectHandle HObj2 = appDomain.CreateInstance("Remoting1","Foo") ;
Foo obj2 = (Foo)HObj2.Unwrap() ;
...
Une instance de la classe System.Runtime.Remoting.ObjectHandle contient les informations
ncessaires pour pouvoir utiliser un objet distant. En appelant la mthode UnWrap() vous rcuprez un proxy transparent si lobjet distant est MBR ou un clone si lobjet distant est MBV.
Vous pouvez optimiser vos applications en utilisant le fait que .NET scinde lopration de
rcupration dun objet distant en deux tapes : rcupration dune instance de la classe
ObjectHandle puis unwrappage de cette instance. En eet, le chargement des mtadonnes
de type dcrivant la classe de lobjet distant ne se fait qu la seconde tape, lors du unwrappage . Si le client courant nutilise pas lobjet distant et ne fait que le transmettre une autre
partie de lapplication, il na pas besoin de raliser le unwrappage . On conomise ainsi le
chargement des mtadonnes et donc, le chargement de lassemblage. Voici un programme qui
illustre ceci. Notez que ce programme exploite le fait que le constructeur statique dune classe est
appel par le CLR, lorsque celui-ci charge les mtadonnes de type de la classe concerne dans
un domaine dapplication.
Exemple 22-3 :
using System ;
using System.Runtime.Remoting ;
[Serializable]
public class Foo {
// Constructeur de la classe.
static Foo() {
Console.WriteLine(
"Chargement des metadonn
ees de Foo dans le domaine:" +
AppDomain.CurrentDomain.FriendlyName) ;
}
// Constructeur des instances de la classe.
public Foo() {

WrapTest.cs

Introduction lactivation des objets

793

Console.WriteLine(
"Appel au constructeur de Foo dans le domaine:" +
AppDomain.CurrentDomain.FriendlyName) ;
}
}
public class Program {
static void Main() {
Console.WriteLine("-->Avant CreateDomain()") ;
AppDomain appDomain = AppDomain.CreateDomain("Autre domaine.");
Console.WriteLine("-->Avant CreateInstance()") ;
ObjectHandle hObj = appDomain.CreateInstance("WrapTest", "Foo");
Console.WriteLine("-->Apr`es CreateInstance() et avant UnWrap()") ;
Foo obj = (Foo) hObj.Unwrap();
Console.WriteLine("-->Apr`es UnWrap()") ;
}
}
Voici ce que ce programme ache :
-->Avant CreateDomain()
-->Avant CreateInstance()
Chargement des metadonnees de Foo dans le domaine:Autre domaine.
Appel au constructeur de Foo dans le domaine:Autre domaine.
-->Apr`es CreateInstance() et avant UnWrap()
Chargement des metadonnees de Foo dans le domaine:WrapTest.exe
-->Apr`es UnWrap()
Ce programme illustre aussi le fait que le constructeur nest pas appel lors de la construction
dun clone.
Lorsque lon excute ce programme en faisant de Foo une classe MBR, lavant dernire ligne de
lachage napparat pas. Ceci souligne clairement que lobjet proxy nest pas du mme type que
lobjet distant.

Introduction lactivation des objets


Si vous avez lu les sections prcdentes, vous pouvez entrer dans le vif du sujet, autrement dit :
faire franchir les frontires virtuelles entre les processus et les frontires physiques entre les
machines vos appels dobjets dans le cas MBR, et vos objets dans le cas MBV.

Elments dune architecture distribue


Dans larchitecture dune application distribue qui utilise .NET Remoting avec les objets serveurs en MBR, il y a quatre grandes sortes dentits :

Les clients, qui appellent les objets serveurs ;

les htes, qui hbergent les objets serveur ;

les mtadonnes des types des objets serveur ;

les implmentations des objets serveurs.

794

Chapitre 22 : .NET Remoting

En rgle gnrale, on isole chacune de ces entits dans un ou plusieurs assemblages. Cette rgle
nest absolument pas une contrainte. Physiquement vous pouvez mlanger toutes sortes dentits dans un assemblage. Cependant, utiliser cette rgle prsente des avantages que vous dcouvrirez dans les pages suivantes. En outre, nous expliquerons pourquoi et comment sparer les
mtadonnes de type des objets serveurs des implmentations de ceux-ci. La Figure 22-3 illustre
la rpartition des assemblages dans une architecture distribue classique.
Domaine dapplication du client

Domaine dapplication serveur

Assemblage contenant
le code du client

Assemblage contenant le code de lhte

Assemblage(s) contenant
seulement les mtadonnes
des types des objets serveurs

Assemblage(s) contenant les


implmentations des objets serveurs et les
mtadonnes des types des objets serveurs

Figure 22 -3 : Elments dune architecture distribue

Responsabilit dun hte


Un hte a plusieurs responsabilits :

Crer un ou plusieurs canaux (channels en anglais).


Exposer des classes ou des objets serveurs accessibles par des clients, au travers dURIs.
Maintenir le processus qui contient les objets serveurs.

Il peut tre surprenant quun hte puisse exposer des objets mais aussi des classes. Cela rsulte
du fait quun objet serveur peut tre soit activ par le serveur, soit activ par le client. Dans le
premier cas un client doit connatre un objet pour pouvoir lutiliser alors que dans le second
cas, le client doit connatre une classe pour pouvoir activer une instance de cette classe.

Brve description des canaux


Un canal est un objet qui permet de communiquer travers le rseau. Il existe aussi des canaux
spcialiss dans la communication intra processus. Un canal contient trois grands paramtres
qui sont le port rseau de la machine quil utilise, le protocole de communication quil utilise
(TCP, HTTP, IPC) et la faon dont les donnes sont formates pour les faire transiter (format
binaire standard, SOAP, format binaire ou XML propritaire). Par dfaut le formatage binaire
est associ aux protocoles TCP et IPC, et le formatage SOAP est associ au protocole HTTP, mais
ce comportement est facilement modifiable. Pour utiliser .NET Remoting il faut deux canaux
avec le mme protocole et la mme faon de formater les donnes. Un canal du ct client et un
canal du ct serveur. Pour aborder cette section vous navez pas besoin den savoir plus sur les
canaux. Mais nous revenons sur ce sujet en dtail un peu plus loin dans ce chapitre, dans une
section consacre aux canaux.

Appels synchrones, asynchrones et asynchrones sans retour


Les appels dobjets distants peuvent tre synchrones, asynchrones ou asynchrones sans retour. Il
est apprciable de constater que la technique pour appeler un objet dune manire asynchrone
est la mme, que lobjet soit distant ou non. Cette technique est dcrite page 171.

Service dactivation par le serveur (WKO)

795

Activation dun objet vs. Cration dun objet


Prcisons quen .NET Remoting on parle plutt dactivation dun objet que de cration dun objet.
Cela vient du fait que lors de la cration dun objet destin tre utilis dune faon distante,
il y a potentiellement un grand nombre dactions qui ont lieu entre la demande de cration et
la disponibilit eective du nouvel objet. Cest le droulement de ces actions qui porte le nom
dactivation.

Service dactivation par le serveur (WKO)


Les sources et les excutables de cette section sont dans les rpertoires :
Chap22 .NET Remoting/WKO singlecall
Chap22 .NET Remoting/WKO singleton
Une tape important, lorsque lon spcifie une architecture distribue, est de dfinir pour
chaque objet distant qui, du client ou du serveur (i.e de lhte) va lactiver. .NET Remoting
permet aux clients dactiver un objet distant sur le serveur ou dutiliser un objet dj existant
sur le serveur. Nous soulignions que lorsquun objet est activ par le serveur, le client na pas
appeler le constructeur de la classe de lobjet. Cet aspect peut tre dterminant dans le choix
de lactivation par le serveur ou par le client. Nous exposons ici le cas o lobjet est activ par
le serveur et nous prsenterons ensuite le cas de lactivation par le client.
Dans le cas o un client utilise un objet distant activ par le serveur, il est fortement conseill
de faire en sorte que le client ne connaisse pas la classe dont lobjet distant est instance. Pour
cela, il est judicieux de dfinir les interfaces supportes par la classe de lobjet distant dans un
assemblage spcialement prvu cet eet. Dans notre vue densemble des lments dune architecture distribue (celle de la Figure 22-3), cet assemblage joue le rle des mtadonnes de
type des objets serveurs. Concrtement, cet assemblage sera prsent la fois dans le domaine
dapplication du client et dans le domaine dapplication du serveur. Voici par exemple le code
dun tel assemblage :
Exemple 22-4 :

Interface.cs

namespace NommageInterface {
public interface IAdditionneur {
double Add(double d1, double d2) ;
}
}
Intressons nous maintenant lhte et aux classes des objets serveurs. Nous allons placer ces
deux types dentits dans le mme assemblage. Cependant nous verrons quil existe des htes
standard, et que pour les utiliser, il faut que les classes des objets serveurs soient isoles dans
des assemblages. Voici le code dun assemblage contenant la fois la classe dobjet serveur et un
hte propritaire. Notez bien lordonnancement des trois tches dans le code de lhte : cration
dun canal, activation dun objet serveur, maintient du processus.
Exemple 22-5 :
using System ;
using System.Runtime.Remoting ;
using System.Runtime.Remoting.Channels ;

Serveur.cs

796

Chapitre 22 : .NET Remoting


using System.Runtime.Remoting.Channels.Http ;
using NommageInterface ;
namespace NommageServer {
// Implementation de la classe des objets serveurs.
public class CAdditionneur : MarshalByRefObject, IAdditionneur {
public CAdditionneur() {
Console.WriteLine("CAdditionneur ctor") ;
}
public double Add(double d1, double d2) {
Console.WriteLine("CAdditionneur Add( {0} + {1} )", d1, d2) ;
return d1 + d2 ;
}
}
// Implementation de lhote.
class Program {
static void Main() {
// 1)Creation dun canal http sur le port 65100
// enregistre ce canal dans le domaine dapplication courant.
HttpChannel canal = new HttpChannel(65100);
ChannelServices.RegisterChannel(canal, false);
// 2) Ce domaine dapplication pr
esente un objet de type
//
IAdditionneur associ
e au point terminal ServiceAjout.
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CAdditionneur),
"ServiceAjout",
WellKnownObjectMode.SingleCall);
// 3) Maintient du processus courant.
Console.WriteLine(
"Pressez une touche pour stopper le serveur.");
Console.Read() ;
}
}
}

Notez lutilisation du point terminal (end point en anglais) "ServiceAjout" associ lobjet activ. En concatnant les informations de protocole, de machine, de port et de point terminal
on obtient un URI qui localise compltement lobjet activ par le serveur. En loccurrence cet
URI est http://localhost:65100/ServiceAjout. Pour cette raison, on qualifie dobjet bien connu (wellknown object en anglais) un objet activ par le serveur et qui a un point terminal. Dans la suite
on utilisera lacronyme WKO (Well-Known Object) pour parler dun objet activ par le serveur.
Lorsque lon dcrira plus en dtail le mcanisme interne de .NET Remoting, on verra que le
serveur peut publier un objet quil active sans utiliser de point terminal.
Il ne nous reste plus qu coder lassemblage qui contient le code du client. Il est ncessaire
de crer un canal, puis de rcuprer un proxy transparent vers lobjet distant associ lURI

Service dactivation par le serveur (WKO)

797

http://localhost:65100/ServiceAjout. ce stade on peut utiliser le proxy transparent, matrialis


par une rfrence, exactement comme une rfrence vers un objet non distant.
Client.cs

Exemple 22-6 :
using
using
using
using

System ;
System.Runtime.Remoting ;
System.Runtime.Remoting.Channels ;
System.Runtime.Remoting.Channels.Http ;

using NommageInterface ;
namespace NommageClient {
class Program {
static void Main() {
// Cree un canal HTTP puis enregistre
// ce canal dans le domaine dapplication courant.
// (la valeur 0 pour le num
ero de port c
ot
e client signifie
// que ce numero de port est choisi automatiquement
// par le CLR).
HttpChannel canal = new HttpChannel(0);
ChannelServices.RegisterChannel(canal, false);
` partir de
// Obtient un proxy transparent sur lobjet distant a
// son URI, puis transtype le proxy distant en IAdditionneur.
MarshalByRefObject objRef = (MarshalByRefObject)
RemotingServices.Connect(
typeof(IAdditionneur),
"http://localhost:65100/ServiceAjout");
IAdditionneur obj = objRef as IAdditionneur;
// Appel dune methode sur lobjet distant.
double d = obj.Add(3.0, 4.0);
Console.WriteLine("Valeur retourn
ee:" + d) ;
}
}
}
Nous disposons maintenant de trois fichiers sources C  , qui vont chacun permettre de produire
un assemblage avec les trois instructions en ligne de commande suivantes :
>csc.exe /target:library Interface.cs
>csc.exe Serveur.cs /r:Interface.dll
>csc.exe Client.cs /r:Interface.dll
Nous aurions pu aussi utiliser Visual Studio pour placer ces trois projets dans un mme espace
de travail.
Il vous sut maintenant de lancer Serveur.exe puis Client.exe. Voici les achages de ces
programmes :

798

Chapitre 22 : .NET Remoting

Achage de Serveur.exe
Pressez une touche pour stopper le serveur.
CAdditionneur ctor
CAdditionneur Add( 3 + 4 )
Achage de Client.exe
Valeur retournee:7

Service dactivation par le serveur : simple appel (single call) ou


singleton
Dans lexemple de la section prcdente, nous avons volontairement eectu un seul appel de
mthode sur lobjet serveur. Examinons ce quaurait ach le serveur si nous avions eectu
conscutivement deux appels Add(), du ct client :
Client.cs
...
obj.Add( 3.0 , 4.0 ) ;
obj.Add( 5.0 , 6.0 ) ;
...
Achage du serveur :
Pressez une touche pour stopper le serveur.
CAdditionneur ctor
CAdditionneur Add( 3 + 4 )
CAdditionneur ctor
CAdditionneur Add( 5 + 6 )
Daprs cet achage, il semblerait que le serveur active un objet chaque appel du client
puisque le constructeur est appel chaque appel. Cest en fait, la description exacte de la
ralit. Ce comportement est adopt par le serveur car nous avions dclar lobjet WKO en
mode simple appel (single call en anglais) lorsque nous avions crit :
...WellKnownObjectMode.SingleCall...
Il existe un autre mode dappel pour les objets activs par le serveur, appel le mode singleton.
Ce mode dappel est choisi en remplaant la valeur SingleCall par la valeur Singleton. Le
mode dappel singleton oblige le serveur activer lobjet au premier appel dun client, puis
maintenir cet objet pour tous les prochains appels de tous les clients. Une consquence immdiate est que tous les clients partagent le mme objet. Plusieurs threads du pool de thread
peuvent excuter les mthodes de cet objet simultanment. Il est donc ncessaire de prvoir un
mcanisme de synchronisation pour protger ces ressources des accs concurrents. Voici ce que
le serveur aurait ach si nous avions dclar lobjet en mode dappel singleton :
Pressez une touche pour stopper le serveur.
CAdditionneur ctor
CAdditionneur Add( 3 + 4 )
CAdditionneur Add( 5 + 6 )

Activation par le client (CAO)

799

Le serveur attend un premier appel dun client avant dactiver lobjet dans les deux modes dappel, singleton et simple appel.
Nous prfrons parler de service dactivation dun objet par le serveur que de parler dactivation
dobjet par le serveur. En eet, le client na pas vraiment une rfrence vers un objet distant mais
bien une rfrence vers un service dactivation dobjets distants. Concrtement si lobjet utilis
par un client est dtruit, au prochain appel le client utilisera un autre objet, activ automatiquement par le serveur.

Activation par le client (CAO)


Les sources et les excutables de cette section sont dans les rpertoires :
Chap22 .NET Remoting/CAO
Chap22 .NET Remoting/CAO avec le mot-cl new
Nous allons utiliser lacronyme CAO pour parler dun objet activ par un client (Client Activated
Object). Pour activer un objet partir du client, ce dernier doit connatre la classe de lobjet. Ce
point constitue une dirence fondamentale par rapport larchitecture mise en uvre lors
du service dactivation dobjets par le serveur. En eet, dans le cas WKO, le client se contente
de connatre les interfaces quil souhaite utiliser sur lobjet. Dans le cas dun objet CAO, pour
rendre accessible au client les mtadonnes de type de la classe de lobjet, il faut lui fournir lassemblage qui contient la classe de lobjet. Cette contrainte vous choque srement, aussi sachez
que la section suivante va expliquer comment la contourner. En attendant, dfinissons le code
C  dun assemblage qui contient la classe CAdditionneur :
Exemple 22-7 :

ObjServeur.cs

using System ;
namespace NommageObjServeur {
public interface IAdditionneur {
double Add(double d1, double d2) ;
}
// Implementation de la classe des objets serveurs.
public class CAdditionneur : MarshalByRefObject, IAdditionneur {
public CAdditionneur() {
Console.WriteLine("CAdditionneur ctor") ;
}
public double Add(double d1, double d2) {
Console.WriteLine("CAdditionneur Add( {0} + {1} )", d1, d2) ;
return d1 + d2 ;
}
}
}
Intressons-nous maintenant au code source de lassemblage contenant lhte. Cet hte est trs
similaire celui que nous avons utilis pour activer un objet par le serveur. La seule dirence
vient du fait que lon utilise la mthode statique RegisterActivatedServiceType() pour indiquer que la classe est activable partir dun autre domaine dapplication au lieu dutiliser

800

Chapitre 22 : .NET Remoting

la mthode RegisterWellKnownServiceType() qui permet denregistrer le service dactivation


dun objet bien connu (WKO).
Serveur.cs

Exemple 22-8 :
using
using
using
using

System ;
System.Runtime.Remoting ;
System.Runtime.Remoting.Channels ;
System.Runtime.Remoting.Channels.Http ;

using NommageObjServeur ;
namespace NommageServer {
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(65100) ;
ChannelServices.RegisterChannel(canal, false) ;
// Fait en sorte que ce domaine dapplication pr
esente
// la classe CAdditionneur, qui peut ainsi
etre instanci
ee
// par des clients distants.
RemotingConfiguration.RegisterActivatedServiceType(
typeof(CAdditionneur));
Console.WriteLine(
"Pressez une touche pour stopper le serveur.") ;
Console.Read() ;
}
}
}
Le client est lui aussi, trs similaire un client qui utilise un objet activ par le serveur. La seule
dirence vient du fait que lon utilise la mthode statique Activator.CreateInstance() pour
rcuprer un proxy transparent sur lobjet que lon active.
Exemple 22-9 :
using
using
using
using
using
using

System ;
System.Runtime.Remoting ;
System.Runtime.Remoting.Channels ;
System.Runtime.Remoting.Channels.Http ;
System.Runtime.Remoting.Activation ;
NommageObjServeur ;

namespace NommageClient {
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(0) ;
ChannelServices.RegisterChannel(canal, false) ;

Client.cs

Activation par le client (CAO)

801

// Active une instance du type distant et obtient un


// proxy transparent sur cette nouvelle instance.
IAdditionneur obj = Activator.CreateInstance(
typeof(CAdditionneur),
null,
new Object[] { new UrlAttribute("http://localhost:65100")})
as IAdditionneur;
// Appel dune methode sur lobjet distant.
double d = obj.Add(3.0, 4.0) ;
Console.WriteLine("Valeur retourn
ee:" + d) ;
}
}
}
Nous disposons maintenant de trois fichiers sources C  , qui vont chacun permettre de produire
un assemblage, avec les trois instructions en ligne de commande suivante :
>csc.exe /target:library ObjServeur.cs
>csc.exe Serveur.cs /r:ObjServeur.dll
>csc.exe Client.cs /r:ObjServeur.dll
Il vous sut maintenant de lancer Serveur.exe puis Client.exe. Vous obtiendrez les mmes
achages que ceux obtenus lors de lactivation du serveur. Cet exemple ne sut donc pas
donner une dirence perceptible par rapport au mode dactivation serveur. Cependant, la diffrence de comportement avec le cas o lobjet est activ par le serveur en mode singleton, est
que chaque client disposera de son propre objet. La dirence de comportement avec le cas o
lobjet est activ par le serveur en mode simple appel, est que cest le mme objet qui est utilis
lors de plusieurs appels.

Activation de lobjet partir du client avec le mot-cl new


Le client prcdent soure de gros dfauts : le code nactive pas lobjet distant avec la syntaxe
C  utilisant le mot-cl new. Pire encore, le client ne peut pas choisir quel constructeur de
la classe CAdditionneur doit utiliser le serveur. La mthode statique RemotingConfiguration.RegisterActivatedClientType() permet de remdier ces deux problmes. Elle permet
de spcifier que chaque instanciation de la classe CAdditionneur dans le domaine dapplication
courant se fasse sur le serveur.
Client.cs

Exemple 22-10 :
...
static void Main() {
HttpChannel canal = new HttpChannel(0) ;
ChannelServices.RegisterChannel(canal, false) ;
RemotingConfiguration.RegisterActivatedClientType(
typeof(CAdditionneur),
"http://localhost:65100");
IAdditionneur obj = (IAdditionneur)new CAdditionneur();

802

Chapitre 22 : .NET Remoting

double d = obj.Add(3.0, 4.0) ;


Console.WriteLine("Valeur retourn
ee:" + d) ;
}
...

Problme potentiel quant la dure de vie


Contrairement au comportement de la technologie COM/DCOM, le client nest pas responsable
de la dure de vie dun objet quil active. Une consquence fcheuse est quun client peut chercher utiliser un objet distant quil a activ, mais qui nexiste plus. Dans ce cas une exception de
type System.Runtime.Remoting.RemotingException est retourne au client. Pour cette raison
il faut toujours prvoir le traitement des exceptions du ct client.
La technique utilise par le serveur pour grer la dure de vie dun objet est prsente un peu
plus loin.

Le design pattern factory et loutil soapsuds.exe


Lors de la prsentation de la technique dactivation dun objet par le client (CAO), nous nous
sommes aperus quil fallait que lassemblage du client rfrence lassemblage contenant la
classe de lobjet. On rappelle que cette contrainte a pu tre vite dans le cas WKO, puisque le
client peut se contenter de ne connatre que des interfaces. Nanmoins si la classe de lobjet serveur ne supporte pas dinterfaces, on serait a priori oblig de rfrencer lassemblage contenant
cette classe dans lassemblage client.
Il est souvent inacceptable de dployer lassemblage contenant limplmentation de lobjet serveur chez les clients. Cette implmentation doit absolument tre diuse le moins possible.
Dans cet assemblage, le client na besoin que des mtadonnes de la classe de lobjet serveur.
Il existe deux solutions pour rsoudre ce problme : le design pattern (Gof) factory ou loutil
Soapsuds.exe.

Le design pattern factory


Les sources et les excutables de cette section sont dans le rpertoire :
Chap22 .NET Remoting/CAO design pattern factory
Lide du design pattern factory (usine ou fabrique en franais) est de faire construire lobjet CAO
par un objet WKO. Lhte na alors besoin dexposer que le service WKO. Comme nous avons
vu que dans ce cas le client peut se satisfaire dune interface, le problme est rsolu.
Modifions le code prsent pour activer lobjet partir du serveur. Il faut tout dabord prsenter
une nouvelle interface IFabrique au client :
Exemple 22-11 :
namespace NommageInterface {
public interface IAdditionneur {
double Add(double d1, double d2) ;
}
public interface IFabrique {

Interface.cs

Le design pattern factory et loutil soapsuds.exe

803

IAdditionneur FabriqueNouvelAdditionneur();
}
}
Ensuite il faut prvoir la classe CFabrique qui implmente IFabrique, et exposer un objet
CFabrique en mode dappel singleton (il vaut mieux ne pas utiliser le mode simple appel dans
ce cas) :
Exemple 22-12 :

Serveur.cs

...
public class CAdditionneur : MarshalByRefObject, IAdditionneur {
public CAdditionneur() {
Console.WriteLine("CAdditionneur ctor") ;
}
public double Add(double d1, double d2) {
Console.WriteLine("CAdditionneur Add( {0} + {1} )", d1, d2) ;
return d1 + d2 ;
}
}
public class CFabrique : MarshalByRefObject, IFabrique {
public IAdditionneur FabriqueNouvelAdditionneur() {
return (IAdditionneur)new CAdditionneur();
}
}
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(65100) ;
ChannelServices.RegisterChannel(canal, false) ;
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CFabrique),
"ServiceFabrique",
WellKnownObjectMode.Singleton);
Console.WriteLine(
"Pressez une touche pour stopper le serveur.") ;
Console.Read() ;
}
}
...
Enfin, on peut activer une instance de CAdditionneur partir du client, sans connatre les classes
CAdditionneur et CFabrique. Cest exactement le but de cette manipulation :
Exemple 22-13 :
...
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(0) ;
ChannelServices.RegisterChannel(canal, false) ;

Client.cs

804

Chapitre 22 : .NET Remoting


MarshalByRefObject tmpObj = (MarshalByRefObject)
RemotingServices.Connect(
typeof(IFabrique),
"http://localhost:65100/ServiceFabrique");
IFabrique fabrique = tmpObj as IFabrique;
IAdditionneur obj = fabrique.FabriqueNouvelAdditionneur();
double d = obj.Add(3.0, 4.0) ;
Console.WriteLine("Valeur retourn
ee:" + d) ;
}
}
...

Ce design pattern marche car la classe CAdditionneur et la classe CFabrique hritent toutes les
deux de la classe MarshalByRefObject.

Loutil Soapsuds.exe
Les sources et les excutables de cette section sont dans le rpertoire :
Chap22 .NET Remoting/CAO utilisation de soapsuds.exe
Loutil Soapsuds.exe, fourni avec le framework .NET est capable dextraire les mtadonnes de
la classe de lobjet serveur partir de lassemblage contenant cette classe. partir de ces mtadonnes, Soapsuds.exe peut fabriquer soit un fichier C  prt tre compil, soit un assemblage
qui ne contient que ces mtadonnes. Voici la ligne de commande saisir pour fabriquer lassemblage ObjServeurPourClient.dll partir de lassemblage ObjServeur.dll.
>soapsuds /ia:ObjServeur /oa:ObjServeurPourClient.dll
Voici la ligne de commande saisir pour fabriquer le fichier source C  ObjServeur.cs partir
de lassemblage ObjServeur.dll.
>soapsuds /ia:ObjServeur /gc
Voici un extrait du fichier source C  ObjServeur.cs :
Exemple :

ObjServeur.cs

...
public class CAdditionneur :
System.Runtime.Remoting.Services.RemotingClientProxy, IAdditionneur {
// Constructor
public CAdditionneur() { }
public Object RemotingReference { get { return (_tp) ; } }
[SoapMethod(SoapAction =
@"http://schemas.microsoft.com/clr/nsassem/
NommageInterface.CAdditionneur/Interface#Add")]
public virtual Double Add(Double d1, Double d2) {
return ((CAdditionneur)_tp).Add(d1, d2) ;
}
}
...

Service de dure de vie

805

On a donc bien une nouvelle classe CAdditionneur prsentant les mmes mthodes publiques que loriginale et drivant de la classe System.Runtime.Remoting.Services.RemotingClientProxy. Nous vous conseillons de consulter la documentation relative cette classe
dans les MSDN, car vous pouvez lutiliser pour avoir un mcanisme dauthentification du
client ou pour spcifier quel serveur proxy utiliser si le client est derrire un pare feu (firewall
en anglais). Vous pouvez nanmoins indiquer Soapsuds.exe que vous ne souhaitez pas que
les classes drivent de RemotingClientProxy en prcisant loption /nowp. Dans ce cas elles
driveront directement de MarshalByRefObject.
Loutil Soapsuds.exe permet aussi de fabriquer lassemblage contenant les mtadonnes des
classes serveurs partir dun serveur distant qui expose une instance de cette classe, via un canal
HTTP. Voici le code du serveur :
...
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(65100) ;
ChannelServices.RegisterChannel(canal, false) ;
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CAdditionneur),
"MonRep/ServiceAjout.soap",
WellKnownObjectMode.SingleCall);
...
}
}
...
Voici la ligne de commande utiliser du ct client :
>Soapsuds /url:http://localhost:65100/MonRep/ServiceAjout.soap?wsdl
/oa:ObjServeurPourClient.dll
Notez lextension soap associe au point terminal et lutilisation du suxe ?wsdl sur lURL qui
permet dindiquer au serveur ce quon attend de lui, savoir obtenir les mtadonnes de type.
Prcisons enfin que loutil soapsuds.exe ne prend pas en compte les constructeurs avec arguments. Lutilisation du design pattern factory est donc plus adapte aux classes qui doivent absolument tre utilises avec des constructeurs avec arguments.

Service de dure de vie


La question de la dure de vie dun objet WKO en mode simple appel ne se pose pas, puisque
lobjet est dtruit immdiatement aprs le premier appel. Nous allons exposer ici la solution
mise en place par .NET Remoting pour dterminer quand un objet activ par une entit distante doit tre dtruit. Cette solution repose sur un mcanisme de bail (lease en anglais). Il est
important de noter que cette solution concerne la fois les objets CAO et les objets WKO en
mode singleton. Il est aussi important de noter que tout ce que nous allons expliquer ici ne
sapplique quaux objets dont la classe drive de MarshalByRefObject et qui sont accds par
une entit situe hors de leurs domaines dapplication. Rappelons que les objets qui nentrent
pas dans cette catgorie voient leurs dures de vie gres par le ramasse-miettes.

806

Chapitre 22 : .NET Remoting

Dans un domaine dapplication, il nexiste pas de rfrences fortes vers un objet qui est accd
par une entit situe hors de son domaine dapplication. Pour empcher le ramasse-miettes
de collecter un tel objet, le CLR fait en sorte que chaque domaine dapplication contienne un
administrateur de baux (lease manager en anglais). Un administrateur de baux alloue une dure
de bail, au moment de lactivation de chaque objet MBR utilis dune manire distante. Ladministrateur de baux vrifie priodiquement le bail de chacun de ces objets. Ceux dont le bail nest
plus valide seront automatiquement dtruits lors de la prochaine collecte du ramasse-miettes.
Cependant, .NET Remoting rend ce mcanisme de bail assez souple. Concrtement, la dure du
bail dun objet peut tre prolonge de trois faons direntes :

La dure du bail est automatiquement prolonge chaque appel de lobjet.


La dure du bail peut tre prolonge directement par un appel une certaine mthode.
Enfin, lorsque ladministrateur de baux dtecte un bail invalide, il consulte squentiellement les sponsors de lobjet concern avant de prendre la dcision de le dtruire. Les sponsors sont des objets qui ont la capacit de prolonger le bail dun objet qui est sur le point
dtre dtruit. La classe dun sponsor doit driver de MarshalByRefObject. Les sponsors
peuvent tre des objets distants. Si la consultation dun sponsor dpasse une certaine dure, il ne sera plus consult et ladministrateur de baux passe la consultation du prochain
sponsor.

Lespace de noms System.Runtime.Remoting.Lifetime contient les deux interfaces ILease et


ISponsor, spcialement conues pour la gestion des mcanismes que nous venons de dcrire :
interface System.Runtime.Remoting.Lifetime.ILease{
// Etat du bail. Lenumeration LeaseState a les valeurs :
//
Null
Le bail nest pas initialis
e.
//
Initial Le bail est en cours dinitialisation.
//
Active
Le bail est initialis
e et valide.
//
Renewing Le bail nest plus valide et les sponsors sont en train
//
detre consult
es, les uns apr`
es les autres.
//
Expired Le bail a expir
e.
LeaseState
CurrentState{get;}
// Valeur initiale de la dur
ee bail.
// Cette valeur est modifiable seulement pendant
// linitialisation du bail.
TimeSpan
InitialLeaseTime{get;set;}
// Valeur du prolongement de la dur
ee bail lors
// de lappel dune methode.
TimeSpan
RenewOnCallTime{get;set;}
// Valeur courante de la duree du bail.
TimeSpan
CurrentLeaseTime{get;}
// Prolonge la duree du bail.
TimeSpan
Renew(TimeSpan) ;
// Methodes pour gerer lensemble des sponsors de lobjet.

Service de dure de vie


void
void
void

807

Register(ISponsor) ;
Register(ISponsor,TimeSpan) ;
UnRegister(ISponsor) ;

// Duree maximale dattente pour la consultation dun sponsor.


TimeSpan
SponsorshipTimeout{get;set;}
}
interface System.Runtime.Remoting.Lifetime.ISponsor{
Timespan Renewal(ILease) ;
}
Comme vous le constatez, lutilisation de ces interfaces est particulirement intuitive. Vous pouvez obtenir le bail dun objet en appelant la mthode object MarshalByRefObject.GetLifetimeService() sur lobjet, ou en appelant la mthode statique object RemotingServices.GetLifetimeService(object). Par exemple :
...
ILease Bail = (ILease) obj.GetLifetimeService() ;
Bail.Renew(TimeSpan.FromSeconds(30)) ;
...
Limplmentation du bail est la classe interne Lease de mscorlib.dll. Vous navez pas accs
cette classe.
Les trois paramtres du bail dun objet sont la dure initiale du bail, la dure de prolongement
lors de lappel dune mthode et la dure maximale dattente pour la consultation dun sponsor.
Pour un objet donn, ces trois paramtres prennent leurs valeurs par dfaut dans le fichier de
configuration de la machine. Par dfaut ces dures sont respectivement de 5 minutes, 2 minutes et 2 minutes. Les proprits statiques de la classe System.Runtime.Remoting.Lifetime.
LifetimeServices permettent de configurer ces valeurs par dfaut au niveau du domaine dapplication courant. Pour aecter ces valeurs au niveau des objets dune classe, il faut rcrire la
mthode virtuelle object MarshalByRefObject.GetLifetimeService(). Par exemple :
Exemple 22-14 :
...
using System.Runtime.Remoting.Lifetime ;
...
public class CAdditionneur : MarshalByRefObject, IAdditionneur {
public override object InitializeLifetimeService() {
ILease bail = (ILease)base.InitializeLifetimeService();
if (bail.CurrentState == LeaseState.Initial) {
bail.InitialLeaseTime = TimeSpan.FromSeconds(50);
bail.RenewOnCallTime = TimeSpan.FromSeconds(20);
bail.SponsorshipTimeout = TimeSpan.FromSeconds(20);
}
return bail ;
}
...
}
...

808

Chapitre 22 : .NET Remoting

Vous avez la possibilit de spcifier une dure de bail infinie en aectant la valeur TimeSpan.
Zero la proprit InitialLeaseTime dun bail.
Vous pouvez dfinir votre propre classe de sponsors. Nous rappelons quune telle classe doit
driver de MarshalByRefObject et implmenter linterface ISponsor. Lutilit dune classe de
sponsor apparat durant la spcification de larchitecture distribue, lorsque la vie dun objet
dpend logiquement dune condition. La condition la plus courante est que certains clients
soient toujours actifs. Il sut alors davoir un sponsor pour chaque client, qui prolonge le bail
de lobjet tant que le client associ au sponsor le dsire. Mfiez-vous cependant de ce type de
mcanisme, dit de ping. La technologie DCOM utilise ce type de mcanisme. Lexprience a
prouv que son cot nest pas acceptable ds que le nombre de clients dpasse un certain seuil
fonction de larchitecture.

Configurer la partie Remoting dune application


Les sources et les excutables de cette section sont dans le rpertoire :
Chap22 .NET Remoting/Configurer la partie remoting dune application
Les exemples fournis pour exposer les mcanismes dactivation dobjets distants sourent dun
problme majeur : ils ne sont pas configurables. Cest--dire quune fois compils, on ne peut
plus changer les numros de port des canaux ou le nom de la machine du serveur. Nous allons
montrer comment faire en sorte que les paramtres de configuration soient consultables et modifiables, aussi bien du ct de lhte que du ct du client. Comme vous pouvez vous en douter,
ces paramtres vont tre spcifis dans un document XML.
Pour illustrer ces possibilits de configuration, nous allons nous servir de trois classes dfinies
dans un nouvel assemblage :
Exemple 22-15 :

ObjServeur.cs

using System ;
namespace NommageObjServeur {
public interface IAdditionneur {
double Add(double d1, double d2) ;
}
public class CAdditionneur : MarshalByRefObject, IAdditionneur {
public CAdditionneur() {
Console.WriteLine("CAdditionneur ctor") ;
}
public double Add(double d1, double d2) {
Console.WriteLine("CAdditionneur Add( {0} + {1} )", d1, d2) ;
return d1 + d2 ;
}
}
public interface IMultiplicateur {
double Mult(double d1, double d2) ;
}
public class CMultiplicateur : MarshalByRefObject, IMultiplicateur {
public CMultiplicateur() {

Configurer la partie Remoting dune application

809

Console.WriteLine("CMultiplicateur ctor") ;
}
public double Mult(double d1, double d2) {
Console.WriteLine("CMultiplicateur Mult( {0} + {1} )", d1, d2) ;
return d1 * d2 ;
}
}
public interface IDiviseur {
double Div(double d1, double d2) ;
}
public class CDiviseur : MarshalByRefObject, IDiviseur {
public CDiviseur() {
Console.WriteLine("CDiviseur ctor") ;
}
public double Div(double d1, double d2) {
Console.WriteLine("CDiviseur Div( {0} + {1} )", d1, d2) ;
return d1 + d2 ;
}
}
}

Configuration dun hte


Le document XML suivant permet dexposer la classe CAdditionneur en mode dappel WKO
singleton et la classe CMultiplicateur en mode WKO simple appel. Paralllement la classe
CDiviseur est expose de faon pouvoir tre active par des clients distants. Ces trois expositions se font par le canal HTTP, accessible via le port 65100. Lattribut ref aurait pu tre positionn lune des valeurs tcp ou ipc si nous souhaitions exploiter un canal de type TCP ou
IPC. Dans le cas IPC, nous naurions pas eu besoin de fournir un numro de port.
Chaque type expos doit tre prfix par son espace de noms. De plus, ce nom de classe doit tre
suivi par le nom de lassemblage dans lequel il est dfini. Si cet assemblage a un nom fort, vous
devez mettre le nom fort tout entier :
Exemple 22-16 :

Hote.config

<configuration>
<system.runtime.remoting>
<application name = "Serveur">
<service>
<wellknown type="NommageObjServeur.CAdditionneur,ObjServeur"
mode ="Singleton" objectUri="Service1.rem" />
<wellknown type="NommageObjServeur.CMultiplicateur,ObjServeur"
mode ="SingleCall" objectUri="Service2.rem" />
<activated type="NommageObjServeur.CDiviseur,ObjServeur" />
</service>
<channels>
<channel port="65100" ref="http" />
</channels>

810

Chapitre 22 : .NET Remoting


</application>
</system.runtime.remoting>
</configuration>

Grce la mthode statique void RemotingConfiguration. Configure(string) qui prend en


paramtre le nom du document XML, le code de lhte est vraiment rduit au minimum :
Exemple 22-17 :

Serveur.cs

...
class Program {
static void Main() {
RemotingConfiguration.Configure("Hote.config", false) ;
Console.WriteLine("Pressez une touche pour stopper le serveur.") ;
Console.Read() ;
}
}
...

Configuration du client
Le document XML suivant permet dutiliser partir dun client les deux services WKO et la
classe expose par le serveur prcdent :
Exemple 22-18 :

Client.config

<configuration>
<system.runtime.remoting>
<application name = "Client">
<client>
<wellknown
type="NommageObjServeur.CAdditionneur,ObjServeur"
url="http://localhost:65100/Service1.rem" />
<wellknown
type="NommageObjServeur.CMultiplicateur,ObjServeur"
url="http://localhost:65100/Service2.rem" />
</client>
<client url="http://localhost:65100/">
<activated type ="NommageObjServeur.CDiviseur,ObjServeur"/>
</client>
</application>
</system.runtime.remoting>
</configuration>
Les URLs spcifies commencent par le mode daccs http:// mais elles auraient pu commencer
par un des modes daccs tcp:// ou ipc:// si notre canal rcepteur tait de type TCP ou IPC.
Dans le cas IPC, il aurait fallu remplacer la paire nom de la machine hte/port (en loccurrence
localhost:65100) par le nom du pipe nomm sous jacent. Ce nom est fourni par lattribut
portName dans la dclaration du canal dans le fichier de configuration du serveur. Pour charger
les paramtres de configuration de ce fichier XML dans le domaine dapplication du client, on
utilise galement la mthode statique void RemotingConfiguration.Configure(string) :

Configurer la partie Remoting dune application

811

Exemple 22-19 :
...
using NommageObjServeur ;
...
class Program {
static void Main() {
RemotingConfiguration.Configure("Client.config", false);
CAdditionneur objA = new CAdditionneur() ;
double dA = objA.Add(3.0, 4.0) ;
CMultiplicateur objM = new CMultiplicateur() ;
double dM = objM.Mult(3.0, 4.0) ;
CDiviseur objD = new CDiviseur() ;
double dD = objD.Div(3.0, 4.0) ;
}
}
...
Les appels aux constructeurs des objets WKO (objA et objM) ne sont l que pour rcuprer une
rfrence (en fait un proxy transparent) vers le type adquat. Concrtement ils ne provoquent
aucun accs au rseau.
partir dun fichier de configuration serveur, vous pouvez configurer les paramtres de ladministrateur de baux. Pour plus dinformations ce sujet nous vous conseillons de consulter
larticle <lifetime> Element des MSDN.
A priori, on ne peut pas utiliser dinterfaces puisquon est oblig dappeler le constructeur. Le
design pattern factory ne peut sappliquer puisque mme dans le cas de lactivation du serveur, le
client ne se contente pas dune interface. Il faut alors utiliser loutil Soapsuds.exe pour viter de
fournir les assemblages contenant les implmentations des objets serveurs aux clients. Cependant nous allons montrer une astuce permettant lutilisation dinterfaces en mode WKO, et
donc, en mode CAO aussi en utilisant le design pattern factory.

Utilisation dinterfaces et de fichiers de configuration


Les sources et les excutables de cette section sont dans le rpertoire :
Chap22 .NET Remoting/Utilisation dinterfaces et de fichiers de configuration
Tout dabord, plaons les interfaces dans un assemblage qui va tre rfrenc la fois par le client
et par le serveur :
Exemple 22-20 :
namespace NommageInterface {
public interface IAdditionneur {
double Add(double d1, double d2) ;
}
public interface IMultiplicateur {

Interface.cs

812

Chapitre 22 : .NET Remoting


double Mult(double d1, double d2) ;
}
public interface IDiviseur {
double Div(double d1, double d2) ;
}
public interface IFabrique {
IDiviseur FabriqueNouvelDiviseur() ;
}
}

Ensuite, toute lastuce repose sur lutilisation dune table, qui associe les services WKO configurs par le serveur des interfaces configures dans le client. Voici donc le code du client, avec la
gestion de la table dicoTypes :
Exemple 22-21 :
using
using
using
using
using
using

Client.cs

System ;
System.Runtime.Remoting ;
System.Runtime.Remoting.Channels ;
System.Runtime.Remoting.Channels.Http ;
System.Runtime.Remoting.Activation ;
System.Collections ;

using NommageInterface ;
namespace NommageClient {
class NotreActivateur {
private static bool bInit ;
// Table dassociation : interfaces/services distants WKO.
private static IDictionary dicoTypes ;
public static Object GetObject(Type type) {
if (!bInit)
InitdicoTypes() ;
WellKnownClientTypeEntry entry = (WellKnownClientTypeEntry)
dicoTypes[type] ;
return Activator.GetObject(entry.ObjectType, entry.ObjectUrl) ;
}
private static void InitdicoTypes() {
bInit = true ;
dicoTypes = new Hashtable() ;
foreach ( WellKnownClientTypeEntry entry in
RemotingConfiguration.GetRegisteredWellKnownClientTypes() )
dicoTypes.Add(entry.ObjectType, entry) ;
}
}
class Program {
static void Main() {

Configurer la partie Remoting dune application

813

RemotingConfiguration.Configure("Client.config", false) ;
IAdditionneur objA = (IAdditionneur)
NotreActivateur.GetObject(typeof(IAdditionneur));
double dA = objA.Add(3.0, 4.0) ;
IMultiplicateur objM = (IMultiplicateur)
NotreActivateur.GetObject(typeof(IMultiplicateur));
double dM = objM.Mult(3.0, 4.0) ;
// Utilisation du design pattern factory pour un objet CAO.
IFabrique objFabrique = (IFabrique)
NotreActivateur.GetObject(typeof(IFabrique));
IDiviseur objD = objFabrique.FabriqueNouvelDiviseur();
double dD = objD.Div(3.0, 4.0) ;
}
}
}
Le fichier de configuration du client ressemble alors :
Exemple 22-22 :

Client.config

<configuration>
<system.runtime.remoting>
<application name = "Client">
<client>
<wellknown type="NommageInterface.IAdditionneur,Interface"
url="http://localhost:65100/Service1.rem" />
<wellknown type="NommageInterface.IMultiplicateur,Interface"
url="http://localhost:65100/Service2.rem" />
<wellknown type="NommageInterface.IFabrique,Interface"
url="http://localhost:65100/Service3.rem" />
</client>
</application>
</system.runtime.remoting>
</configuration>
Comprenez bien que le contenu de la table dassociations interfaces/services WKO distants est
crit explicitement dans ce fichier de configuration. Ce contenu est rcupr dans le code du
client par la mthode RemotingConfiguration.GetRegisteredWellKnownClientTypes().
Le fichier de configuration du serveur prsente trois services WKO. Nous rappelons que le troisime service sert utiliser des objets CAO par lintermdiaire du design pattern factory :
Exemple 22-23 :
<configuration>
<system.runtime.remoting>
<application name = "Serveur">
<service>

Hote.config

814

Chapitre 22 : .NET Remoting


<wellknown type="NommageServer.CAdditionneur,Serveur"
mode ="Singleton" objectUri="Service1.rem" />
<wellknown type="NommageServer.CMultiplicateur,Serveur"
mode ="SingleCall" objectUri="Service2.rem" />
<wellknown type="NommageServer.CFabrique,Serveur"
mode ="SingleCall" objectUri="Service3.rem" />
</service>
<channels>
<channel port="65100" ref ="http" />
</channels>
</application>
</system.runtime.remoting>
</configuration>

Bien videmment, les classes CAdditionneur, CMultiplicateur, CFabrique, CDiviseur peuvent


maintenant tre places dans le code du serveur :
Exemple 22-24 :
using
using
using
using

Serveur.cs

System ;
System.Runtime.Remoting ;
System.Runtime.Remoting.Channels ;
System.Runtime.Remoting.Channels.Http ;

using NommageInterface ;
namespace NommageServer{
public class CAdditionneur : MarshalByRefObject, IAdditionneur {
...
public class CMultiplicateur : MarshalByRefObject, IMultiplicateur {
...
public class CDiviseur : MarshalByRefObject, IDiviseur {
...
public class CFabrique : MarshalByRefObject, IFabrique {
public IDiviseur FabriqueNouvelDiviseur() {
return new CDiviseur() ;
}
}
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(65100) ;
ChannelServices.RegisterChannel(canal, false) ;
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CFabrique),
"ServiceFabrique",
WellKnownObjectMode.Singleton) ;
Console.WriteLine("Pressez une touche pour stopper le serveur.") ;
Console.Read() ;
}

Dploiement dune application distribue .NET

815

}
}

Dploiement dune application distribue


.NET Remoting
Lors du dploiement dune application distribue base sur .NET Remoting, il est fortement
recommand de fournir au moins un fichier de configuration pour chaque composant. Vous
disposez ainsi dun moyen standard pour configurer lapplication sans avoir recompiler aucun
assemblage.
Les problmes de versioning des classes des objets serveurs peuvent aussi tre grs partir de ces
fichiers de configuration. En eet, lorsque vous prcisez un type dans un attribut XML <type>,
vous devez prciser le nom de son assemblage. Or, ce nom peut contenir le numro de version
de lassemblage.
Tous les htes de ce chapitre sont des excutables en mode console. Dans une application relle,
on prfre utiliser soit un service Windows soit ASP.NET pour hberger nos objets serveur.

Services Windows
Lavantage principal dun service Windows par rapport une application sexcutant en mode
console, rside dans le fait quil nest pas ncessaire quun utilisateur soit logu sur la machine
pour excuter lapplication hberge par le service Windows. Un autre avantage est que la manipulation dun service en excution peut se faire directement partir dune interface graphique
qui prvoit les commandes dmarrer / arrter / suspendre / reprendre / redmarrer . Un autre
argument en faveur de cette pratique est le fait quun service peut tre excut automatiquement aprs un reboot de la machine. Nous ne dtaillons pas dans cet ouvrage la conception dun
service. Sachez quavec les classes contenues dans lespace de noms System.ServiceProcess,
cette tche a t grandement simplifie par rapport lutilisation des fonctions spcialises
win32. Tout ceci est dcrit dans les MSDN larticle System.ServiceProcess Namespace.

IIS
Lutilisation de IIS constitue une autre alternative au mode console et lutilisation dun service Windows, pour hberger vos objets serveurs. Les avantages principaux de ce choix sont les
suivants :

Vous avez accs la gestion de la scurit de IIS. Notamment, si votre serveur IIS supporte
les certificats SSL, vous pouvez avoir accs au service dencryption de vos donnes. Vous
pouvez aussi avoir accs au mcanisme dauthentification de Windows.

Lutilisation dIIS vous dcharge de la responsabilit dcrire un hte. Concrtement, vous


navez qu dvelopper vos classes drivant de MarshalByRefObject et prvoir un fichier de
configuration.

Les problmes de versioning sont aussi pris en charge par IIS. Celui-ci dtecte quand vous
installez une nouvelle version et soccupe entirement de la transition vers cette nouvelle
version. Installer une nouvelle version signifie remplacer un assemblage par un autre, et
remplacer le fichier de configuration par un autre, sans avoir stopper IIS. IIS ne bloque

816

Chapitre 22 : .NET Remoting


pas laccs en criture ces fichiers lorsquil les utilise, car en fait, IIS travaille en interne
avec des copies de ces fichiers. Ce mcanisme, trs pratique, est nomm shadow copy.

Cependant toutes ces options ont un cot, et le principal problme de IIS est quil a un impact
non ngligeable sur les performances. Sachez aussi que vous ne pouvez pas utiliser de canaux
TCP ou IPC avec IIS. Pour utiliser IIS deux tapes sont ncessaires :

Sous mmc (la console gnrique dadministration de Windows), avec le snap-in IIS, crer un
nouveau rpertoire virtuel IIS. Nous prcisons quun rpertoire virtuel IIS permet de faire en
sorte quun rpertoire de la machine soit accessible partir dune URL. Dans ce rpertoire,
vous devez placer votre fichier de configuration. Ce fichier ne peut pas prendre nimporte
quel nom. Son nom doit tre web.config.

Crer un sous rpertoire bin dans le rpertoire de la premire tape. Placez les assemblages
contenant les classes de vos objets serveur dans cet assemblage. Une autre option consiste
placer ces assemblages dans le rpertoire GAC. Ils doivent alors avoir un nom fort.

Lorsque vous utilisez ce type de dploiement, llment <application> du fichier de configuration ne doit pas avoir dattribut Name. De plus vous ne devez pas spcifier de port dans le fichier
de configuration, pour ne pas interfrer avec la gestion des numros de port dIIS.

Scuriser une conversation .NET Remoting


Scurisation dun canal TCP
Si vous utilisez un canal de type TCP, vous avez la possibilit dutiliser les protocoles NTLM et
Kerberos pour authentifier lutilisateur Windows sous lequel sexcute le client et pour crypter
les donnes changes. Pour fournir cette possibilit, le canal TCP exploite en interne la classe
NegociatedStream qui elle-mme exploite les services de lAPI win32 relative ces protocoles
connue sous le nom de SSPI. Ces protocoles ainsi que cette classe sont dtaills en page 660.
Pour avoir recours cette possibilit il sut de positionner lattribut secure du canal TCP
true dans le fichier de configuration du client et du serveur :
<configuration>
<system.runtime.remoting>
<application name = "XXX">
<channels>
<channel port="65100" ref="tcp" secure="true"/>
</channels>
...
Vous pouvez aussi positionner la valeur de lattribut secure true programmatiquement grce
la liste des proprits dun canal :
Exemple 22-25 :
...
static void Main() {
IDictionary properties = new Hashtable();

Client.cs

Proxys et messages

817

properties.Add("secure", true);
TcpChannel canal = new TcpChannel(properties,null,null) ;
ChannelServices.RegisterChannel(canal, true) ;
...
Vous pouvez galement positionner du cot serveur lattribut tokenImpersonationLevel dun
canal TCP la valeur Impersonation si vous souhaitez que la requte du cliente sexcute du
cot serveur avec le compte de lutilisateur client (autrement dit la requte est impersonifie).

Scurisation dun canal HTTP


Si vous souhaitez scuriser les donnes changes lorsque vous avez recours au protocole HTTP,
deux solutions sont possibles :

Hberger le serveur avec IIS et utiliser le protocole SSL (donc HTTPS) au niveau dIIS.
Crer votre propre canal scuris HTTP.

Proxys et messages
Le but de cette section est danalyser de prs les proxys transparents et de prsenter les notions
de proxy rel et dintercepteur de messages. Rsumons tout ce que nous avons dj expos
propos des proxys transparents :

Un proxy transparent est une instance dune classe interne lassemblage mscorlib.dll.
Cette classe ne nous est donc pas accessible.
Un proxy transparent est cr automatiquement par le CLR pour rfrencer un objet distant dont la classe drive de MarshalByRefObjectet.
Dans un assemblage, la gestion interne dune rfrence vers un objet varie selon que lassemblage est charg dans le mme domaine dapplication que lobjet ou non. Dans le cas o
lassemblage et lobjet sont distants, on a une rfrence vers un proxy transparent. Dans le
cas o ils sont dans le mme domaine dapplication, on a une rfrence directe vers lobjet.
On a vu que la mthode statique bool RemotingServices. IsTransparentProxy(object)
permet de savoir si une rfrence vers un objet est un proxy transparent ou est une rfrence
directe.
Les mtadonnes du type par lequel on manipule lobjet distant doivent tre charges dans
le domaine dapplication, lorsque le CLR construit le proxy transparent vers cet objet distant. On a vu plusieurs faons dobtenir ces mtadonnes.

Transformer un appel de mthode en un message


Comme le montre la Figure 22-4, un appel de mthode classique peut tre vu comme une transformation de la pile du thread eectuant lappel.
Cette transformation se fait en deux tapes : avant lexcution de la mthode le CLR dpile les
arguments pour en faire des variables locales la mthode, aprs lexcution le CLR empile la
valeur de retour et les arguments de retour.
Lors de lappel dune mthode sur un objet distant, situ dans un autre domaine dapplication,
voire dans un autre processus, il est impossible dutiliser cette technique de passage darguments

818

Chapitre 22 : .NET Remoting


int f(int argVal, ref int argRef)
Pile avant lappel

Excution de f

Pile aprs lappel

argVal
addr
this

result

int argRef

Figure 22 -4 : Appel de mthode = transformation de la pile


par la pile. En eet, le thread qui appelle la mthode peut tre dirent du thread qui excute la
mthode. Les arguments sont passs par une technique dchange de messages, un lappel de la
mthode (contenant les informations consommes par la mthode) et un au retour (contenant
les informations produites par la mthode).
Le rle dun proxy transparent est de grer du ct client la transition entre le mode de passage
darguments par la pile et le mode de passage darguments par messages. Un objet interne au
CLR, appel constructeur de pile (stack builder sink en anglais) ralise lopration inverse du ct
serveur. Tout ceci est illustr par la Figure 22-5.
Domaine dapplication client

Domaine dapplication serveur

Client

Proxy transparent

Objet serveur

Message dappel

Constructeur de pile

Message de retour

Figure 22 -5 : Proxy transparent et constructeur de pile

Manipulation dun message


Nous allons voir dans la prochaine section que vous pouvez intercepter dirents niveaux ces
messages changs entre un proxy transparent et un constructeur de pile. On sapercevra alors
que ces messages sont en fait des objets .NET. Intressons-nous ici aux interfaces qui permettent
de manipuler ces messages. Voici leur hirarchie :
System.Runtime.Remoting.Messaging.IMessage
System.Runtime.Remoting.Messaging.IMethodMessage
System.Runtime.Remoting.Messaging.IMethodCallMessage
System.Runtime.Remoting.Activation.IConstructionCallMessage
System.Runtime.Remoting.Messaging.IMethodReturnMessage
System.Runtime.Remoting.Activation.IConstructionReturnMessage
Voici la description des interfaces IMessage et IMethodMessage :

Proxys et messages

819

using System.Runtime.Remoting.Messaging ;
using System.Collections ;
public interface IMessage {
IDictionary Properties { get;}
}
public interface IMethodMessage : IMessage {
object
GetArg(int index) ;
string
GetArgName(int index) ;
int
ArgCount { get;}
object[]
Args { get;}
bool
HasVarArgs { get;}
string
MethodName { get;}
object
MethodSignature { get;}
string
TypeName { get;}
string
Uri { get;}
LogicalCallContext
LogicalCallContext { get;}
MethodBase
MethodBase {get;}
}
Linterface IMethodCallMessage permet essentiellement de parcourir les donnes contenues
dans un message fabriqu par un proxy transparent. Linterface IMethodReturnMes\-sage
permet essentiellement de parcourir les donnes contenues dans un message fabriqu par
un constructeur de pile.

Proxy transparent, proxy rel et la classe ObjRef


Pour ne pas compliquer notre expos, nous vous avons cach jusquici lexistence des proxys
rels (real proxy en anglais). Un proxy rel est un objet, instance de la classe System.Runtime.
Remoting.Proxies.RealProxy, ou dune classe drive de celle-ci. chaque proxy transparent
correspond un et un seul proxy rel. Le proxy transparent et le proxy rel se partagent le travail
lors dun appel sur lobjet distant. Comme nous lavons vu, le proxy transparent gre le passage
du mode passage darguments par la pile au mode passage darguments par message . La
tche du proxy rel est de trouver le canal adquat pour envoyer le message fabriqu par le proxy
transparent, et de lenvoyer. Une autre tche du proxy rel est de rcuprer du canal le message
reprsentant le retour de lappel de la mthode, et de transfrer ce message de retour au proxy
transparent. Contrairement aux proxys transparents, vous pouvez concevoir vos propres classes
de proxys rels, comme on le verra un peu plus loin.

Type et proxy rel


Une autre responsabilit dun proxy rel est de connatre le type de lobjet distant. Le constructeur dun proxy rel prend en argument un type. Ce type est obligatoirement soit une interface,
soit une classe qui drive de la classe MarshalByRefObject ; dans le cas contraire le constructeur
du proxy rel lance une exception. La mthode Type GetProxiedType() est automatiquement
appele par le CLR sur un proxy rel, lorsque lutilisateur transtype ce quil croit tre une rfrence, mais est en fait un proxy transparent.

Cration du couple proxy transparent/proxy rel et la classe ObjRef


Dans un couple proxy transparent/proxy rel, cest toujours le proxy rel qui est fabriqu en premier. Le proxy transparent est ensuite fabriqu par le proxy rel. Un proxy rel est fabriqu par

820

Chapitre 22 : .NET Remoting

le CLR soit partir dune instance de la classe System.Runtime.Remoting.ObjRef soit partir


dinformations de localisation dun objet distant. De telles informations peuvent contenir par
exemple lURI dun service dactivation dobjets par le serveur. Les instances de la classe ObjRef
sont MBV et peuvent donc faire traverser des informations travers les frontires des domaines
dapplication. Une instance de ObjRef contient les informations ncessaires un proxy rel,
pour rfrencer un objet distant. Ces informations sont : le type de lobjet distant, les informations de localisation de lobjet distant, le type de canal utiliser pour contacter lobjet distant et
des informations utilises en internes par le CLR. Une instance de la classe ObjRef rfrenant
un objet distant peut tre obtenue de direntes manires.
Dans le cas dun objet activ par le client, une instance de la classe ObjRef est eectivement
obtenue par le client lorsquil active lobjet. Ce comportement concerne la fois lutilisation
du mot-cl new, lutilisation de la mthode AppDomain.CreateInstance() ou lutilisation de la
mthode Activator.CreateInstance(). Nous vous avons parl au dbut de ce chapitre de la
classe ObjectHandle dont les instances permettent de wrapper un objet MBV ou une rfrence MBR. Nous pouvons maintenant prciser que si une instance de ObjectHandle contient
une rfrence MBR, vous pouvez obtenir cette rfrence en appelant la mthode ObjRef ObjectHandle.CreateObjRef(Type).
Dans le cas dun objet WKO, le client na pas besoin dune instance de la classe ObjRef initialise
par le serveur. Ceci est logique puisque seule la connaissance de lURI du service dactivation
dobjets par le serveur sut au client pour avoir accs au service. Une consquence est que,
contrairement ce que lon pourrait penser, lappel une des mthodes Activator.GetObjet()
ou RemotingServices.Connect() ne provoquent pas lenvoi dun message sur le rseau. En plus
doptimiser lutilisation du rseau, ce comportement permet au serveur de ne pas crer dobjets
tant quil ny a pas eu un appel de la part dun client. Cette situation concerne la fois les objets
activs par le serveur en mode singleton et ceux activs par le serveur en mode simple appel.
Comprenez bien que la mise en uvre de ce mcanisme conomise un aller/retour rseau, par
rapport au mcanisme dactivation de lobjet par le client.
Enfin, sachez quune instance de la classe ObjRef est retourne par le domaine dapplication
serveur pour chaque valeur de retour de type rfrence et chaque argument de retour de type
rfrence, lors de lappel dune mthode sur un objet distant. Bien videmment, il faut que les
types de ces arguments soient des interfaces ou des classes drivants de MarshalByRefObject.

Une utilisation intressante de la classe ObjRef : publier un objet


Les sources et les excutables de cette section sont dans le rpertoire :
Chap22 .NET Remoting/Publication dun objet
Une instance de la classe ObjRef contient toutes les informations ncessaires pour localiser et
identifier un objet distant. Daprs cette constatation, en srialisant une telle instance dans un
fichier, on dispose dune rfrence vers un objet. Lide est de rendre accessible ce fichier un
client, par exemple en lui envoyant par e-mail. En dsrialisant linstance de la classe ObjRef, le
client dispose dune rfrence vers lobjet distant et peut ainsi lutiliser. Cette pratique sappelle
la publication dun objet. Grce certaines mthodes de .NET Remoting, la publication dun objet
est une tche particulirement aise implmenter.
Voici le code du serveur qui srialise le rfrence dun objet dans le fichier Additionneur.txt. Notez
quil faut que la classe de lobjet drive de la classe MarshalByRefObject :

Proxys et messages

821
Serveur.cs

Exemple 22-26 :
...
static void Main() {
HttpChannel canal = new HttpChannel(65100) ;
ChannelServices.RegisterChannel(canal, false) ;
CAdditionneur obj = new CAdditionneur() ;
ObjRef objRef = RemotingServices.Marshal(obj) ;
FileStream fStream = new
FileStream("Additionneur.txt", FileMode.Create);
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.Serialize(fStream, objRef);
fStream.Close();

Console.WriteLine("Pressez une touche pour stopper le serveur.") ;


Console.Read() ;
}
...
Voici le code du client qui dsrialise le rfrence vers lobjet distant partir du fichier Additionneur.txt :
Client.cs

Exemple 22-27 :
using System.IO ;
using System.Runtime.Serialization.Formatters.Soap ;
...
static void Main() {
HttpChannel canal = new HttpChannel(0) ;
ChannelServices.RegisterChannel(canal, false) ;
FileStream fStream = new FileStream("Additionneur.txt",
FileMode.Open);
SoapFormatter soapFormatter = new SoapFormatter();
IAdditionneur obj =
soapFormatter.Deserialize(fStream) as IAdditionneur;
double d = obj.Add(3.0, 4.0) ;
}
...

Cette application nous permet de vrifier que .NET Remoting assigne une identit propre
chaque instance dune classe drivant de MarshalByRefObject. Cest--dire que chaque objet a
un URI unique au monde, bas sur un GUID (Global Unique Identity). Un tel URI ressemble
ceci :
/6b03659f_0164_43e2_99cf_f36eda31adae/367459709_1.rem

822

Chapitre 22 : .NET Remoting

Lorsquun objet publi est dtruit, le fichier qui lui servait de rfrence nest plus daucune utilit. Cet URI nest communiqu au client que si lobjet est activ par le client ou publi par le
serveur. Autrement dit, cet URI nest pas communiqu au client dans le cas dun objet WKO.
La technique de publication dun objet est donc une solution intermdiaire entre la technique
de service dactivation dun objet distant par le serveur et la technique dactivation dun objet
distant par le client. Lobjet est eectivement activ par le serveur mais le client tient compte
de lidentit de lobjet. Nous rcapitulons les dirences de ces quatre modes dactivation dun
objet la fin de ce chapitre.
Le fichier de publication dun objet est constitu dune trentaine de lignes au format XML, fastidieuses lire. En voici quelques extraits pertinents :
Exemple :

Additonneur.txt

...
<uri id="ref-2">/6b03659f_0164_43e2_99cf_f36eda31adae/367459709_1.rem</uri>
...
<serverType id="ref-5">NommageServer.CAdditionneur, Server,
Version=1.0.1140.28752, Culture=neutral, PublicKeyToken=null</serverType>
...
<item id="ref-8">NommageInterface.IAdditionneur, Interface,
Version=1.0.1138.27103, Culture=neutral, PublicKeyToken=null</item>
...
<a3:CrossAppDomainData id="ref-9" xmlns:a3=
"http://schemas.microsoft.com/clr/ns/System.Runtime.Remoting.Channels">
<_ContextID>1362648</_ContextID>
<_DomainID>1</_DomainID>
<_processGuid id="ref-11">faf88595_9b2e_4f23_99ee_1d0046915a98</_processGuid>
</a3:CrossAppDomainData>
...
<item id="ref-13">http://213.36.58.1:65100</item>
...

Intercepteur de messages (message sink)


Pour rsumer, en .NET Remoting un appel de mthode est en fait un change de deux messages, un qui contient les informations que va consommer lexcution de la mthode et un qui
contient les informations produites par lexcution de la mthode. Chacun de ces deux messages
subit plusieurs traitements entre le proxy rel, situ dans le domaine dapplication client, et
le constructeur de pile, situ dans le domaine dapplication serveur. Par exemple, le message
contenant les arguments entrants de la mthode est srialis du ct client, puis dsrialis du
ct serveur. Chacun de ces traitements est eectu par un objet appel intercepteur de messages
(message sink en anglais). Concrtement, un intercepteur de messages est une instance dune
classe qui implmente linterface System.Runtime.Remoting.Messaging.IMessageSink et qui
est marque avec lattribut .NET Serializable :
public interface System.Runtime.Remoting.Messaging.IMessageSink{
IMessageSink
NextSink{get;}
IMessage
SyncProcessMessage( IMessage request ) ;
IMessageCtrl
AsyncProcessMessage( IMessage request ,

Proxys et messages

823
IMessageSink

replySink) ;

}
Grce la proprit NextSink, les intercepteurs de messages peuvent tre chans. Pour cette
raison, on les appelle parfois des chaneurs de messages. Comme vous vous en doutez, le traitement dun message reprsentant un appel synchrone se fait dans la mthode SyncProcessMessage() et le traitement dun message reprsentant un appel asynchrone se fait dans la mthode
AsyncProcessMessage(). Voici une implmentation o nous indiquons o vous pouvez placer
le code de vos traitements :

Il faut toujours marquer une classe dintercepteurs de messages avec lattribut .NET
Serializable.
[Serializable]
public class MonMsgSink : IMessageSink{
private IMessageSink m_NextSink ;
public IMessageSink NextSink{ get { return m_NextSink;} }
public MonMsgSink(IMessageSink NextSink){m_NextSink = NextSink;}
IMessage SyncProcessMessage(IMessage MsgIn){
// Ici vous pouvez faire un traitement sur MsgIn.
IMessage MsgOut = m_NextSink.SyncProcessMessage(MsgIn) ;
// Ici vous pouvez faire un traitement sur MsgOut.
return MsgOut ;
}
IMessageCtrl AsyncProcessMessage(
IMessage MsgIn,
IMessageSink replySink){
// Ici vous pouvez faire un traitement sur MsgIn.
// Vous pouvez aussi ajouter un intercepteur de msgs pour la
// chaine dintercepteurs de msgs qui sera utilis
ee pour
// traiter le msg representant le retour de lappel.
IMessageCtrl MsgCtrl =
m_NextSink.AsyncProcessMessage(MsgIn,replySink) ;
// Ici vous pouvez indiquer au CLR de ne plus attendre de retour
// pour cet appel asynchrone apr
es une dur
ee de
// 1000 millisecondes en
ecrivant : MsgCtrl.Cancel(1000);
return MsgCtrl ;
}
}
Remarquez que la mthode SyncProcessMessage() peut traiter le message entrant et le message
sortant alors que la mthode AsyncProcessMessage() ne traite que le message entrant. En revanche, la mthode AsyncProcessMessage() a la possibilit de participer la construction de la
chane dintercepteurs de messages qui sera utilise pour traiter le message contenant les informations retournes par lappel de la mthode. Naturellement, les traitements senchaneront
dans lordre inverse dans lesquels ils ont t chans.
Vous savez maintenant comment concevoir des intercepteurs de messages. Nous allons vous
expliquer, dans la suite de ce chapitre, comment injecter vos propres intercepteurs de message

824

Chapitre 22 : .NET Remoting

dans la chane dun appel de mthode, et surtout, quels types de bnfices vous pouvez tirer de
cette pratique. Auparavant, nous prsentons une implmentation dun proxy rel propritaire.
Vous allez vous apercevoir que nous allons pour cela avoir accs au premier intercepteur de
messages de la chane dun appel synchrone de mthode.

Pourquoi utiliser un proxy rel propritaire ?


Avant de montrer des exemples de cration de classes de proxys rels propritaires, il est utile
de donner quelques exemples dutilisation dun proxy rel propritaire.
Vous pouvez vous servir dun proxy rel propritaire simplement pour tracer les appels un
objet distant.
Vous pouvez vous servir dun proxy rel propritaire pour empcher certains appels distants,
qui peuvent tre raliss localement. Vous pouvez ainsi raliser un systme de cache dinformations. Lorsquune information est demande lobjet distant, le proxy rel propritaire va
dabord vrifier si linformation nest pas disponible localement.
Vous pouvez utiliser un proxy rel propritaire pour modifier les arguments dun appel de mthode dune manire transparente. Par exemple vous pourriez traduire les chanes de caractres,
passes en argument dune mthode, dune langue source vers une langue destination.
Vous pouvez vous servir dun proxy rel propritaire pour permettre le transtypage dun proxy
transparent en un type qui nest pas forcment le type dtenu par le proxy rel. Nous ne dcrivons pas en dtail cette possibilit, mais sachez que pour la raliser, votre classe de proxy rel
propritaire doit implmenter linterface System.Runtime.Remoting. IRemotingTypeInfo. La
description larticle IRemotingTypeInfo members des MSDN des deux membres de cette
interface, vous permettra de comprendre comment lutiliser.
Vous pouvez vous servir dun proxy rel propritaire pour faire de la rpartition de charge entre
les serveurs (load balancing en anglais). Il vous sut dobtenir rgulirement la charge de chaque
serveur et de router les appels un objet distant, vers le serveur le moins surcharg. Vous pouvez
aussi tenir compte de la puissance de chaque serveur lors de lvaluation de sa charge. Nous
vous conseillons aussi dessayer de router les appels alatoirement vers les serveurs, avec des
densits de probabilit proportionnelles aux charges que chaque serveur peut supporter. Cette
technique donne souvent des rsultats quivalents lapplication dun algorithme bas sur la
charge de chaque serveur. Nous prcisons que la rpartition de charge entre plusieurs serveurs
ne peut se faire que lorsque vous travaillez avec des objets distants WKO sans tat.

Implmenter et utiliser une classe de proxys rels propritaires


Il est trs facile dimplmenter une classe de proxys rels propritaires. Il vous sut de
faire driver une classe de la classe RealProxy, et dimplmenter la mthode IMessage Invoke(IMessage). Cette mthode est automatiquement appele par le CLR lors dun appel
synchrone sur le proxy transparent associ. Le message en entre reprsente le message
fabriqu par le proxy transparent. Le message en sortie est le message reprsentant le retour de lappel, qui sera automatiquement pass au proxy transparent. Pour transmettre
un appel synchrone au sein de la mthode Invoke() il sut dappeler la mthode IMessage.SyncProcessMessage(IMessage) sur le premier intercepteur de messages de la chane.
Nous allons voir comment rcuprer une rfrence vers cet intercepteur de message.
Voici un exemple de classe de proxy rel propritaire. Dans cet exemple, lintercepteur de messages que la mthode Invoke() utilise est fourni par le canal. Cependant, on verra quil peut y

Proxys et messages

825

avoir dautres intercepteurs de messages intercals entre le proxy rel et le canal. Dans la mthode Invoke() on ne fait quacher les arguments en entre de la mthode, puis la valeur de
retour. Notez que lon place lURI du service dactivation dobjets par le serveur, dans le message
dentre. Cette tape pourrait tre trs facilement modifie pour faire de la rpartition de charge
entre plusieurs serveurs.
Exemple 22-28 :

Client.cs

using System ;
using System.Runtime.Remoting ;
using System.Runtime.Remoting.Channels ;
using System.Runtime.Remoting.Channels.Http ;
using System.Runtime.Remoting.Proxies ;
using System.Runtime.Remoting.Messaging ;
using System.Collections ;
using NommageInterface ;
// Il faut utiliser soapsuds.exe
// pour que le client connaisse la classe CAdditionneur.
using NommageServer;
public class MonProxyReel : RealProxy {
// URI du service dactivation dobjet par le serveur.
String m_Uri ;
// Correspond au premier intercepteur de msg du canal HttpSender.
IMessageSink m_MsgSink ;
public MonProxyReel(Type type, String uri,
IChannelSender CanalEnvoi) : base(type) {
m_Uri = uri ;
string unused ;
// Obtient du canal, une chaine dintercepteurs de messages.
m_MsgSink = CanalEnvoi.CreateMessageSink(m_Uri, null, out unused);
}
// Methode appelee par le CLR avant chaque appel du client.
public override IMessage Invoke(IMessage msgIn) {
// Place lURI de lobjet distant dans MsgIn.
IDictionary d = msgIn.Properties;
d["__Uri"] = m_Uri;
// Affiche les arguments en entr
ee contenus dans MsgIn.
IMethodCallMessage msgAppel = (IMethodCallMessage)msgIn ;
Console.Write("MonProxyReel : Avant lappel de:{0}(",
msgAppel.MethodName) ;
for (int i = 0 ; i < msgAppel.InArgCount ; i++)
Console.Write(" {0}={1} ",
msgAppel.GetArgName(i), msgAppel.GetArg(i)) ;
Console.WriteLine(")") ;
// Realise lappel distant.

826

Chapitre 22 : .NET Remoting


IMethodReturnMessage msgOut =
(IMethodReturnMessage)m_MsgSink.SyncProcessMessage(msgIn);
// Affiche la valeur de sortie contenue dans msgOut.
Console.WriteLine("MonProxyReel : Apr`
es appel de:{0}() RetVal={1}",
MsgAppel.MethodName, msgOut.ReturnValue) ;
return msgOut;
}
}
namespace NommageClient {
class Program {
static void Main() {
HttpChannel canal = new HttpChannel(0) ;
ChannelServices.RegisterChannel(canal, false) ;
// Fabrication de notre propre proxy r
eel.
// On lui fournit le type de lobjet, lURI du service WKO
// et le canal denvoi que lon souhaite utiliser.
MonProxyReel proxy = new MonProxyReel(
typeof(CAdditionneur),
"http://localhost:65100/ServiceAjout",
(IChannelSender)canal);
IAdditionneur obj = (IAdditionneur)
proxy.GetTransparentProxy();
// `A ce stade, il ny a toujours pas eu de contact r
eseau.
double d = obj.Add(3.0, 4.0) ;
}
}
}

Voici lachage du client, lorsquon lutilise avec un serveur adquat :


MonProxyReel : Avant lappel de:Add( d1=3 d2=4 )
MonProxyReel : Apr`es appel de:Add() RetVal=7

Aectation automatique dun proxy rel propritaire toutes les


instances dune classe
Dans le programme prcdent, nous avons explicitement construit le proxy rel et nous avons
explicitement demand le proxy transparent. Vous pouvez faire en sorte que toutes les instances
de la classe CAdditionneur aient chacune un proxy rel propritaire. Dans ce cas, une instance
aura son proxy rel propritaire mme si elle est construite avec loprateur new et mme si elle
est utilise dune manire non distante. En fait, la technique que nous vous proposons ici est
surtout utilise pour pouvoir obtenir les bnfices dun proxy rel propritaire sur des objets
utiliss dune manire non distante.

Proxys et messages

827

Pour cela, il faut dabord que la classe de nos instances, la classe CAdditionneur en loccurrence,
drive de la classe System.ContextBoundObject. Nous donnons la signification prcise de la
classe ContextBoundObject un peu plus loin dans ce chapitre. Ensuite il faut dfinir une classe
dattribut .NET qui drive de la classe System.Runtime.Remoting.Proxies.ProxyAttribute.
Dans cette classe, vous rcrivez la mthode virtuelle MarshalByRefObject ProxyAttribute.CreateInstance(Type t) de faon quelle intercale un proxy rel entre lobjet cr et sa
rfrence. Il faut marquer la classe CAdditionneur avec cet attribut .NET. Voici un programme
illustrant tout ceci :
Exemple 22-29 :
using
using
using
using
using
using

System ;
System.Runtime.Remoting ;
System.Runtime.Remoting.Services ;
System.Runtime.Remoting.Messaging ;
System.Runtime.Remoting.Proxies ;
System.Runtime.Remoting.Activation ;

public class MonProxyReel : RealProxy {


readonly bool m_bAffiche ;
readonly MarshalByRefObject m_ObjCible ;
public MonProxyReel(
MarshalByRefObject objCible, Type type, bool bAffiche):base(type){
m_bAffiche = bAffiche ;
m_ObjCible = objCible ;
}
public override IMessage Invoke(IMessage msgIn) {
IMessage msgOut ;
if (msgIn is IConstructionCallMessage) {
IConstructionCallMessage appelCtor =
(IConstructionCallMessage)msgIn;
// Obtient le proxy par d
efaut.
RealProxy proxyParDefaut =
RemotingServices.GetRealProxy(m_ObjCible);
// Invoque le constructeur sur ce proxy r
eel.
proxyParDefaut.InitializeServerObject(appelCtor);
// Retourne notre Proxy transparent sur le nouvel objet.
msgOut=EnterpriseServicesHelper.CreateConstructionReturnMessage(
appelCtor, (MarshalByRefObject)GetTransparentProxy());
if (m_bAffiche)
Console.WriteLine("MonProxyReel : appel du constructeur") ;
}
else {
IMethodCallMessage appel = (IMethodCallMessage)msgIn ;

828

Chapitre 22 : .NET Remoting

if (m_bAffiche)
Console.WriteLine("MonProxyReel : Avant lappel de:{0}",
appel.MethodName) ;
msgOut = RemotingServices.ExecuteMessage(m_ObjCible, appel) ;
if (m_bAffiche)
Console.WriteLine("MonProxyReel : Apr
es lappel de:{0}",
appel.MethodName) ;
}
return msgOut ;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class MonProxyAttribute : ProxyAttribute {
bool m_bAffiche ;
public MonProxyAttribute(bool bAffiche) {
m_bAffiche = bAffiche ;
}
public override MarshalByRefObject CreateInstance(Type T) {
// Creation dune nouvelle instance.
MarshalByRefObject objCible = base.CreateInstance(T);
// Intercale un proxy reel entre la nouvelle instance
// et le client.
RealProxy realProxy = new MonProxyReel(objCible, T, m_bAffiche);
return (MarshalByRefObject)realProxy.GetTransparentProxy();
}
}
// Le param`etre true indique que lon souhaite que le proxy r
eel
// signale sa presence en affichant des messages sur la console.
[MonProxyAttribute(true)]
public class CAdditionneur : ContextBoundObject {
public int Add(int a, int b) { return a + b ; }
}
public class Program {
static void Main() {
CAdditionneur obj = new CAdditionneur() ;
obj.Add(5, 6) ;
}
}

Ce programme ache ceci sur la console :

Proxys et messages

829

MonProxyReel : Appel du constructeur


MonProxyReel : Avant lappel de:Add
MonProxyReel : Apres lappel de:Add

Consultation et modification des arguments dun appel


Nous avons vu comment lire les arguments dun appel, partir dun message reprsentant cet
appel. On peut aussi utiliser un proxy rel propritaire ou un intercepteur de messages propritaire pour modifier les arguments dun appel. Par exemple vous pourriez utiliser cette possibilit pour traduire les chanes de caractres passes en argument du ne langue source vers une
langue destination. Laccs en criture aux arguments contenus dans un message est possible
mais elle nest pas aussi directe que laccs en lecture, qui rappelons-le, peut tre eectu ainsi :
...
public override IMessage Invoke(IMessage msgIn){
IMethodCallMessage msgAppel = (IMethodCallMessage)msgIn ;
Console.Write("MonProxyReel : Avant lappel de:{0}(",
msgAppel.MethodName) ;
for(int i=0 ; i< msgAppel.InArgCount ; i++)
Console.Write(" {0}={1} ", msgAppel.GetArgName(i),
msgAppel.GetArg(i) ) ;
Console.WriteLine(")") ;
...
En eet, quelle que soit linterface que lon utilise pour manipuler un message, on ne peut
avoir accs en criture aux arguments contenus dans le message. Autrement dit, chacune de ces
interfaces supporte un accesseur get sur le tableau dargument mais pas daccesseur set. Pour arriver nos fins, nous vous proposons dutiliser la classe System.Runtime.Remoting.Messaging.
MethodCallMessageWrapper qui a un accesseur set sur le tableau des arguments. Voici un extrait
de code illustrant cette manipulation :
...
public override IMessage Invoke(IMessage msgIn){
IMethodCallMessage msgAppel = (IMethodCallMessage)msgIn ;
MethodCallMessageWrapper newAppel = new
MethodCallMessageWrapper(appel);
object[] tmpArgs = newAppel.Args;
tmpArgs[0] = 1; // Affecte une valeur au premier argument.
tmpArgs[1] = 2; // Affecte une valeur au second argument.
newAppel.Args = methodArgs;
appel = newAppel;
msgOut = RemotingServices.ExecuteMessage(m_ObjCible,appel) ;
...

830

Chapitre 22 : .NET Remoting

Canaux (channels)
Introduction
Les canaux sont les entits qui transmettent les messages reprsentant les appels de mthodes
inter domaines dapplication. De ce fait, il existe au moins un canal dans le domaine dapplication du client et un canal dans le domaine dapplication du serveur. Cependant, un domaine
dapplication peut contenir plusieurs canaux, et une implmentation dun canal peut tre utilise par un client ou par un serveur.
Un canal est une instance dune classe implmentant linterface System.Runtime.Remoting.
Channels.IChannel. Un canal qui peut tre utilis par un client est appel canal metteur. Par
dfinition, un canal metteur implmente linterface System.Runtime.Remoting.Channels.
IChannelSender. Un canal qui peut tre utilis par un serveur est appel canal rcepteur. Par
dfinition, un canal rcepteur implmente linterface System.Runtime.Remoting.Channels.
IChannelReceiver.
Le framework .NET expose trois implmentations pour les canaux : la classe System.Runtime.
Remoting.Channels.Http.HttpChannel, la classe System.Runtime.Remoting.Channels.Tcp.
TcpChannel et la classe System.Runtime.Remoting.Channels.Ipc.Ipc\-Chan\-nel. Chacune
de ces implmentations peut la fois servir de canal metteur et de canal rcepteur.
Les protocoles HTTP et TCP sont bien videmment supports respectivement par les classes
HttpChannel et TcpChannel. La classe IpcChannel supporte quant elle la notion de pipe nomm.
Un pipe nomm est un objet Windows permettant deux processus Windows hbergs sur la
mme machine de communiquer. Vous pouvez aussi faire communiquer deux processus Windows hbergs sur la mme machine avec un protocole rseau tel que HTTP ou TCP. Lavantage
des pipes nomms rside dans le fait quils sont implments au niveau du systme dexploitation et quils nutilisent donc pas dAPI rseau. En consquence ils sont plus performants lorsquil sagit de faire communiquer deux processus hbergs sur la mme machine. Lacronyme
IPC signifie Inter Processus Communication. La documentation anglo saxonne parle aussi parfois
de same-box communication.
Pour enregistrer un canal dans un domaine dapplication, vous pouvez soit utiliser la mthode
statique ChannelServices.RegisterChannel() soit charger un fichier de configuration .NET
Remoting avec la mthode statique RemotingConfiguration.Configure(). Ces deux techniques
ont dj t prsentes dans les pages prcdentes.
Chaque canal a un nom. Deux canaux dans le mme domaine dapplication ne peuvent pas
avoir le mme nom. Par dfaut le nom dun canal instance de HttpChannel est "http", le nom
dun canal instance de TcpChannel est "tcp" et le nom dun canal instance de IpcChannel est
"ipc". Voici comment aecter un nom particulier un canal :
...
static void Main() {
IDictionary prop = new Hashtable() ;
prop["name"] = "tcp2";
prop["port"] = "65101" ;
ChannelServices.RegisterChannel(new TcpChannel(prop, null, null)) ;
...
Chaque canal de type HTTP ou TCP a besoin dun numro de port de la machine. Un numro
de port ne peut tre utilis par plusieurs canaux existant sur une mme machine. Pour viter

Canaux (channels)

831

toute collision dans le choix dun numro de port, vous pouvez aecter le numro de port 0
durant la cration dun tel canal. Le CLR fera en sorte de trouver un numro de port libre
et daecter ce numro au nouveau canal. Comme on peut sen douter, les implmentations
Microsoft des canaux HTTP et TCP se basent sur une socket en interne.
Lorsque deux domaines dapplications se trouvent dans le mme processus, il serait dommage
dutiliser ces mcanismes lourds prvus pour la communication interprocessus. Pour cette raison, lassemblage mscorlib.dll contient la classe interne CrossAppDomain qui est prvue spcialement pour le cas o le domaine dapplication contenant le client et le domaine dapplication contenant les objets serveurs sont dans le mme processus. Cette classe est utilise automatiquement par le CLR. Vous navez donc rien faire de particulier pour bnficier de cette
optimisation.

Relations entre les canaux metteurs et les proxys dun domaine


dapplication client
La relation entre les canaux metteurs et les proxys situs dans un mme domaine dapplication,
est souvent mal comprise. Cela mne souvent des aberrations, comme la cration dun canal
metteur pour chaque objet distant !
Un canal metteur dans un domaine dapplication est une fabrique chanes dintercepteurs
de messages pour les proxys. Revenons un instant au programme illustrant la conception
dun proxy rel propritaire (lExemple 22-28). Le constructeur de la classe de proxy rel
MonProxyReel avait besoin de connatre le canal metteur utiliser pour contacter lobjet
distant :
...
public MonProxyReel(Type type, String Uri, IChannelSender CanalEnvoi):
base(type){
m_Uri = Uri ;
string Unused ;
// Obtient une chaine dintercepteurs de messages du canal.
m_MsgSink = CanalEnvoi.CreateMessageSink(m_Uri, null,out Unused);
}
...
La mthode IChannelSender.CreateMessageSink() demande au canal metteur sous-jacent de
fabriquer une chane dintercepteurs de messages. Cette chane est paramtre notamment par
lURI du service dactivation dobjet distant et par le port du canal.

Relations entre les canaux rcepteurs et les objets dun domaine


dapplication serveur
La relation entre les canaux rcepteurs et les objets dun serveur, situs dans un mme domaine
dapplication, est galement mal comprise. Cela mne souvent des aberrations comme la cration dun canal rcepteur pour chaque objet du serveur.
Cette relation est mal comprise en partie parce que ni la mthode RemotingConfiguration.RegisterWellKnownServiceType() qui sert exposer un service dactivation dobjet WKO,
ni la mthode RemotingConfiguration.RegisterActivatedServiceType() qui sert exposer

832

Chapitre 22 : .NET Remoting

une classe partir du serveur, ne prend de canal rcepteur en argument. La raison est simple :
chaque canal rcepteur dun domaine dapplication peut recevoir des appels pour chaque objet
de ce domaine utilisable dune manire distante.
Lappel la mthode RemotingConfiguration.RegisterWellKnownServiceType() cre une association interne et globale au domaine dapplication entre lURI du service WKO et le service
WKO lui-mme. Ce service est paramtr par le mode dappel et une classe. Lappel la mthode RemotingConfiguration.RegisterActivatedServiceType() enregistre dans le domaine
dapplication le fait quune certaine classe drivant de la classe MarshalByRefObject peut tre
instancie par un client distant. Nous rappelons que chaque fois quune instance de la classe
ObjRef est cre pour rfrencer un objet du domaine dapplication, un URI unique bas sur
un GUID est cr pour cet objet. Une association interne et globale au domaine dapplication,
entre cet URI et lobjet rel, est alors cre.
Lorsquun canal rcepteur reoit un message reprsentant un appel, trois cas peuvent se prsenter :

Lappel se fait sur un service WKO. Dans ce cas un objet est activ si le mode dappel est
simple appel. Si le mode dappel est singleton, soit un objet existe dj pour lURI spcifie,
soit un nouvel objet est activ.

Lappel se fait sur un objet du domaine dapplication, associ avec un URI fourni. Si un objet
est eectivement associ avec cet URI, lappel est eectu sur cet objet, sinon un message
est retourn au client indiquant que lobjet nexiste pas.

Lappel est un appel un constructeur sur une classe CAO. Dans ce cas, un nouvel objet est
cr. Cet objet est associ avec un nouvel URI bas sur un nouveau GUID. Ce nouvel URI
est retourn au client laide dune instance de la classe ObjRef.

Intercepteurs de messages, formateurs et canaux


linstar de ce qui se fait du ct client, du ct serveur un message reprsentant un appel
est trait par des intercepteurs de messages, chans les uns aux autres. Cependant une dirence conceptuelle fondamentale existe entre un canal metteur et un canal rcepteur. Un canal
metteur fabrique une chane dintercepteurs de messages pour chaque proxy rel vers un objet
distant. Un canal rcepteur fabrique une chane dintercepteurs de messages ds quil est cr.
Cette chane sera utilise pour tous les appels transitant par ce canal rcepteur, quel que soit
lobjet concern par lappel. Cette dirence est illustre par la Figure 22-6 :
Sur la Figure 22-6, chaque message est trait par quatre intercepteurs de messages, deux du ct
client et deux du ct serveur. Ceci est une simplification et ne reflte pas la ralit. Il existe en
fait quatre catgories dintercepteurs de messages qui agissent au niveau du canal :

Les intercepteurs de messages qui travaillent avec le message non srialis.

Les intercepteurs de messages qui srialisent/dsrialisent le message, pour quil puisse transiter sur le rseau. On les appelle formateurs (formater sink en anglais). Il y a toujours un
formateur qui srialise dans une chane dintercepteurs de messages ct client, et un formateur qui dsrialise dans une chane dintercepteurs de messages ct serveur. Le framework .NET fourni deux types de formateurs : les formateurs qui srialisent/dsrialisent un
message dans un stream binaire et les formateurs qui srialisent/dsrialisent un message
dans un document SOAP. Vous avez aussi la possibilit de crer vos propres formateurs.

Les intercepteurs de messages qui travaillent avec le message srialis.

Canaux (channels)

833
Domaine dapplication ct client
Proxy transparent vers OBJ1

Proxy transparent vers OBJ2

Proxy rel vers OBJ1

Proxy rel vers OBJ2

Msg Sink

Canal metteur

Msg Sink

Msg Sink

Msg Sink
transport

Msg Sink

Domaine dapplication ct serveur


Canal rcepteur

Msg Sink Transport

Msg Sink

Canal
rcepteur
Msg
Sink

Constructeur de pile

OBJ1

OBJ2

Figure 22 -6 : Intercepteurs de messages et canaux

Les intercepteurs de messages transporteurs, qui soccupent de faire transiter le message


srialis sur le rseau. Il est possible de crer votre propre niveau de transport mais ce sujet
dpasse le cadre de cet ouvrage.

Toutes les classes dintercepteurs de messages implmentent linterface IMessageSink, vue


prcdemment, indpendamment de leur catgorie. Les classes dintercepteurs de messages
destines tre instancies par un canal metteur implmentent linterface System.Runtime.
Remoting.Channels.IClientChannelSink. Les classes dintercepteurs de messages destines
tre instancies par un canal rcepteur implmentent linterface System.Runtime.Remoting.
Channels.IServerChannelSink. Les classes de formateurs destines tre instancies par un canal metteur implmentent linterface System.Runtime.Remoting.Channels.IClientFormatterSink qui tend la fois les interfaces IMessageSink et IClientChannelSink. Les classes
de formateurs destines tre instancies par un canal rcepteur implmentent linterface
System.Runtime.Remoting.Channels.IServeurFormatterSink qui tend la fois les interfaces
IMessageSink et IServerChannelSink.
Avant de pouvoir exposer tout ceci dans un exemple concret, il faut expliquer comment dfinir
la production de chanes dintercepteurs de messages dun canal.

Fournisseurs dintercepteurs de messages (channel sink providers)


Pour dfinir la production de chanes dintercepteurs de messages dun canal, il faut agir au
niveau de sa chane de fournisseurs dintercepteurs de messages. Un fournisseur dintercepteurs
de messages (channel sink providers en anglais) dun canal metteur (respectivement dun canal

834

Chapitre 22 : .NET Remoting

rcepteur) est une instance dune classe qui implmente linterface System.Runtime.Remoting.
Channels.IClientChannelSinkProvider (respectivement IServerChannelSinkProvider).
Rappelons que dans un canal metteur, cette production est sollicite pour chaque proxy rel
alors que dans un canal rcepteur, cette production est sollicite une seule fois, la cration du
canal.
Chacune de ces deux interfaces prsente naturellement une mthode CreateSink(). Cette mthode est appele par limplmentation dun canal pour obtenir un nouvel intercepteur de messages du fournisseur :
IServerChannelSink IServerChannelSinkProvider.CreateSink(
IChannelReceiver canal) ;
IClientChannelSink IClientChannelSinkProvider.CreateSink(
IChannelSender
canal,
String
url,
Object
remoteChannelData) ;
Pour que lon puisse crer une chane de tels fournisseurs, chacune de ces deux interfaces prsente la proprit Next, du type de linterface concerne. Vous pouvez donc facilement crer une
telle chane dans votre code et la communiquer un canal lors de sa construction, en utilisant les
constructeurs des classes de canaux prvus cet eet. Nanmoins on prfre toujours configurer
la chane de fournisseurs dun canal par lintermdiaire du fichier de configuration Remoting
de lapplication. Ceci est illustr dans la section suivante.

Un exemple rcapitulatif : Acher le nombre doctets passant sur


le rseau
Nous allons construire nos propres intercepteurs de messages pour canaux et nos propres fournisseurs pour ces intercepteurs. Le but est dacher sur la console, du ct client et du ct
serveur, le nombre doctets envoys et reus.

Intercepteurs de messages et fournisseurs


Nous avons donc besoin de dvelopper quatre classes pour ce projet :

Une classe nomme CustomClientSink, dont les instances sont des intercepteurs de messages. Les instances de cette classe doivent tre places dans le canal metteur, aprs le formateur.

Une classe nomme CustomServerSink, dont les instances sont des intercepteurs de messages. Les instances de cette classe doivent tre places dans le canal rcepteur, avant le formateur.

Une classe nomme CustomClientSinkProvider, dont les instances sont des fournisseurs
dinstances de CustomClientSink. Les instances de cette classe doivent tre chanes aprs
le fournisseur de formateurs dans le canal metteur.

Une classe nomme CustomServerSinkProvider, dont les instances sont des fournisseurs
dinstances de CustomServerSink. Les instances de cette classe doivent tre chanes avant
le fournisseur de formateurs dans le canal rcepteur.

Nous dveloppons aussi une classe Helper, qui fournit la mthode statique GetStreamLength()
qui retourne la taille dun stream. Cette mthode est capable de retourner cette taille, que le

Canaux (channels)

835

stream ait la possibilit daccs alatoire (seek) ou non. Voici le code du nouvel assemblage qui
contient toutes ces classes :
Exemple 22-30 :
using
using
using
using
using

CustomChannelSink.cs

System ;
System.Runtime.Remoting.Channels ;
System.Runtime.Remoting.Messaging ;
System.Collections ;
System.IO ;

namespace CustomChannelSink {
internal class Helper {
static public Stream GetStreamLength(Stream inStream,
out long length) {
// Les acc`es aleatoires sont-ils autoris
es sur InStream ?
if (inStream.CanSeek) {
length = inStream.Length ;
return inStream ;
}
// Les acc`es aleatoires ne sont pas autoris
es sur InStream.
// Copie de InStream dans OutStream pour obtenir la taille.
Stream outStream = new MemoryStream() ;
byte[] tampon = new Byte[1024] ;
int tmp, nBytesRead = 0 ;
while ((tmp = inStream.Read(tampon, 0, 1024)) > 0) {
outStream.Write(tampon, nBytesRead, tmp) ;
nBytesRead += tmp ;
}
outStream.Seek(0, SeekOrigin.Begin) ;
length = nBytesRead ;
return outStream ;
}
}
//
// Intercepteur proprietaire de messages, pour un canal
emetteur.
//
public class CustomClientSink : BaseChannelSinkWithProperties,
IClientChannelSink {
private IClientChannelSink m_NextSink ;
public CustomClientSink(IClientChannelSink nextSink) {
m_NextSink = nextSink ; }
public IClientChannelSink NextChannelSink {
get { return m_NextSink ; }
}
public void AsyncProcessRequest(

836

Chapitre 22 : .NET Remoting


IClientChannelSinkStack sinkStack,
IMessage msgIn,
ITransportHeaders headers,
Stream msgStream) {
long length ;
msgStream = Helper.GetStreamLength(msgStream, out length) ;
Console.WriteLine(
"CustomClientSink:Async, taille du stream denvoi {0}",length);
// Auto-chanage du pr
esent intercepteur pour le retour
// dun appel asynchrone.
sinkStack.Push(this, null) ;
m_NextSink.AsyncProcessRequest(sinkStack,
msgIn, headers, msgStream) ;
}
public void AsyncProcessResponse(
IClientResponseChannelSinkStack sinkStack,
Object state,
ITransportHeaders headers,
Stream msgStream) {
long length ;
msgStream = Helper.GetStreamLength(msgStream, out length) ;
Console.WriteLine(
"CustomClientSink:Async, taille du stream de retour {0}",
length);
m_NextSink.AsyncProcessResponse(
sinkStack, state, headers, msgStream) ;
}
public Stream GetRequestStream(
IMessage msg,
ITransportHeaders headers) {
return m_NextSink.GetRequestStream(msg, headers) ;
}
public void ProcessMessage(
IMessage msg,
ITransportHeaders headersIn,
Stream msgInStream,
out ITransportHeaders headersOut,
out Stream msgOutStream) {
long length ;
msgInStream = Helper.GetStreamLength(msgInStream, out length) ;
Console.WriteLine(
"CustomClientSink:Sync, taille du stream denvoi {0}",length);
m_NextSink.ProcessMessage(msg, headersIn, msgInStream,
out headersOut, out msgOutStream) ;
msgOutStream = Helper.GetStreamLength(msgOutStream,out length) ;

Canaux (channels)
Console.WriteLine(
"CustomClientSink:Sync, taille du stream de retour {0}",
length);
}
}
//
// Intercepteur proprietaire de messages, pour un canal r
ecepteur.
//
public class CustomServerSink : BaseChannelSinkWithProperties,
IServerChannelSink {
private IServerChannelSink m_NextSink ;
public CustomServerSink(IServerChannelSink nextSink) {
m_NextSink = nextSink ;
}
public IServerChannelSink NextChannelSink {
get { return m_NextSink ; }
}
public void AsyncProcessResponse(
IServerResponseChannelSinkStack sinkStack,
object state,
IMessage msg,
ITransportHeaders headers,
Stream msgStream) {
long length ;
msgStream = Helper.GetStreamLength(msgStream, out length) ;
Console.WriteLine(
"CustomServerSink:Async, taille du stream de retour {0}",
length);
m_NextSink.AsyncProcessResponse(
sinkStack, state, msg, headers, msgStream) ;
}
public Stream GetResponseStream(
IServerResponseChannelSinkStack sinkStack,
object state,
IMessage msg,
ITransportHeaders headers) {
return null ;
}
public ServerProcessing ProcessMessage(
IServerChannelSinkStack sinkStack,
IMessage msgIn,
ITransportHeaders headersIn,
Stream msgInStream,
out IMessage msgOut,
out ITransportHeaders headersOut,

837

838

Chapitre 22 : .NET Remoting


out Stream msgOutStream) {
long length ;
msgInStream = Helper.GetStreamLength(msgInStream, out length) ;
Console.WriteLine(
"CustomServerSink:Sync, taille du stream denvoi {0}",length);
// Auto-chanage du present intercepteur pour le retour
// pour prevoir le cas o`u lappel est asynchrone.
sinkStack.Push(this, null) ;
ServerProcessing svrProc = m_NextSink.ProcessMessage(
sinkStack, msgIn, headersIn, msgInStream,
out msgOut, out headersOut, out msgOutStream) ;
msgOutStream = Helper.GetStreamLength(msgOutStream,out length) ;
Console.WriteLine(
"CustomServerSink:Sync, taille du stream de retour {0}",
length);
return svrProc ;
}
}
//
// Fournisseur proprietaire dintercepteurs de messages,
// pour un canal emetteur.
//
public class CustomClientSinkProvider : IClientChannelSinkProvider {
private IClientChannelSinkProvider m_NextProvider ;
public CustomClientSinkProvider(IDictionary prop,
ICollection providerData) { }
public IClientChannelSinkProvider Next {
get { return m_NextProvider ; }
set { m_NextProvider = value ; }
}
public IClientChannelSink CreateSink(
IChannelSender canal,
string url,
object remoteChannelData) {
IClientChannelSink next =
m_NextProvider.CreateSink(canal, url, remoteChannelData) ;
Console.WriteLine(
"CustomClientSinkProvider:Creation dun intercepteur de msg.");
return new CustomClientSink(next) ;
}
}
//
// Fournisseur proprietaire dintercepteurs de messages,
// pour un canal recepteur.
//

Canaux (channels)

839

public class CustomServerSinkProvider : IServerChannelSinkProvider {


private IServerChannelSinkProvider m_NextProvider ;
public CustomServerSinkProvider(IDictionary prop,
ICollection providerData) { }
public IServerChannelSinkProvider Next {
get { return m_NextProvider ; }
set { m_NextProvider = value ; }
}
public IServerChannelSink CreateSink(IChannelReceiver canal) {
IServerChannelSink next = m_NextProvider.CreateSink(canal) ;
Console.WriteLine(
"CustomServerSinkProvider:Creation dun intercepteur de msg.");
return new CustomServerSink(next) ;
}
public void GetChannelData(IChannelDataStore channelData) { }
}
}
Quelques prcisions simposent :

Dans les constructeurs des fournisseurs, le dictionnaire pass en argument correspond aux
proprits que vous souhaitez aecter aux fournisseurs. Ici nous nutilisons pas cette possibilit. Il surait dcrire dans le fichier de configuration :
<provider type = "CustomChannelSink.CustomClientSinkProvider,ChannelSink"
Prop1="hello"/>
Vous pouvez obtenir la valeur de la proprit comme ceci :
public CustomClientSinkProvider(IDictionary Prop,
ICollection ProviderData) {
string s = (string) Prop["Prop1"];
...
Vous pouvez ainsi configurer votre fournisseur. Par exemple vous pouvez spcifier le nom
dun algorithme de compression ou de cryptage des streams.

Le type IServerChannelSinkStack utilis dans les mthodes dans lintercepteur de messages ct serveur, concerne le retour des appels asynchrone. Cette pile correspond en fait
la chane dintercepteurs de messages qui sera utilise du ct serveur, pour faire transiter
le message de retour.

Le type ITransportHeaders utilis dans les mthodes dans les intercepteurs de messages
ct client et ct serveur, permet de passer des informations concernant le message. Par
exemple, si la fonction des intercepteurs de message est de crypter/dcrypter le stream, vous
pouvez spcifier dans cet en-tte si le message est eectivement crypt :
class CustomClientSink{ ...
public void ProcessMessage(
IMessage

Msg,

840

Chapitre 22 : .NET Remoting


ITransportHeaders
HeadersIn, ...){
HeadersIn["Crypte"] ="Affirmatif";
...
Cela permet lintercepteur dans le canal rcepteur de tester que le message a t eectivement crypt, avant dessayer de le dcrypter.
class CustomServerSink{ ...
public ServerProcessing ProcessMessage(
IServerChannelSinkStack
SinkStack,
IMessage
MsgIn,
ITransportHeaders
HeadersIn,...){
String sCrypte = (string) HeadersIn["Crypt
e"] ;
if( sCrypte != null &&
sCrypt
e == "Affirmatif"){ ...
Ce canal rcepteur devient ainsi polyvalent, et peut traiter la fois les messages crypts et
non crypts.

Ct serveur
Du ct serveur, toutes les informations relatives aux canaux se trouvent dans le fichier de configuration :
Serveur.cs
...
static void Main() {
RemotingConfiguration.Configure("Serveur.config") ;
Console.WriteLine("Pressez une touche pour stopper le serveur...") ;
Console.Read() ;
}
...
Nous aectons ici un formateur binaire avec un canal rcepteur HTTP.
Exemple 22-31 :

Serveur.config

<configuration>
<system.runtime.remoting>
<application name = "Serveur">
<service>
<wellknown type="NommageInterface.CAdditionneur,Interface"
mode ="Singleton" objectUri="Service1.rem" />
</service>
<channels>
<channel port="65100" ref ="http">
<serverProviders>
<provider type=
"CustomChannelSink.CustomServerSinkProvider,ChannelSink" />
<formatter ref="binary"/>
</serverProviders>
</channel>
</channels>

Canaux (channels)

841

</application>
</system.runtime.remoting>
</configuration>

Ct client
Du ct client, toutes les informations pertinentes sont galement dans le fichier de configuration :
Client.cs
...
static void Main() {
RemotingConfiguration.Configure("Client.config",false) ;
CAdditionneur objA = new CAdditionneur() ;
double dA = objA.Add( 3.0 , 4.0 ) ;
Console.WriteLine("Valeur retourn
ee:"+dA) ;
CAdditionneur objB = new CAdditionneur() ;
double dB = objB.Add( 3.0 , 4.0 ) ;
Console.WriteLine("Valeur retourn
ee:"+dB) ;
}
...
Nous aectons ici un formateur binaire avec un canal metteur HTTP.
Exemple 22-32 :

Client.config

<configuration>
<system.runtime.remoting>
<application name = "Client">
<client>
<wellknown type="NommageInterface.CAdditionneur,Interface"
url="http://localhost:65100/Service1.rem" />
</client>
<channels>
<channel ref ="http">
<clientProviders>
<formatter ref="binary"/>
<provider type =
"CustomChannelSink.CustomClientSinkProvider,ChannelSink"/>
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>

Excution du projet
Voici ce quache le serveur sur sa console :

842

Chapitre 22 : .NET Remoting


CustomServerSinkProvider:Creation dun intercepteur de msg.
Pressez une touche pour quitter le serveur...
CustomServerSink:Sync, taille du stream denvoi 155
CAdditionneur ctor
CAdditionneur Add( 3 + 4 )
CustomServerSink:Sync, taille du stream de retour 32
CustomServerSink:Sync, taille du stream denvoi 155
CAdditionneur Add( 3 + 4 )
CustomServerSink:Sync, taille du stream de retour 32

Voici ce quache le client sur sa console :


CustomClientSinkProvider:Creation dun intercepteur de msg.
CustomClientSink:Sync, taille du stream denvoi 155
CustomClientSink:Sync, taille du stream de retour 32
Valeur retournee:7
CustomClientSinkProvider:Creation dun intercepteur de msg.
CustomClientSink:Sync, taille du stream denvoi 155
CustomClientSink:Sync, taille du stream de retour 32
Valeur retournee:7
Si lon avait choisi un formateur SOAP et non pas un formateur binaire, la taille du stream
denvoi aurait t de 574 octets et la taille du stream de retour aurait t de 586 octets.
Si vous disposez dune bibliothque de compression ou de cryptage, cet exemple pourrait tre
trs facilement modifi pour compresser ou crypter les donnes binaires passants sur le rseau.

Contexte .NET
La place de la notion de contexte dans larchitecture .NET
Nous avons vu que les domaines dapplication permettent lisolation lexcution au niveau
des types, de la scurit et de la gestion des exceptions. En revanche, il existe une entit plus
fine que le domaine dapplication pour stocker les objets. Un domaine dapplication .NET peut
contenir plusieurs de ces entits, appeles contexte .NET. Pour simplifier la lecture, on les appellera contextes tant quil ny a pas de confusion avec la notion de contexte COM+, qui est
dirente (cette notion est prsente page 300). Certains auteurs utilisent les termes contexte gr
ou contexte dexcution pour un contexte .NET et le terme de contexte non gr pour les contextes
COM+. Tous les objets .NET vivent dans un contexte, et il existe au moins un contexte par
domaine dapplication. Ce contexte, appel contexte par dfaut du domaine dapplication, est cr
en mme temps que son domaine. La Figure 22-7 rsume ces relations dinclusions :
La notion de contexte ore aux dveloppeurs la fonctionnalit trs pratique dinterception des
appels un objet. Nous rappelons quintercepter un appel signifie que lon peut eectuer un
ou plusieurs traitements chaque appel entrant ou sortant vers un objet dun contexte. Ces
traitements sont matrialiss par des intercepteurs de messages. Lide centrale est que lorsquun
client appelle une mthode dun objet, il na pas conscience que son appel est intercept et que
des traitements sont eectus avant et aprs lexcution de lappel.

Contexte .NET

843
Processus
CLR
Domaine dapplication
Rgles pour
la scurit
Rgles pour les
exceptions non gres

Assemblage

Contexte
Objet
Objet
Objet

Type

Figure 22 -7 : Processus, domaines dapplication et contextes

Context-bound et context-agile
Daprs la section prcdente, un contexte peut tre vu comme une zone dun domaine
dapplication, contenant des objets et des intercepteurs de messages. Les appels vers les objets
dun contexte sont transforms en messages qui sont intercepts et traits par les intercepteurs
du message. Nous savons maintenant que pour transformer un appel en un message, il faut
passer par lintermdiaire dun proxy transparent. Or, nous savons aussi que le CLR ne fabrique
un proxy transparent vers un objet que si ce dernier est une instance dune classe drive de
MarshalByRefObject appele par une entit situe hors de son domaine dapplication. Ici, nous
souhaiterions bnficier du mcanisme dinterception de messages pour tous les appels, mme
ceux eectus entre entits situes dans le mme domaine dapplication. Cest exactement
pour cela quexiste la classe System.ContextBoundObject. Une instance dune classe qui drive
de ContextBoundObject est accessible seulement partir de proxys transparents. Dans ce cas,
mme la rfrence this utilise dans les mthodes de la classe est un proxy transparent et
non une rfrence directe. Il est logique que la classe ContextBoundObject drive de la classe
MarshalByRefObject, puisquelle ne fait que renforcer le comportement de cette classe qui est
dindiquer au CLR quune classe est potentiellement utilise au travers dun proxy transparent.
Une instance dune classe drivant de ContextBoundObject est qualifie de context-bound (lie au
contexte en franais). Une instance dune classe ne drivant pas de ContextBoundObject est qualifie de context-agile (dsolidariss du contexte en franais). Un objet context-bound est toujours
excut au sein de son contexte. Dans le cas dun appel non distant, un objet context-agile est
toujours excut au sein du contexte de celui qui lappelle. La Figure 22-8 illustre ceci.
Domaine dapplication
Contexte B

Contexte A

Objet context-agile

Proxy transparent

Objet context-bound

Proxy transparent

Figure 22 -8 : Objets context-bound vs. objets context-agile

844

Chapitre 22 : .NET Remoting

Illustrons maintenant ces notions de context-bound et context-agile avec un programme qui


utilise des objets context-bound et des objets context-agile.
Nous allons utiliser dans ce programme la classe System.Runtime.Remoting.Contexts.Context
dont une instance reprsente un contexte gr, un peu comme une instance de la classe
Thread reprsente un thread gr. Nous allons aussi utiliser dans ce programme lattribut
System.Runtime.Remoting.Contexts.Context.Synchronization appliqu une classe drivant de ContextBoundObject. Cet attribut oblige les instances de cette classe tre dans
un contexte spcialement cr pour supporter ce service de synchronisation. Le service de
synchronisation propos par cet attribut est dcrit page 160. Ce service de synchronisation est
assur par un mcanisme dinterception de messages compltement transparent aussi bien
pour lappelant que pour lappel.
Nous profitons aussi de cet exemple pour montrer lutilisation de la mthode statique bool
IsOutOfContext(object) de la classe System.Runtime.Remoting.RemotingServices qui permet de savoir si une rfrence pointe vers un objet hors du contexte de lappelant de cette mthode.
Exemple 22-33 :
using
using
using
using

System ;
System.Runtime.Remoting.Contexts ;
System.Runtime.Remoting ;
System.Threading ;

[Synchronization(SynchronizationAttribute.REQUIRED)]
public class Foo1 : ContextBoundObject {
public void AfficheContexte() {
Console.WriteLine("Foo1: ContextID:" +
Thread.CurrentContext.ContextID) ;
}
public Foo2 GetFoo2() {
Foo2 obj = new Foo2() ;
obj.AfficheContexte();
return obj ;
}
}
public class Foo2 {
public void AfficheContexte() {
Console.WriteLine("Foo2: ContextID:" +
Thread.CurrentContext.ContextID) ;
}
}
public class Program {
static void Main() {
Console.WriteLine("Main: ContextID:" +
Thread.CurrentContext.ContextID) ;
Foo1 obj1 = new Foo1() ;

Contexte .NET

845

obj1.AfficheContexte();
Console.WriteLine("IsObjectOutOfContext(obj1):" +
RemotingServices.IsObjectOutOfContext(obj1)) ;
Foo2 obj2 = obj1.GetFoo2() ;
obj2.AfficheContexte();
Console.WriteLine("IsObjectOutOfContext(obj2):" +
RemotingServices.IsObjectOutOfContext(obj2)) ;
}
}
Ce programme ache :
Main: ContextID:0
Foo1: ContextID:1
IsObjectOutOfContext(obj1):True
Foo2: ContextID:1
Foo2: ContextID:0
IsObjectOutOfContext(obj2):False
On voit bien que lobjet obj2 context-agile peut tre excut hors du contexte dans lequel il
a t cr. On observe aussi que lobjet obj1 context-bound sexcute au sein de son contexte
lorsquon lappelle partir dun autre contexte.
Dans un mme domaine dapplication, les ContextID sont tous dirents. Cependant il est possible que deux ContextID de deux contextes appartenant deux domaines dapplication dirents soient gaux.
Daprs nos propres tests, dans une mthode statique, la proprit Thread.CurrentContext.
ContextID est gale au ContextID du contexte par dfaut du domaine dapplication. Ce fait est
logique tant donn que le comportement du CLR est de crer un objet dans le contexte du
client, si cela est possible. Or, une mthode statique peut constituer un client dun objet.

Attribut de contexte et proprit de contexte


Nous allons exposer ici la technique permettant dinjecter des intercepteurs de messages au niveau du contexte. Commenons par introduire les notions dattribut de contexte et de proprit
de contexte.

Attribut de contexte
Un attribut de contexte est un attribut .NET qui agit sur une classe context-bound. Une classe attribut de contexte implmente linterface System.Runtime.Remoting.Contexts.IContextAttribute.
Une classe context-bound peut avoir plusieurs attributs de contexte. Lors de la cration dun
objet de cette classe, chaque attribut de contexte de la classe vrifie si le contexte de celui qui
construit lobjet lui convient. Cette opration seectue dans la mthode :
public bool IContextAttribute.IsContextOK(
Context
IConstructionCallMessage

ctxDuClient,
ctorMsg)

846

Chapitre 22 : .NET Remoting

Si au moins un attribut de contexte retourne false, le CLR doit crer un nouveau contexte pour
accueillir le nouvel objet. Dans ce cas chaque attribut de contexte peut injecter une ou plusieurs
proprits de contexte dans le nouveau contexte. Ces injections ont lieu dans la mthode suivante :
public void IContextAttribute.GetPropertiesForNewContext(
IConstructionCallMessage
ctorMsg)

Proprit de contexte
Une proprit de contexte est une instance dune classe qui implmente linterface System.
Runtime.Remoting.Contexts.IContextProperty. Chaque contexte peut avoir plusieurs proprits. Les proprits dun contexte sont injectes par les attributs de contexte dans le contexte,
lorsque le contexte est cr. Une fois que chaque attribut de contexte a inject ses proprits,
la mthode suivante est appele sur chaque proprit. Il nest alors plus possible dinjecter une
proprit dans ce contexte :
public void IContextProperty.Freeze(Context ctx)
Le CLR demande ensuite chaque proprit si elle est satisfaite par ce nouveau contexte en
appelant la mthode :
public bool IContextProperty.IsNewContextOK(Context ctx)
Chaque proprit dun contexte a un nom dfini par la proprit Name :
public string IContextProperty.Name{ get }
Les mthodes des objets hbergs dans le contexte peuvent avoir accs aux proprits du
contexte en appelant la mthode :
IContextProperty Context.GetProperty(string sPropertyName)
Cette possibilit peut tre intressante, puisque les objets du contexte peuvent ainsi partager des
informations et accder des services grce aux proprits de leur contexte. Cependant le rle
principal des proprits dun contexte nest pas de fournir cette possibilit :

Le rle principal des proprits dun contexte est dinjecter des intercepteurs de messages,
dans les rgions dinterception de messages des contextes concerns.
La description de ces rgions dinterceptions fait lobjet de la prochaine section. Familiarisonsnous dabord avec ces concepts dattributs et de proprits dun contexte laide dun exemple.
Pour ceux qui ont assimil la section sur les canaux, un attribut de contexte joue un peu le
rle dun fichier de configuration qui injecte des fournisseurs dans les canaux. De mme une
proprit de contexte joue un peu le rle du fournisseur dintercepteur de message.

Exemple utilisant un attribut et une proprit de contexte


Le programme suivant dfinit la classe dattribut de contexte LogContextAttribute et la
classe de proprit de contexte LogContextProperty. Toute instance dune classe ayant pour
attribut LogContextAttribute est hberge dans un contexte qui a une proprit de type
LogContextProperty. Une telle instance peut ainsi avoir accs aux services prsents par cette

Contexte .NET

847

proprit. Ces services sont en loccurrence la possibilit dcrire une chane de caractres dans
un fichier en appelant la mthode LogContextProperty.Log(string). Le nom de ce fichier est
un paramtre de lattribut LogContextAttribute. On peut ainsi avoir un fichier pour chaque
classe. Lorsquune nouvelle instance dune classe ayant pour attribut LogContextAttribute est
cre, la mthode bool LogContextAttribute.IsContextOK(Context) permet de vrifier si le
contexte dans lequel rside lentit qui appelle le constructeur, contient dj une instance de
LogContextAttribute avec le mme nom de fichier. Si ce nest pas le cas, un nouveau contexte
doit alors tre cr. La mthode LogContextAttribute.GetPropertiesForNewContext(IConstructionCallMessage ctor) cre une instance de LogContextProperty. Au retour de cette
mthode, la nouvelle proprit est automatiquement injecte dans le nouveau contexte par le
CLR. Voici le programme :
Exemple 22-34 :
using
using
using
using

System ;
System.Runtime.Remoting.Contexts ;
System.Runtime.Remoting.Activation ;
System.Threading ;

public class LogContextProperty : IContextProperty {


public LogContextProperty(string sFileName) {
m_sFileName = sFileName ; }
string m_sFileName ;
public string sFileName { get { return m_sFileName ; } }
public string Name { get { return "Log" ; } }
public bool IsNewContextOK(Context ctx) { return true ; }
public void Freeze(Context ctx) { }
public void Log( string sLog ) {
// Nous nous contentons d
ecrire les donn
ees sur la console.
Console.WriteLine(
"ContextID={0} Ecrire {1} dans le fichier {2}",
Thread.CurrentContext.ContextID,
sLog,
m_sFileName) ;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class LogContextAttribute : Attribute, IContextAttribute {
string m_sFileName ;
public LogContextAttribute(string sFileName) {
m_sFileName = sFileName ; }
// Pas la peine de creer un nouveau contexte si celui a d
ej`
a
// la propriete de log dans le bon fichier.
public bool IsContextOK(Context ctx_courant,
IConstructionCallMessage ctor) {
LogContextProperty prop = ctx_courant.GetProperty("Log")
as LogContextProperty;
if ( prop == null ) return false;
return ( prop.sFileName == m_sFileName );

848

Chapitre 22 : .NET Remoting


}
public void GetPropertiesForNewContext(
IConstructionCallMessage ctor) {
IContextProperty prop = new LogContextProperty(m_sFileName);
ctor.ContextProperties.Add(prop);
}
}
[LogContextAttribute("Log_Foo.txt")]
public class Foo : ContextBoundObject {
public Foo CreateNewInst() {
return new Foo() ;
}
public int Ajout(int a, int b) {
string s = string.Format("Ajout de {0}+{1}", a, b) ;
Context ctx = Thread.CurrentContext ;
LogContextProperty Logger =
ctx.GetProperty("Log") as LogContextProperty;
Logger.Log(s);
return a + b ;
}
}
public class Program {
static void Main() {
Foo obj1 = new Foo() ;
obj1.Ajout(4, 5) ;
Foo obj2 = new Foo() ;
obj2.Ajout(6, 7) ;
Foo obj3 = obj1.CreateNewInst() ;
obj3.Ajout(8, 9) ;
}
}

Ce programme ache :
ContextID=1 Ecrire Ajout de 4+5 dans le fichier LogPourFoo.txt
ContextID=2 Ecrire Ajout de 6+7 dans le fichier LogPourFoo.txt
ContextID=1 Ecrire Ajout de 8+9 dans le fichier LogPourFoo.txt
Les objets obj1 et obj3 sont hbergs dans le mme contexte puisque obj3 a t construit partir
du contexte dobj1.

Notion de rgion dinterception de messages


Il existe quatre rgions dinterception de messages, la rgion serveur , la rgion objet , la rgion envoi et la rgion client . Pour comprendre ces notions de rgions, il faut se placer
dans le cas o un objet context-bound est appel par une entit situe dans un autre contexte.
Cette entit peut tre une mthode statique ou un autre objet. Dans notre discussion sur ces
rgions, on appelle le contexte de cette entit le contexte appelant et le contexte de lobjet appel

Contexte .NET

849

le contexte cible. Chacune des proprits de contexte du contexte cible a la possibilit dinjecter
des intercepteurs de messages dans chacune de ces rgions.

Les intercepteurs de messages injects dans la rgion serveur interceptent tous les messages dappels venant dun autre contexte vers tous les objets du contexte cible. Il existe donc
une rgion serveur par contexte cible.

Les intercepteurs de messages injects dans la rgion objet interceptent tous les messages
dappels venant dun autre contexte vers un objet particulier du contexte cible. Il y a donc
une rgion objet pour chaque objet dun contexte. Les rgions objet se situent donc
dans le contexte cible.

Les intercepteurs de messages injects dans la rgion envoi interceptent tous les messages dappels venant dun autre contexte vers un objet particulier du contexte cible. Il y
a donc une rgion envoi pour chaque objet du contexte cible. La dirence entre une
rgion envoi et une rgion objet est quune rgion envoi se situe dans le contexte
appelant lobjet et non pas dans le contexte cible contenant lobjet. On utilise les rgions
envoi pour transmettre aux intercepteurs de messages du contexte cible des informations sur le contexte appelant.

Les intercepteurs de messages injects dans la rgion client interceptent tous les messages dappels du contexte cible vers des objets situs dans dautres contextes. Il y a donc
une rgion client pour chaque contexte cible.

La Figure 22 -9 illustre ces notions de rgions. Le contexte cible contient deux objets OBJ1 et
OBJ2. Nous avons choisi de placer deux objets dans le contexte cible et pas un, pour bien montrer que les types de rgion objet et envoi sont concernes par linterception des messages
au niveau dun objet alors que les types de rgion serveur et client sont concernes par
linterception des messages au niveau dun contexte.
Nous avons plac deux intercepteurs propritaires de messages (Msg sink) par rgion pour bien
montrer que chaque rgion peut avoir zro, un ou plusieurs intercepteurs de messages. Concrtement, tous ces intercepteurs propritaires de messages sont injects dans les rgions par les
proprits du contexte cible, mme lorsque la rgion nappartient pas au contexte cible. Comme
vous pouvez dfinir vos propres classes de proprits de contexte vous pouvez choisir quels sont
les intercepteurs de messages quil faut injecter.
Vous remarquez que chaque rgion contient un intercepteur systme de messages dit de terminaison, qui permet dindiquer au CLR la sortie dune rgion. Concrtement, vous naurez pas
vous proccuper de ces intercepteurs systmes de messages de terminaison.
Sachez que lorsque le contexte appelant et le contexte cible sont dans le mme domaine dapplication, le CLR utilise une instance de la classe CrossContextChannel interne mscorlib.dll.
Cette instance fait basculer la proprit Context du thread courant. Notre figure reprsente ces
instances.

Exemple dutilisation des rgions dinterception


Voici tous ce que nous exposons avec le programme de lExemple 22-35 :

Le code dun attribut de contexte propritaire (classe MyAfficheContextAttribute) injectant une proprit de contexte propritaire (classe MyAfficheContextProperty) dans

850

Chapitre 22 : .NET Remoting


Contexte appelant
Entit
appelant les
objets OBJ1
et OBJ2

Proxy transparent de OBJ1


Proxy rel de OBJ1

Rgion envoi pour OBJ1


Msg sink

Msg sink
Msg sink de terminaison

Proxy transparent de OBJ2


Proxy rel de OBJ2

Rgion envoi pour OBJ2


Msg sink

Msg sink

Msg sink de terminaison

CrossContextChannel

Rgion serveur pour le contexte cible


Msg sink

Msg sink

Msg sink de terminaison

Rgion objet pour OBJ1


Msg sink

Msg sink
Msg sink de terminaison

Proprit du
contexte cible
(injecte des msg sink
dans les rgions)
Rgion objet pour OBJ2
Msg sink

Msg sink

Msg sink de terminaison

Stack builder sink (constructeur de pile)


OBJ1

OBJ2

Proxy transparent de OBJ3

Proxy rel de OBJ3

Rgion client pour le contexte cible


Msg sink de terminaison

Msg sink

Msg sink

CrossContextChannel
Contexte contenant lobjet OBJ3 qui est appel par les objets OBJ1 et OBJ2

Figure 22 -9 : Interceptions des appels au niveau du contexte


un contexte. Cette proprit de contexte injecte des intercepteurs propritaires de messages (classe MyAfficheMessageSink) dans les rgions objet serveur et client du
contexte cible et dans la rgion envoi du contexte appelant.

La modification du comportement des intercepteurs de messages en fonction dun paramtre pass dans lattribut du contexte. En loccurrence, ce paramtre est un boolen qui
indique aux intercepteurs de messages sils doivent ou non acher quelque chose sur la
console.

Linjection dintercepteurs de messages dans chacune des quatre rgions par une proprit
de contexte. Lattribut de contexte MyAfficheContextAttribute fait en sorte de crer un
contexte pour chaque objet de la classe Foo. Nous crons une premire instance de Foo et
nous appelons une mthode sur cette instance pour passer par les intercepteurs de messages
des rgions envoi , serveur et objet . Pour passer par les intercepteurs de messages

Contexte .NET

851

de la rgion client , nous crons une seconde instance de Foo et nous lappelons partir
de la premire instance. Il y a donc trois contextes en jeu : le contexte dans lequel sexcute la mthode Main() (ContextID=0), le contexte qui contient la premire instance de Foo
(ContextID=1) et le contexte qui contient la seconde instance de Foo (ContextID=2).

Le fait quun appel intra contexte ne dclenche pas lappel de tous ces intercepteurs de
messages. Ce comportement est expos lorsque la premire instance de Foo sauto appelle.

Linjection de plusieurs intercepteurs de messages dans une rgion (en loccurrence la rgion client ).

Le moment o le CLR injecte eectivement des intercepteurs de messages dans les rgions.
On voit clairement que ce moment dpend du type de la rgion.

Voici le programme :
Exemple 22-35 :
using
using
using
using
using
using

System ;
System.Runtime.Remoting.Contexts ;
System.Runtime.Remoting.Messaging ;
System.Runtime.Remoting.Activation ;
System.Threading ;
System.Collections ;

//
// Les instances de cette classe de propri
et
e de contexte injectent
// des intercepteurs de messages dans les quatre r
egions dinterception.
//
public class MyAfficheContextProperty :
IContextProperty,
IContributeEnvoySink,
IContributeObjectSink,
IContributeServerContextSink,
IContributeClientContextSink {
public MyAfficheContextProperty(bool bAffiche) {
m_bAffiche = bAffiche ;
}
bool m_bAffiche ;
public bool bAffiche { get { return m_bAffiche ; } }
// IContextProperty
public string Name { get { return "PropAffiche" ; } }
public bool IsNewContextOK(Context ctx) { return true ; }
public void Freeze(Context ctx) {
Console.WriteLine("
Freeze ContextID={0}", ctx.ContextID) ;
}
// Injection de deux intercepteurs de messages
// dans la region client.
public IMessageSink GetClientContextSink( IMessageSink nextSink) {
Console.WriteLine("
GetClientContextSink()") ;

852

Chapitre 22 : .NET Remoting


IMessageSink nextnextSink = new MyAfficheMessageSink(
nextSink, "R
egion Client1 ", m_bAffiche) ;
return new MyAfficheMessageSink(
nextnextSink, "Region Client2 ", m_bAffiche) ;
}
// Injection dun intercepteur de messages dans la r
egion serveur.
public IMessageSink GetServerContextSink( IMessageSink nextSink) {
Console.WriteLine("
GetServerContextSink()") ;
return new MyAfficheMessageSink(
nextSink, "Region Serveur ", m_bAffiche) ;
}
// Injection dun intercepteur de messages dans la r
egion envoi
// NOTE:Vous pouvez vous servir de mbro pour obtenir une r
ef
erence
// vers lobjet et ainsi modifier linjection dintercepteurs de
// messages en fonction de lobjet.
public IMessageSink GetEnvoySink( MarshalByRefObject mbro,
IMessageSink nextSink) {
Console.WriteLine("
GetEnvoySink()") ;
return new MyAfficheMessageSink(
nextSink, "R
egion Envoi
", m_bAffiche) ;
}
// Injection dun intercepteur de messages dans la r
egion objet
// NOTE : meme remarque sur mbro que ci-dessus.
public IMessageSink GetObjectSink( MarshalByRefObject mbro,
IMessageSink nextSink) {
Console.WriteLine("
GetObjectSink()") ;
return new MyAfficheMessageSink(
nextSink, "R
egion Objet
", m_bAffiche) ;
}
}
//---------------------------------------------------------------------//
// Attribut de contexte qui force la cr
eation dun contexte par objet
// et qui injecte une instance de MyAfficheContextProperty dans chaque
// nouveau contexte cree.
//
[AttributeUsage(AttributeTargets.Class)]
public class MyAfficheContextAttribute : Attribute, IContextAttribute {
bool m_bAffiche ;
public MyAfficheContextAttribute(bool bAffiche) {
m_bAffiche = bAffiche ;
}
// Force `a creer un nouveau context pour chaque nouvel objet.
public bool IsContextOK(Context ctx_courant,
IConstructionCallMessage ctor) {

Contexte .NET
return false ;
}
// Injection dune nouvelle instance de MyAfficheContextProperty
// dans chaque contexte cree.
public void GetPropertiesForNewContext(
IConstructionCallMessage ctor){
IContextProperty prop = new MyAfficheContextProperty(m_bAffiche) ;
ctor.ContextProperties.Add(prop) ;
}
}
//---------------------------------------------------------------------//
// Les instances de MyAfficheMessageSink sont des intercepteurs de
// messages qui ne font que signaler leur pr
esence en affichant deux
// lignes sur la console (ils naffichent rien si (m_bAffiche==false) ).
//
[Serializable]
public class MyAfficheMessageSink : IMessageSink {
// Prochain intercepteur de messages `
a qui transmettre le message.
IMessageSink m_NextSink ;
// Message `a afficher.
string m_sAffiche ;
// Doit-on afficher un message sur la console ?
bool m_bAffiche ;
public IMessageSink NextSink { get { return m_NextSink ; } }
public MyAfficheMessageSink(IMessageSink nextSink,
string sAffiche,
bool bAffiche) {
m_NextSink = nextSink ;
m_sAffiche = sAffiche ;
m_bAffiche = bAffiche ;
}
public IMessage SyncProcessMessage(IMessage msg) {
if (m_bAffiche)
Console.WriteLine("
Deb MsgSink:{0} ContextID={1}",
m_sAffiche, Thread.CurrentContext.ContextID);
// Transmet le msg au prochain intercepteur de message...
IMessage retMsg = m_NextSink.SyncProcessMessage(msg) ;
if (m_bAffiche)
Console.WriteLine("
Fin MsgSink:{0} ContextID={1}",
m_sAffiche, Thread.CurrentContext.ContextID);
return retMsg ;
}
public IMessageCtrl AsyncProcessMessage(IMessage msg,
IMessageSink replySink) {
return m_NextSink.AsyncProcessMessage(msg, replySink) ;

853

854

Chapitre 22 : .NET Remoting


}
}
//---------------------------------------------------------------------//
// La classe Foo supporte lattribut de contexte
// MyAfficheContextAttribute.
// Largument true signifie que les intercepteurs de messages en
// presence dun message concernant un appel vers une instance de
// Foo, peuvent effectuer leurs affichages sur la console.
//
[MyAfficheContextAttribute(true)]
public class Foo : ContextBoundObject {
public Foo() {
Console.WriteLine("
Constructeur de Foo") ;
}
public int Ajout(int a, int b) {
Console.WriteLine("
Ajout de {0}+{1}", a, b) ;
return a + b ;
}
public int AjoutCross(Foo tmp, int a, int b) {
Console.WriteLine("
Ajout cross de {0}+{1}", a, b) ;
return tmp.Ajout(a, b) ;
}
}
//---------------------------------------------------------------------//
public class Program {
static void Main() {
Console.WriteLine( "Avant la construction de obj1..." ) ;
Foo obj1 = new Foo() ;
Console.WriteLine( "...avant lappel `
a obj1..." ) ;
obj1.Ajout(4, 5) ;
Console.WriteLine( "...avant la construction de obj2..." ) ;
Foo obj2 = new Foo() ;
Console.WriteLine( "...avant quobj1 appelle obj2..." ) ;
obj1.AjoutCross(obj2, 6, 7) ;
Console.WriteLine( "...avant quobj1 appelle obj1..." ) ;
obj1.AjoutCross(obj1, 8, 9) ;
}
}

Lexcution de ce programme ache :


Avant la construction de obj1...
Freeze ContextID=1
GetServerContextSink()
Deb MsgSink:Region Serveur
ContextID=1
Constructeur de Foo

Contexte .NET
GetEnvoySink()
Fin MsgSink:Region Serveur
ContextID=1
...avant lappel `a obj1...
Deb MsgSink:Region Envoi
ContextID=0
Deb MsgSink:Region Serveur
ContextID=1
GetObjectSink()
Deb MsgSink:Region Objet
ContextID=1
Ajout de 4+5
Fin MsgSink:Region Objet
ContextID=1
Fin MsgSink:Region Serveur
ContextID=1
Fin MsgSink:Region Envoi
ContextID=0
...avant la construction de obj2...
Freeze ContextID=2
GetServerContextSink()
Deb MsgSink:Region Serveur
ContextID=2
Constructeur de Foo
GetEnvoySink()
Fin MsgSink:Region Serveur
ContextID=2
...avant quobj1 appelle obj2...
Deb MsgSink:Region Envoi
ContextID=0
Deb MsgSink:Region Serveur
ContextID=1
Deb MsgSink:Region Objet
ContextID=1
Ajout cross de 6+7
Deb MsgSink:Region Envoi
ContextID=1
GetClientContextSink()
Deb MsgSink:Region Client2
ContextID=1
Deb MsgSink:Region Client1
ContextID=1
Deb MsgSink:Region Serveur
ContextID=2
GetObjectSink()
Deb MsgSink:Region Objet
ContextID=2
Ajout de 6+7
Fin MsgSink:Region Objet
ContextID=2
Fin MsgSink:Region Serveur
ContextID=2
Fin MsgSink:Region Client1
ContextID=1
Fin MsgSink:Region Client2
ContextID=1
Fin MsgSink:Region Envoi
ContextID=1
Fin MsgSink:Region Objet
ContextID=1
Fin MsgSink:Region Serveur
ContextID=1
Fin MsgSink:Region Envoi
ContextID=0
...avant quobj1 appelle obj1...
Deb MsgSink:Region Envoi
ContextID=0
Deb MsgSink:Region Serveur
ContextID=1
Deb MsgSink:Region Objet
ContextID=1
Ajout cross de 8+9
Ajout de 8+9
Fin MsgSink:Region Objet
ContextID=1
Fin MsgSink:Region Serveur
ContextID=1
Fin MsgSink:Region Envoi
ContextID=0

855

856

Chapitre 22 : .NET Remoting

Passage dinformation entre le contexte appelant et le contexte cible


(call context)
Vous avez la possibilit de passer des informations entre un intercepteur de messages sexcutant
dans le contexte appelant, et un intercepteur de messages sexcutant dans le contexte cible. On
utilise cette technique principalement pour faire passer une information concernant le contexte
appelant au contexte cible (par exemple linformation : le contexte appelant supporte-t-il certaines proprits ?). Cette fonctionnalit porte le nom de call context. Malgr son nom, cette fonctionnalit peut cependant tre utilise dans nimporte quel intercepteur de messages, y compris
ceux qui ne font pas partie dun contexte.
Pour cela il faut dabord dfinir une classe implmentant linterface System.Runtime.Remoting.
Messaging.ILogicalThreadAffinative pour dcrire les informations passer. Dans le code
de lintercepteur de messages sexcutant dans le contexte appelant (dans une rgion envoi ou client ) il faut accrocher une instance de cette classe au message reprsentant
lappel. Dans le code de lintercepteur de messages sexcutant dans le contexte cible (dans
une rgion objet ou serveur ) il faut dcrocher du message reprsentant lappel,
linstance de cette classe. Ces oprations seectuent en utilisant la proprit IMethodMessage.
LogicalCallContext.
Voici du code montrant comment implmenter cette technique.
Exemple 22-36 :
...
public class DonneDeContexte : ILogicalThreadAffinative{
public int Donnee ;
public DonneDeContexte(int i){Donn
ee=i;}
}
[Serializable]
public class MyEnvoiMessageSink : IMessageSink{
public IMessage SyncProcessMessage(IMessage msg){
DonneDeContexte dc = new DonneDeContexte(691);
// accroche la donnee au message repr
esentant lappel.
((IMethodMessage)Msg).LogicalCallContext.SetData("UneDC",dc);
return m_NextSink.SyncProcessMessage(msg) ;
}...}
[Serializable]
public class MyServeurMessageSink : IMessageSink{
public IMessage SyncProcessMessage(IMessage msg){
// decroche la donnee du message repr
esentant lappel.
DonneDeContexte dc = (DonneDeContexte)
((IMethodCallMessage)Msg).LogicalCallContext.GetData("UneDC");
Console.WriteLine("
DonneDeContexte:"+dc.Donn
ee) ;
IMessage retMsg = m_NextSink.SyncProcessMessage(msg) ;
return retMsg ;
}...}...

Rcapitulatif

857

On a vu dans la section prcdente que linjection dintercepteurs de messages dans une rgion
client ou envoi se fait aprs lappel au constructeur dun objet. Un intercepteur de messages dans une rgion client ou envoi ne peut donc pas accrocher dinformation
un message reprsentant lappel au constructeur. Or, un intercepteur de messages dans une rgion serveur peut potentiellement chercher dcrocher une information dun message
reprsentant lappel au constructeur. Dans ce cas vous pouvez accrocher linformation dans la
mthode GetPropertiesForNewContext() de lattribut du contexte :
Exemple 22-37 :
...
public class MyAfficheContextAttribute : Attribute, IContextAttribute{
public void GetPropertiesForNewContext(IConstructionCallMessage ctor){
DonneDeContexte dc = new DonneDeContexte(10);
ctor.LogicalCallContext.SetData("UneDC",dc);
IContextProperty prop = new MyAfficheContextProperty(m_bAffiche) ;
ctor.ContextProperties.Add(prop) ;
}...}

Rcapitulatif
Les modes dactivations dun objet
Nous avons vu quatre modes dactivation dun objet, WKO mode dappel simple, WKO mode
dappel singleton, CAO et publication dun objet. Voici un tableau rcapitulatif des principales
dirences de ces modes :
WKO mode
simple appel

WKO mode
singleton

CAO

Publication

Quand lobjet est-il


activ ?

chaque
appel.

Au premier
appel dun
client.

Lors de lappel
du
constructeur
par le client.

Lors de lappel
du
constructeur
par le serveur.

Quelle information
dtient le client
pour accder
lobjet ?

Un URI
contenant le
point
terminal.

Un URI
contenant le
point
terminal.

Un ObjRef
obtenu
implicitement
lors de lappel
au
constructeur.

Un ObjRef
obtenu
explicitement.

Un objet est-il
partageable entre
plusieurs clients ?

Non

Oui

Non

Oui

Le client a-t-il une


instance de ObjRef
sur lobjet ?

Non

Non

Oui

Oui

858

Chapitre 22 : .NET Remoting

Si lobjet est dtruit


par ladministrateur
de baux, un appel
retourne- t-il une
exception ?

Non

Non

Oui, il ny a
pas de
reconstruction
automatique
dun objet.

Oui, il ny a
pas de
reconstruction
automatique
dun objet.

Le client est-il oblig


de connatre les
mta- donnes de la
classe ?

Non, le client
peut se
satisfaire des
mtadonnes
dune
interface

Non, le client
peut se
satisfaire des
mtadonnes
dune
interface

Oui, moins
dutiliser le
design pattern
factory.

Oui

Peut-on configurer
ce mode dans un
fichier de
configuration ?

Oui

Oui

Oui

Non

Peut-on utiliser un
constructeur avec
arguments pour
lactivation de
lobjet ?

Non

Non

Ca dpend.
Oui si on
utilise le design
pattern factory.

Oui

Les niveaux dinterception dun appel


Nous avons consacr la moiti de ce chapitre exposer comment les messages reprsentant
les appels, peuvent tre intercepts, et pour quelles raisons. La Figure 22 -10 prsente un bref
rcapitulatif des niveaux dinterceptions possibles :
Domaine dapplication client
Client

Niveau dinterception proxy


Modification des arguments, log,
cache dinformation, rpartition
de la charge...

Niveau dinterception contexte


Synchronisation, scurit,
optimisation, transaction,
pool dobjet, log...

Niveau dinterception canal


Compression, authentification,
protocole, log rseau...

Domaine dapplication serveur


Client

Proxy transparent

Proxy transparent

Proxy rel

Proxy rel

Rgion envoi

Rgion envoi

Rgion client

Rgion client

Avant srialisation

Avant srialisation

Formateur (srialisation)

Formateur (srialisation)

Aprs srialisation

Aprs srialisation

Transport

Transport

Figure 22 -10 : Les niveaux dinterception dun appel

23
ASP.NET 2.0

Introduction
Une application web est une application qui renvoie des pages rdiges avec le langage HTML,
en rponse des requtes HTTP. Ces pages HTML sont exploitables par le client partir dune
application appele navigateur (browser, tel que Internet Explorer). Les navigateurs sont spcialiss dans la visualisation des pages HTML. En 1995, la socit Netscape fournit le premier navigateur exploitable par le grand public. Ce type de logiciel a rapidement boulevers le quotidien
de centaines de millions de personnes.
Les avantages des applications web sur les applications graphiques encapsules dans un excutable (les clients riches) sont nombreux :

Le dploiement de nouvelles versions de lapplication est automatique puisque tout est centralis du ct serveur.

Les navigateurs prennent en compte une grande partie de la complexit de la gestion des
contrles.

La ralisation dapplications distribues est simplifie. Les outils de dveloppement sont de


plus en plus puissants et les protocoles sous-jacents du web (HTML, HTTP etc) sont bien
connus et trs largement adopts.

La principale dirence est quune application graphique produit des bitmaps achs sur
lcran raison de plusieurs dizaines de rafrachissements par seconde alors quune application
web produit des pages HTML renvoyes aux clients la demande. Grce larchitecture .NET
qui permet et encourage une programmation de haut niveau bas sur les composants et les
objets, le dveloppement dapplications graphiques web et le dveloppement dapplications
graphiques riches nont jamais taient aussi proche. On utilise parfois le terme de modle de
programmation unifi .

860

Chapitre 23 : ASP.NET 2.0

Historique
Au cours des annes 90, le dveloppement dapplications web sest simplifi. Durant la mme priode les applications web taient de plus en plus interactives grce lintroduction de contrles.
Les informations saisies par lutilisateur sont incluses dans les requtes HTTP. HTTP 1.1 prsente
sept mthodes pour invoquer une ressource. Les deux mthodes les plus usites sont HTTPGET et HTTP-POST. La mthode HTTP-GET permet de passer les paramtres dune requte en
ajoutant des informations lURL. La mthode HTTP-POST permet de passer les paramtres
dune requte dans le corps de cette requte. Dans ce cas, les paramtres ne sont pas visibles
dans lURL.
Les pages HTML renvoyes en rponses aux requtes HTTP sont de moins en moins statiques.
Elles sont fabriques dynamiquement en fonction des informations saisies par lutilisateur.
Cette fabrication est ralise par du code appel par le serveur HTTP.
Au dbut des annes 90, on utilisait des programmes CGI (Common Gateway Interface) pour
construire dynamiquement des pages HTML. Avec de tels programmes, vous pouviez accder
des bases de donnes et raliser des traitements. Cependant le dveloppement de programmes
CGI tait fastidieux et souvent les performances ntaient pas au rendez-vous. Dj, des techniques dutilisation de scripts rdigs en langages interprts tels que Perl mergeaient.
En 1996, Microsoft intgre les filtres ISAPI son serveur dapplication IIS (Internet Information
Server) pour faciliter la redirection des flux HTTP. Mais surtout, la mme anne, lentreprise
de Redmond cre les ASP (Active Serveur Page). Une nouveaut apporte par ASP tait que le
code tait insr dans lHTML alors quen CGI, cest lHTML qui tait insr dans le code. De
nombreuses amliorations sont apportes cette technologie de la version 1 en 1996 la version
3 en 2000. Malgr son succs les principaux concurrents, JSP et PHP, continuaient gagner des
parts de march.

ASP vs. ASP.NET


Aussi, lors de la conception de la plateforme .NET, Microsoft a dcid de fournir une technologie
nomme ASP.NET pour succder ASP. Les applications ASP.NET peuvent tre rdiges dans
nimporte quel langage .NET tel que C  ou VB.NET. Contrairement aux applications ASP, elles
prsentent la particularit dtre compiles en langage IL et excutes par le CLR. Il y a donc
un gain de performance consquent puisquil ny a plus dinterprtation du code.
ASP.NET a t conu avec le souci de rsoudre les problmes majeurs de la technologie ASP
tels que :

En ASP.NET le code HTML et le code de fabrication dynamique des pages HTML ont la
possibilit dtre stocks dans des fichiers dirents pour permettre une meilleure collaboration entre les designers du site web et les dveloppeurs.

Les sessions dASP.NET peuvent tre partages entre plusieurs machines, par exemple, en
les stockant dans une base de donnes.

ASP.NET prsente plusieurs mthodes pour garder jour ltat des contrles dune page,
dun chargement lautre. Cela fait autant de code fastidieux en moins crire et maintenir.

Les paramtres de configuration dune application ASP.NET sont stocks dans un fichier au
format XML. Pour rpliquer une configuration, il sut de copier un tel fichier. Cela rsout
la dicult inhrente lutilisation des mta bases IIS.

Architecture gnrale dASP.NET

861

ASP.NET 1.x vs. ASP.NET 2.0


Nous en sommes maintenant la version 2.0 dASP.NET. Cette seconde version prsente de trs
nombreuses amliorations sans bouleverser les acquis des dveloppeurs ASP.NET 1.x. ASP.NET
2.0 fournit de nombreux composants prts lemploi. Ces composants adressent la plupart des
tches rcurrentes qui ncessitent des eorts de la part des dveloppeurs pour tre mises en
uvre avec ASP.NET 1.x. Aussi, Microsoft va jusqu dire quil y a 70% de code en moins dans
les scnarios les plus favorables. Malgr des APIs plus toes et donc plus dinformations
assimiler de la part des dveloppeurs, ces derniers gagnent en productivit du fait quils nont
pas rinventer la roue pour la plupart des tches classiques.
Vous navez pas besoin davoir dj dvelopp avec ASP.NET 1.x pour aborder ce chapitre. Si
vous souhaitez ne vous intresser quaux nouveauts ASP.NET 2.0, sachez quelles sont listes
en annexe page 1029, avec pour chacune, une rfrence vers la page o elle est expose.

Architecture gnrale dASP.NET


Notion de Web Forms
Une web form est une page dune application web. Durant la phase de dveloppement, le code
source dune web form est contenu dans un fichier texte dextension .aspx auquel sajoute ventuellement un fichier source .NET (C  ou VB.NET par exemple) voire aussi un fichier ressource.
lexcution, une web form est une classe .NET drive de la classe System.Web.UI.Page. Une
instance de cette classe est cre pour chaque requte de la page concerne. Une telle instance
est responsable de la cration dune page HTML. ASP.NET collabore avec un serveur web sousjacent pour faire en sorte que cette page HTML soit retourne lutilisateur responsable de la
requte.

ASP.NET, IIS et lexcution des applications web


Une application web est lensemble des web forms et des assemblages correspondant, situs dans
larborescence dun rpertoire virtuel. lexcution dune ou plusieurs applications web sur
une machine, il y a deux processus sur la machine :

Le processus INETINFO.EXE reprsente le processus IIS. Toutes les requtes HTTP destines
aux applications web et aux services web sont rceptionnes dans ce processus par le code
du filtre ISAPI aspnet_isapi.dll. Les rgles de scurit IIS sont appliques puis les requtes sont transmises au processus aspnet_wp.exe par lintermdiaire dun pipe nomm.
Si la rception dune requte le processus aspnet_wp.exe nexiste pas, le filtre ISAPI aspnet_isapi.dll soccupe de le dmarrer et de crer un pipe nomm ddi.

Le processus aspnet_wp.exe (wp pour Worker Process) contient un domaine dapplication


par application web ou par service web tournant sur la machine. Quel que soit le nombre
dapplications web ou de services web sexcutant sur la machine, ils seront tous excuts
dans ce processus, moins dune configuration multi processeurs (prsentes dans les prochaines pages). Lisolation entre applications web rsulte ainsi de lisolation garantie par le
CLR entre les domaines dapplications.

Une autre architecture est mise en uvre avec IIS 6.0 sous Windows Server 2003. Dans ce cas IIS
et ASP.NET sexcute au sein dun mme processus nomm w3wp.exe.

862

Chapitre 23 : ASP.NET 2.0


Requte HTTP

Rponse HTTP
Machine hbergeant le serveur Web

INETINFO.EXE
(IIS 5.0)

aspnet_wp.exe
CLR
Domaine dapplication
Assemblage de lapplication Web

aspnet_isapi.dll

Pipe

Pipeline
HTTP
Instance
dune classe

Figure 23 -1 : Interaction des processus de IIS et de ASP.NET

ASP.NET a t conu pour tre indpendant du serveur web sous-jacent. Concrtement, cela
implique quASP.NET peut sinterfacer avec dautres serveurs web quIIS. Par exemple, Visual
Studio .NET 2005 est fourni avec le serveur web WebDev.WebServer.EXE permettant de tester
et de dboguer vos applications web en cours de dveloppement. Ce serveur web est bas sur
lancien serveur web nomm Cassini qui tait fourni gratuitement par Microsoft. Ceci est trs
pratique puisque IIS nest disponible que sur les versions professionnelles de Windows. Il est
donc maintenant possible de dvelopper une application web sur une machine nayant quune
dition familiale de Windows. Notez que vous pouvez configurer Visual Studio 2005 pour quil
sinterface avec IIS en modifiant loption Proprit du projets web  Options de dmarrage  Utiliser un serveur de votre choix.
ASP.NET eectue un certain nombre de tches pour traiter les requtes et les rponses HTTP
dans ce que nous appelons pipeline HTTP dans la Figure 23 -1. Parmi ces tches nous pouvons
citer lauthentification ASP.NET de lutilisateur qui a initi une requte ou la gestion des informations concernant la session courante pour cet utilisateur. Tout au long de ce chapitre, nous
aurons loccasion de dcrire ces tches. Nous expliquerons aussi comment vous pouvez tendre
ce pipeline en injectant vos propres traitements certains points prcis.
Lexcution dune requte au sein du processus aspnet_wp.exe se fait sur le mme thread de
bout en bout. Pour cela, ASP.NET exploite les threads I/O du pool de threads du CLR et peut
donc excuter plusieurs requtes simultanment. En plus dviter by-design un certains nombre
de problmes de concurrence cette faon de faire est ecace car la rception dune requte
partir du pipe nomm dbute directement sur un thread I/O. On vite donc une transition
dinformation entre threads. Puisquil ny a pas de pipe nomm avec IIS 6.0 les choses se passent
diremment et dans cette version, ASP.NET utilise les threads ouvriers du CLR.
Il est trs important de remarquer que le processus aspnet_wp.exe sexcute par dfaut sous
un compte utilisateur Windows nomm ASPNET (ou Network Service sous IIS 6.0) aux privilges
restreints. En eet, durant le dveloppement il se peut que vous ne vous rendiez pas compte de
certains problmes du fait que WebDev.WebServer.EXE sexcute par dfaut sous votre compte
utilisateur. Nous verrons comment ventuellement configurer ASP.NET pour que ce processus
sexcute sous un autre utilisateur. Notez enfin que si lutilisateur initiateur dune requte a pu

Architecture gnrale dASP.NET

863

tre authentifi comme un utilisateur Windows, il est possible que le thread qui traite la requte
sexcute sous le compte de cet utilisateur.
Vous pouvez hberger simultanment plusieurs applications web avec ASP.NET. Par exemple
un serveur IIS avec les adresses IP mappes suivantes et les sites logiques suivants, peut exposer
les applications web suivantes :
Adresses IP mappees :
www.xyz.com
www.smacchia.com
Sites logiques :
http://www.xyz.com
http://www.smacchia.com
Applications web :
http://www.xyz.com/holidays
http://www.xyz.com/holidays/cadre
http://www.smacchia.com/Documents
http://www.smacchia.com

Saisie des jours de cong


es
Saisie des jours de cong
es des cadres
Publication de documents
Pr
esentation dactivit
e

Les rpertoires physiques contenant les applications web nont pas de contraintes quant leur
localisation. Ils peuvent tre sur le systme de gestion de fichiers local ou sur un autre systme
distant. Par exemple le rpertoire de lapplication web http://www.xyz.com/holidays/cadre
nest pas tenu dtre physiquement imbriqu dans le rpertoire de lapplication web http://
www.xyz.com/holidays.
Enfin, il est possible de faire cohabiter une version 1.x et une version 2.0 dASP.NET sur la mme
machine. Dans ce cas, vous devez prciser au filtre ISAPI aspnet_isapi.dll quelle version utiliser en vous servant de loutil aspnet_regiis.exe. Il sut de taper la ligne de commande suivante avec la version souhaite de cet outil :
aspnet_regiis.exe -r

Hberger ASP.NET dans vos applications .NET


Lespace de nom System.Web.Hosting prsente des classes permettant dhberger ASP.NET
dans une application .NET quelconque. Cette possibilit est illustre par lexemple suivant :
Exemple 23-1 :

AspnetHosting.cs

using System ;
using System.Web ;
using System.Web.Hosting ;
class Program {
static void Main() {
Console.WriteLine("Main Appdomain:" +
AppDomain.CurrentDomain.FriendlyName) ;
CustomSimpleHost host = (CustomSimpleHost)
ApplicationHost.CreateApplicationHost(
typeof(CustomSimpleHost), @"/",
System.IO.Directory.GetCurrentDirectory());

864

Chapitre 23 : ASP.NET 2.0


// Passe en argument la page web et les param`
etres de la requ
ete.
host.ProcessRequest("Default.aspx", string.Empty) ;
}
}
public class CustomSimpleHost : MarshalByRefObject {
public void ProcessRequest(string file, string query) {
Console.WriteLine("ASP.NET AppDomain:" +
AppDomain.CurrentDomain.FriendlyName) ;
SimpleWorkerRequest aspnetWorker =
new SimpleWorkerRequest(file, query, Console.Out) ;
HttpRuntime.ProcessRequest(aspnetWorker);
}
}

On dclare dabord vouloir hberger ASP.NET dans le processus courant grce lappel de la
mthode statique ApplicationHost.CreateApplicationHost(). Cette mthode cre un nouveau domaine dapplication et y charge ASP.NET. ASP.NET y charge alors lassemblage contenant la classe CustomSimpleHost. En loccurrence cet assemblage est celui de notre application,
savoir AspnetHosting.exe. Une instance de cette classe est ensuite cre dans ce nouveau domaine dapplication. La mthode CreateApplicationHost() retourne une rfrence vers cette
instance dans le domaine dapplication initial. Ceci est possible car la classe CustomSimpleHost
drive de la classe MarshalByRefObject. Ainsi grce la technologie .NET Remoting, une instance
de CustomSimpleHost peut tre rfrence et utilise partir dun domaine dapplication dirent de celui dans lequel elle rside.
Le code de la mthode ProcessRequest se sert dune instance de la classe System.Web.Hosting.
SimpleWorkerProcess pour traiter une requte HTTP GET. Ici, nous crons artificiellement
cette requte en fournissant le nom de la web form demande (Default.aspx), les paramtres de
la requte GET (ici il ny en a pas) et un flot de donnes vers lequel ASP.NET va pouvoir diriger
la rponse HTTP (en loccurrence ce flot est la console). La page HTML produite par ASP.NET
partir de la web form Default.aspx est ache sur la console. Ce programme ache donc
ceci :
Main Appdomain:ConsoleApplication6.exe
ASP.NET AppDomain:c6ed2272-1-127654065004140000
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<center>
<form name="Form1" method="post" action="Default.aspx" id="Form1">
...
Pour excuter cet exemple il faut que vous prvoyiez de stocker une web form nomme
Default.aspx dans le mme rpertoire que lassemblage AspnetHosting.exe. Vous pouvez par
exemple vous servir de la page Default.apsx prsente un peu plus loin dans lExemple 23-3. En
outre il faut prvoir de dupliquer lassemblage AspnetHosting.exe dans le sous rpertoire /bin
du rpertoire contenant la version originale de AspnetHosting.exe. En eet, ASP.NET va automatiquement rechercher dans ce rpertoire lassemblage contenant la classe CustomSimpleHost
pour le charger dans le nouveau domaine dapplication.

Architecture gnrale dASP.NET

865

Utiliser conjointement ASP.NET avec la couche HTTP.SYS


Dans la section prcdente nous avons bien prcis que nous simulions la requte GET transmise ASP.NET. En utilisant un serveur web bas sur les services de la couche HTTP.SYS il
est ais de construire un serveur web complet hbergeant ASP.NET en se passant compltement dIIS. Cette couche HTTP.SYS fait lobjet de la section page 654. Nous y exposons notamment des exemples de programmes .NET qui exploitent cette couche grce aux services de la
classe HttpListener. Le programme suivant utilise conjointement les services dhbergement
dASP.NET et de HTTP.SYS :
Exemple 23-2 :
using System ;
using System.Web ;
using System.Web.Hosting ;
using System.IO ;
using System.Net ;
class Program {
static void Main() {
CustomSimpleHost host = (CustomSimpleHost)
ApplicationHost.CreateApplicationHost(
typeof(CustomSimpleHost), @"/",
System.IO.Directory.GetCurrentDirectory()) ;
HttpListener httpListener = new HttpListener() ;
string uri = string.Format("http://localhost:8008/") ;
httpListener.Prefixes.Add(uri) ;
httpListener.Start() ;
Console.WriteLine("En attente sur " + uri) ;
while (true) {
HttpListenerContext ctx = httpListener.GetContext();
string page = ctx.Request.Url.LocalPath.Replace("/", "");
string query = ctx.Request.Url.Query;
Console.WriteLine("Requ
ete recue : {0}?{1}",page, query) ;
StreamWriter writer = new
StreamWriter( ctx.Response.OutputStream,
System.Text.Encoding.Unicode) ;
host.ProcessRequest(page, query, writer);
writer.Flush() ;
writer.Close() ;
ctx.Response.Close() ;
}
}
}
public class CustomSimpleHost : MarshalByRefObject {
public void ProcessRequest(string file, string query,
TextWriter writer) {
SimpleWorkerRequest aspnetWorker =
new SimpleWorkerRequest(file, query, writer) ;
HttpRuntime.ProcessRequest(aspnetWorker) ;

866

Chapitre 23 : ASP.NET 2.0


}
}

Pour tester ce serveur web, il sut de demander lURL suivante dans un navigateur sexcutant
sur la mme machine :
http://localhost:8008/Default.aspx

Stockage du code source


Code-inline
Chaque page a un seul fichier dextension .aspx et un tel fichier ne correspond qu une seule
page. Voici le code dune page web :
Exemple 23-3 :

Default.aspx

<%@ Page Language="C#" Debug="false" Description="Ma premi`


ere page !" %>
<script language=C# runat="server">
void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<center>
<form id="Form1" action="Default.aspx" method="post" runat="server">
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
<br/>
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click"
runat="server"/>
<br/>
<asp:label id="Msg" runat="server"/>
</form>
</center>
</body>
</html>
Ce fichier commence par des directives qui peuvent fournir de multiples renseignements
ASP.NET tels que le langage .NET utilis pour coder la page, des options de compilation, des
options de dbogage etc.
Hormis ces directives, le reste du contenu ressemble du code HTML ceci prs quil contient
quelques balises non HTML telles que <script>, <form> ou <asp:button> qui sont reconnues
par ASP.NET. Une balise <script> contient du code .NET crit par exemple en C  ou en
VB.NET. On parle alors de code-inline. ASP.NET fera en sorte que ce code fasse partie de la
classe reprsentant la page lexcution.

Stockage du code source

867

Les autres balises non HTML contiennent des descriptions de contrles ASP.NET. Pour linstant, retenez quun contrle ASP.NET est un champ dinstance de la classe de la web form qui
lexcution, remplace sa description dans le fichier .aspx par du code HTML.
Notons que pour une mme page .apsx Visual Studio fournit deux vues. La vue design plutt
utilise par les graphistes et la vue qui prsente le contenu du fichier .aspx. Comprenez bien
quil ne sagit que de vues des mmes donnes et que tous changements partir dune vue est
immdiatement rpercuts sur lautre vue :

Figure 23 -2 : Vue design et vue source

Bloc de restitution de code


linstar dASP, en ASP.NET vous avez la possibilit dcrire du code dans des blocs de restitution
de code dlimits par des balises <% et %>. Ce code doit tre rdig dans le langage .NET de la page,
prcis par lattribut Language. lexcution de la page, ASP.NET remplace un tel bloc par la
chane de caractres produit par ce code. Par exemple, cette page .aspx produit le code HTML
suivant :
Exemple 23-4 :

Default.aspx

<%@ Page Language="C#" Debug="true" Description="Ma deuxi`


eme page !" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<% for(int i=4 ; i<8 ; i++) { %>
<font size="<%=i%>"> Bonjour </font>
<% } %>
</form>
</body>
</html>
Page HTML produite
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form method="post" action="Default.aspx" id="Form1">
...
<font size="4"> Bonjour </font>
<font size="5"> Bonjour </font>
<font size="6"> Bonjour </font>
<font size="7"> Bonjour </font>

868

Chapitre 23 : ASP.NET 2.0


</form>
</body>
</html>

Si vous excutez cet exemple, vous vous apercevrez quil y a du code HTML en plus l o nous
avons plac des points de suspension. Il est pour linstant trop tt dans ce chapitre pour sintresser ce code. Retenez juste pour linstant que les blocs de restitutions de code ont t excuts
et ont produits les quatre lignes en gras dans la page HTML.
Il est intressant de noter que le code compris dans les blocs de restitutions de code a t compil
en une mthode de la classe qui reprsente la page :
...
private void __RenderForm1( HtmlTextWriter __w,
Control parameterContainer) {
__w.Write("\r\n
");
for (int num1 = 4 ; num1 < 8 ; num1++) {
__w.Write("\r\n
<font size=\"");
__w.Write(num1);
__w.Write("\"> Bonjour </font> \r\n
");
}
}
...
Cette mthode est automatiquement appele par ASP.NET au moment o la page est cre.

Code-Behind
Si vous crez un nouveau site web C  avec Visual Studio (avec Fichier  Nouveau Site Web...) vous
vous apercevrez que lorsque vous crez une nouvelle page, vous pouvez cocher loption Placez le
code dans un fichier spar. Dans ce cas, un fichier source C  nomm [page].aspx.cs sera associ
votre nouvelle page [page].aspx. Vous avez la possibilit de dfinir une classe dans ce fichier
qui contiendra des membres exploitables directement lexcution dans la classe reprsentant
la page. On nomme ce code code-behind de la page. Clairement, les avantages du code-behind sur
le code-inline et les blocs de restitution de code sont dallger les fichiers .aspx et de permettre
aux designers web et aux dveloppeurs de collaborer plus ecacement puisque ces premiers
travaillent avec des fichiers .aspx tandis que ces derniers travaillent avec des fichiers .aspx.cs.
Pour utilisez cette technique avec notre mthode Btn_Click(), il faut dabord prciser dans
le fichier Default.aspx quelle classe contient le code-behind avec la directive Inherits et quel
fichier source contient cette classe avec la directive CodeFile :
Exemple 23-5 :

Default.aspx

<%@ Page Language="C#" Inherits="MyDefaultPage"


CodeFile="Default.aspx.cs" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<center>
<form id="Form1" action="Default.aspx" method="post" runat="server">
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>

Stockage du code source

869
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>

<br/>
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click"
runat="server"/>
<br/>
<asp:label id="Msg" runat="server"/>
</form>
</center>
</body>
</html>
Ensuite il faut dclarer la classe MyDefaultPage comme partielle avec le mot cl C  2 partial :
Default.aspx.cs

Exemple 23-6 :

using System ;
public partial class MyDefaultPage : System.Web.UI.Page {
protected void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value ;
}
}
En eet, ASP.NET 2.0 prsente un modle de construction du code-behind dirent de celui
dASP.NET 1.x. Ces deux modles sont illustrs par la figure suivante :
Modle ASP.NET 1.x
System.Web.UI.Page

Classe
Default_aspx
gnre partir
du fichier .aspx

Classe
contenant le
code-behind
Prcompil par
Visual Studio

Compil
lexcution
par ASP.NET

MaClasse.dll

Classe
partielle
contenant le
code-behind

Classe
partielle
gnre partir
du fichier .aspx

Compil en une seule classe qui


drive de System.Web.UI.Page

Temp.dll

Notez que

Modle ASP.NET 2.0


System.Web.UI.Page

Classe Default_aspx gnre


partir du fichier .aspx

signifie que la classe B


drive de la classe A

MaClasse.dll

Compil lexcution
par ASP.NET

Figure 23 -3 : Modles 1.x et 2.0 de construction des classes


On voit quen ASP.NET 2.0, la classe de base contenant le code-behind a la particularit dtre la
runion de deux classes partielles : notre classe partielle MyDefaultPage et une classe partielle
gnre partir du fichier .aspx. Ce modle prsente plusieurs avantages :

Il allge la dfinition de la classe contenant le code-behind de nombreux dtails tels que


la dfinition des contrles. Ces dtails taient anciennement gnrs par Visual Studio. Ils

870

Chapitre 23 : ASP.NET 2.0


sont maintenant invisibles pour le dveloppeur puisquils sont ajouts automatiquement
et implicitement par ASP.NET la compilation (notez cependant que le mcanisme dintellisense de Visual Studio tient compte de ces dtails).

Il diminue le risque derreur de synchronisation puisque les dtails gnrs ne sont plus
accessibles aux dveloppeurs.

Il facilite la migration des projets ASP.NET 1.x en prservant le fait que la classe [pageName]_aspx drive de la classe contenant le code-behind qui elle-mme drive de la classe
Page.

Une mme classe de base partielle peut tre exploite peut tre rutilise par plusieurs
pages.

Par curiosit, voici quoi ressemblent la classe partielle et la classe Default_aspx gnres dans
notre exemple par ASP.NET :
public partial class MyDefaultPage : System.Web.UI.Page,
System.Web.SessionState.IRequiresSessionState {
protected System.Web.HttpApplication ApplicationInstance {
get{ return return this.Context.ApplicationInstance ; }
}
protected System.Web.Profile.DefaultProfile Profile {
get { return (DefaultProfile)this.Context.Profile ; }
}
protected System.Web.UI.WebControls.Button Button1 ;
protected System.Web.UI.WebControls.DropDownList Couleur ;
protected System.Web.UI.HtmlControls.HtmlForm Form1 ;
protected System.Web.UI.WebControls.Label Msg ;
}
namespace ASP {
public class Default_aspx : MyDefaultPage {
public Default_aspx() {
base.AppRelativeVirtualPath = "~/Default.aspx" ;
if (!Default_aspx.__initialized) {
string[] textArray1 = new string[2]
{ "~/Default.aspx", "~/Default.aspx.cs" } ;
Default_aspx.__fileDependencies =
base.GetWrappedFileDependencies(textArray1) ;
Default_aspx.__initialized = true ;
}
}
private void __BuildControl__control2(ListItemCollection __ctrl) {
ListItem item1 = this.__BuildControl__control3() ;
__ctrl.Add(item1) ;
ListItem item2 = this.__BuildControl__control4() ;
__ctrl.Add(item2) ;
}
private ListItem __BuildControl__control3() { ... }
private ListItem __BuildControl__control4() { ... }
private Button __BuildControlButton1() { ... }

Modles de compilation et de dploiement

871

private DropDownList __BuildControlCouleur() { ... }


private HtmlForm __BuildControlForm1() { ... }
private Label __BuildControlMsg() { ... }
private void __BuildControlTree(Default_aspx __ctrl) { ... }
protected override void FrameworkInitialize() {
base.FrameworkInitialize() ;
this.__BuildControlTree(this) ;
base.AddWrappedFileDependencies(Default_aspx.__fileDependencies) ;
base.Request.ValidateInput() ;
}
public override int GetTypeHashCode() {return 0x36b54869;}
private static object __fileDependencies ;
private static bool __initialized ;
}
}
Si vous utilisez la directive Src la place de la directive CodeFile, ou si vous ne prcisez ni lune
ni lautre mais que vous dployez lassemblage contenant la classe contenant le code-behind dans
le rpertoire bin, vous revenez au modle ASP.NET 1.x et il vous incombe dajouter les dtails
tels que la dfinition des contrles votre classe de base. Enfin, sachez que la directive ASP.NET
1.x CodeBehind nest plus utilisable.

Modles de compilation et de dploiement


ASP.NET 2.0 prsente trois modles de compilation : la compilation dynamique (parfois
nomm full runtime compilation en anglais), la pr compilation sur place (in-place pre-compilation
en anglais) et la pr compilation pour dploiement. (deployment pre-compilation en anglais).

Compilation dynamique
Dans ce modle, vous dployez les fichiers sources sur le serveur et ASP.NET soccupe de les
compiler lexcution. Chaque fichier source est compil lors de sa premire utilisation. Si un
fichier source dj compil est modifi, ASP.NET le dtecte et le recompile. Lavantage principal de ce modle est de faciliter le travail des dveloppeurs puisquils nont pas se soucier des
mises jours, du dploiement et de ltape de la compilation. En revanche, ce modle dgrade
lgrement les performances de votre serveur puisque chaque compilation a un cot.
La notion de compilation dynamique existait dj en ASP.NET 1.x puisque comme le montre
la Figure 23 -3, chaque page aspx tait compile lexcution lors de la premire demande.
En revanche, en ASP.NET 1.x vous deviez compiler manuellement (ou avec Visual Studio) les
classes contenant le code-behind ainsi que toutes les autres classes utilises par votre application.
Si vous souhaitiez rutiliser des classes dj compiles dans des assemblages, il fallait placer ces
assemblages dans le rpertoire [AppRootDir]/bin de votre application (ou dans le GAC) et les
rfrencer la compilation de votre application.
La compilation dynamique en ASP.NET 2.0 a considrablement volu. Vous pouvez dployer
vos fichiers sources dans le rpertoire [AppRootDir]/App_Code de votre application (ou dans un
de ses sous rpertoires). Cette arborescence peut contenir des fichiers sources rdigs ventuellement avec dans plusieurs langages tels que C  , VB.NET, XSD (pour les datasets typs) etc. Les
classes contenues dans ces fichiers peuvent tre exploites dans le code de nimporte quelle page

872

Chapitre 23 : ASP.NET 2.0

de votre application. Pour amliorer les performances de la compilation, llment <configuration>/<system.web>/<compilation> du fichier web.config prsente plusieurs sous lments
permettant de paramtrer finement la quantit de code compiler chaque compilation (batch
compilation en anglais).
Les assemblages contenus dans le rpertoire /bin nont pas besoin dtre rfrenc daucune
sorte. Les classes et autres ressources contenues dans ces assemblages peuvent tre exploites
dans le code de nimporte quelle page de votre application. ASP.NET 2.0 se charge de les trouver
lexcution.
En plus des rpertoires /App_Code et /bin, ASP.NET sait aller chercher dautres types de ressources que des classes dans les rpertoires suivants (tous directement placs dans le rpertoire
racine de votre application) :

/App_GlobalResources et /App_LocalResources pour les fichiers ressources globaux ou locaux lapplication web (voir page 953).

/App_WebReferences pour les fichiers WSDL compiler en proxy.

/App_Data pour les fichiers contenant des donnes.

/App_Browsers pour les fichiers .browser qui dclarent quelles possibilits doit supporter
un navigateur (ce rpertoire replace llment <browserCaps> in machine.config).

/App_Themes pour les fichiers .css et .skin.

Toute mise jour de nimporte quel fichier contenu dans un de ces rpertoires sera automatiquement dtecte et prise en compte par ASP.NET 2.0 lexcution. La mise jour dun fichier
source provoque sa recompilation tandis que la mise jour dun assemblage dans le rpertoire
/bin provoque son rechargement par le CLR. Ce mcanisme de rechargement dassemblage exploite la possibilit nomme shadow copy du CLR qui est dcrite en page 108. Enfin, sachez que le
filtre ISAPI aspnet_filer.dll fait en sorte quaucun des fichiers contenus dans ces rpertoires
nest accessible par une requte HTTP.

Pr compilation sur place


Ce modle est similaire au modle prcdant mis part que vous pouvez dclencher la compilation complte de votre application avec une requte HTTP sur le rpertoire racine de votre
application suivie de precompile.axd. Par exemple :
http://localhost/MonSiteWeb/precompile.axd
En plus de matriser le moment de la compilation, la pr compilation sur place permet de
rencontrer les ventuelles erreurs de compilation avant quelles ne soient dcouvertes par les
clients.

Pr compilation pour dploiement


Le nouvel outil aspnet_compiler.exe permet de compiler compltement une application web.
Ainsi, il vous permet de ne dployer que des DLLs sans aucun fichier de code source. En plus des
avantages vidents au niveau des performances, ce modle de compilation et de dploiement
est adapt aux projets de grande envergure. En eet, il permet la mise en place de processus
volus de compilation (avec une batterie de tests unitaires ou le recours la technologie MSI
par exemple) et ventuellement le renforcement de la proprit intellectuelle en permettant

Web forms et contrles

873

lobfuscation des assemblages. Bien videmment, ceci se paye par une souplesse amoindrie dans
le processus de mise jour.
Loutil aspnet_compiler.exe sutilise trs simplement. Vous prcisez en entre le rpertoire
virtuel contenant le code source de votre application web avec loption /m (ou le rpertoire
Windows racine avec loption /p), le nom de votre application avec loption /v et le rpertoire
de sortie contenant le rsultat de la compilation.
aspnet_compiler.exe /m /LM/W3SVC/1/ROOT/MonSiteWeb D:/TestDeploy
aspnet_compiler.exe /v WebSite /p D:/Site/MonSiteWeb D:/TestDeploy
Il vous sut ensuite de copier/coller le contenu de ce rpertoire de sortie dans le rpertoire
virtuel adquat sur le serveur. Il est intressant de remarquer que ce rpertoire de sortie prsente
toujours vos fichiers dextensions .aspx mais vids de leur contenu, votre fichier web.config
ainsi que des assemblages aux noms gnrs dans le rpertoire /bin. Vous remarquez aussi la
prsence dun fichier PrecompiledApp.config. Les lments XML contenus dans ce fichier indique ASP.NET si il est autoris ou pas compiler des pages .aspx. Ainsi, les paramtres de
ce fichier peuvent empcher la prise en compte de nouvelles pages .aspx ajoutes un site.

Web forms et contrles


Contrle serveur
Un contrle serveur est un objet dont la classe drive de System.Web.UI.Control. Tout lintrt dun contrle serveur rside dans sa capacit fabriquer un fragment de code HTML la
demande. Pour cela, la classe Control prsente des mthodes virtuelles telles que la mthode
void Render(HtmlTextControl). Les classes qui drivent de Control ont la possibilit de redfinir une ou plusieurs de ces mthodes. Notez que le fragment de code HTML produit par un
contrle serveur ASP.NET peut correspondre un ou plusieurs contrles HTML. Dans la suite,
nous prciserons contrle HTML ou contrle serveur lorsquil peut y avoir ambigut.
Grce la proprit ControlCollection Controls{get;set;} de la classe Control, chaque
contrle serveur a la possibilit de se comporter comme un containeur de contrle serveur
fils. Notamment, la classe Page drive de la classe de Control. Une page web est donc un
contrle serveur qui a la particularit de ntre fils daucun autre contrle. En outre, la classe
Control prsente la mthode void RenderChildren(HtmlTextControl) qui appelle tour tour
les mthode Render() de chaque contrle fils en respectant lordre dans lequel ils sont stocks.
Reprenons la page de lExemple 23-5 :
<%@ Page Language="C#" ... %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<center>
<form id="Form1" action="Default.aspx" method="post" runat="server">
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
<br/>
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click"

874

Chapitre 23 : ASP.NET 2.0


runat="server"/>
<br/>
<asp:label id="Msg" runat="server"/>
</form>
</center>
</body>
</html>

Lorsque ASP.NET construit la classe qui reprsente cette page, il y insre du code qui construira
une arborescence de contrle serveur. Cette arborescence est illustre par la figure suivante :
Page
.Controls
LiteralControl
.Text =
"<html>
<body>
<center>"
HtmlFormControl
.Controls
LiteralControl
.Text =
" </center>
</body>
</html>"

LiteralControl
.Text =
"
Couleur : "
DropDownList .ID = "Couleur"
Button .Text = "Soumettre"
LiteralControl
.Text =
"
<br>"
Label .ID = "Msg"
.Text = "Vous avez
slectionn : blanc"
LiteralControl .Text = ""

Figure 23 -4 : Arborescence de contrles serveur


On voit que les classes de contrle Page, LiteralControl, HtmlFormControl, Label, DropDownList
et Button sont impliques. Le comportement dune instance de la classe LiteralControl est
trs simple. Lors de lappel de sa mthode Render() un tel objet ne fait quajouter la page
HTML en construction le texte quil contient dans sa proprit string Text{get;set;}. Nous
aurons dans la suite loccasion de dtailler les comportements plus actifs des autres types de
contrle.
Reprenons le code C  de cette page :
using System ;
public partial class MyDefaultPage : System.Web.UI.Page {
protected void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value ;
}
}
Nous attirons votre attention sur le fait que lon se sert des deux champs Label Msg et DropDownList Couleur. Ces deux champs sont deux rfrences vers les deux objets de type Label
et DropDownList de notre arborescence de contrles. On remarque que leurs noms sont les
mmes que les chanes de caractres prciss dans les attributs id. Lors de la discussion sur le
code-behind, nous avons dj nonc que la dclaration de ces champs est automatiquement
faite par ASP.NET dans la deuxime moiti partielle de notre classe MyDefaultPage. Nous

Web forms et contrles

875

pouvons maintenant prciser quASP.NET gnre aussi du code responsable de linitialisation de ces champs. Ceci souligne limportance de la comprhension de lenchanement
des actions lors de la vie dune page. En eet, si vous essayez dutiliser les champs Msg ou
Couleur avant quils naient t initialiss par ce code, vous obtiendrez une exception de type
NullReferenceException. Nous dtaillons un peu plus tard cet enchanement crucial mais
avant, intressons nous la logique de notre page Default.aspx.

Interaction client/serveur
La Figure 23 -5 illustre la logique de notre page Default.aspx en prcisant les principales tapes
de linteraction client/serveur web. Le client initie linteraction en demandant au serveur la
page /WebSite1/Default.aspx au moyen dune requte HTTP GET. Le serveur lui renvoie la
page HTML demande. Cette dernire est fabrique par une nouvelle instance de la classe Default_aspx. Lors de la premire demande dun client, les contrles serveur ont leurs tats initiaux. Pour le DropDownList ltat initial est blanc et pour le Label ltat initial est une chane de
caractres vide. Une fois la page reue, le client slectionne noir sur le contrle HTML correspondant notre DropDownList et clique sur le bouton. Cette dernire action entrane lenvoi
dune requte HTTP POST au serveur. Cette requte contient notamment ltat du contrle
HTML correspondant notre DropDownList. Le traitement interne des pages .aspx de ASP.NET
(dans le pipeline HTTP) est capable de dtecter que la requte POST est due un click du bouton
Button1. Aussi, il invoque la mthode Btn_Click() sur une nouvelle instance de la classe Default_aspx. En eet, cette mthode a t associe lvnement click sur Button1 dans notre
page Default.aspx grce la ligne <...id="Button1"...OnClick="Btn_Click"...>. Lexcution de cette mthode positionne ltat de notre contrle Label "Vous avez s
electionn
e :
noir". Le fragment HTML ajout par ce contrle est alors "<span id="Msg">Vous avez s
electionne : noir</span>".
Dans linteraction client/serveur de la section prcdente, vous avez peut tre remarqu que
la slection noir du contrle Couleur a t retenue lorsque le client charge la page pour la seconde fois. Cela rsulte du fait quASP.NET initialise automatiquement et implicitement les valeurs des contrles avec les valeurs trouves dans une requte POST. Certains types de contrles
HTML comme le contrle <span> (correspondant un contrle serveur de type Label) nont pas
leurs valeurs sauves explicitement dans la requte POST. Cela se voit en analysant la requte
POST. Seules les contrles nomms Couleur, Button1 et __VIEWSTATE (dont nous allons parler)
ont leurs valeurs sauves dans le corps de la requte POST :
POST /WebSite1/Default.aspx HTTP/1.1
...
Content-Length: 104
__VIEWSTATE=%2FwEPDwUJOTE3ODUwMjE3ZGS%2ByRcRG2v6m0v5xTATxgcXe0GIOA%
3D%3D&Couleur=blanc&Button1=Soumettre
Et pourtant, en dboguant la mthode Btn_Click() on saperoit lors dun deuxime click du
bouton que le serveur connat le contenu du contrle Msg de type Label :
Il est trs important que vous soyez convaincu que dans cet exemple le serveur ne retient aucun
tat. La valeur du contrle Label Msg est donc forcment encode dune manire implicite dans
la requte POST.
En eet, lorsque le navigateur client fabrique une requte HTTP, il rassemble dans une chane
de caractres toutes les valeurs de tous les contrles dont les valeurs nont pas t places dune

876

Chapitre 23 : ASP.NET 2.0


Ct client
GET

webSite/Default.aspx

Ct serveur
ASP.NET
Cration
Render()

Instance de
Default_aspx

HTTP/1.1 200 OK
<html>
<body>
...
<span id="Msg">
</span>
...

POST
HTTP/1.1
...

/WebSite1/Default.aspx

ASP.NET
Cration
Btn_Click()
Render()

Instance de
Default_aspx

HTTP/1.1 200 OK
<html>
<body>
...
<span id="Msg">
Vous avez
slectionn :
noir</span> ...

Figure 23 -5 : Interaction client/serveur

Figure 23 -6 : Dbogage pour mettre en vidence le rle du ViewState


manire standard dans la requte POST en construction. Cette chane est ensuite encode dans
un tableau binaire qui est lui-mme encod dans une chane de caractres base64.
Il est maintenant utile de remarquer que toutes les pages HTML produites par une page
ASP.NET incluent un contrle HTML invisible de type Input nomm __VIEWSTATE. Pour sen
convaincre, voici un extrait dune telle page HTML produite par une instance de notre classe
Default_aspx :
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<center>
<form method="post" action="Default.aspx" id="Form1">

Web forms et contrles

877

<div>
...
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"
value="/wEPDwUJOTE3ODUwMjE3D2QWAgIBD2QWAgIFDw8WAh4EVGV4dAUfVm91cyBhdm
V6IHPDqWxlY3Rpb25uw6kgOiBibGFuY2RkZGsF3jbEtiYP6owub0heigPWF2Fq" />
</div>
...
Lorsque le navigateur fabrique une requte HTTP, il aecte la chane de caractres base64 la
valeur du contrle HTML __VIEWSTATE. On retrouve notamment cette valeur dans notre extrait
prcdent dune requte POST (en gras). Ainsi, en dcodant ces informations le traitement interne des pages .aspx de ASP.NET est capable de retrouver tous les tats de tous les contrles.
La premire fois que lon rencontre cette astuce elle ne nous parait pas bien propre . Comprenez bien que cette technique est l pour pallier labsence dtat dans un environnement web.
Un tel comportement se rvle fort utile puisquen naviguant dune page une autre, les utilisateurs sattendent ce que les valeurs des contrles ne changent pas. Il ny avait pas de technique
quivalente dans la technologie ASP et les dveloppeurs taient obligs de prvoir du code pour
chaque contrle sens se souvenir de son tat entre les requtes.

Evnements postback et non-postback


Lorsquun utilisateur travaille sur une page HTML dans un navigateur, il eectue des actions
susceptibles de dclencher deux types dvnements : Les vnements postback et les vnements
non-postback.
Les vnements postback provoquent lenvoi dune requte POST au serveur pour lui fournir
lensemble des tats des contrles de la page. Fort de ces informations, le serveur peut rgnrer
dynamiquement la page et la renvoyer au navigateur. Typiquement, notre exemple montre que
le clic dun bouton est un vnement postback.
Les vnements non-postback ne provoquent pas dappel serveur. Un vnement non-postback est
sauvegard et sera envoy au serveur lors du prochain vnement postback. Lorsque le serveur
doit excuter plusieurs vnements non-postback, vous ne devez pas compter sur un quelconque
ordre dexcution. Typiquement les vnements non-postback sont les changements dtats de
contrles. Notre exemple montre que le changement de la slection dans un combo box est
un vnement non-postback. On peut aussi citer lcriture dun texte dans une boite ddition
de texte. Vous pouvez forcer un vnement non-postback se comporter comme un vnement
postback en positionnant true la proprit AutoPostBack du contrle serveur sous-jacent. Par
exemple, si vous ajoutez lattribut autopostback="true" notre contrle serveur Couleur, une
requte POST sera envoye au serveur chaque modification de la slection de notre combo
box :
...
<form id="Form1" action="Default.aspx" method="post" runat="server">
Couleur : <asp:dropdownlist id="Couleur" runat="server" autopostback="true">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
<br/>
...

878

Chapitre 23 : ASP.NET 2.0

Dans ce contexte, nous pouvons maintenant prciser que llment <form> dfinit un contrle
serveur de type System.Web.UI.HtmlControls.HtmlForm. Tous les contrles serveur qui peuvent
provoquer un vnement postback doivent tre dclars entre les balises dun lment <form>.
Nous avons dj mentionn quASP.NET sait associer la mthode Btn_Click() de notre classe
MyDefaultPage lvnement postback click sur Button1 du fait que lon aecte la valeur
Btn_Click lattribut OnClick de Button1 dans notre page Default.aspx :
...
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click" runat="server"/>
...
En fait, la classe System.Web.UI.WebControls.Button prsente un vnement Click de type la
dlgation EventHandler. Lors de la compilation dune page aspx, ASP.NET sait que la valeur
dun attribut OnXXX correspond au nom dune mthode quil faut associer lvnement XXX
du contrle serveur sous-jacent. On peut se passer de cette facilit du moment que lon eectue
cette association nous-mme lors de linitialisation de notre instance de Default_aspx dclenche par une requte POST. Ainsi la page suivante est quivalente notre page (nous dtaillons
un peu plus tard lvnement Page_Load() dclench par ASP.NET lors du chargement dune
page).
Exemple 23-7 :

Default.aspx

...
<asp:button id="Button1" text="Soumettre" runat="server"/>
...
Exemple 23-8 :

Default.aspx.cs

using System ;
public partial class MyDefaultPage : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
if (IsPostBack)
Button1.Click += Btn_Click;
}
protected void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value ;
}
}
Notez laccs la proprit bool Page.IsPostBack{get;}. Cette proprit vaut true si la requte courante est une requte POST. Nous lutilisons car il ny a pas lieu de sabonner lvnement Button1.Click si lon nest pas dans le cas dune requte postback.
Une question se pose : Comment la plomberie ASP.NET dtermine quel vnement de quel
contrle serveur il faut dclencher lors de la rception dune requte POST dun client ?
linstar de ce que lon a vu pour le viewstate ASP.NET ajoute deux contrles cachs nomms
__EVENTTARGET et __EVENTARGUMENT chaque page HTML gnre, ainsi que du code javascript
pour les initialiser. Par exemple voici un extrait dune telle page HTML produite par une instance de notre classe Default_aspx :
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>

Web forms et contrles

879

<center>
<form method="post" action="Default.aspx" id="Form1">
<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT"
value="" />
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="..." />
</div>
<script type="text/javascript">
<!-var theForm = document.forms[Form1];
if (!theForm) {
theForm = document.Form1;
}
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
// -->
</script>
...
Ainsi, toute requte POST contient dans les paramtres __EVENTTARGET et __EVENTARGUMENT linformation permettant la plomberie ASP.NET didentifier le contrle HTML responsable du
postback.

ContolState
Un problme courant rencontr en ASP.NET 1.x provient du fait que la chane base64 du viewstate devient norme (>10.000 caractres) ds que lon essaye dy stocker des informations telles
que le contenu dune table par exemple pour un contrle serveur de type DataGrid. Lorsque ce
problme est rencontr, on peut choisir de dsactiver le viewstate pour ce contrle et de grer son
tat autrement, par exemple en stockant le contenu de la table dans un cache du ct serveur ou
en rechargeant le contenu chaque requte. Il se pose alors un second problme : pour certains
contrles tels que le DataGrid les navigateurs utilisent le contenu du viewstate pour des raisons
fonctionnelles telles que le dclenchement dun vnement postback lors du changement des
donnes. Ainsi, en ASP.NET 1.x on peut tre tent de dsactiver le viewstate dun contrle pour
des raisons de bande passante et de lactiver pour des raisons fonctionnelles.
En ASP.NET 2.0 le problme du viewstate norme est amoindri du fait que les informations
sont stockes dune manire plus ecace dans la chane base64. Mais surtout, ASP.NET 2.0
introduit la notion de controlstate. Un contrle serveur peut profiter du controlstate pour stocker
les informations ncessaires son bon fonctionnement. Les contrles serveur suivants peuvent
ainsi profiter du controlstate pour des raisons fonctionnelles et dsactiver le viewstate pour des
raisons de bande passante : CheckBoxList, DetailsView, FormView, GridView, ListControl et
ses classes drives, LoginView, MultiView et Table.

880

Chapitre 23 : ASP.NET 2.0

Sachez que vous ne verrez pas de contrles cachs spciaux dans vos pages HTML pour stocker
les controlstate. Les controlstate sont stocks comme une sous-section du viewstate.

Changer de page lors dune requte


Par dfaut, toute requte postback ralise par une page se fait sur elle-mme. Cependant, il est
souvent ncessaire de changer de page lors dune requte postback. Cela est possible en spcifiant
la nouvelle page cible avec la proprit PostBackUrl des contrles serveurs de type boutons de
la page source. Rcrivons notre page source Default.aspx de faon ce quelle communique
le choix de couleur une autre page cible DisplayColor.aspx spcialise dans lachage dune
couleur :
Default.aspx

Exemple 23-9 :

<%@ Page Language="C#" %>


<html xmlns="http://www.w3.org/1999/xhtml" >
<script language=C# runat="server">
private string m_SelectedColor;
public string SelectedColor { get { return m_SelectedColor ; } }
void Btn_Click(Object sender, EventArgs e) {
m_SelectedColor = Couleur.SelectedItem.Value;
}
</script>
<body><center>
<form id="Form1" action="Default.aspx" method="post" runat="server">
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist><br/>
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click"
postBackUrl ="~/DisplayColor.aspx" runat="server"/><br/>
</form>
</center></body></html>
Il faut bien comprendre qutant donn que la page cible DisplayColor.aspx na pas les mmes
contrles serveurs que la page source Default.aspx, les donnes contenues dans notre requte
postback ne peuvent tre directement exploites. En fait, tout se passe comme si lon eectu une
requte GET sur la page cible. En interne, les donnes de la requte postback ont t exploites
par une instance de la classe reprsentant la page source. Cette instance est accessible par la proprit PreviousPage de la page cible. Aussi, si vous souhaitez exploiter les donnes de la requte
postback partir de la page cible, il est conseill de les rendre accessible laide de proprits de
la page source. Cette technique est illustre par la proprit SelectedColor de notre page source
Default.aspx et par la page cible DisplayColor.aspx :
Exemple 23-10 :

DisplayColor.aspx

<%@ Page Language="C#" %>


<%@ previousPageType virtualpath ="~/Default.aspx" %>
<script runat="server">
void Page_Load(object sender, EventArgs e) {
if (PreviousPage != null && PreviousPage.IsCrossPagePostBack) {

Web forms et contrles

881

Msg.Text = "Vous avez selectionn


e : " + PreviousPage.SelectedColor ;
} else { Msg.Text = "Pas de couleur s
electionn
ee." ; }
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" ><body>
<form id="form1" runat="server"><asp:label id="Msg" runat="server"/></form>
</body></html>
Il faut remarquer que la rfrence PreviousPage prend le type de notre classe Default_aspx
puisque nous pouvons invoquer directement notre proprit SelectedColor{get;}. ASP.NET
a pu typer cette rfrence grce la directive <%@ PreviousPageType> qui lui indique la page
source. Dans certains cas, cette facilit peut se rvler tre une limitation puisquelle empche
une page cible davoir plusieurs types de page source. Aussi, il est possible de se passer de cette
directive condition den payer le prix : avoir une page source non type. Rcrivons notre page
cible DisplayColor.aspx sans directive <%@ PreviousPageType> :
Exemple 23-11 :

DisplayColor.aspx

<%@ Page Language="C#" %>


<script runat="server">
void Page_Load(object sender, EventArgs e) {
if (PreviousPage != null ) {
DropDownList Couleur =
PreviousPage.FindControl("Couleur") as DropDownList;
if (Couleur != null)
Msg.Text = "Vous avez s
electionn
e : " +
Couleur.SelectedItem.Value ;
}
if( Msg.Text.Length==0 ) Msg.Text = "Pas de couleur s
electionn
ee." ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<html xmlns="http://www.w3.org/1999/xhtml" ><body>
<form id="form1" runat="server"><asp:label id="Msg" runat="server"/></form>
</body></html>
Cette page peut ainsi acher la couleur slectionne partir de nimporte quelle page source
contenant un contrle DropDownList nomm Couleur.
Une autre technique disponible depuis ASP.NET 1.x permet aussi de raliser un changement
de page. Pour cela, il sut dutiliser la mthode HttpServerUtility.Transfert( string pageCible) comme ceci :
Exemple 23-12 :
...
void Btn_Click(Object sender, EventArgs e) {
m_SelectedColor = Couleur.SelectedItem.Value ;
Server.Transfer("~/DisplayColor.aspx");
}
...

Default.aspx

882

Chapitre 23 : ASP.NET 2.0


<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click" runat="server"/>
...

Cette technique prsente un dsavantage par rapport lutilisation dun vnement PostBackUrl :
lURL ne change pas dans le navigateur.
Enfin, signalons la prsence dune nouvelle proprit bool IsCrossPagePostBack{get;}. Voici
un tableau qui illustre la valeur de cette proprit selon le contexte :
PageSource realise un postback sur elle-m
eme :
PageSource.IsPostBack
true
PageSource.IsCrossPagePostBack
false
PageSource.PreviousPage
null
PageSource realise un postBackUrl sur PageCible :
PageSource.IsPostBack
true
PageSource.IsCrossPagePostBack
true
PageSource.PreviousPage
null
PageCible.IsPostBack
false
PageCible.IsCrossPagePostBack
false
PageCible.PreviousPage
r
ef
erence vers PageSource
PageSource realise un transfert sur PageCible :
PageSource.IsPostBack
false
PageSource.IsCrossPagePostBack
false
PageSource.PreviousPage
null
PageCible.IsPostBack
false
PageCible.IsCrossPagePostBack
false
PageCible.PreviousPage
r
ef
erence vers PageSource

Notion de contrles serveur HTML et de contrles serveur Web


Jusquici nous navons parl que de contrle serveur et de contrles HTML. Les contrles serveur
sont exploits par ASP.NET et nexistent que du ct serveur. Les contrles HTML sont inclus
dans les pages HTML et sont exploits par les navigateurs, ct client.
Il existe deux catgories bien distinctes de contrles serveurs : les contrles serveurs HTML et
les contrles serveur web. chaque type de contrle HTML correspond un type de contrle
serveur HTML. La rciproque est fausse puisquil existe des contrles serveur HTML tels que
HtmlGenericControl. Un contrle serveur HTML ne fait que produire le fragment de code
HTML correspondant son contrle HTML associ. En revanche, les contrles serveurs web
sont plus volus et en plus de prsenter une API plus complte, ils produisent en gnral des
fragments de code HTML contenant plusieurs contrles HTML. Ainsi les contrles serveur web
ont souvent la prfrence des dveloppeurs. Dailleurs notre page Default.aspx exemple exhibe trois contrles serveur web (<asp:dropdownlist>, <asp:button> et <asp:label>) pour un
contrle serveur HTML (<form>). Lavantage des contrles serveur HTML rside dans la simplicit de leur dclaration pour celui qui connat le langage HTML. En eet, il sut dajouter
lattribut runat="server" nimporte quel contrle HTML dune page .aspx pour quASP.NET
cre un contrle serveur HTML associ. De ce fait, les contrles serveur HTML sont adapts
un processus de migration de la technologie ASP vers ASP.NET. Voici notre page Default.aspx
rcrite quavec des contrles serveur HTML :

Cycle de vie dune page


Exemple 23-13 :

883
Default.aspx

<%@ Page Language="C#" %>


<script language=C# runat="server">
void Btn_Click(Object sender, EventArgs e) {
Msg.InnerText = "Vous avez s
electionn
e : " + Couleur.Value ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<center>
<form id="Form1" action="Default.aspx" method="post" runat="server" >
Couleur : <select id="Couleur" runat="server" >
<option value="blanc" />
<option value="noir" />
</select>
<br/>
<input id="Button1" type="Submit" value="Soumettre"
OnServerClick="Btn_Click" runat="server" />
<br/>
<span id="Msg" runat="server" />
</form>
</center>
</body>
</html>
La liste des contrles serveur HTML est disponible dans larticle HTML Server Controls
Hierarchy des MSDN. Ces contrles font tous partie de lespace de noms System.Web.UI.
HtmlControls et drivent tous de la classe System.Web.UI.HtmlControls.HtmlControl (qui
elle-mme drive de la classe System.Web.UI.Control).
La liste des contrles serveur web est disponible dans larticle Web Server Controls Hierarchy
des MSDN. Ces contrles font pratiquement tous partie de lespace de noms System.Web.
UI.WebControls et drivent pratiquement tous de la classe System.Web.UI.WebControls.
WebControl (qui elle-mme drive de la classe System.Web.UI.Control).

Cycle de vie dune page


Maintenant que nous avons dfini les notions dvnements postback, de viewstate/controlstate et
darborescence de contrles, il est temps dexposer la chronologie des vnements qui ponctuent
le traitement de toutes requtes. Une bonne comprhension dASP.NET ne peut se faire sans la
connaissance de ce cycle de vie. Nous rappelons que le modle de threading dASP.NET consiste
associer un thread du pool pour traiter entirement une requte. Aussi, les traitements abonns aux vnements sont excuts les uns aprs les autres par le mme thread.
Dans le tableau suivant, chaque vnement est index par le nom de sa mthode abonne par
dfaut. Nous prcisons si les mthodes sont invoques rcursivement sur tous les contrles ou
bien uniquement sur la page. La plupart de ces mthodes sont protges et virtuelles, aussi vous
pouvez les rcrire avec vos propres implmentations. Le cas chant, il est souvent conseill

884

Chapitre 23 : ASP.NET 2.0

dinvoquer limplmentation de la classe de base partir de votre implmentation sous peine


de perturber gravement le traitement de votre requte. Certains de ces vnements peuvent
tre intercepts autrement quen rcrivant une mthode virtuelle. Par exemple vous pouvez
vous abonner lvnement Control.Load au niveau dune page autrement quen rcrivant la
mthode OnLoad(). Pour cela, il sut de fournir une mthode nomm Page_Load(). Si vous
positionnez la sous directive AutoEventWireUp true, ASP.NET sait retrouver cette mthode
par rflexion et linvoquer au bon moment. Si vous ne souhaitez pas subir la cot de la rflexion,
vous pouvez positionner cette sous directive false et soit assigner vous-mme les dlgu aux
vnements, soit utiliser la rcriture des mthodes virtuelles.
Pour vous aider retenir cet enchanement dvnement, nous les avons classs par tapes. Les
vnements qui ne sont dclenchs que lors dune requte de type POST sont reprsents par
des cellules grises. Quant aux nouveaux vnements introduits avec la version 2.0 dASP.NET,
ils sont crits en gras :
tape

vnements
thodes

Construction

Appel des constructeurs

Tous

Possibilit dinitialiser les champs.

Initialisation

InitializeCulture

Page

Permet de personnaliser linitialisation de la culture.

DeterminePostBackMode

Page

Dtermine si cest une requte postback ou non. Possibilit de simuler


une requte postback.

OnPreInit

Page

Permet de spcifier une master page


ou un thme.

OnInit

Tous

Initialisation des tats avec les valeurs spcifies dans la page.

OnInitComplete

Page

Possibilit daltrer les tats initiaux


des contrles et de crer des
contrles dynamiquement.

LoadPageStateFromPersistenceMedium

Page

Possibilit de charger le viewstate


partir dun autre endroit que dans
la page (par exemple partir dune
sauvegarde eectue lors de la dernire requte).

LoadViewState

Tous

Restaure les tats partir du viewstate et du controlstate.

LoadControlState

Tous

ProcessPostData

Page

Restaure
tats

les

et

m-

ContrlesCommentaires

Restaure les tats des contrles partir des valeurs spcifies dans les paramtres postback.

Cycle de vie dune page

Chargement de
la page

Avant la fabrication

Sauve les tats

Rendu

885

OnPreLoad

Page

OnLoad

Tous

OnDataBinding

Tous

Charge les donnes lintrieur des


contrles qui supportent une liaison vers une source de donnes.

ProcessPostData

Page

Appele une deuxime fois au cas


o des contrles sont crs dynamiquement dans OnLoad(). Ainsi,
leurs tats peuvent tre restaurs
partir des valeurs spcifies dans les
paramtres postback.

Validate

Page

Dclenche la validation par les


contrles spcialiss.

RaiseChangedEvent

Page

Dclenche les vnements dus des


changements dtats (suite logique
de ProcessPostData).

RaisePostBackEvent

Page

Dclenche les vnements postback.

OnLoadComplete

Page

OnPreRender

Tous

OnPreRenderComplete

Page

Page.SaveViewState

Tous

SaveControlState

Tous

SavePageState\
-To-Persistence\
-Medium

Page

OnSaveStateComplete

Page

Render

Tous

Cest ici que vous eectuez la


plupart des actions personnalises
telles que labonnement un
vnement ou lobtention dune
connexion BD.

Dernire chance dagir sur larborescence des contrles ainsi que leur
tats.

Sauve les tats dans le viewstate et le


controlstate.

Possibilit de sauver le viewstate


autre part que dans la page. Trs
utile pour ne pas surcharger la
bande passante.

Fabrication de la page HTML puis


envoie immdiat au client.

886

Chapitre 23 : ASP.NET 2.0

Finalisation

UnLoad

Tous

Dispose

Tous

Dernire chance de librer des


ressources non gres tels quune
connexion une BD. Ces mthodes
sont appeles aprs lenvoi de la
page HTML.

Traitement dune page sur plusieurs threads


Il arrive que le traitement dune requte implique une longue attente du thread. Typiquement,
cela se produit lorsque lon accde un service web lent ou lorsque lon eectue une requte
coteuse sur une base de donne. Dans ce cas, le modle de threading dASP.NET est inecace.
En eet, lattente dun thread nest pas sans consquence puisque le nombre de threads du pool
pouvant servir une requte est limit. Si la plupart des requtes impliquent une attente de la
part des threads, le serveur sera trs vite surcharg et ce, quelque soit sa puissance. On est face
un goulot dtranglement by design.
ASP.NET 2.0 propose une infrastructure pour rgler ce problme. Lide est que la finalisation
du traitement dune requte puisse se faire avec un thread dirent de celui qui a servi amorcer
le traitement. Entre temps, laccs une ressource tel quun service web ou une base de donnes
sest fait sans aucune monopolisation dun thread du pool. Dans le cas de laccs une base de
donnes, cela est possible grce aux nouvelles requtes asynchrones dADO.NET 2.0 dcrites en
page 738. Cette possibilit permet deectuer une requte une base de donnes sans mobiliser aucun thread du pool pendant lattente des donnes. Lexemple suivant illustre une page
qui exploite la fois la possibilit ASP.NET 2.0 de traiter une page sur plusieurs threads et la
possibilit ADO.NET 2.0 deectuer une requte asynchrone. Les donnes rcupres de la base
sont alors prsentes dans une GridView :
Exemple 23-14 :

Default.aspx

<%@ Page Language="C#" Async="true" CodeFile="Default.aspx.cs"


Inherits="_Default" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body><form id="form1" runat="server">
<asp:GridView ID="MyGridView" runat="server"
AutoGenerateColumns="true" />
</form></body></html>
Exemple 23-15 :
using System ;
using System.Data ;
using System.Data.SqlClient ;
using System.Web ;
using System.Web.UI ;
using System.Threading ;
public partial class _Default : System.Web.UI.Page {
private SqlCommand cmd ;

Default.aspx.cs

Cycle de vie dune page

887

private SqlConnection cnx ;


protected void Page_Load(object sender, EventArgs e) {
Response.Write("PageLoad thread:"+
Thread.CurrentThread.ManagedThreadId +"<br/>") ;
PageAsyncTask task = new PageAsyncTask(BeginInvoke, EndInvoke,
EndTimeOutInvoke, null);
Page.RegisterAsyncTask(task);
}
public IAsyncResult BeginInvoke(object sender,
EventArgs e,
AsyncCallback callBack,
object extraData) {
Response.Write("BeginInvoke thread:" +
Thread.CurrentThread.ManagedThreadId + "<br/>") ;
cnx =new SqlConnection("async=true ; server = localhost ; " +
"uid=sa ; pwd = ; database = ORGANISATION") ;
cmd =new SqlCommand("SELECT * FROM EMPLOYES", cnx) ;
cnx.Open() ;
return cmd.BeginExecuteReader(callBack, extraData,
CommandBehavior.CloseConnection) ;
}
public void EndInvoke(IAsyncResult result) {
Response.Write("EndInvoke thread:" +
Thread.CurrentThread.ManagedThreadId + "<br/>") ;
SqlDataReader rdr = cmd.EndExecuteReader(result) ;
if (rdr != null && rdr.HasRows) {
MyGridView.DataSource = rdr ;
MyGridView.DataBind() ;
}
rdr.Close() ;
}
public void EndTimeOutInvoke(IAsyncResult result) {
if (cnx != null && cnx.State != ConnectionState.Closed)
cnx.Close() ;
Response.Write("TimeOut") ;
Response.End() ;
}
protected override void OnPreRender(EventArgs e) {
Response.Write("OnPreRender thread:" +
Thread.CurrentThread.ManagedThreadId + "<br/>") ;
}
protected override void OnPreRenderComplete(EventArgs e) {
Response.Write("OnPreRenderComplete thread:" +
Thread.CurrentThread.ManagedThreadId + "<br/>") ;
}
}
Pour supporter ce modle de traitement sur plusieurs threads, il faut positionner true la sousdirective Async de la directive <%@ Page>. Lors de lvnement Load on peut alors fabriquer des

888

Chapitre 23 : ASP.NET 2.0

instances de la classe System.Web.UI.PageAsyncTask. Chacune de ces instances reprsente un


traitement qui va tre eectu en dehors du processus aspnet_wp.exe. Aucun thread du pool
nest utilis pour attendre la terminaison de ce traitement. Un tel traitement est reprsent par
trois dlgus :

Un dlgu vers la mthode damorcage du traitement qui est excute par le thread qui a
initi le traitement de la requte (i.e celui qui excute Page_Load()). En loccurrence, notre
dlgu rfrence la mthode BeginInvoke(). Elle est excute juste aprs lvnement PreRender().
Un dlgu vers la mthode de finalisation du traitement qui est excute par un thread
du pool lorsque le traitement a t signal comme fini. En loccurrence, notre dlgu rfrence la mthode BeginInvoke. Elle est excute juste avant lvnement PreRenderComplete.
Un dlgu vers une mthode excute par un thread du pool lorsque le traitement ne sest
pas eectu dans les temps impartis. En loccurrence, notre dlgu rfrence la mthode
EndTimeOutInvoke().

La page HTML gnre par cet exemple commence par ceci :


PageLoad thread:4
OnPreRender thread:4
BeginInvoke thread:4
EndInvoke thread:8
OnPreRenderComplete thread:8
...
Si vous souhaitez utiliser cette technique pour traiter dune manire asynchrone un appel vers un service web, vous pouvez utiliser les mthodes IAsyncResult BeginXXX() et
EndXXX(IAsyncResult) de la classe proxy gnre par loutil wsdl.exe (voir page 994).

Configuration dune application ASP.NET


Il est prfrable davoir assimil la section relative au fichier de configuration dune application
.NET page 58 avant daborder celle-ci.

Organisation des fichiers de configuration


La configuration dune application ASP.NET se fait au moyen de fichiers XML dextension
.Config. Dans ces fichiers, la plupart des paramtres relatifs ASP.NET se trouvent dans la
section <configuration>/<system.web>. Les paramtres de configuration dune mme application ASP.NET peuvent tre dissmins dans les fichiers .Config suivant :

Le fichier Machine.Config qui se trouve dans le rpertoire dinstallation du framework


C:\WINDOWS\Microsoft.NET\Framework\v2.0.XXXXX\CONFIG\machine.config. Cest le
seul fichier de configuration obligatoire.
Le fichier Web.Config qui se trouve dans le rpertoire racine des sites hbergs sur la machine, en gnral C:\Inetpub\wwwroot.
Le fichier Web.Config qui se trouve dans le rpertoire racine de lapplication concerne.
Des fichiers Web.Config qui peuvent se trouver dans nimporte quels sous rpertoire de
lapplication concerne.

Configuration dune application ASP.NET

889

Pour une page stocke dans un sous rpertoire [racine]\Foo de votre application web, les
valeurs des paramtres dfinis dans le fichier [racine]\Foo\Web.Config masquent les valeurs
des paramtres dfinis dans le fichier [racine]\Web.Config qui elles-mmes masquent les valeurs des paramtres dfinis dans le fichier C:\textbackslashInetpub\textbackslashwwwroot\
textbackslashWeb.Config qui elles-mmes masquent les valeurs des paramtres dfinis dans le
fichier Machine.Config. Avec ce modle, il est par exemple ais de dfinir une stratgie de scurit par dfaut commune toutes les applications web hberges sur la machine dans le fichier
Machine.Config tout en se laissant la libert dappliquer une stratgie de scurit exceptionnelle
pour certaines pages confidentielles stockes dans un sous rpertoire dune application. En plus
de cette souplesse, ce modle permet aussi le dploiement dune application ainsi que de ses
fichiers de configuration par simple copie dune arborescence de rpertoires et de fichiers (dploiement xcopy). Notez enfin quASP.NET rend inaccessible de lextrieur les accs aux fichiers
de configuration.

Les sections de configuration ASP.NET


Voici la liste exhaustive des lments de configuration ASP.NET 2.0 contenus dans llment
<system.web>. Ceux qui sont en gras ont t introduits avec la version 2.0 dASP.NET. Nous donnons les rfrences vers les explications dans le prsent ouvrage lorsquelles sont disponibles :
<anonymousIdentification/> voir page <pageref id="ANONYMOUS_IDENTIFICATION"/>
<authentication/> voir page <pageref id="ASPNET_CONFIG_AUTHENTICATION"/>
<authorization/> voir page <pageref id="ASPNET_CONFIG_AUTHORIZATION"/>
<browserCaps/>
<caching/> voir page <pageref id="ASPNET_CACHING"/>
<clientTarget/>
<compilation/>
<customErrors/> voir page <pageref id="ASPNET_CUSTOM_ERRORS"/>
<deployment/>
<deviceFilters/>
<globalization/> voir page <pageref id="LOCALIZATION_WEBFORM"/>
<healthMonitoring/> voir page
<hostingEnvironment/>
<httpCookies/>
<httpHandlers/> voir page <pageref id="HTTP_MODULES"/>
<httpModules/> voir page <pageref id="HTTP_HANDLERS"/>
<httpRuntime/>
<identity/> voir page <pageref id="ASPNET_IMPERSONATION"/>
<machineKey/>
<membership/> voir page <pageref id="SECURITY_MEMBERSHIP"/>
<mobileControls/>
<pages/> voir page <pageref id="ASPNET_CONFIG_PAGES"/>
<processModel/> voir page <pageref id="ASPNET_PROCESS_MODEL"/>
<profile/> voir page
<protocols/>
<roleManager/> voir page <pageref id="SECURITY_ROLES"/>
<securityPolicy/>
<sessionPageState/>
<sessionState/> voir page <pageref id="ASPNET_SESSION_STATE"/>

890

Chapitre 23 : ASP.NET 2.0


<siteMap/> voir page <pageref id="ASPNET_CONFIG_SITEMAP"/>
<trace/> voir page <pageref id="ASPNET_TRACE"/>
<trust/>
<urlMappings/>
<webControls/>
<webParts/> voir page <pageref id="ASPNET_WEBPARTS"/>
<webServices/>
<xhtml11Conformance/>

Pour plus de dtails sur la configuration dune application web, nous vous conseillons de consulter larticle ASP.NET Configuration des MSDN.
Enfin, ajoutons quun lment <location>, enfant direct de llment <configuration>, permet de redfinir pour chaque page les paramtres de configuration.

Configuration du modle de processus


La fiabilit fait partie des contraintes prpondrantes imposes une application web. Lors du
dploiement dune application web, vous devez porter une attention particulire llment de
configuration <processModel> qui ne peut se trouver que dans le fichier Machine.config. Cet
lment permet de paramtrer les possibilits suivantes destines consolider la fiabilit dune
application web :

Vous pouvez indiquer ASP.NET quand redmarrer un processus qui a un comportement anormal. Lattribut requestQueueLimit prcise le nombre maximal de demandes en
attentes. Lorsque ce nombre est atteint, ASP.NET considre que le processus a un comportement anormal. Lattribut memoryLimit prcise le pourcentage maximal de mmoire
systme pouvant tre utilise. Au-del de cette limite, ASP.NET considre que le processus a un comportement anormal. Dans ce cas, il lui donne la dure spcifie par lattribut
shutdownTimeout pour sarrter automatiquement. Passe cette limite, ASP.NET arrte ce
processus si ce nest dj fait et lance un nouveau processus. Notez que les demandes en
attentes sont automatiquement rassignes ce nouveau processus.

Vous pouvez forcer ASP.NET redmarrer un processus priodiquement, mme si aucune


condition anormale na t dtecte. Lattribut timeout indique la dure au-del de laquelle
le processus doit tre redmarr. Lattribut requestLimit indique le nombre de demandes
traites au-del duquel le processus doit tre redmarr. Lattribut idleTimeout indique la
dure dinactivit au-del de laquelle le processus doit tre arrt.

Les attributs webGarden et cpuMask indique ASP.NET comment se comporter sur un serveur multi processeurs. En eet, si un serveur possde plusieurs processeurs, il est trs ecace de pouvoir faire en sorte que chaque processeur gre seulement certains processus. On
dit que lon cre des anits entre processus et processeurs. Cette technique est nomme
web garden.

Les attributs userName et Password indiquent lidentit de lutilisateur Windows sous lequel
doit sexcuter le processus aspnet_wp.exe.

Mise jour de la configuration


En ASP.NET v1.x pour mettre jour la configuration dune application vous aviez le choix entre
mettre jour les fichiers XML .Config la main ou manipuler programmatiquement le XML

Pipeline HTTP

891

contenu dune manire non type. ASP.NET 2.0 prsente plusieurs facilits pour raliser cette
opration :

Visual Studio 2005 vous assiste avec lintellisense lors de ldition manuelle dun fichier de
configuration.

Une nouvelle interface graphique web vous permet de mettre jour la configuration directement partir dun navigateur excut en local (pour des raisons de scurit). Vous pouvez
y avoir accs partir de Visual Studio avec longlet Site Web  ASP.NET Configuration.

Une nouvelle interface graphique de mise jour de la configuration vient sinsrer dans la
console de configuration dIIS.

Des nouvelles classes de base sont fournies pour manipuler programmatiquement et surtout, dune manire fortement type, le XML contenu (voir page 58).

Prise en compte des mises jour


Grce au mcanisme de shadow copy, toutes modifications sur un fichier de configuration est
prise en compte par ASP.NET. En consquence, vous navez pas besoin darrter puis de redmarrer lapplication web pour que les changements soient pris en compte. Cela entrane nanmoins la perte des tats maintenus en mmoire de lapplication (session et application). En
consquence, il faut viter les mises jour des fichiers de configuration dune application web
en production puisquelles peuvent gner les utilisateurs. En outre, il faut savoir que les mises
jour concernant llment <processModel> ne sont prises en compte quau redmarrage dIIS.

Pipeline HTTP
Introduction
Jusquici, nous avons expliqu comment ASP.NET traite les demandes de pages .aspx. Lors de
lacheminement dune requte HTTP entre la sortie du pipeline nomm (provenant en gnral
du processus dIIS) et le traitement de la page, nous avons eu loccasion de mentionner le pipeline http ainsi que du code pour le traitement en interne des pages .aspx. Le pipeline HTTP est
un mcanisme dASP.NET permettant principalement :

De fournir des traitements qui agissent sur les requtes ou (non exclusif) les rponses HTTP.
Un tel traitement se nomme module HTTP. ASP.NET a ses propres modules HTTP qui
traitent par exemple les identifiants de sessions ou lauthentification des utilisateurs. Nous
allons voir comment crer nos propres modules HTTP.

De fournir un traitement qui sert les requtes HTTP (i.e qui fabrique une rponse HTTP en
fonction dune requte). Un tel traitement se nomme handler HTTP. ASP.NET a ses propres
handlers HTTP, dont notamment celui qui soccupe du traitement des pages .aspx en interne. Nous allons voir comment crer nos propres handlers HTTP.

La Figure 23 -7 exhibe la place que tient le pipeline HTTP ainsi que les modules et handlers
HTTP dans ASP.NET. Cette figure se base sur le modle de processus de IIS 5.0 qui, rappelons
le, est dirent de celui de IIS 6.0 :

892

Chapitre 23 : ASP.NET 2.0


Requte HTTP

Rponse HTTP

as
aspnet_isapi.dll

IIS

Machine
hbergeant
le serveur
Web

aspnet_wp.exe
Pipe nomm
CLR

httpModule
httpModule

Pipeline HTTP

httpModule

httpHandler

Figure 23 -7 : Module HTTP et Handler HTTP

HttpApplication et le fichier Global.asax


Avant daborder les modules et handlers HTTP il est ncessaire de comprendre la notion dapplication HTTP. Nous avons dj expliqu que le processus dASP.NET maintient un domaine
dapplication pour chaque application web hberge. ASP.NET fait en sorte que chacun de ces
domaines dapplication contienne un objet instance de la classe System.Web.HttpApplication
(ou dune classe drive de celle-ci). Cette classe prsente notamment des vnements qui sont
dclenchs direntes tapes cls, comme le lancement de lapplication ou la cration dune
nouvelle session. En vous abonnant certains de ces vnements vous pouvez alors faire excuter vos traitements au moment adquat.
La technique standard pour sabonner ces vnements est de fournir les traitements dans des
mthodes dfinies dans un fichier nomm Global.asax. Ce fichier doit tre stock la racine
de votre application. ASP.NET compilera ces mthodes dans une classe ASP.Global_asax qui
drive de la classe HttpApplication. Lobjet application sera lexcution une instance de cette
classe. Voici un exemple de fichier Global.asax :
Exemple 23-16 :

Global.asax

<%@ Application Language="C#" %>


<script runat="server">
protected void Application_Start(object src, EventArgs e) { /*...*/ }
protected void Application_End(object src, EventArgs e) { /*...*/ }
protected void Application_AuthenticateRequest(object src, EventArgs e)
{ /*...*/ }
protected void Application_Error(object src, EventArgs e) { /*...*/ }
protected void Session_Start(object src, EventArgs e) { /*...*/ }
protected void Session_End(object src, EventArgs e) { /*...*/ }
protected void Session_BeginRequest(object src, EventArgs e) { /*...*/ }
protected void Session_EndRequest(object src, EventArgs e) { /*...*/ }
protected void Application_PostMapRequestHandler(object src,EventArgs e)
{ /*...*/ }

Pipeline HTTP

893

/*...*/
</script>
Nous vous invitons consulter les MSDN pour obtenir la liste des vnements. Il est intressant
de remarquer quASP.NET utilise la rflexion pour associer les mthodes dfinies dans le fichier
Global.asax aux vnements de HttpApplication. Le nom dune telle mthode commence par
Application_ ou Session_ suivit du nom de lvnement. En consquence, vous devez porter
une attention particulire au nom de ces mthodes puisque lintellisense nest pas l pour dtecter les erreurs de syntaxe. Notez que certains vnements tel que Application_Start() ne sont
exploitables que par lintermdiaire du fichier global.asax tandis que dautres sont en plus de
cette technique, exploitables comme tout vnement dune classe .NET.

Contexte HTTP
Une instance de la classe System.Web.HttpContext est automatiquement cre par ASP.NET
pour servir chaque requte HTTP. Lors du traitement dune requte, cet objet contexte est accessible tous les niveaux du pipeline HTTP grce la proprit statique Current{get;} de la
classe HttpContext. Lors du traitement dune requte, on a souvent besoin des informations
accessibles au travers des proprits de lobjet contexte, telles que le principal de lutilisateur
initiateur de la requte (si il est authentifi), la requte HTTP elle-mme ou la rponse en cous
de fabrication.

Module HTTP
Nous pouvons maintenant nous intresser au dveloppement de nos propres modules HTTP.
Un module HTTP est une instance dune classe qui implmente linterface System.Web.
IHttpModule :
public interface IHttpModule {
void Dispose() ;
void Init( HttpApplication app ) ;
}
Un objet module est cr au lancement dune application ASP.NET. Aprs lappel au constructeur de sa classe, ASP.NET invoque la mthode Init(). Vous devez profiter de cette mthode
pour abonner vos traitements sur les requtes et les rponses HTTP. En gnral, on sabonne aux
vnements BeginRequest() et EndRequest() de HttpApplication mais vous pouvez choisir de
vous abonner nimporte quel autre vnement. Voici un exemple de module HTTP :
Exemple 23-17 :
using System ;
using System.Web ;
public class MyHttpModule : IHttpModule {
public void Dispose() { }
public void Init( HttpApplication app ) {
app.BeginRequest += OnBeginRequest;
app.EndRequest += OnEndRequest;
}
public void OnBeginRequest( object source, EventArgs args ) {
HttpApplication app = source as HttpApplication ;

894

Chapitre 23 : ASP.NET 2.0


app.Response.Write("BeginRequest : " +
DateTime.Now.ToLongTimeString()) ;
}
public void OnEndRequest( object source, EventArgs args ) {
HttpApplication app = source as HttpApplication ;
app.Response.Write("EndRequest : " +
DateTime.Now.ToLongTimeString()) ;
}
}

Ce module ajoute la date de dbut et de fin de traitement la rponse, en loccurrence un fichier


HTML. Remarquez que dans cet exemple les chanes de caractres ajoutes en dbut et en fin de
page HTML ne sont pas inclues dans la balise <html> mais la souplesse dIE fait quil les ache
quand mme :

Figure 23 -8 : Eet de notre module HTTP


Notez aussi que la mthode Init() de chaque module nest appele quune fois durant la dure
de vie de lapplication. Cest donc le mme module qui est responsable des traitements sur toutes
les requtes. Puisque plusieurs requtes peuvent tre traites par plusieurs threads simultanment, cette remarque est pertinente car elle souligne le besoin de synchronisation des accs aux
ressources dun module.
Enfin, il faut savoir que pour quASP.NET prenne en compte un module particulier, il faut le
lui prciser comme ceci dans le fichier de configuration de lapplication :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<httpModules>
<add name="MyHttpModuleName" type="MyHttpModule,MyAsm"/>
</httpModules>
...
Lattribut type prcise le nom complet (i.e avec les espaces de noms) de la classe implmentant
linterface IHttpModule, suivi dune virgule puis suivi du nom fort de lassemblage contenant
cette classe. Si le code de la classe est contenu dans le rpertoire /App_Code, vous navez pas

Pipeline HTTP

895

besoin de prciser lassemblage. Sinon, lassemblage doit tre contenu dans le rpertoire /bin.
Notez que la dclaration dun module HTTP peut se faire dans la balise <httpModules> du
fichier Machine.config afin quil soit pris en compte par toutes les applications ASP.NET de
la machine. Dans ce cas, lassemblage contenant la classe du module doit imprativement tre
prsent dans le GAC.
Vous devez absolument concevoir des modules aussi indpendants que possible du fait que
lon ne matrise pas lordre de lenchanement des appels aux traitements des modules dans le
pipeline HTTP. Cet ordre denchanement est le mme que lordre des appels aux mthodes
Init() des modules. Donc, la Figure 23 -7 nest pas tout fait exacte puisque les traitements
EndRequest() sont appels dans le mme ordre que les traitements BeginRequest() et non dans
lordre inverse. Empiriquement, on saperoit que lordre des appels aux mthodes Init() des
modules est le mme que celui de la dclaration des modules dans la balise <httpModules> mais
cet ordre nest pas garanti par Microsoft.

Handler HTTP
Vous pouvez dfinir vos propres handlers HTTP pour traiter vos propres types de ressources.
Pour cela il faut dfinir une classe implmentant linterface System.Web.IHttpHandler :
public interface IHttpHandler {
bool IsReusable { get ; }
void ProcessRequest(HttpContext context) ;
}
Une telle classe est en gnral dclare dans un fichier dextension .ashx. Par exemple, le handler HTTP suivant est une calculatrice qui prend une opration ainsi que deux oprandes dans
les paramtres dune requte GET et ache le rsultat dans la rponse. Notez que depuis Visual
Studio 2005, nous avons lintellisense sur les fichiers .ashx. Pour garder lexemple simple, la
rponse est un simple document texte sans balise <html> mais IE sait lacher :
Exemple 23-18 :

MyCalc.ashx

<%@ WebHandler Language="C#" Class=MyCalcHttpHandler %>


using System ;
using System.Web ;
public class MyCalcHttpHandler : IHttpHandler {
public bool IsReusable { get { return true ; } }
public void ProcessRequest( HttpContext context ) {
try {
int a = int.Parse( context.Request["a"] ) ;
int b = int.Parse( context.Request["b"] ) ;
switch (
context.Request["op"]) {
case "add": context.Response.Write(a + b) ; break ;
case "sub": context.Response.Write(a - b) ; break ;
case "mul": context.Response.Write(a * b) ; break ;
case "div": context.Response.Write(a / b) ; break ;
default: context.Response.Write("Invalid op!") ; break ;
}
} catch { context.Response.Write("Invalid params!") ; }

896

Chapitre 23 : ASP.NET 2.0


}
}

Vous pouvez alors vous servir de cette calculatrice en tapant la requte GET suivante dans votre
navigateur :
http://localhost:1232/WebSite1/MyCalc.ashx?a=11&b=3&op=mul
Notez quune instance de MyClassHttpHandler est cre pour servir chaque requte. En outre,
la ressource MyCalc.ashx est relle car elle est matrialise par un fichier la racine de lapplication. ASP.NET a su router la requte vers cette ressource et donc, vers le bon handler.
Vous pouvez aussi vous servir de handlers HTTP pour traiter des requtes vers des ressources
virtuelles i.e non matrialises par un fichier la racine de lapplication. linstar de ce que
lon a vu pour les modules HTTP, il faut prciser un tel handler HTTP en indiquant la classe qui
implmente IHttpHandler dans la balise <httpHandlers> du fichier Web.Config de lapplication
comme ceci :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<httpHandlers>
<add verb="GET" path="*.calc" type="MyCalcHttpHandler"/>
</httpHandlers>
...
Ce que lon a nonc concernant lassemblage contenant la classe, concernant le formatage de
lattribut type et concernant la dclaration dans le fichier Machine.config reste valable pour
les handlers HTTP. Cependant cette technique est potentiellement plus puissante puisque vous
pouvez par exemple router vers votre handler toutes les requtes vers une ressource dont le nom
se termine par lextension .calc. Bien entendu, avec cette seconde technique il ny a pas besoin
dun fichier .ashx et vous pouvez dfinir la classe MyCalcHttpHandler dans un fichier C  du
rpertoire /App_Code. Voici le genre de requte GET que peut alors servir notre handler :
http://localhost:1232/WebSite1/XYZ.calc?a=11&b=3&op=mul
Cette technique marche parfaitement avec le serveur web de Visual Studio. Il nen est pas de
mme en production avec IIS. En eet, il faut indiquer ce dernier que dans le cadre dun
rpertoire virtuel, les requtes dextension .calc doivent tre traites par ASP.NET. Pour cela
il faut eectuer la manipulation suivante sur loutil de configuration dIIS :
[click droits sur le rpertoire virtuel concern]  Proprits  Rpertoire virtuel  Configuration...  Ajouter  Extension=.calc Chemin de lexcutable=C:\WINDOWS\Microsoft.NET\Framework\v2.0.XXXX\aspnet_isapi.dll verbe=GET
Dans le cas o vous utilisez un fichier .ashx, cette manipulation est inutile car IIS est configur
par dfaut pour router les requtes concernes vers ASP.NET.
Dans le modle de dveloppement de handlers HTTP qui vient dtre expos, un objet handler est cr par ASP.NET la premire requte et est alors exploit pour servir toutes les

Gestion des sessions et des tats

897

requtes suivantes. Vous pouvez agir sur ce comportement en ayant recours linterface
IHttpHandlerFactory. Par exemple vous pouvez faire en sorte quun objet handler soit cr
pour servir chaque requte ou mme grer un pool dobjets handler recyclable.
public interface IHttpHandlerFactory {
IHttpHandler GetHandler( HttpContext context,
string
requestType,
string
url,
string
pathTranslated) ;
void ReleaseHandler(IHttpHandler handler) ;
}
Pour cela, il sut de prciser dans le fichier Web.Config votre classe qui implmente linterface
IHttpHandlerFactory la place de celle qui implmente IHttpHandler. La mthode GetHandler() est invoque par ASP.NET chaque traitement dune nouvelle requte et la mthode
ReleaseHandler() chaque terminaison de requte. Cela vous laisse la libert de dcider quand
vos handler sont crs, quand ils doivent tre recycls et quand ils doivent tre dtruits.

Gestion des sessions et des tats


Les pages sont entirement recres chaque requte vers le serveur. La consquence est que
lon entend souvent dire que le Web est un environnement sans tat. Nanmoins, de nombreuses applications web ont besoin de pouvoir grer un tat. Par exemple, la plupart des applications commerciales proposent un panier qui permet de stocker vos achats durant votre
navigation. Cela permet de constamment avoir une vision globale de vos achats et de neectuer
la transaction dachat quune seule fois, la fin de votre shopping .
Il y a de nombreuses faons de grer des tats avec ASP.NET. Nous avons dj vu notamment la
gestion de viewstate/controlstate qui a la particularit de faire transiter les donnes dans toutes les
requtes et les rponses. Cette technique est donc bien adapte aux donnes non confidentielles
de petite taille. Voici les principaux critres prendre en compte lorsque vous dcidez que votre
application va grer des tats :

La taille de vos donnes : Il est maladroit de faire transiter des donnes volumineuses (>
4Ko) sur le rseau. Il est aussi maladroit de stocker des donnes volumineuses dans la mmoire dun processus. Au del dune certaine quantit de donnes, le mcanisme de mmoire virtuelle de Windows grve les performances dune manire inacceptable cause des
nombreux accs au disque dur induits. On prfre stocker ce genre de donnes dans une
base de donnes.
Le niveau de scurit requis : Il est dangereux de stocker chez le client ou de faire transiter
chaque requte des donnes confidentielles telles quun numro de carte bancaire. Ces
donnes transitent en gnral une fois sur le rseau sous une forme crypte, lorsque le client
les communique au serveur, puis sont stockes du ct serveur.
Les performances souhaites : Faire transiter des donnes sur le rseau, stocker des donnes
en mmoire ou accder une base de donnes sont des oprations directement ou indirectement coteuses en termes de performance. Il faut donc trouver un compromis selon vos
besoins.

linstar de la gestion des viewstate/controlstate, le stockage de cookies chez le client permet de


grer des tats dont les donnes associes sont peu volumineuses et non confidentielles. Cette

898

Chapitre 23 : ASP.NET 2.0

technique prsente linconvnient majeur de ne pas tre applicable tous les clients puisque de
nombreux utilisateurs dcident de dsactiver les cookies sur leurs navigateurs.
Linstance de HttpApplication globale lapplication maintient une instance de la classe
System.Web.HttpApplicationState. Cet objet, lui aussi global lapplication, est accessible
au travers de la proprit Application{get;} prsente la fois par la classe HttpApplication
et par la classe Page. Cet objet peut tre vu comme un dictionnaire qui permet de stocker des
donnes globales lapplication. Voici un exemple dutilisation conjointe de ce dictionnaire et
de cookies pour assigner un identifiant unique chaque client qui se connecte (notez les appels
aux mthodes Lock() et UnLock() pour synchroniser les accs au dictionnaire) :
Exemple 23-19 :
using System ;
using System.Web ;
public partial class MyDefaultPage : System.Web.UI.Page {
protected void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value ;
}
protected void Page_Load(object src, EventArgs args) {
if (Application["ClientCounter"] == null) {
Application["ClientCounter"] = 0 ;
}
HttpCookie cookie = Request.Cookies["ClientCounterCookie"] ;
int clientNumber = -1 ;
if (cookie == null) {
Application.Lock() ;
clientNumber = (int) Application["ClientCounter"] + 1 ;
Application["ClientCounter"] = clientNumber ;
Application.UnLock() ;
cookie = new HttpCookie( "ClientCounterCookie" ) ;
cookie.Value = clientNumber.ToString() ;
Response.Cookies.Add(cookie) ;
}
else {
clientNumber = Int32.Parse( cookie.Value ) ;
}
Response.Write("Client Number : " + clientNumber ) ;
}
}
En fait, on pourrait sinspirer de ce dernier exemple pour dvelopper notre scnario de panier
pour stocker les achats de chaque client. Les informations sur les achats courant seraient stockes du ct serveur et indexes par lidentifiant client. Cependant cette faon de faire a plusieurs points faibles :

Si lapplication redmarre, plusieurs clients peuvent avoir le mme identifiant.


Un client dont le navigateur ne supporte pas les cookies aura un identifiant dirent
chaque requte.
Il faudrait crire pas mal de code pour pouvoir retrouver et stocker les informations relatives chaque client hors du processus client.

Gestion des sessions et des tats

899

Il faudrait aussi maintenir une logique permettant dinvalider un identifiant dun client
aprs une certaine dure sans requte de sa part.

Pour toutes ces raisons, on prfre utiliser en gnral la notion de session fournie par ASP.NET.

Gestion dune session


Nous allons exposer comment utiliser une session pour sauvegarder du ct serveur lhistorique
des slections des couleurs. Notre page ressemblera alors ceci :

Figure 23 -9 : Sauvegarde des slections dans une session


Tout dabord, nous prvoyons une classe Item dont chaque instance reprsente une slection de
couleur. Il est important de faire en sorte que tout objet stock dans une session soit srialisable.
En eet, nous verrons que les sessions sont susceptibles dtre srialises selon leur mode de stockage. Notre classe Item est srialisable puisquelle nest constitue que de champs srialisables :
Exemple 23-20 :
public class Item {
public Item( string couleur , System.DateTime time ) {
m_Couleur = couleur ; m_Time = time ;
}
private string m_Couleur ;
private System.DateTime m_Time ;
public override string ToString() {
return m_Couleur + " selectionn
e `
a " + m_Time.ToLongTimeString() ;
}
}
chaque rception dune nouvelle slection, nous crons une nouvelle instance de Item que
nous stockons dans la session courante. Puis, nous construisons lhistorique des slections qui
sera insr dans la page HTML retourne :
Exemple 23-21 :
...
void Btn_Click(Object sender, EventArgs e) {
// Ajout dune nouvelle selection dans la session :
System.Collections.Generic.List<Item> listItems =

900

Chapitre 23 : ASP.NET 2.0


Session["ItemsSelected"] as System.Collections.Generic.List<Item> ;
listItems.Add(
new Item( Couleur.SelectedItem.Value , DateTime.Now ) ) ;
Msg.Text = string.Empty ;
// Fabrication de lhistorique des sessions :
foreach( Item item in listItems )
Msg.Text += item.ToString() + "<br/>" ;
}
...

Pour que vous puissiez y sauver vos donnes, chaque session maintient en interne un dictionnaire. Pour que notre exemple fonctionne, il faut qu un moment donn nous initialisons lentre "ItemsSelected" de ce dictionnaire avec une nouvelle liste dItem. Pour cela nous pouvons profiter de lvnement Session_Start auquel nous nous abonnons au sein du fichier
Global.asax :
Exemple 23-22 :

Global.asax

<%@ Application Language="C#" %>


<script runat="server">
protected void Session_Start(object src, EventArgs args) {
Session["ItemsSelected"] = new
System.Collections.Generic.List<Item>() ;
}
</script>
Enfin, pour forcer ASP.NET grer les sessions, il sut dajouter une balise <sessionState>
la configuration avec un attribut mode positionn une valeur autre que "Off" (nous allons
dtailler les direntes valeurs que peut prendre lattribut mode) :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<sessionState mode="InProc"/>
...
Note que vous pouvez spcifier au moyen de lattribut timeout la dure en minutes durant
laquelle une session peut rester inactive avant quASP.NET la dtruise (la valeur par dfaut est
de 20 minutes) :
<sessionState mode="InProc" timeout="10"/>

Gestion de lidentifiant dune session


En interne, ASP.NET gre les sessions grce un mcanisme didentifiants. Un nouvel identifiant de session est gnr chaque cration dune nouvelle session. Cet identifiant est renvoy
au client qui devra le communiquer lors de chaque requte dans le cadre de cette session. Par
dfaut, cet identifiant est stock dans un cookie du ct client. Il est alors insr par le navigateur dans chaque requte POST. Puisque tous les navigateurs ne supportent pas les cookies, vous

Gestion des sessions et des tats

901

pouvez dcider de configurer ASP.NET pour que les identifiants de sessions soient stocks dans
lURI. Pour cela il faut aecter la valeur "UseUri" lattribut cookieless :
<sessionState mode="InProc" cookieless="UseUri"/>
Une URI contenant un identifiant de session ressemble alors ceci :
http://localhost/WebSite1/(S(e53uti455tgobvi2czsh4q45))/default.aspx
En plus de la valeur par dfaut "UseCookies" et de la valeur "UseUri", lattribut cookieless
peut prendre la valeur "AutoDetect". Ainsi ASP.NET utilise un cookie pour passer lidentifiant
de la session si le navigateur du client le permet. Sinon il se met en mode URI. Avec la valeur
"UseDeviceProfile" vous pouvez spcifier ASP.NET de choisir ou non le mode cookie selon
les paramtres de Device Profile dans le fichier Machine.Config.

Les modes de stockage des sessions


ASP.NET prsente par dfaut trois modes de stockage des sessions ct serveur. Vous communiquez ASP.NET le mode utiliser avec une des trois valeurs "InProc", "StateServer" et
"SQLServer" pour lattribut mode dans la configuration.
Le mode "InProc" stocke les sessions dans le domaine dapplication contenant lapplication
web. Ce mode est la fois le plus facile exploiter et souvent le plus performant puisquil nimplique aucun accs hors du processus courant. Cependant il est viter en production pour de
multiples raisons. Tous dabord, si vous hbergez votre application dans une ferme web (i.e votre
application tourne sur plusieurs machine) il faut sassurer que les requtes dune mme session
soient toujours traites sur le mme serveur. Ensuite, les sessions sont volatiles et sont dtruites
chaque dchargement du domaine dapplication. Or, un domaine dapplication dune application web est susceptible dtre dcharg rgulirement pour direntes raisons : recompilation
dune page, crash, redmarrage rgulier du processus etc. Ainsi, en production il vaut mieux
prfrer un mode de stockage des sessions hors du processus aspnet_wp.exe tel que "StateServer" ou "SQLServer".
Le mode "StateServer" stocke les sessions dans un processus ddi qui peut se trouver sur la
mme machine qui hberge lapplication web ou sur une autre machine. Vous prcisez cette
machine ainsi quun port au moyen de lattribut stateConnectionString :
<sessionState mode="StateServer"
stateConnectionString="tcpip:127.0.0.1 :42424"/>
Le processus contenant les sessions se prsente sous la forme du service Windows nomm
ASP.NET State Service. Ce service est install ds lors que vous installez ASP.NET sur une
machine. Par dfaut ce service est dmarrage manuel. Le port choisi par ce service pour
couter les requtes de stockage de session est stock dans la base des registres lentre :
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\aspnet_state\Parameters.
Bien que le mode "StateServer" amliore grandement la fiabilit de la gestion de vos sessions
par rapport au mode "InProc", ces dernires sont toujours volatiles et un crash du service entrane leur perte. Ainsi, vous pouvez opter pour le mode "SQLServer" qui stocke les sessions
dans une base de donne type SQL Server. Ce mode est le plus fiable mais aussi le plus coteux en terme de performances. Il prsente notamment lavantage de pouvoir exploiter les donnes concernant les sessions expires afin de faire des statistiques par exemple. Pour exploiter
ce mode il faut fournir la chane de connexion la base de donnes au moyen de lattribut
sqlConnectionString comme ceci :

902

Chapitre 23 : ASP.NET 2.0


<sessionState mode="StateServer"
sqlConnectionString="data source=127.0.0.1 ; user id=sa ;password="/>

Bien videmment, il faut avoir install au pralable les bases de donnes adquates sur le serveur SQL Server concern. Pour cela, il faut vous servir des scripts SQL InstallSqlState.sql ou
InstallPersistSqlState.sql selon que vous souhaitez garder ou non vos sessions aprs leurs
expirations. Ces scripts se trouvent dans le rpertoire dinstallation de .NET.

Fournir son propre mcanisme de gestion de sessions


ASP.NET 2.0 vous permet de fournir votre propre mcanisme de gestion de sessions. Vous pouvez avoir besoin dun tel mcanisme par exemple pour stocker vos sessions dans un autre SGBD
que SQL Server. Une autre raison possible pourrait tre le besoin de stocker les sessions dans des
tables personnalises, direntes de celles proposes par dfaut.
En interne, le mcanisme de sessions est gr par un module HTTP nomm "Session" qui
utilise un objet pour la gestion des IDs de session (dont la classe implmente linterface System.
Web.SessionState.ISessionIDManager) et un objet pour la gestion du stockage (dont la classe
implmente linterface System.Web.SessionState.IHttpSessionState). Vous pouvez communiquer ASP.NET votre propre module ou bien votre propre classe pour la gestion des IDs de
sessions ou bien votre propre classe pour la gestion du stockage des sessions au moyen du fichier
Web.Config comme ceci :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<httpModules>
<remove name="Session" />
<add
name="Session"
type="MyNamespace.MyStateModuleType,MyAsm"/>
</httpModules>
<sessionState mode="Custom"
sessionIDManagerType="MyNamespace.MySessionIDType,MyAsm"
customProvider="MySessionStateProvider" >
<providers>
<add name="MySessionStateProvider"
type="MyNamespace.MySessionStateProviderType,MyAsm" />
</providers>
</sessionState>
...
La description plus avant de limplmentation de votre propre mcanisme de gestion de session
dpasse le cadre de cet ouvrage. Nous vous invitons consulter les articles Implementing a
Session-State Store Provider et Sample Session State Store Provider des MSDN.

Le design pattern provider


Le mcanisme que nous venons de prsenter pour permettre de fournir son propre mcanisme
de gestion de sessions est standard en ASP.NET. Il porte le nom de design pattern provider. Nous

Gestion des erreurs

903

aurons loccasion de le retrouver dans dautres domaines de fonctionnalits dASP.NET tels que
la possibilit de fournir son propre mcanisme de gestion dutilisateurs ou de rles. Les caractristiques du design pattern provider sont :

Possibilit de fournir plusieurs fournisseurs avec des lments <add> dans un lment <providers> qui est lui-mme contenu dans llment reprsentant le domaine de fonctionnalit (en loccurrence <sessionState>).

Chaque fournisseur est nomm au moyen dun attribut name.

Chaque fournisseur est implment au moyen dune classe qui drive dune certaine classe
de base ou qui implmente une certaine interface.

Cette classe ainsi que lassemblage qui la contient son prcis au moyen dun attribut type.

Limplmentation dun fournisseur est totalement libre de choisir le mode de persistance


quelle gre pour stocker les donnes. Un tel mode de persistance peut tre une base de
donnes relationnelle, un fichier XML, un fichier texte, un service web etc.

Le domaine de fonctionnalit prsente un attribut qui prend le nom du fournisseur exploiter lexcution.

Signalons que linterface graphique web dASP.NET 2.0 prsente un onglet provider qui vous
permet dadministrer les fournisseurs par dfauts pour chaque domaine de fonctionnalits.

Gestion des erreurs


Il y a plusieurs types derreurs qui peuvent survenir dans une application ASP.NET en production :

Le rseau peut tre indisponible.

Le serveur peut avoir crash.

Le serveur peut tre surcharg.

Un client demande une page qui nexiste pas ou quil nest pas autoris voir.

Un traitement applicatif du ct serveur lance une exception non rattrape cause dun
bug ou cause de donnes corrompues.

Dans tous les cas, les consquences de lerreur seront une indisponibilit du service pour le
client. En tant que dveloppeur, seul les deux derniers types derreurs vous incombent. Dans
le cas dune demande dune page qui ne peut tre satisfaite, vous avez la possibilit de rediriger
lutilisateur vers une page derreur spciale. Dans le cas dune exception, ASP.NET produit par
dfaut une page HTML contenant le fragment de code responsable de lexception ainsi que ltat
de la pile ce moment. Bien que ce comportement soit utile pour aider les dveloppeurs
rsoudre le problme, il est plutt gnant quun utilisateur visualise une telle page. Cela peut
aussi induire des problmes de scurit. Aussi, ASP.NET prsente plusieurs techniques pour
personnaliser la gestion des erreurs.

904

Chapitre 23 : ASP.NET 2.0

La balise <system.web>/<customErrors>
Vous pouvez indiquer ASP.NET la page qui doit tre retourne lutilisateur en cas dexception non rattrape. La valeur de lattribut defaultRedirect de la balise <customError> du
fichier de configuration doit tre le nom de cette page. En positionnant lattribut mode "RemoteOnly" vous pouvez faire en sorte que la page indique par defaultRedirect ne soit retourne
quaux clients distants. Ainsi, lors de vos tests en local vous ne vous priverez pas des informations
prcieuses de la page derreur retourne par dfaut par ASP.NET. Lattribut mode peut aussi
prendre la valeur "On" pour toujours rediriger le client vers la page prcise mme si ce dernier
est en local, ou "Off" pour dsactiver ce service. Enfin, vous pouvez aussi indiquer une page
retourner pour chaque erreur HTTP au moyen de sous balises <error> :
Web.Config

Exemple :
<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<customErrors mode="On" defaultRedirect="WebFormError.aspx">
<error statusCode="403" redirect="MyError403.htm"/>
<error statusCode="404" redirect="MyError404.aspx"/>
</customErrors>
...

Lvnement Application_Error
Lvnement Error de la classe HttpApplication est dclench par ASP.NET lorsquune exception est non rattrape. Vous pouvez vous abonner cette vnement en fournissant une mthode Application_Error() dans le fichier Global.asax. Vous pouvez alors rcuprer lexception et construire une page rponse comme ceci :
Exemple 23-23 :

Global.asax

<%@ Application Language="C#" %>


<script runat="server">
protected void Application_Error(object src, EventArgs args) {
HttpUnhandledException eHttp =
this.Server.GetLastError() as HttpUnhandledException ;
// eApp est lexception lanc
ee non rattrap
ee.
Exception eApp = eHttp.InnerException ;
Response.Write("Erreur : " + eApp.Message) ;
Response.End() ; // <- ne pas oublier !!!
}
</script>
Pour quASP.NET renvoie eectivement la page cre, il est obligatoire dappeler la mthode
End() sur lobjet HttpResponse courant durant lexcution de la mthode Application_Error().
Autrement, labonnement cet vnement empche ASP.NET dappliquer la stratgie de redirection de llment <customErrors>.

Traces, diagnostics et gestion des vnements

905

La proprit ErrorPage
Durant le traitement dune page vous pouvez tout moment positionner la proprit string
Page.ErrorPage{get;set;}. Vous prcisez ainsi programmatiquement la page vers laquelle
ASP.NET redirigera le client dans le cas dune exception non rattrape.
<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<script language=C# runat="server">
void Btn_Click(Object sender, EventArgs e) {
this.ErrorPage = "WebFormError.aspx";
Msg.InnerText = "Vous avez s
electionn
e : " + Couleur.Value ;
throw new ApplicationException("Une erreur survient !") ;
}
</script>
...
Pour quASP.NET applique eectivement la redirection, il faut que lattribut mode soit positionn "On" dans la balise <customErrors> du fichier de configuration.
Cette technique est trs pratique puisquelle permet de prciser la page derreur en fonction du
contexte courant dexcution. Ainsi, si une page a plusieurs boutons, vous pouvez prciser une
page derreur pour chaque traitement de chaque bouton. En outre, lors dune exception non
rattrape lors du traitement dune page, ASP.NET ne dclenche lvnement HttpApplication.
Error que si la valeur de cette proprit est nulle.

Traces, diagnostics et gestion des vnements


Tracer le fonctionnement dune application ASP.NET
Vous pouvez tracer le fonctionnement dune application grce au handler HTTP trace.axd.
Pour avoir accs cette possibilit il faut activer les traces dans le fichier Web.Config comme
ceci :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<trace enabled="true" localOnly="true" />
...
Vous avez alors accs aux traces en tapant une URL du type http://[machine]/[Racine]
/Trace.axd dans un navigateur. La page retourne contient un tableau de traces, une par
requte HTTP traite par le serveur. Ce tableau nest pas mis jour automatiquement et
vous devez recharger la page (par exemple en cliquant F5) pour avoir accs la liste des
dernires requtes. Vous avez la possibilit dacher une page dinformation pour chaque
trace en cliquant lURL View Details dune trace. Cette page est trs complte. Elle contient
des informations telles que larborescence des contrles serveurs impliqus lors du traitement,
ltat de la session et de lapplication, les informations contenues dans la requte POST (si lon

906

Chapitre 23 : ASP.NET 2.0

a aaire une requte POST) ltat des variables internes au serveur etc. Naturellement, il
nest pas souhaitable que de telles informations soient visibles par les clients. Aussi en gnral
vous positionnerez lattribut localOnly "true" pour empcher que le handler Trace.axd soit
exploitable distance. Nous vous invitons consulter les MSDN pour en savoir plus sur les
attributs de la balise <trace>.
Vous pouvez vous servir de ce systme pour inclure vos propres traces. Pour cela, il suffit de vous servir de lobjet de type System.Web.TraceContext accessible par la proprit
Page.Trace{get;} comme ceci :
<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<script language=C# runat="server">
void Btn_Click(Object sender, EventArgs e) {
Msg.InnerText = "Vous avez s
electionn
e : " + Couleur.Value ;
Trace.Write("Ma trace write.");
Trace.Warn("Ma trace warn.");
}
</script>
...
Dans une page de trace spcifique une requte, les traces provoques par la mthode Warn()
sont aches en rouge, alors que les traces provoques par la mthode Write() sont aches
en noir.

Les compteurs de performance dASP.NET


Lors de linstallation dASP.NET, une cinquantaine de compteurs de performances Windows
sont aussi installs. On peut citer le nombre dapplications web en cours dexcution, le nombre
dentres dans le cache ou le nombre de requtes traites par seconde. Vous trouverez ces compteurs dans les deux catgories ASP.NET Apps v 2.0.XXXXX et ASP.NET v 2.0.XXXXX. Prcisons que
les compteurs de performances sont visualisables avec loutil perfmon.exe (accessible avec Menu
dmarrer  Excuter...  perfmon.exe). En outre, en page 115, nous expliquons comment accder
programmatiquement la valeur dun compteur de performance.

Gestion standard des vnements ASP.NET


ASP.NET 2.0 prsente un framework permettant de grer dune manire standard les vnements qui surviennent durant la vie dune application web. Les types de ce framework sont dans
le nouvel espace de noms System.Web.Management.
Un type dvnement est reprsent par une classe qui drive directement ou indirectement de
la classe WebManagementEvent. Le framework prsente par dfaut plusieurs classes dvnements.
Par exemple un vnement de type WebErrorEvent survient lorsquune exception nest pas rattrape lors du traitement dune requte tandis quun vnement de type WebRequestEvent survient au dbut du traitement de chaque requte. Vous pouvez crer vos propres classes drives
de WebManagementEvent pour reprsenter vos propres types dvnements.
Ici aussi, larchitecture ASP.NET 2.0 utilise le design pattern provider pour la notion de fournisseur de traitement dvnements. Un tel fournisseur est une classe qui drive de la classe
WebEventProvider. Trois fournisseurs sont exploitables par dfaut :

Traces, diagnostics et gestion des vnements

907

Le fournisseur EventLogWebEventProvider permet de loguer les vnements dans le log de


Windows.

Le fournisseur TraceWebEventProvider permet de loguer les vnements dans les traces


ASP.NET.

Le fournisseur WmiLogWebEventProvider permet de loguer les vnements en exploitant le


framework WMI (Windows Management Instrumentation) de Windows.

En outre, la classe abstraite BufferedWebEventProvider reprsente une classe de base permettant de dvelopper des fournisseurs qui stockent en mmoire des vnements. Vous pouvez crer vos propres fournisseurs de traitement dvnements avec des classes drives de
WebEventProvider.
Vous pouvez vous servir de la sous section <eventMappings> de la section <healthMonitoring>
du fichier Web.Config pour prciser quels types dvnements ASP.NET doit grer. La sous section <providers> permet de dfinir les fournisseurs de traitement dvnements exploitables.
Enfin, la sous section <rules> contient les associations entre vnements et fournisseurs. Par
exemple, le fichier Web.Config suivant contraint linfrastructure ASP.NET loguer toutes les
exceptions non rattrapes dans les traces :
Exemple :
<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<trace enabled="true"/>
<healthMonitoring enabled="true">
<providers>
<add name="TraceLogProvider"
type="System.Web.Management.TraceWebEventProvider,
System.Web,Version=2.0.3600.0,Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"/>
</providers>
<eventMappings>
<add name="Erreurs"
type="System.Web.Management.WebErrorEvent,System.Web,
Version=2.0.3600.0,Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</eventMappings>
<rules>
<add name="Tracage des erreurs."
eventName="Erreurs"
provider="TraceLogProvider"/>
</rules>
</healthMonitoring>
...

Web.Config

908

Chapitre 23 : ASP.NET 2.0

Validation des donnes saisies


Contrle de validation
ASP.NET prsente des contrles spcialiss dans la validation des donnes saisies dans des
contrles de type HTMLInputText, HTMLTextArea, HTMLSelect , HTMLInputFile, TextBox, DropDownList, ListBox et RadioButtonList. Lexemple suivant illustre cette possibilit en utilisation
deux contrles de validation qui agissent sur une TextBox. Le contrle de type RequiredFieldValidator assure que la valeur saisie dans la TextBox nest pas vide. Le contrle de type
CompareValidator assure que la valeur saisie dans la TextBox est un entier suprieur ou gal
dix. Remarquez lutilisation de la proprit ControlToValidate pour prciser sur quel contrle
agit chaque contrle de validation :
Exemple 23-24 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<asp:Button ID="MyButton" runat="server" Text="Soumettre" />
<asp:TextBox ID="MyTextBox" runat="server" />
<asp:RequiredFieldValidator
ID="MyRequiredFieldValidator"
runat="server"
ControlToValidate="MyTextBox"
SetFocusOnError="true"
ErrorMessage="Ne peut
etre vide !" Display="Dynamic" />
<asp:CompareValidator
ID="MyCompareValidator"
runat="server"
ControlToValidate="MyTextBox"
SetFocusOnError=" true "
ErrorMessage="CompareValidator"
Operator="GreaterThanEqual"
Type="Integer"
ValueToCompare="10">
Doit etre superieur ou
egal `
a10 !</asp:CompareValidator>
</form>
</body>
</html>
Si vous excutez cet exemple, vous vous apercevrez que la validation se fait ct client. En eet,
lorsque vous utilisez des contrles de validation, ASP.NET gnre automatiquement du code
javascript pour la validation. Ce code est insr dans la page HTML envoye au client. Si le
navigateur du client supporte javascript, ce code est excut du ct client chaque click dun
bouton. Si la validation choue, le code javascript de chacun des contrles de validation qui a
chou gnre une balise <span>. Une telle balise ache le message derreur spcifi par la proprit ErrorMessage sans dclencher dvnement postback. Vous pouvez dcider dempcher la
validation ct client en positionnant la proprit EnableClientScript false.
Lors du traitement dune requte postback dun client, chaque contrle de validation eectue sa
vrification du ct serveur. Ces validations se font indpendamment du fait que la validation
avec du code javascript sest eectue ou non ct client. Si la validation choue, la page est
renvoye au client. Sur une telle page, les contrles de validation qui ont dtect un problme
ont insrs leurs textes derreur dans des balises <span>. Malgr cette garantie de validation ct
serveur, nous vous conseillons de toujours activer la validation ct client pour viter des vnements postback superflus.

Validation des donnes saisies

909

Chaque type de contrle de validation implmente linterface IValidator qui expose notamment la proprit bool IsValid{get;set;}. La classe Page prsente aussi une proprit
bool IsValid{get;}. La validation ct serveur se fait lorsquASP.NET appelle la mthode
Page.Validate(). Cette tape positionne les proprits IsValid de la page et des contrles
de validation. Un peu plus loin dans ce chapitre, nous expliquerons que cet appel se fait
notamment aprs lappel Page_Load(). En consquence, soit vous appelez vous-mme la
mthode Page.Validate() durant lexcution de Page_Load(), soit vous ne testez aucune des
proprits IsValid durant cette excution.
Dans lexemple prcdent nous avons positionn la proprit Display du contrle de validation MyRequiredFieldValidator la valeur Dynamic. Cela signifie que lorsque ce contrle
a eectu sa validation avec succs, il noccupe pas despace sur la page. Cela permet de
traiter proprement le cas o un autre contrle de validation plac physiquement juste aprs
MyRequiredFieldValidator et se rapportant aussi MyTextBox choue dans sa validation. En
eet, le texte de ce deuxime contrle de validation sera alors physiquement plac directement
cot de MyTextBox.
En plus des contrles RequiredFieldValidator et CompareValidator que nous avons illustr, ASP.NET prsente les contrles de validation RangeValidator et RegularExpressionValidator. Ces quatre classes de contrles drivent de la classe BaseValidator.

Grce aux proprits ValidationDataType Type{get;set;}, string MaximumValue{get ;


set;} et string MinimumValue{get;set;} de la classe RangeValidator vous pouvez vous
assurer que la valeur dans un contrle est bien dans un intervalle donn. Les types possibles
prsents par lnumration ValidationDataType sont String, Integer, Double, Date et
Currency.

Grce la proprit string ValidationExpression{get;set;} de la classe RegularExpressionValidator vous pouvez fournir une expression rgulire pour la validation dune
donne saisie. Les expressions rgulires sont dcrites en page 625.

En ce qui concerne la classe CompareValidator, nous attirons votre attention sur le fait que
les types disponibles sont aussi dans lnumration ValidationDataType. En outre, plutt
que de comparer la donne saisie une valeur fixe comme dans notre exemple, vous pouvez
la comparer celle dun autre contrle en prcisant ce dernier dans la proprit string
ControlToCompare{get;set;}.

Il est courant dutiliser un contrle de type RequiredFieldValidator en plus dun de ces trois
contrles de validation puisque aucun deux ne dtecte un problme si la donne saisie est vide.
ASP.NET 2.0 introduit la proprit bool SetFocusOnError{get;set;} prsente par chacun
des contrles de validation. Elle permet de prciser sil faut mettre le focus sur le contrle
valider lorsque sa donne est invalide. Laction de cette proprit seectue indpendamment
du fait que la vrification choue du ct client ou serveur.

Logique personnalise de validation


Si les validations proposes par les quatre classes de contrles prsentes ne vous conviennent
pas, vous avez la possibilit de fournir votre propre logique de validation en ayant recours un
contrle de validation de type CustomValidator. Lexemple suivant illustre cette possibilit en
fournissant deux fonctions qui testent si un entier saisi est bien multiple de cinq. La fonction
MultipleDeCinq_Clnt() est rdige en javascript et est destine tre excute du ct client.

910

Chapitre 23 : ASP.NET 2.0

La fonction MultipleDeCinq_Svr() est rdige en C  et est destine tre excute du ct


serveur :
Exemple 23-25 :
<%@ Page Language=C# %>
<script language="JavaScript">
function MultipleDeCinq_Clnt( source, args) {
if( args.Value % 5 == 0 )
args.IsValid = true;
else
args.IsValid = false;
}
</script>
<script language="C#" runat="server">
void MultipleDeCinq_Svr(object source, ServerValidateEventArgs e) {
e.IsValid = false;
int num;
if ( Int32.TryParse(e.Value, out num) )
if ( num % 5 == 0 )
e.IsValid = true;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<asp:Button ID="MyButton" runat="server" Text="Soumettre" />
<asp:TextBox ID="MyTextBox" runat="server" />
<asp:CustomValidator
ID="MyCustomValidator" runat="server"
ControlToValidate="MyTextBox"
ClientValidationFunction="MultipleDeCinq_Clnt"
OnServerValidate="MultipleDeCinq_Svr"
ErrorMessage="Doit etre multiple de 5 !"
ValidateEmptyText="False" />
</form>
</body>
</html>
ASP.NET 2.0 introduit la proprit bool CustomValidator.ValidateEmpty{get;set;} qui permet de prciser si une valeur vide doit tre considre comme valide ou non.

Groupe de validation
ASP.NET 2.0 introduit la notion de groupe de validation. Cette possibilit rsout un problme
courant rencontr en ASP.NET 1.x. Par dfaut, chaque vnement tous les contrles de validation sont excuts. Or, il est courant que sur une mme page on puisse dclencher un vnement sans ncessairement devoir valider toutes les donnes saisies. Par exemple, une page de
formulaire peut contenir un bouton Chercher sur le web dont laction na rien voir avec les
donnes saisies du formulaire.

Validation des donnes saisies

911

Grce la nouvelle proprit string ValidationGroup{get;set;} prsente par les contrles


de validation, la classe Button et les classes dont les donnes peuvent tre valides, vous pouvez
partitionner lensemble des contrles dune page dans des groupes. Un click sur un bouton
dclenchera seulement la vrification des contrles de son groupe. Cette possibilit est illustre
par lexemple suivant qui expose une page avec deux groupes de validation, chacun contenant
un bouton, une TextBox et un RequiredFieldValidator :
Exemple 23-26 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<asp:Button ID="MyButton1"
runat="server"
Text="Soumettre 1" ValidationGroup="Groupe1"/>
<asp:TextBox ID="MyTextBox1"
runat="server"
ValidationGroup="Groupe1"/>
<asp:RequiredFieldValidator ID="MyRequiredFieldValidator1"
ControlToValidate="MyTextBox1" runat="server"
ValidationGroup="Groupe1"
ErrorMessage="Champ 1 vide !" />
<br/>
<asp:Button ID="MyButton2"
runat="server"
Text="Soumettre 2" ValidationGroup="Groupe2"/>
<asp:TextBox ID="MyTextBox2"
runat="server"
ValidationGroup="Groupe2"/>
<asp:RequiredFieldValidator ID="MyRequiredFieldValidator2"
ControlToValidate="MyTextBox2" runat="server"
ValidationGroup="Groupe2"
ErrorMessage="Champ 2 vide !" />
</form>
</body>
</html>
Vous pouvez dclencher la validation dun groupe de validation du ct serveur grce la nouvelle mthode void Validate(string validationGroup) de la classe Page.
Les contrles qui nont pas de groupe sont placs implicitement par ASP.NET dans un groupe
global et anonyme. Ainsi la compatibilit ascendante avec ASP.NET 1.x est assure.

La classe ValidationSummary
Dans un formulaire avec plusieurs donnes saisir vous pouvez souhaiter runir tous les
messages derreurs des contrles de validation un seul endroit. Ceci est possible grce
la classe de contrle de validation ValidationSummary. Un tel contrle ache la liste des
messages derreur des contrles de validation de son groupe. Vous pouvez choisir la faon
dont les messages derreur sont lists grce la proprit ValidationSummaryDisplayMode
DisplayMode{get;set;}. En outre, plutt que dacher cette liste dans un lment <span>
de la page vous pouvez choisir de lacher dans un message box en positionnant les proprits
bool ShowMessageBox{get;set;} et bool ShowSummary{get;set;}.

912

Chapitre 23 : ASP.NET 2.0

Contrles utilisateurs
ASP.NET vous ore la possibilit de dfinir vos propres contrles serveur. Un tel contrle
est nomm contrle utilisateur. Un contrle utilisateur est en gnral dfini au moyen dun
fichier dextension .ascx dbutant par une directive <%@ Control>. Ce fichier sera compil par
ASP.NET en une classe drive de la classe Control. Le nom de cette classe est prcis par la
sous directive ClassName. Voici un exemple de contrle utilisateur trs simple :
MyUserCtrl.ascx

Exemple 23-27 :
<%@ Control Language=C# ClassName="MyUserCtrl" %>
<% Response.Write("HTML du contr
ole.") ; %>

Voici un exemple dune page cliente de ce contrle. On voit quune directive <%@ Register%>
est ncessaire pour importer le nom de la classe, le nom du fichier .ascx qui la dfinit ainsi
quun prfixe qui joue le rle dun espace de noms :
Exemple 23-28 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" Src="~/MyUserCtrl.ascx"
TagName=UserCtrl %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<% Response.Write("HTML de la page.") ; %>
<PRATIQUE:UserCtrl runat="server" />
</body>
</html>
On souhaite en gnral avoir des contrles utilisateurs paramtrables. Pour cela, on se sert de
proprits prsentes par la classe qui reprsente notre contrle :
Exemple 23-29 :

MyUserCtrl.ascx

<%@ Control Language=C# ClassName="MyUserCtrl" %>


<script language=C# runat="server">
private string m_Color ;
public string Color{ get{ return m_Color;} set{ m_Color = value ; } }
private string m_Text ;
public string Text { get{ return m_Text ; } set{ m_Text = value ; } }
</script>
<p><font color="<%= Color %>"><%= Text %></font></p>
Exemple 23-30 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" Src="~/MyUserCtrl.ascx"
TagName=UserCtrl %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<PRATIQUE:UserCtrl runat="server" Color="red" Text="hello red"/>
<PRATIQUE:UserCtrl runat="server" Color="green" Text="hello green"/>
</body>
</html>

Contrles utilisateurs

913

Vous pouvez dfinir vous-mme la classe reprsentant un contrle utilisateur sans vous servir
dun fichier .ascx. Dans ce cas, chaque page cliente dun tel contrle doit inclure une sous directive namespace de la directive <@ Register> pour prciser lespace de noms contenant la classe :
Exemple 23-31 :

MyUserCtrl.cs

using System.Web.UI ;
namespace MyUserCtrls {
public class MyUserCtrl : Control {
private string m_Color ;
public string Color{ get{return m_Color;} set{m_Color = value;}}
private string m_Text ;
public string Text{ get{return m_Text ; } set{m_Text = value;}}
protected override void Render(HtmlTextWriter writer) {
writer.Write("<p><font color=\"" + m_Color +
"\">" + m_Text + "</font></p>") ;
}
}
}
Exemple 23-32 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" namespace="MyUserCtrls" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<PRATIQUE:MyUserCtrl runat="server" Color="red" Text="hello red"/>
<PRATIQUE:MyUserCtrl runat="server" Color="green"
Text="hello green"/>
</body>
</html>

Contrles utilisateurs composites


On se sert souvent de contrles utilisateurs pour exploiter un mme formulaire sur plusieurs
pages. Aussi, linstar de tous contrles, un contrle utilisateur peut avoir des contrles serveurs
enfants. Notez que vous pouvez vous aider du mode design de Visual Studio pour diter un tel
contrle utilisateur :
Exemple 23-33 :

MyUserCtrl.ascx

<%@ Control Language=C# ClassName="MyUserCtrl" %>


<script language=C# runat="server">
void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value ;
}
</script>
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>

914

Chapitre 23 : ASP.NET 2.0


<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click"
runat="server"/>
<asp:label id="Msg" runat="server"/>

Exemple 23-34 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" Src="~/MyUserCtrl.ascx"
TagName=UserCtrl %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<PRATIQUE:UserCtrl ID="UserCtrl1" runat="server" /><br/>
<PRATIQUE:UserCtrl ID="UserCtrl2" runat="server" />
</form>
</body>
</html>
En outr ASP.NET renomme automatiquement lexcution les noms des contrles enfants
en les prfixant par le nom du contrle utilisateur parent suivi dun _ (i.e un underscore, un
soulign). Cela vite que plusieurs contrles dune mme page aient des noms identiques.

Evnements sur un contrle utilisateur


Vous avez la possibilit de crer vos vnements dans un contrle utilisateur et de vous y
abonner partir dune page cliente avec la syntaxe On[NomDeL
evn`
enement]. Ceci est illustr
par lexemple ci-dessous :
MyUserCtrl.ascx

Exemple 23-35 :

<%@ Control Language=C# ClassName="MyUserCtrl" %>


<script language=C# runat="server">
public event EventHandler BlancSelected;
void Btn_Click(Object sender, EventArgs e) {
if (Couleur.SelectedItem.Value == "blanc")
BlancSelected(this, EventArgs.Empty);
}
</script>
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click" runat="server"/>
Exemple 23-36 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" Src="~/MyUserCtrl.ascx"
TagName=UserCtrl %>
<script language=C# runat="server">
void BlancSelectedHandler(Object sender, EventArgs e) {

Contrles utilisateurs

915

Msg.Text = "Blancs selectionn


es !" ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<PRATIQUE:UserCtrl ID="UserCtrl1" runat="server"
OnBlancSelected="BlancSelectedHandler"/>
<asp:label id="Msg" runat="server" EnableViewState="false"/>
</form>
</body>
</html>

Maintenir ltat dun contrle utilisateur entre les requtes


Un contrle utilisateur peut se servir du viewstate pour maintenir son tat entre les requtes
dun mme utilisateur. Lexemple suivant illustre cette possibilit en rcrivant notre contrle
utilisateur MyUserCtrl avec une classe sans tat (i.e sans champs). Les tats Color et Text sont
maintenus entre les requtes grce au viewstate. Ils ne sont initialiss par le client que lors de la
premire requte :
MyUserCtrl.cs

Exemple 23-37 :

using System.Web.UI ;
namespace MyUserCtrls {
public class MyUserCtrl : Control {
public MyUserCtrl() {
ViewState["Color"] = string.Empty;
ViewState["Text"] = string.Empty;
}
public string Color {
get { return ViewState["Color"] as string ; }
set { ViewState["Color"] = value ; }
}
public string Text {
get { return ViewState["Text"] as string ; }
set { ViewState["Text"] = value ; }
}
protected override void Render(HtmlTextWriter writer) {
writer.Write("<p><font color=\"" + ViewState["Color"] +
"\">" + ViewState["Text"] + "</font></p>") ;
}
}
}
Exemple 23-38 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" namespace="MyUserCtrls" %>
<script language=C# runat="server">

916

Chapitre 23 : ASP.NET 2.0


void Page_Load(Object sender, EventArgs e) {
if ( !IsPostBack ) {
MyUserCtrl1.Color = "red";
MyUserCtrl1.Text = "hello red";
} else { /* Pas la peine dinitialiser MyUserCtrl1 */ }
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" action="Default.aspx" method="post" runat="server">
<PRATIQUE:MyUserCtrl ID="MyUserCtrl1" runat="server" />
<asp:button ID="Button1" runat="server" text="Soumettre" />
</form>
</body>
</html>

Notez la ncessit dinitialiser les entres du viewstate requises dans le constructeur de notre
contrle utilisateur. Nous pourrions rcrire nos accesseurs get comme ceci pour nous aranchir de cette contrainte :
Exemple 23-39 :

MyUserCtrl.cs

...
public string Color {
get {
string s = ViewState["Color"] as string;
return (s == null) ? string.Empty : s;
}
set { ViewState["Color"] = value ; }
}
public string Text {
get {
string s = ViewState["Text"] as string;
return (s == null) ? string.Empty : s;
}
set { ViewState["Text"] = value ; }
}
...
On peut aussi surcharger les mthodes LoadViewState() et SaveViewState() de la classe
Control pour maintenir ltat dun contrle utilisateur dans le viewstate. Voici notre contrle
MyUserCtrl rcrit avec cette technique :
Exemple 23-40 :

MyUserCtrl.cs

using System.Web.UI ;
namespace MyUserCtrls {
public class MyUserCtrl : Control {
private string m_Color ;
public string Color { get{return m_Color;} set{ m_Color=value;} }
private string m_Text ;

Contrles utilisateurs

917

public string Text { get{return m_Text;} set{ m_Text=value;} }


protected override object SaveViewState() {
object[] state = new object[2];
state[0] = m_Color;
state[1] = m_Text;
return state;
}
protected override void LoadViewState(object _state) {
if (_state != null) {
object[] state = _state as object[];
if (state[0] != null) m_Color = state[0] as string;
if (state[1] != null) m_Text = state[1] as string;
}
}
protected override void Render(HtmlTextWriter writer) {
writer.Write("<p><font color=\"" + m_Color +
"\">" + m_Text + "</font></p>") ;
}
}
}

Sachez que vous pouvez aussi vous servir du controlstate pour sauver vos tats entre les diffrentes requtes. Pour cela il faut rcrire les mthodes Control.LoadControlState() et
Control.SaveControlState() et en plus dclarer la page que ce contrle supporte le controlstate en appelant la mthode RegisterRequiresControlState() :

Exemple 23-41 :
using System.Web.UI ;
namespace MyUserCtrls {
public class MyUserCtrl : Control {
...
protected override void OnInit(System.EventArgs e) {
Page.RegisterRequiresControlState(this);
base.OnInit(e);
}
protected override object SaveControlState() {
...
}
protected override void LoadControlState(object _state) {
...
}
...
}
}

MyUserCtrl.cs

918

Chapitre 23 : ASP.NET 2.0

Intgration dun contrle utilisateur avec le mode design de Visual


Studio
ASP.NET prsente des facilits pour peaufiner lintgration dun contrle utilisateur avec le
mode design de Visual Studio. La description de ces possibilits dpasse le cadre du prsent ouvrage aussi nous vous invitons consulter larticle Attributes and Design-Time Support des
MSDN.

Amliorer les performances avec la mise en cache


ASP.NET prsente un service de cache. Un service de cache permet damliorer sensiblement
les performances en gardant en mmoire des donnes la fois coteuses obtenir et souvent
utilises. Dans le cas dune application web, de nombreuses donnes vrifient ces deux proprits. En eet, la plupart des donnes sont obtenues partir dune base de donnes et sont donc
coteuses obtenir. De plus, la gnration de pages HTML requiert des ressources ct serveur.
Or, les clients demandent souvent les mmes pages qui contiennent les mmes donnes. Des
objets comme des instances de DataSet, dconnects de la base de donnes et coteux obtenir,
constituent aussi de bons candidats pour la mise en mmoire cache. Lore de service de cache
dASP.NET se dcline sous trois formes : La mise en cache de pages, la mise en cache de fragments
de page et la mise en cache de donnes.

Mise en cache de pages


Lexemple suivant montre comment signifier ASP.NET quil doit mettre une page en cache
pour une minute avec la directive OutputCache :
Exemple 23-42 :
<%@ Page Language="C#" %>
<%@ OutputCache Duration="60" VaryByParam="none" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<% Response.Write("Page g
en
er
ee `
a : " + DateTime.Now) ; %>
</body>
</html>
La page est gnre une premire fois puis mise en cache pour une minute. Il est ici ais de
vrifier ce comportement puisque la page contient lheure laquelle elle a t gnre.
Vous pouvez vous passer de la directive OutputCache et configurer programmatiquement la
mise en cache grce aux direntes mthodes de la classe System.Web.HttpCachePolicy. En effet, la classe Page maintient un objet de ce type accessible au travers de la proprit Cache{get;} :
Exemple 23-43 :
<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<% Response.Write("Page gen
er
ee `
a : " + DateTime.Now) ; %>
</body>
<script language=C# runat="server">

Amliorer les performances avec la mise en cache

919

void Page_Load(Object sender, EventArgs e) {


Response.Cache.SetExpires( DateTime.Now.AddSeconds(60) );
Response.Cache.SetCacheability( HttpCacheability.Public );
Response.Cache.SetSlidingExpiration(true);
}
</script>
</html>
Dans cet exemple, nous dclarons une stratgie dexpiration dcale grce lappel de la mthode SetSlidingExpiration() avec largument true. Cela signifie que le dlai dune minute
aprs lequel la page sera dtruite du cache est ractiv aprs chaque requte la page. Les stratgies dexpiration dcales sont trs utiles puisquelles permettent de garantir que le cache ne
contient que des ressources eectivement utilises. Cependant, sachez que vous ne pouvez dclarer une stratgie dexpiration dcale au moyen de la directive OutputCache. Vous tes donc
contraint de le faire programmatiquement.
Lappel la mthode SetCacheability() permet de communiquer les endroits o une page
peut tre cache. En eet, le protocole HTTP 1.1 connat la notion de cache. Il permet de cacher
une page directement chez le client ou dans un proxy transparent qui se place entre le serveur
et les clients. Avec le cache maintenu par ASP.NET, cela fait trois caches possibles o une page
peut tre stocke. Veuillez vous rfrer la description de lnumration HttpCacheability
dans les MSDN pour connatre les direntes options possibles. Sachez aussi que vous pouvez
vous servir de la sous directive Location de la directive OutputCache pour communiquer les
endroits o une page peut tre cache.
Enfin sachez que lorsque lexpiration dcale est active, chaque requte au serveur entrane la
rgnration de la page. Cette technique nest donc utile que si vous avez des caches autres que
celui maintenu par ASP.NET.

Mise en cache de plusieurs versions dune page


Vous pouvez mettre en cache plusieurs versions dune mme page selon les paramtres contenus dans les requtes GET et POST. Pour cela, vous navez qu communiquer ASP.NET lensemble des paramtres concerns au moyen de la sous directive VaryByParam de la directive
OutputCache. Voici les direntes valeurs que peut prendre la sous directive VaryByParam :

La prsence de la sous directive VaryByParam est obligatoire. Si vous ne souhaitez pas exploiter cette possibilit, il faut lui fournir la valeur "none".

Si vous souhaitez utiliser plusieurs paramtres il faut sparer leurs noms avec des virgules.
Dans ce cas, ASP.NET cachera une page pour chaque valeur du produit cartsien des paramtres spcifis.

Si vous souhaitez utiliser tous les paramtres, vous pouvez utiliser la valeur "*".

Lexemple suivant se base sur lExemple 23-3. Il montre comment mettre en cache deux versions
de cette page : une lorsque le client slectionne la valeur blanc, lautre lorsquil slectionne la
valeur noir.

920

Chapitre 23 : ASP.NET 2.0

Exemple 23-44 :
<%@ Page Language=C# %>
<%@ OutputCache Duration=60 VaryByParam="Couleur" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
...
<body>
<% Response.Write("Page gen
er
ee `
a : " + DateTime.Now) ; %> <br/>
...
Couleur : <asp:dropdownlist id=Couleur runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
...
Attention : Comprenez bien quil ne faut pas exploiter cette possibilit avec des paramtres susceptibles de varier pour chaque client, tels que les noms des clients. Cela serait inecace puisque
le cache serait encombr avec des pages rarement demandes.
Il existe aussi une sous directive VaryByControl dont le comportement est assez proche de celui
de VaryByParam. En eet, en spcifiant lidentifiant dun contrle dans VaryByControl, plusieurs
versions dune page peuvent tre caches selon la valeur fournie par le client pour ce contrle.
Ainsi, lexemple suivant a un comportement similaire celui de lexemple prcdent (notez
que la prsence dune sous directive VaryByControl rend optionnelle la prsence dune sous
directive VaryByParam) :
Exemple 23-45 :
<%@ Page Language=C# %>
<%@ OutputCache Duration=60 VaryByControl="Couleur" %>
<script language=C# runat="server">
void Btn_Click(Object sender, EventArgs e) {
Msg.Text = "Vous avez selectionn
e : "+Couleur.SelectedItem.Value ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<% Response.Write("Page gen
er
ee `
a : " + DateTime.Now) ; %> <br/>
...
Couleur : <asp:dropdownlist id=Couleur runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
...
<asp:label id=Msg runat="server"/>
...
Comprenez bien quASP.NET se base sur ltat du contrle fourni par le client et non ltat du
contrle quil assigne lors de la fabrication de la page. Une consquence est que cet exemple ne
fonctionnerait plus si nous avions crit VaryByControl="Msg".
Vous pouvez aussi utiliser la sous directive VaryByHeader pour signifier ASP.NET quil peut
mettre en cache plusieurs versions dune mme page selon le contenu des enttes des requtes

Amliorer les performances avec la mise en cache

921

des clients. Par exemple, la langue dsire par le client est mis dans lentte Accept-Language.
Aussi, plusieurs versions de la page suivante peuvent tre caches, une pour chaque langue spcifie par un client :
Exemple 23-46 :
<%@ Page Language="C#" %>
<%@ OutputCache Duration="60"
VaryByParam="none" VaryByHeader="Accept-Language" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<script language=C# runat="server" >
void Page_Load(Object sender, EventArgs e) {
if (!IsPostBack) {
switch ( Request.UserLanguages[0] ) {
case "fr": Response.Write("Bonjour !") ; break ;
case "de": Response.Write("Guten Tag!") ; break ;
default: Response.Write("Hello!") ; break ;
}
}
}
</script>
<body>
<% Response.Write("Page gen
er
ee `
a : " + DateTime.Now) ; %>
</body>
</html>
Enfin, vous pouvez utiliser la sous directive VaryByCustom pour signifier ASP.NET quil peut
mettre en cache plusieurs versions dune mme page selon vos propres critres. Ces critres sont
en gnral relatifs au type de navigateur quutilise le client. Si vous dsirez cacher une version de
page par type/version de navigateur, vous pouvez aecter la valeur "Browser" cette sous directive. Sinon, il faut rcrire la mthode GetVaryByCustomString() de la classe HttpApplication
pour fabriquer vous-mme vos paramtres tester.
Lexemple suivant expose une page qui est gnre diremment selon que le navigateur client
supporte ou nom les chanes de caractres en gras. Nous fournissons donc notre propre paramtre SupportsBold. Pour chaque requte, ASP.NET peut connatre la valeur de ce paramtre
en le demandant notre code dans la mthode GetVaryByCustomString().
Exemple 23-47 :
<%@ Page Language="C#" %>
<%@ OutputCache Duration="60" VaryByParam="none"
VaryByCustom="SupportsBold" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<script language=C# runat="server" >
void Page_Load(Object sender, EventArgs e) {
if ( Request.Browser.SupportsBold )
Response.Write("<B> Bonjour ! </B>") ;
else
Response.Write("Bonjour ! ") ;
}

922

Chapitre 23 : ASP.NET 2.0


</script>
<body>
<% Response.Write("Page g
en
er
ee `
a : " + DateTime.Now) ; %>
</body>
</html>
Global.asax

Exemple 23-48 :

<%@ Application Language="C#" %>


<script runat="server">
public override string GetVaryByCustomString(
HttpContext ctx, string custom) {
if( custom == "SupportsBold" )
return "SupportsBold=" + ctx.Request.Browser.SupportsBold;
return string.Empty ;
}
</script>

Mise en cache de fragments de pages


Il est plutt courant que seule une partie dune page soit spcifique chaque client. Par exemple,
dans un site marchand la partie qui ache les achats en cours varie avec chaque client tandis que
la partie de la page qui ache les caractristiques des produits reste identique. Aussi, ASP.NET
vous permet de ne mettre en cache quun fragment de page. Plus exactement, vous avez la
possibilit de ne gardez en cache que des fragments HTML issus de contrles utilisateurs. Cela
est possible grce lutilisation de la directive OutputCache lors de la dfinition dun contrle
utilisateur dans un fichier .ascx :
MyUserCtrl.ascx

Exemple 23-49 :
<%@ Control Language=C# ClassName="MyUserCtrl" %>
<%@ OutputCache Duration=60 VaryByParam=none %>
<br/>
<% Response.Write("Fragment gener
e `
a : " + DateTime.Now) ; %>

Par exemple la page suivante exploite le contrle MyUserCtrl. En excutant cet exemple, vous
voyez que la page est gnre chaque requte tandis que le fragment HTML gnr par le
contrle est cach et rutilis.
Exemple 23-50 :

MyUserCtrlClient.aspx

<%@ Page Language=C# %>


<%@ Register TagPrefix="PRATIQUE" Src="~/MyUserCtrl.ascx"
TagName=UserCtrl %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<% Response.Write("Page g
en
er
ee `
a : " + DateTime.Now) ; %>
<form runat="server">
<PRATIQUE:UserCtrl runat="server" />
</form>
</body>
</html>

Amliorer les performances avec la mise en cache

923

Dans ce contexte, vous pouvez stocker plusieurs fragments HTML gnrs par un mme
contrle utilisateur. En eet, ici aussi les sous directives VaryByParam, VaryByControl et
VaryByCustom sont utilisables au sein de la directive OutputCache. En revanche les sous directives VaryByHeader et Location ne sont pas utilisables dans ce contexte.
Si votre contrle prsente des proprits publiques, ASP.NET stockera automatiquement dans
le cache un fragment HTML gnr pour chaque valeur du produit cartsien de ces proprits.
Pour cela, il faut que ces valeurs soient fournies statiquement la cration du contrle. Par
exemple :
MyUserCtrl.ascx

Exemple 23-51 :
<%@ Control Language=C# ClassName=MyUserControl %>
<%@ OutputCache Duration=60 VaryByParam=none %>
<script runat="server">
private string m_Couleur ;
public string Couleur { get { return m_Couleur ; }
set { m_Couleur = value ; } }
</script>
<br/>
<% Response.Write("Couleur : " + Couleur) ; %>
<br/>
<% Response.Write("Fragment gener
e `
a : " + DateTime.Now) ; %>

MyUserCtrlClient.aspx

Exemple 23-52 :
...

<PRATIQUE:UserCtrl Couleur=blanc runat="server" />


...
Enfin, soyez conscient quun contrle qui produit des fragments HTML susceptibles dtre cachs ne doit pas tre manipul programmatiquement dans le code. En eet, lorsquASP.NET
dcide de rutiliser un fragment HTML cach il ne cre pas le contrle sous-jacent. Ainsi la
rfrence vers ce contrle est nulle.

Substitution post-cache
ASP.NET 2.0 vous permet de ne rgnrer que certaines parties dune page cache. Cette technique est connue sous le nom substitution post-cache. Elle permet dadresser la problmatique du
cache des pages semi variables dune manire complmentaire lutilisation de fragments
cachs. En revanche, la substitution post-cache nest pas exploitable partir de contrles utilisateurs ni partir de master page caches.
Pour exploiter la substitution post-cache, il vous sut davoir recours au contrle <asp:Substitution>. Ce dernier prend en paramtre nomm MethodName le nom dune mthode statique.
Cette dernire renvoie le fragment HTML variable sous forme dune chane de caractres. Par
exemple :

924

Chapitre 23 : ASP.NET 2.0

Exemple 23-53 :
<%@ Page Language=C# %>
<%@ OutputCache Duration=60 VaryByParam=none %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<script runat="server">
public static string Fct( HttpContext ctx ) {
return "Substitution effectu
ee `
a : " + DateTime.Now;
}
</script>
<body>
<% Response.Write("Page g
en
er
ee `
a : " + DateTime.Now) ; %> <br/>
<asp:Substitution Id=Substitution1 MethodName=Fct runat="server" />
</body>
</html>
Vous pouvez aussi exploiter la substitution post-cache au moyen de la mthode HttpResponse.WriteSubstitution().

Mise en cache de donnes


Pour cacher des pages HTML ou des fragments de pages HTML, ASP.NET utilise en interne son
propre moteur de cache. Vous pouvez exploiter ce moteur pour cacher vos propres donnes.
Lexemple suivant illustre cette possibilit en cachant une instance de DataView construite
partir dun DataSet rcupr partir de la base de donnes ORGANISATION. Cette base est dcrite
en page 710 :
Exemple 23-54 :
<%@ Page Language=C# %>
<%@ Import Namespace ="System.Data" %>
<%@ Import Namespace ="System.Data.Common" %>
<%@ Import Namespace ="System.Data.SqlClient" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
DataView dv = Cache["Employes"] as DataView;
if ( dv == null ) {
using ( DbDataAdapter dAdapter = new SqlDataAdapter(
"SELECT * FROM EMPLOYES",
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION")){
DataSet ds = new DataSet() ;
dAdapter.Fill(ds) ;
dv = ds.Tables[0].DefaultView ;
dv.AllowDelete = false ;
dv.AllowEdit = false ;
dv.AllowNew = false ;
Cache["Employes"] = dv;
} // end using dAdapter.
}
else Response.Write("Charge du cache !") ;
MyList.DataSource = dv ;

Amliorer les performances avec la mise en cache

925

MyList.DataTextField = "Nom" ;
DataBind() ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:ListBox ID="MyList" runat="server"/>
</form>
</body>
</html>
La mise en cache de donnes se fait dune manire globale lapplication web. En consquence,
cette technique de mise en mmoire de donnes sapparente avec lutilisation du dictionnaire
global, instance de HttpApplicationState. Il faut noter cependant deux dirences :

Lors de la rcupration dune donne dans le cache, il nest pas certains quelle y soit encore.
Aussi, linstar de notre exemple, il faut toujours prvoir un mcanisme pour la rcuprer
autrement que par le cache pour prvoir le cas o elle ny est plus.

La philosophie de mise jour des donnes caches est dirente de celle des donnes globales. La copie stocke dans le cache dune donne ne doit pas tre modifie. Si la donne
vient changer alors quune copie est toujours dans le cache, il faut prvoir un mcanisme
qui dtruit la copie prime pour la remplacer avec une copie jour. Sachez quen interne,
les accs aux donnes stockes dans le cache sont automatiquement synchroniss avec lutilisation dune instance de ReaderWriterLock.

Dpendances dans le cache


ASP.NET prsente un mcanisme de dpendance pour adresser la problmatique des donnes
du cache primes que nous venons dvoquer. Concrtement, lors de linsertion dune donne
dans le cache, vous pouvez la coupler avec une dpendance. Pour cela, la classe System.Web.
Caching.Cache prvoit plusieurs surchargent de la mthode Insert() qui acceptent une instance de System.Web.Caching.CacheDependency.
Une dpendance peut tre utilise linsertion dune ou de plusieurs donnes dans le cache.
Une mme instance de CacheDependency peut reprsenter une dpendance vers zro, un ou
plusieurs fichiers, zro, un ou plusieurs rpertoires et zro, une ou plusieurs autres dpendances.
Si ltat dune seule de toutes ces entits vient tre modifi, la dpendance concerne le dtecte
et fait en sorte que les donnes qui lui sont associes dans le cache soient dtruites.
Lexemple suivant illustre la mise en cache du contenu dun fichier texte dans le cache. La page
ache le contenu de ce fichier texte en le rcuprant dans le cache. Grce une dpendance sur
ce fichier vous pouvez garantir que chaque client visualisera la dernire version de ce contenu :
Exemple 23-55 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<% string content = this.Cache["Content"] as string ;
if( content == null ) {

926

Chapitre 23 : ASP.NET 2.0


Response.Write(@"Chargement dans le cache.<br/>") ;
content = System.IO.File.ReadAllText(@"D:\Temp\Test.txt") ;
CacheDependency dep = new CacheDependency(@"D:\Temp\Test.txt");
Context.Cache.Insert("Content", content, dep ) ;
}
Response.Write(@"Contenu du fichier D:\Temp\Test.txt:" + content) ;
%>
</body>
</html>

La classe CacheDependency prsente de nombreux constructeur vous permettant de communiquer les entits associes avec une dpendance.
En outre, la mthode Cache.Insert() prsente une surcharge qui prend en paramtre une valeur de lnumration CacheItemPriority. Cette valeur vous permet de donner plus ou moins
dimportance vos donnes caches. Bien entendu, lalgorithme de purge du cache dASP.NET
tient compte de cette priorit.
Enfin, cette surcharge de la mthode Cache.Insert() prend aussi en paramtre un dlgu de
type CacheItemRemovedCallback. Cela vous donne un moyen dtre averti par un appel de mthode lorsquune donne du cache est dtruite.

Dpendances sur des donnes dune base SQL Server


ASP.NET 2.0 prsente aussi la classe SqlCacheDependency qui permet de crer des dpendances
vers une table ou des lignes dune table dune base de donnes type SQL Server. Une telle dpendance peut provoquer la destruction des donnes du cache associes ds que les donnes dune
table sont modifies. Voici comment modifier lExemple 23-54 pour crer une dpendance sur
la table EMPLOYES :
Exemple 23-56 :
...
dv.AllowNew = false ;
Cache.Insert("Employes", dv,
new SqlCacheDependency("DbORGANISATION","EMPLOYES"));
} // end using dAdapter.
...
Naturellement, il faut prciser quelle base de donnes correspond lalias DbORGANISATION
comme ceci :
Exemple :

Web.config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<connectionStrings>
<add name="CnxStrORGANISATION"
connectionString="Data Source=localhost;
Integrated Security=SSPI;Initial Catalog=ORGANISATION"
providerName="System.Data.SqlClient"/>

Amliorer les performances avec la mise en cache

927

</connectionStrings>
<system.web>
<caching>
<sqlCacheDependency enabled="true">
<databases>
<add name="DbORGANISATION"
connectionStringName="CnxStrORGANISATION"
pollTime="500"/>
</databases>
</sqlCacheDependency>
</caching>
...
Si vous utilisez SQL Server 2005 comme SGBD sous-jacent, vous pouvez mme ne dpendre
que sur la mise jour de certaines lignes dune table. Vous prcisez ces lignes au moyen dune
commande SELECT comme ceci :
Exemple 23-57 :
...
dv.AllowNew = false ;
SqlConnection cnx = new SqlConnection(
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION");
SqlCommand cmd = new SqlCommand(
"SELECT * FROM EMPLOYES WHERE EmployeID=6 OR EmployeID=7", cnx);
Cache.Insert("Employes", dv,
new SqlCacheDependency(cmd));
} // end using dAdapter.
...
Cette possibilit de dpendance sur les donnes dune base est aussi exploitable lors de la mise
en cache dune page ou dun fragment de page au travers de la sous directive SqlDependency de
la directive <%@ OutputCache> :
<%@ OutputCache Duration=60 VaryByParam="none"
SqlDependency="DbORGANISATION:EMPLOYES" %>
En interne, le mcanisme mis en uvre pour dtecter les changements est compltement diffrent selon la version de votre SGBD SQL Server.

Dans le cas de SQL Server 2005, le mcanisme de Query Notification de ce SGBD est exploit.
En interne, une dpendance ADO.NET 2.0 est cre sur la table ou les lignes concernes.
Elle est dclenche par ce mcanisme ds quune mise jour a eu lieu.

Dans le cas dune version antrieure SQL Server 2005 (7 ou 2000), un mcanisme de polling
est mis en uvre du ct serveur ASP.NET. Un thread background est utilis rgulirement
pour dtecter si les donnes dune table ont t modifies. Lintervalle utilis est spcifi en
milli secondes par lattribut pollTime de llment caching\sqlCacheDependency\databases.
Sa valeur est de 5 secondes par dfaut. Une certaine prparation avec loutil en ligne
de commande aspnet_regsql.exe est ncessaire pour activer ce mcanisme. Il faut tout
dabord indiquer votre SQBD quelle base de donnes supporte ce mcanisme (ed pour
enable database) :

928

Chapitre 23 : ASP.NET 2.0


>aspnet_regsql.exe -S <host> -U <usr> -P <pwd> -d <database> -ed
Ensuite, il faut utiliser cet outil pour activer une une chaque table qui supporte ce mcanisme (et pour enable table) :
>aspnet_regsql.exe -S <host> -U <usr> -P <pwd> -d <database>
-t <table> -et
En interne, cet outil cre dans votre base de donnes des procdures stockes, des dclencheurs et des tables ncessaires lexcution de ce mcanisme.

Dpendances personnalises dans le cache


ASP.NET 2.0 ore la possibilit de fournir des dpendances vers vos propres types de ressources.
Pour cela, il faut prvoir une classe drive de CacheDependency qui surveille ltat de la ressource. Une instance de cette classe doit appeler la mthode NotifyDependencyChanged() lorsquelle dtecte un changement dtat. Le moteur de cache dtruira alors les donnes du cache qui
sont associes cette instance et appellera la mthode DependencyDispose() pour lui donnes
une chance de librer les ressources quelle utilise.
Tout ceci est illustr par lexemple suivant qui prsente un squelette de classe drive de
CacheDependency. Nous utilisons un timer de type System.Timers.Timer pour surveillez
priodiquement ltat dune ressource fictive :
Exemple 23-58 :
using System ;
using System.Web.Caching ;
using System.Timers ;
public class CustomCacheDependency : CacheDependency {
private Timer m_Timer = new Timer() ;
private string m_RessourceId ;
public CustomCacheDependency( int pollIntervalSec,
string ressourceId ) {
m_RessourceId = ressourceId ;
m_Timer.Interval = pollIntervalSec * 1000 ;
m_Timer.Elapsed += this.CheckDependencyHandler ;
m_Timer.Start() ;
}
private void CheckDependencyHandler( object sender,
ElapsedEventArgs e) {
// Ici, placez le code pour d
etecter si l
etat de la resource
// indexee par m_RessourceId a chang
e.
bool ressourceHasChanged = false ;
if (ressourceHasChanged)
NotifyDependencyChanged(this, EventArgs.Empty);
}
protected override void DependencyDispose() {
m_Timer.Stop() ;
}
}

Sources de donnes

929

Sources de donnes
Lier programmatiquement contrles et sources de donnes
Dans la section prcdente, lExemple 23-54, page 924 montre comment exploiter un contrles
ASP.NET de type ListBox pour prsenter les donnes dune colonne contenue dans une
DataView. Pour cela, il sut de fournir la DataView au travers de la proprit object ListBox.DataSource{set;}, de fournir le nom de la colonne acher au travers de la proprit
string ListBox.DataTextField{set;}, puis dappeler la mthode DataBind() sur lobjet reprsentant la page courante. Cette mthode est prsente par la classe Control. Par consquent,
elle est aussi prsente par la classe Page et donc par toutes les classes reprsentant des pages,
ainsi que tous les contrles serveurs. Limplmentation par dfaut de cette mthode consiste
appeler la mthode DataBind sur chacun des contrles enfants.
Seuls certains types de contrles serveur tels que ListBox implmentent eectivement cette mthode. Cette implmentation consiste rcuprer dans une copie en interne toutes les donnes
de lobjet fourni laide de la proprit DataSource. Ces donnes sont alors exploites par la
mthode Render() du contrle qui les ache dans la page HTML.
La classe reprsentant une source de donnes doit implmenter une des interfaces :

System.ComponentModel.IListSource (implmente par les classes DataSet, DataTable).

IEnumerable (implmente par les classes DataView, ArrayList, List<>, Dictionary<,>


etc).

IDataReader (implmente par la classe DbDataReader ainsi que ses classes drives).

Dans lExemple 23-54, les donnes sont dj contenues dans le DataSet sous-jacent lors de lappel la mthode DataBind. Cela implique quil existe trois versions de ces donnes : dans le
DataSet, dans la ListBox aprs lappel la mthode DataBind() puis dans la section __VIEWSTATE de la page HTML rendue. Si vous pouvez vous le permettre, vous pouvez dsactiver le stockage de donnes dun contrle dans la section __VIEWSTATE. En outre, certaines sources de donnes telles quun DataReader ne contiennent pas en interne les donnes. Elles ne reprsentent
quun moyen daccder aux donnes. Ainsi dans lexemple suivant il ny a plus quune seule
copie des donnes, celle cre dans lobjet ListBox lors de lappel la mthode DataBind().
Exemple 23-59 :
<%@ Page Language=C# %>
<%@ Import Namespace ="System.Data" %>
<%@ Import Namespace ="System.Data.Common" %>
<%@ Import Namespace ="System.Data.SqlClient" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
string sCnx =
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION" ;
string sCmd = "SELECT * FROM EMPLOYES" ;
using (DbConnection cnx =new SqlConnection(sCnx)) {
using (DbCommand cmd =new SqlCommand(sCmd, cnx as SqlConnection)){
cnx.Open() ;
using ( DbDataReader rdr = cmd.ExecuteReader() ) {

930

Chapitre 23 : ASP.NET 2.0


MyList.DataSource = rdr;
MyList.DataTextField = "Nom";
DataBind();
MyList.EnableViewState = false;
} // end using rdr.
} // end using cmd.
} // end using cnx.
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:ListBox ID="MyList" runat="server" />
</form>
</body>
</html>

Ainsi nous voyons apparatre trois pratiques permettant lamlioration des performances
lorsque lon travaille avec des sources de donnes.

Soit on limite le nombre de copies des donnes au strict minimum chaque chargement
de page linstar de lexemple prcdent.
Soit on ne charge les donnes qu la premire utilisation dune page par un utilisateur puis
on se sert du viewstate pour les stocker.
Soit on se permet de maintenir une copie en mmoire (au niveau du cache, de la session
ou de lapplication) linstar de lExemple 23-54.

Selon le contexte, lune ou lautre de ces pratiques peut donner les meilleures performances.

Lier dclarativement contrles et sources de donnes


ASP.NET 2.0 introduit plusieurs nouveaux contrles serveur permettant de se lier dclarativement une source de donne. Ils soulagent le dveloppeur de lcriture du code manipulant
lAPI ADO.NET ncessaire pour rcuprer et modifier des donnes dune base. Concrtement,
lexemple suivant est quivalent lExemple 23-59 :
Exemple 23-60 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form2" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES"
DataSourceMode="DataReader" />
<asp:ListBox ID="MyList" DataSourceID="DataSrc" runat="server"
DataTextField="Nom" EnableViewState="False" />
</form>
</body>
</html>

Sources de donnes

931

On voit que le contrle source de donnes de type SqlDataSource a besoin de connatre la


chane de connexion la base, la commande SQL permettant de rcuprer les donnes et optionnellement le mode de rcupration des donnes : connect (valeur DataReader pour la proprit
DataSourceMode) ou dconnect (valeur DataSet). La valeur DataSet est choisie par dfaut. Cet
exemple ne montre pas que le contrle serveur SqlDataSource prsente de nombreuses autres
possibilits telles que la mise en cache des donnes (proprits EnableCaching, CacheDuration,
CacheExpirationPolicy) ou le filtrage des donnes.
Lexemple suivant illustre comment exploiter cette notion de filtre afin dacher les employs
appartenant un dpartement. Le dpartement est choisi dans une liste droulante remplie
avec la source de donnes DataSrc1. chaque changement de slection, cette liste dclenche
un vnement postback. Du ct serveur, on se sert dun contrle de type ControlParameter
pour rcuprer le dpartement slectionn et le fournir en paramtre la requte SQL de type
SELECT de la source de donnes utilise pour remplir la liste :
Exemple 23-61 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc1" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT DISTINCT DepId FROM EMPLOYES ORDER BY DepId"/>
<asp:DropDownList ID="ListDep" DataSourceID="DataSrc1"
DataTextField="DepId" autopostback="true" runat="server" />
<asp:SqlDataSource ID="DataSrc2" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES WHERE DepId=@ParamDepId">
<SelectParameters>
<asp:ControlParameter Name="ParamDepId" ControlID="ListDep"
PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
<asp:ListBox ID="MyList" DataSourceID="DataSrc2" runat="server"
DataTextField="Nom" />
</form>
</body>
</html>

Lexcution de cette page ressemble ceci :


Nous verrons quun contrle serveur source de donnes peut aussi servir la modification des
donnes (insertion, mise jour, destruction). Nous verrons aussi que les contrles serveur qui
exploitent les contrles source de donnes rajoutent des facilits telles que la pagination ou le
tri des donnes.

932

Chapitre 23 : ASP.NET 2.0

Figure 23 -10 : Filtrage des donnes dune source

Les types de source de donnes


Il existe principalement deux types de sources de donnes : les sources de donnes plates et les
sources de donnes hirarchiques.
Les sources de donnes plates sont par exemple les tables dune base de donnes relationnelles
ou une liste dobjets. Les contrles serveur qui permettent lexploitation de telles sources drivent de la classe DataSourceControl. En voici la liste (ces classes sont toutes dans lespace de
noms System.Web.UI.WebControls) :

La classe SqlDataSource que nous venons de prsenter. Elle peut servir lexploitation de
tous types de SGBD puisquelle supporte les fournisseurs de donnes SQL Server, ODBC et
OleDB.

La classe ObjectDataSource que nous allons dcrire dans la prochaine section.

Les sources de donnes hirarchiques sont utilises pour exploiter les donnes des documents
XML. Les contrles serveur qui permettent lexploitation de telles sources sont XmlDataSource
et SiteMapDataSource. Ces classes drivent de la classe HierarchicalDataSourceControl. La
classe SiteMapDataSource permet dexploiter des documents XML spciaux reprsentant larborescence dun site. Elle est dcrite page 956. En outre, nous revenons sur la notion de sources
de donnes hirarchiques un peu plus loin en page 945.

La source de donnes ObjectDataSource


La classe ObjectDataSource permet dexploiter vos objets de donnes comme une source de
donnes plate. Contrairement aux autres sources de donnes qui encouragent une architecture
2-tiers en liant directement les contrles de donnes graphiques aux contrles de source de donnes, cette classe permet llaboration dune architecture 3-tiers voire N-tiers. Par exemple, supposons que la classe dobjets de donnes suivante Employe reprsente des employs. Vous pouvez
vous servir de la classe ObjectDataSource pour lier une collection demploys un contrle
de prsentation des donnes tel quune ListBox. Pour cela, il faut prvoir une classe sans tat
(i.e sans champs dinstance) prsentant une mthode publique qui retourne la collection demploys. Cette mthode publique, en loccurrence ICollection GetData(), est connue par la
source de donne par la valeur de la proprit SelectMethod. Elle est appele par rflexion. Elle
peut tre statique ou dinstance. Tout ceci est illustr par les deux listings suivants :

Sources de donnes

933

Exemple 23-62 :
using System.Collections ;
using System.Collections.Generic ;
public class Employe {
private int m_EmployeID = -1 ;
private string m_Nom ;
private string m_Prenom ;
public int EmployeID{get{return m_EmployeID;}set{m_EmployeID=value;}}
public string Nom{
get{return m_Nom;}
set{m_Nom = value;} }
public string Prenom{get{return m_Prenom;}
set{m_Prenom = value;} }
}
public class Helper {
private static List<Employe> list = new List<Employe>() ;
static Helper() {
Employe emp = new Employe() ;
emp.EmployeID = 1 ; emp.Nom = "Lafleur" ; emp.Prenom = "L
eon" ;
list.Add(emp) ;
emp = new Employe() ;
emp.EmployeID = 2 ; emp.Nom = "Dupont" ; emp.Prenom = "Anne" ;
list.Add(emp) ;
}
static public ICollection GetData() { return list ; }
}
Exemple 23-63 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form2" runat="server">
<asp:ObjectDataSource ID="ObjDataSrc"
runat="server"
SelectMethod="GetData" TypeName="Helper"/>
<asp:ListBox ID="MyList"
DataSourceID="ObjDataSrc"
DataTextField="Nom" runat="server" />
</form>
</body>
</html>

Utiliser une source de donnes pour la modification


La classe ObjectDataSource permet aussi la modification des donnes. Pour cela, il sut
que la classe helper intermdiaire prsente des mthodes dinsertion, de mise jour et/ou de
destruction. Vous navez plus alors qu prciser ces mthodes dans la dclaration du contrle
ObjectDataSource au moyen des proprits InsertMethod, UpdateMethod et DeleteMethod. Ces
mthodes sont respectivement appeles lorsquune des mthodes IEnumerable Select(), int
Update(), int Insert() et int Delete() est appele sur le contrle ObjectDataSource. La
mise jour de donnes est illustre par lexemple suivant. En reprenant lexemple prcdent,
cette page permet de mettre jour le nom dun employ :

934

Chapitre 23 : ASP.NET 2.0

Figure 23 -11 : Mise jour des donnes avec ObjectDataSource


Exemple 23-64 :
...
public class Employe { ... }
public class Helper {
...
static public void UpdateName(string oldName, string newName) {
foreach (Employe emp in list)
if (emp.Nom == oldName)
emp.Nom = newName;
}
}
Exemple 23-65 :
<%@ Page Language=C# %>
<script language=C# runat="server">
void Btn_Click(Object s, EventArgs e) { ObjDataSrc.Update(); }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form2" runat="server">
<asp:ObjectDataSource ID="ObjDataSrc" runat="server"
TypeName="Helper" SelectMethod="GetData"
UpdateMethod="UpdateName" >
<UpdateParameters>
<asp:ControlParameter name="oldName" type="String"
controlid="OldName"
propertyname="SelectedValue" />
<asp:FormParameter Name="newName" formfield="NewName"
Type=String />
</UpdateParameters>
</asp:ObjectDataSource>
<asp:ListBox ID="MyList"
DataSourceID="ObjDataSrc"
DataTextField="Nom" runat="server" />
<asp:DropDownList ID="OldName" runat="server" DataTextField="Nom"
DataSourceID="ObjDataSrc" />
<asp:TextBox ID="NewName" runat="server"/>
<asp:Button ID="Button1" runat="server" Text="Soumettre"
OnClick="Btn_Click" />

Prsentation et dition des donnes

935

</form>
</body>
</html>
Notez la faon dont les paramtres en entre de la mthode UpdateName() sont lis avec les
contrles OldName et NewName au sein dune section <UpdateParameters>. Naturellement, pour
la slection, linsertion et la destruction vous pouvez pareillement exploiter des sections <SelectParameters>, <InsertParameters> et <DeleteParameters>. Ces sections contiennent les
dclarations de contrles serveurs permettant de rcuprer les paramtres en entre du traitement. Par exemple le contrle ControlParameter permet de rcuprer une valeur partir dune
proprit dun autre contrle tandis quun contrle FormParameter permet de rcuprer une
valeur partir du contenu dune TextBox. Tous ces contrles drivent du contrle Parameter.
Aussi vous pouvez trouver la liste exhaustive en consultant larticle Parameter Hierarchy des
MSDN.

Prsentation et dition des donnes


ASP.NET 2.0 introduit une nouvelle hirarchie de contrles serveurs permettant de prsenter
et dditer des donnes provenant dune source de donnes :
BaseDataBoundControl

DataBoundControl

HierarchicalDataBoundControl
Menu

ListControl
TreeView
BulletedList
CompositeDataBoundControl

AdRotator

CheckBoxList
DropDownList

DetailsView

ListBox

FormView

RadioButtonList

GridView

Classe abstraite
Classe non abstraite

Figure 23 -12 : Hirarchie de contrles de prsentation et ddition des donnes


Si vous connaissez dj ASP.NET 1.x, vous remarquerez labsence du contrle DataGrid. Ce
contrle est toujours prsent par le framework .NET 2.0 pour des raisons de compatibilit ascendante. Cependant, il est conseill dutiliser son successeur, le contrle GridView qui est plus
puissant.
Dans les exemples des sections prcdentes nous avons eu loccasion dexploiter les contrles
ListBox et DropDownList ainsi que leur capacit tre lis une source de donnes.
Les contrles Menu et Treeview qui peuvent tre utiliss pour prsenter une source de donnes
hirarchique sont brivement dcrits en page 956 lorsque nous les utilisons pour acher lorganisation dun site.
Le contrle AdRotator permet dacher alatoirement une bannire en gnral publicitaire.
Nous vous invitons consulter les MSDN pour en savoir plus son sujet.

936

Chapitre 23 : ASP.NET 2.0

Nous allons surtout nous intresser aux contrles GridView, DetailsView et FormView, particulirement adapts la prsentation et ldition de donnes dune source de donnes plate.

Le contrle GridView
linstar de son prdcesseur le contrle DataGrid, le contrle serveur GridView permet de
prsenter et dditer des donnes dans un tableau. En plus de multiples amliorations fonctionnelles, la puissance de ce contrle tient surtout dans son ecacit et sa simplicit travailler
avec des sources de donnes. Voici pour preuve, le code dune page permettant de remplir une
GridView avec les donnes dune table dune base de donnes :
Exemple 23-66 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES" />
<asp:GridView ID="Grid" DataSourceID="DataSrc" runat="server" />
</form>
</body>
</html>

Figure 23 -13 : Utilisation dune GridView


Derrire cette simplicit dutilisation apparente se cache le contrle serveur le plus complexe
dASP.NET 2.0. Pour des raisons de place, nous nous bornerons prsenter ses fonctionnalits
principales. Aussi nhsitez pas consulter les MSDN pour plus dinformations.
La proprit bool AllowPaging du contrle GridView permet de dcider si vous souhaitez
paginer les donnes prsentes sur plusieurs pages. Par dfaut, la pagination se fait sur 10
lignes mais vous pouvez indiquer un autre nombre de lignes avec la proprit PageSize.
Vous pouvez obtenir ou fixer lindex de la page ache avec la proprit int PageIndex.
Lorsque plus dune page est ncessaire lachage des donnes, le contrle GridView gnre
une colonne spcialise dans la navigation des pages. Vous pouvez personnaliser cette ligne
en ayant accs lobjet de type PagerSettings que vous pouvez rcuprer par la proprit
GridView.PagerSettings{get;}. Une grosse amlioration de la pagination de GridView par
rapport celle de DataGrid est que seules les donnes aches sont stockes dans le viewstate.

Prsentation et dition des donnes

937

La proprit bool AllowSorting permet de dcider si vous souhaitez que lutilisateur puisse
trier les donnes en fonction du contenu dune colonne. Lactivation de cette possibilit fait
en sorte que les enttes des colonnes soient des hyperliens. Le trie se fait du ct serveur et est
retourn au client lorsquune telle entte est clique.
Vous pouvez trs simplement diter les donnes dune ligne, voire dtruire une ligne, en vous
servant dune GridView. Pour cela, il sut que la source de donne sous-jacente supporte
ces oprations et que vous positionniez true les proprits AutoGenerateEditButton et
AutoGenerateDeleteButton. Lexemple suivant illustre ceci :
Exemple 23-67 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES"
UpdateCommand= "UPDATE EMPLOYES SET
DepID=@DepID,Nom=@Nom,Pr
enom=@Pr
enom,T
el=@T
el
WHERE EmployeId=@Original_EmployeId"
DeleteCommand="DELETE EMPLOYES WHERE EmployeId=@Original_EmployeId"/>
<asp:GridView ID="MyGrid" DataSourceID="DataSrc" runat="server"
AutoGenerateEditButton="true"
AutoGenerateDeleteButton="true"
DataKeyNames="EmployeId"/>
</form>
</body>
</html>

Figure 23 -14 : dition/Dltion des lignes dune GridView

938

Chapitre 23 : ASP.NET 2.0

Le contrle GridView prsente des vnements tels que RowDeleting ou RowDeleted permettant
dappeler une mthode ct serveur juste avant ou juste aprs une modification. Linsertion de
lignes ncessite le recours un contrle DetailsView aussi nous dcrivons cette opration dans
la prochaine section.
Vous pouvez agir sur la faon dont les donnes sont aches dans la grille en modifiant les
colonnes aches. ASP.NET 2.0 fournit plusieurs styles de colonnes prdfinies grce aux types
drivant de la classe DataControlField tels que ButtonField ou BoundField. Cependant, en
utilisant le type TemplateField vous pouvez dfinir exactement le code HTML contenu dans
une cellule. Ceci est illustr par lexemple suivant qui ache une grille contenant :

Une colonne de type ButtonField contenant des boutons ayant le nom de lemploy.
Lorsquun tel bouton est cliqu, la mthode Grid_RowCommand() et invoque et change le
contenu du label Msg.

Une colonne de type TemplateField qui contient des CheckBox. Une CheckBox est ticke
seulement si le nom de lemploy correspondant contient la lettre o .

Une colonne de type BounText contenant les noms des employs.

Exemple 23-68 :
<%@ Page Language=C# %>
<script language=C# runat="server">
void Grid_RowCommand(Object sender, GridViewCommandEventArgs e) {
if (e.CommandName == "Hello") {
int index = Convert.ToInt32(e.CommandArgument);
GridViewRow selectedRow = Grid.Rows[index];
TableCell cell = selectedRow.Cells[2];
string nom = cell.Text;
Msg.Text = "Vous aves s
electionn
e " + nom + ".";
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES" />
<asp:GridView ID="Grid" DataSourceID="DataSrc" runat="server"
AutoGenerateColumns=False OnRowCommand="Grid_RowCommand" >
<Columns>
<asp:ButtonField DataTextField="Nom" ButtonType=Button
HeaderText="Cliquez svp..." CommandName="Hello"/>
<asp:TemplateField HeaderText="Le nom contient la lettre o">
<ItemTemplate>
--<asp:CheckBox runat="server" Enabled=False
Checked=<%# ((string)Eval("Nom")).Contains("o") %> />-</ItemTemplate>
</asp:TemplateField>

Prsentation et dition des donnes

939

<asp:BoundField DataField="Nom" HeaderText="Nom" />


</Columns>
</asp:GridView>
<asp:Label ID="Msg" runat="server"></asp:Label>
</form>
</body>
</html>

Figure 23 -15 : Personnalisation des colonnes dune GridView

Les templates
Dans lexemple prcdent, peut tre avez-vous remarqu ce bloc qui ressemble un bloc de
restitution de code :
<%# ((string)Eval("Nom")).Contains("o") %>
On nomme un tel bloc un template. Ce template se remplace par une valeur false ou true selon
que le nom de lemploy de la ligne courante contient un o ou non. Les templates constituent un mcanisme permettant de dcider comment un contenu est ach. Les contrles
serveurs de prsentation de donnes qui utilisent les templates sont les contrles GridView,
DetailsView, FormView, LoginView, TreeView, Repeater, DataList et DataGrid. Contrairement
aux blocs de restitution de code, les templates sont valus au moment o la liaison de donnes
se fait (i.e lors de lappel DataBind() sur le contrle). Chaque template est valu une fois par
ligne.
En interne, chaque template induit un contrle enfant du contrle de prsentation dans lequel
il est dfinit. Ces contrles enfants sont de type DataBoundLiteralControl. Lors de la compilation, ASP.NET ajoute la classe reprsentant la page courante une mthode qui est abonne
lvnement DataBinding dun tel contrle. Cet vnement est dclench pour chaque ligne
rcupre durant lappel la mthode DataBind() du contrle de prsentation parent. Voici la
mthode gnre pour notre exemple :
public void __DataBinding__control6(object sender, EventArgs e) {
CheckBox box1 = (CheckBox)sender ;
IDataItemContainer container1 =
(IDataItemContainer) box1.BindingContainer ;
box1.Checked = ( (string) DataBinder.Eval(

940

Chapitre 23 : ASP.NET 2.0


container1.DataItem,"Nom") ).Contains("o") ;
}

La proprit BindingContainer du contrle CheckBox retourne une rfrence vers la ligne


en cours de traitement. En loccurrence, cette ligne est matrialise par un contrle de type
GridViewRow qui prsente linterface IDataItemContainer. Enfin, la mthode Eval() de la
classe DataBinder est capable par rflexion dextraire le nom de lemploy courant.
Bien quils ne soient pas prsents dans la nouvelle hirarchie de contrle de prsentation de
donnes dASP.NET 2.0, nous signalons la possibilit dexploiter deux types de contrles particulirement adapts lutilisation des templates : les contrles Repeater et DataList. Leur
utilisation est illustre par lexemple suivant :
Exemple 23-69 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT EmployeId, Nom, T
el FROM EMPLOYES" />
Repeater:<br/>
<asp:Repeater ID="MyRepeater" DataSourceID="DataSrc" runat="server">
<ItemTemplate>
Nom:<%# Eval("Nom") %> Telephone:<%# Eval("T
el") %><br/>
</ItemTemplate>
</asp:Repeater>
<br/>DataList:<br/>
<asp:DataList ID="MyDataList" DataSourceID="DataSrc" runat="server"
RepeatColumns="3" RepeatDirection="Vertical">
<ItemTemplate>
Nom:<%# Eval("Nom") %> Telephone:<%# Eval("T
el") %><br/>
</ItemTemplate>
</asp:DataList>
</form>
</body>
</html>
Il existe trois autres types de templates en plus du template Eval :
<%#
<%#
<%#
<%#

Eval( "Colonne|Propriete|Champ" [,"format"] ) %>


Bind( "Colonne|Propriete|Champ" [,"format"]) %>
XPath( "Expression-XPath" [,"format"]) %>
XPathSelect( "Expression-XPath") %>

La chane de caractres optionnelle format permet de mettre en forme la chane de caractres


qui reprsente la donne value (voir page 375 pour plus de dtails concernant la mise en
forme dune chane de caractres).
Un template de type Bind sutilise de la mme manire quun template de type Eval. Cependant,
un tel template permet aussi de rcuprer les informations saisies par lutilisateur et de les communiquer au contrle de prsentation/dition de donnes parent. Typiquement, les templates

Prsentation et dition des donnes

941

Figure 23 -16 : Utilisation des contrles Repeater et DataList


de type Bind sutilisent lorsque vous activez le mode dition sur un contrle de prsentation.
Un tel template est exploit un peu plus loin dans lExemple 23-73 lorsque nous exposons le
mode dition dun contrle de type FormView. Plus dinformations sur les templates Bind sont
disponibles dans larticle Two-Way Data Binding Syntax des MSDN.
Les templates de type XPath et XPathSelect sutilisent sur des structures de donnes hirarchiques et sont dcrits un peu plus loin.

Le contrle DetailsView
Le contrle DetailsView permet de prsenter une vue dtaille de chaque ligne. Comme illustr
par lexemple suivant, il est ais dutiliser une DetailsView pour naviguer dans les direntes
lignes dune source de donnes plate :
Exemple 23-70 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES" />
<asp:DetailsView ID="Details" DataSourceID="DataSrc" runat="server"
AllowPaging="True" >
<PagerSettings Mode="NextPreviousFirstLast" />
</asp:DetailsView>
</form>
</body>
</html>
Le type de vue du contrle DetailsView est complmentaire celui de contrle GridView. Aussi,
il est courant dutiliser une symbiose de ces deux contrles pour obtenir ce que lon appelle une
vue matresse dtaille dune source de donnes plate (master details view en anglais). Pour obtenir
une telle vue, il est ncessaire dactiver la slection dune ligne sur la GridView au moyen de la
proprit AutoGenerateSelectButton. En outre, il faut dfinir deux sources de donnes : une

942

Chapitre 23 : ASP.NET 2.0

Figure 23 -17 : Utilisation dune DetailsView

exploite par la GridView qui prsente lensemble des lignes et une exploite par la DetailsView
qui prsente la ligne couramment slectionne. Cette deuxime source de donnes utilise la
proprit FilterExpression pour dterminer quelle est la ligne couramment slectionne :
Exemple 23-71 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc1" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES" />
<asp:GridView ID="MyGrid" DataSourceID="DataSrc1" runat="server"
DataKeyNames="EmployeId" SelectedIndex="0"
AutoGenerateSelectButton=true />
<asp:SqlDataSource ID="DataSrc2" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES"
FilterExpression="EmployeId={0}">
<FilterParameters>
<asp:ControlParameter Name="EmployeId" ControlID="MyGrid"
PropertyName="SelectedValue" />
</FilterParameters>
</asp:SqlDataSource>
<asp:DetailsView ID="MyDetails" DataSourceID="DataSrc2"
runat="server" DataKeyNames="EmployeId" />
</form>
</body>
</html>
Une vue matresse dtaille reprsente lalternative optimale pour ldition et linsertion des
donnes de chaque ligne. Comme le montre lexemple suivant, pour obtenir une telle vue il
sut dactiver ces possibilits dinsertion et ddition sur la source de donnes de la DetailsView
ainsi que sur la DetailsView elle-mme :

Prsentation et dition des donnes

Figure 23 -18 : Vue matresse dtaille


Exemple 23-72 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc1" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES"
/>
<asp:GridView ID="MyGrid" DataSourceID="DataSrc1" runat="server"
DataKeyNames="EmployeId" SelectedIndex="0"
AutoGenerateSelectButton=true />
<asp:SqlDataSource ID="DataSrc2" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES"
UpdateCommand= "UPDATE EMPLOYES SET
DepID=@DepID,Nom=@Nom,Pr
enom=@Pr
enom,T
el=@T
el
WHERE EmployeId=@Original_EmployeId"
DeleteCommand="DELETE EMPLOYES WHERE EmployeId=@Original_EmployeId"
InsertCommand="INSERT INTO EMPLOYES (DepID,Nom,Pr
enom,T
el)
VALUES(@DepID,@Nom,@Pr
enom,@T
el)"
FilterExpression="EmployeId={0}"
OnDeleted=OnChgData OnInserted=OnChgData OnUpdated=OnChgData >
<FilterParameters>
<asp:ControlParameter Name="EmployeId" ControlID="MyGrid"
PropertyName="SelectedValue" />
</FilterParameters>
</asp:SqlDataSource>

943

944

Chapitre 23 : ASP.NET 2.0


<asp:DetailsView ID="MyDetails" DataSourceID="DataSrc2"
runat="server" DataKeyNames="EmployeId"
AutoGenerateDeleteButton="True"
AutoGenerateEditButton="True"
AutoGenerateInsertButton="True"/>
</form>
</body>
</html>
<script language=C# runat="server">
void OnChgData(Object sender, EventArgs e) { MyGrid.DataBind() ; }
</script>

Figure 23 -19 : dition/Insertion avec vue Matresse/Dtaille


Notez lobligation dinvoquer la mthode DataBind() sur notre GridView chaque fois quune
donnes est mise jour par lintermdiaire du DetailsView. Sans cette manipulation, les donnes de la GridView ne seraient pas mises jour automatiquement.
Le contrle DetailsView prsente de nombreuses proprits de configuration ainsi que plusieurs vnements qui peuvent vous tre utile. Nous vous invitons consulter les MSDN pour
plus dinformations. En page 688 nous exposons comment obtenir ce type de vue dans le cadre
dune fentre Windows Forms.

Le contrle FormView
Le contrle serveur FormView est similaire au contrle DetailsView ceci prs quil est particulirement adapt la personnalisation de la prsentation et de ldition des donnes dune ligne.
Contrairement au contrle DetailsView, le contrle FormView ne fournit pas de code par dfaut pour prsenter les donnes. Il est ncessaire de fournir votre propre code dans des sections
<ItemTemplate>, <EditItemTemplate> et <InsertItemTemplate>. Comme le montre lexemple
suivant, lutilisation des templates devient particulirement pratique au sein de ces sections :

Prsentation et dition des donnes

945

Exemple 23-73 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd = ; database = ORGANISATION"
SelectCommand="SELECT EmployeId, Nom, T
el FROM EMPLOYES"
UpdateCommand=
"UPDATE EMPLOYES SET Tel=@T
el WHERE EmployeId=@Original_EmployeId"/>
<asp:FormView ID="Details" DataSourceID="DataSrc" runat="server"
DataKeyNames="EmployeId" AllowPaging="True" >
<ItemTemplate>
Nom : <%# Eval("Nom") %> <br/>
Telephone : <%# Eval("T
el") %> <br/>
<asp:Button ID="BtnEdit" runat="server" CommandName="Edit"
Text="Edition du numero de t
el
ephone"/>
</ItemTemplate>
<EditItemTemplate>
Nom : <%# Eval("Nom") %> <br/>
Telephone : <asp:TextBox ID="EditPhone" runat="server"
Text=<%# Bind("T
el") %> /> <br/>
<asp:Button ID="BtnUpdate" runat="server" CommandName="Update"
Text="Mise `a jour du numero de t
el
ephone."/>
<asp:Button ID="BtnCancel" runat="server" CommandName="Cancel"
Text="Annuler la mise `
a jour."/>
</EditItemTemplate>
</asp:FormView>
</form>
</body>
</html>

Figure 23 -20 : Utilisation du contrle FormView

Prsenter un document XML


Nous ne parlons pas ici des contrles de prsentation de donnes hirarchiques TreeView et
Menu. Sachez quils sont brivement couverts en page 955.

946

Chapitre 23 : ASP.NET 2.0

Nous portons plutt notre attention sur le fait quune source de donnes hirarchique peut
tre exploite par des contrles de prsentation originalement destins tre lis des sources
de donnes plates. Dans ce cas, un tel contrle prsente le premier niveau de hirarchie des
donnes. Lavantage est que vous pouvez utiliser un autre contrle de prsentation de donnes
imbriqu dans le premier afin daccder au second niveau de hirarchie. Par exemple considrez
le document XML suivant qui prsente des livres, et pour chaque livre, la liste des chapitres :
Exemple 23-74 :

books.xml

<?xml version="1.0" encoding="utf-8" ?>


<bookstore>
<book title="Linsoutenable l
eg`
eret
e de l
etre"
author="Kundera" publicationdate="1985" >
<chapter name="La leg`eret
e et la pesanteur"/>
<chapter name="Lame et le corps"/>
</book>
<book title="Lanzarote"
author="Michel Houellebecq" publicationdate="2002" >
<chapter name="Lanzarote"/>
<chapter name="Compte rendu de mission"/>
</book>
<book title="Trilogie New_Yorkaise"
author="Paul Auster" publicationdate="2002" >
<chapter name="Cite de verre"/>
<chapter name="Revenants"/>
<chapter name="La chambre d
erob
ee"/>
</book>
</bookstore>
La page suivante permet de prsenter les livres publis aprs lan 2000 ainsi que pour chaque
livre, la liste des chapitres. Remarquez lutilisation des templates de type XPath et XPathSelect
pour slectionner respectivement un attribut et plusieurs nuds. Notez aussi lutilisation de
deux contrles Repeater imbriqus permettant de prsenter deux niveaux de hirarchie. Enfin,
notez lutilisation dune requte XPath au niveau de la source de donnes pour ne slectionner
que les livres publis aprs lan 2000 :
Exemple 23-75 :
<%@ Page Language=C# %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
Liste des livres publies apr
es 2000:<br/>
<asp:XmlDataSource id="MySrc" DataFile="~/App_Data/books.xml"
XPath="/bookstore/book[@publicationdate>2000]"
runat="server"/>
<asp:Repeater id="ListBook" DataSourceId="MySrc" runat="server">
<ItemTemplate>
Titre : <%# XPath("@title") %>
Auteur:<%# XPath("@author") %>
Date de publication:<%# XPath("@publicationdate")%><br/>

Master pages

947

<asp:Repeater id="ListChapter" runat="server"


DataSource=<%# XPathSelect("chapter") %> >
<ItemTemplate>- <%# XPath("@name")%><br/></ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
</form>
</body>
</html>
Voici le contenu de cette page :

Figure 23 -21 : Exploitation dun document XML

Master pages
Master page et page de contenu
Pour garantir la cohrence visuelle dun site, il est courant que toutes ses pages prsentent des
parties similaires telles que lentte ou le bas de page. En ASP.NET 1.x, il faut avoir recours aux
contrles utilisateurs pour pouvoir rutiliser des parties similaires. Cette approche a cependant
linconvnient dobliger le dveloppeur crer plusieurs contrles utilisateur, par exemple un
pour lentte et un pour le bas de page. En outre, il faut copier la dclaration de chacun de ces
contrles dans chaque page du site, ce qui entrane des problmes de mise jour.
ASP.NET 2.0 prsente la notion de master page qui permet de rutiliser simplement un modle
de design de page sur les pages dun site. Lapplication dun tel modle est illustre par les deux
pages de la figure suivante :
Un modle de design de page (i.e une master page) se prsente sous la forme dun fichier dextension .master qui commence par une directive <%@ master%>. Le contenu dun tel fichier
est similaire celui dune page .aspx au dtail prs quil peut contenir des contrles de type
<asp:ContentPlaceHolder>. Un tel contrle dfinit un emplacement o les pages du site pourront placer leur propre contenu. Voici le code de la master page de lexemple de notre figure :

948

Chapitre 23 : ASP.NET 2.0

Figure 23 -22 : Exemple dune master page

Exemple 23-76 :

MyMasterPage.master

<%@ master language="C#" %>


<html><body>
<form id="Form1" runat="server">
<table id="Tbl1" width="100%" height="30px"
cellspacing="0" cellpadding="0">
<tr> <td width="100%" align="center" bgcolor="silver">Haut</td> </tr>
</table>
<table id="Tbl2" width="100%" height="50%"
cellspacing="0" cellpadding="0">
<tr>
<td width="60px" align="center" bgcolor="yellow">Gauche</td>
<td align="center">
<asp:contentplaceholder id="MyContentPH" runat="Server">
</asp:contentplaceholder>
</td>
<td width="60px" align="center" bgcolor="yellow">Droite</td>
</tr>
</table>
<table id="Tbl3" width="100%" height="30px"
cellspacing="0" cellpadding="0">
<tr> <td width="100%" align="center" bgcolor="silver">Bas</td> </tr>
</table>
</form></body></html>
Voici le code dune page .aspx qui exploite la master page MyMasterPage.master. On dit quune
telle page est une page de contenu :

Master pages
Exemple 23-77 :

949
Default.aspx

<%@ Page Language="C#" MasterPageFile="~/MyMasterPage.master" %>


<asp:content id="MyContent" contentplaceholderid="MyContentPH"
runat="server" >
<asp:label id="lbl"
Height="100%"
Width="100%"
BackColor="Black" ForeColor="White" runat="server"
Font-Size="XX-Large">Contenu de Default.aspx</asp:label>
</asp:content>
On remarque que lon spcifie la master page grce la sous directive MasterPageFile de la directive <%@ Page%>. Une page de contenu ne peut avoir pour contrles racines que des contrles
de type <asp:Content>. La proprit ContentPlaceHolderId dun tel contrle est obligatoire.
Elle doit tre initialise avec lidentificateur dun contrle de type <asp:ContentPlaceHolder>
de la master page. Ainsi, le contenu dfini au sein dun contrle de type <asp:Content> sera
inject lexcution dans la master page la place du contrle <asp:ContentPlaceHolder> associ.
Sachez quune page de contenu nassocie pas obligatoirement un contrle <asp:Content>
chaque contrle <asp:ContentPlaceHolder> de sa master page. En outre, la dfinition
dune master page peut contenir des contrles <asp:Content> associs ses propres contrles
<asp:ContentPlaceHolder>. Le contenu dun tel contrle <asp:Content> sera inject la
place du contrle <asp:ContentPlaceHolder> associ par dfaut lorsque la page de contenu
ne fournit pas un contrle <asp:Content> adquat.
Une particularit intressante des master pages est que Visual Studio .NET 2005 est capable dafficher en mode lecture seule le modle de design lors de ldition dune page qui exploite une
master page.
lexcution, une master page est matrialise par un objet, instance dune classe drive de
la classe System.Web.UI.MasterPage. Cette classe drive elle-mme de la classe UserControl.
Une page de contenu se compile comme toute page .aspx. Cependant, laccesseur get de la
proprit MasterPage Master{get;set;} dune page de contenu retourne lexcution une rfrence vers lobjet qui reprsente la master page associe. Le moteur ASP.NET 2.0 soccupe de
fabriquer larborescence de contrles de la page rendue. Pour cela, il fusionne larborescence
de contrles dfinie dans le contrle utilisateur master page et les arborescences de contrles
dfinies dans chaque contrle <asp:Content> de la page de contenu.
Une consquence du fait quASP.NET implmente la notion de master page sous la forme de
contrles utilisateurs est que les techniques de cache de pages que nous avons dj vu sont exploitables dans une page de contenu.

Master pages encapsules


Vous avez la possibilit dencapsuler une master page dans une autre master page. Cette possibilit est illustre par la page ci-dessous :
Pour reproduire cette page, il sut de construire dans notre solution une nouvelle master
page que nous nommons MyMasterPageNested.master qui est associe notre master page
initiale MyMasterPage.aspx grce une sous directive MasterPageFile de la directive <%@
master%>. linstar dune page de contenu, une master page encapsule ne peut contenir
que des contrles <asp:Content>. En revanche ces contrle peuvent eux-mmes contenir des
contrles <asp:ContentPlaceHolder>.

950

Chapitre 23 : ASP.NET 2.0

Figure 23 -23 : Exemple dune master page encapsule


Exemple 23-78 :

MyMasterPageNested.master

<%@ master language="C#" MasterPageFile="~/MyMasterPage.master"%>


<asp:Content ContentPlaceHolderID=MyContentPH runat="server">
<table id="Tbl1" width="100%" height="30px"
cellspacing="0" cellpadding="0">
<tr> <td width="100%" align="center" bgcolor="olive">Haut</td> </tr>
</table>
<table id="Tbl2" width="100%" height="50%"
cellspacing="0" cellpadding="0">
<tr>
<td width="60px" align="center" bgcolor="red">Gauche</td>
<td align="center">
<asp:contentplaceholder id="MyContentPH2" runat="Server">
</asp:contentplaceholder>
</td>
<td width="60px" align="center" bgcolor="red">Droite</td>
</tr>
</table>
<table id="Tbl3" width="100%" height="30px"
cellspacing="0" cellpadding="0">
<tr> <td width="100%" align="center" bgcolor="olive">Bas</td> </tr>
</table>
</asp:Content>
Une page de contenu peut tre associe une master page encapsule de la mme manire que
si cette dernire ntait pas encapsule :
Exemple 23-79 :

Default.aspx

<%@ Page Language="C#" MasterPageFile="~/MyMasterPageNested.master"


<asp:content id="MyContent" contentplaceholderid="MyContentPH2"
runat="server" >
<asp:label id="lbl"
Height="100%"
Width="100%"
BackColor="Black" ForeColor="White" runat="server"

%>

Master pages

951
Font-Size="XX-Large">Contenu de Default.aspx</asp:label>

</asp:content>
Notez que Visual Studio 2005 ne sait pas passer en mode design de page ds lors que vous utilisez
des master pages encapsules.

Configuration de master page


Dans des gros sites avec un grand nombre de pages, le simple fait de devoir mettre une sous directive MasterPageFile dans chaque page de contenu peut tre problmatique pour des raisons
de maintenance. Aussi, vous pouvez avoir recours la proprit masterPageFile de llment
de configuration <pages> afin de prciser ASP.NET que toutes les pages impactes par cet
lment doivent tre associe avec une certaine master page. Seules seront impactes les pages
de contenu qui nont pas de sous directive MasterPageFile.
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<pages masterPageFile="~/MyMasterPage.master"/>
...
En plus de ces deux techniques dassociation statique (i.e la compilation) de master page
des pages de contenu vous pouvez dynamiquement (i.e lexcution) choisir avec quelle master
page une page de contenu doit tre rendue. Pour cela il sut de positionner la proprit string
MasterPageFile{get;set;} dune page de contenu dans lvnement OnPreInit().
Exemple 23-80 :

Default.aspx

<%@ Page Language="C#" %>


<asp:content id="MyContent" contentplaceholderid="MyContentPH"
runat="server" >
<script language=C# runat="server">
protected override void OnPreInit(EventArgs e) {
this.MasterPageFile ="~/MyMasterPage2.master";
base.OnPreInit(e);
}
</script>
<asp:label id="lbl"
Height="100%"
Width="100%"
BackColor="Black" ForeColor="White" runat="server"
Font-Size="XX-Large">Contenu de Default.aspx</asp:label>
</asp:content>
Cette technique peut se rvler trs puissante pour certains gros sites mais nous vous conseillons
de lutiliser avec parcimonie du fait des contraintes quelle induit. Notamment, elle vous
rend responsable de la maintenance des correspondances entre identificateurs des contrles
<asp:ContentPlaceHolder> car elle empche de facto toute vrification de la part du compilateur.

952

Chapitre 23 : ASP.NET 2.0

Accder une master page partir dune page de contenu


Il peut tre utile de pouvoir accder aux contrles de la master page directement partir
du code dune page de contenu. Pour illustrer cette possibilit, modifions notre master page
MyMasterPage de faon introduire un contrle serveur qui reprsente la cellule dentte :
Exemple 23-81 :

MyMasterPage.master

<%@ master language="C#" %>


<script language=C# runat="server">
public HtmlTableCell CellHeader {
get { return _CellHeader ; }
set { _CellHeader = value ; }
}
</script>
<html>
<body>
<form id="Form1" runat="server">
<table id="Tbl1" width="100%" height="30px"
cellspacing="0" cellpadding="0" >
<tr>
<td id="_CellHeader" runat="server" width="100%" align="center"
bgcolor="silver" >Haut</td>
</tr>
</table>
...
Daprs la faon dont les master pages sont compiles par ASP.NET, il est clair que nous pouvons avoir accs ce contrle _CellHeader partir du code de notre page de contenu comme
ceci :
Exemple 23-82 :

Default.aspx

<%@ Page Language="C#" MasterPageFile="~/MyMasterPage.master" %>


<asp:content id="MyContent" contentplaceholderid="MyContentPH"
runat="server" >
<script language=C# runat="server">
void Page_Load(object sender, EventArgs e) {
HtmlTableCell cell =
Master.FindControl("_CellHeader") as HtmlTableCell;
cell.BgColor = "white";
}
</script>
<asp:label id="lbl"
Height="100%"
Width="100%"
BackColor="Black" ForeColor="White" runat="server"
Font-Size="XX-Large">Contenu de Default.aspx</asp:label>
</asp:content>
Comprenez bien que dans cet exemple, nous crons un lien tardif entre le code de la mthode
Page_Load() de la page de contenu et lobjet reprsentant le contrle responsable du rendu de
la cellule entte dans la master page. Les liens tardifs introduisent de la souplesse au dtriment
des performances et des vrifications statiques.

Internationaliser une application ASP.NET 2.0

953

Dans le cas o la master page est dfinie statiquement dans une sous directive MasterPageFile
de la page de contenu nous navons pas besoin de la souplesse des liens tardifs. En eet, dans
ce cas nous connaissons ds la compilation le type de master page utilis. Aussi, vous pouvez
utiliser une directive <%@ MasterType%> dans la page de contenu afin de spcifier au compilateur quel type de master page vous souhaitez utiliser. Cela peut se faire au moyen dune sous
directive VirtualPath qui spcifie le nom du fichier contenant la master page ou au moyen
dune sous directive TypeName qui nomme directement la classe reprsentant la master page.
Le compilateur fera alors en sorte que le type de la proprit Master{get;set;} de la classe
reprsentant la page de contenu soit le bon. Vous pourrez alors utiliser des liens prcoces sur
cette master page.
Pour exploiter au mieux cette possibilit, il est conseill de fournir des proprits publiques sur
la classe de master page donnant accs aux contrles susceptibles dtre exploits partir de la
page de contenu ( linstar de ce que nous avons fait avec la proprit CellHeader dfinie dans
notre master page) :
Exemple 23-83 :

Default.aspx

<%@ Page Language="C#" MasterPageFile="~/MyMasterPage.master" %>


<%@ MasterType VirtualPath="~/MyMasterPage.master" %>
<asp:content id="MyContent" contentplaceholderid="MyContentPH"
runat="server" >
<script language=C# runat="server">
void Page_Load(object sender, EventArgs e) {
Master.CellHeader.BgColor = "white";
}
</script>
<asp:label id="lbl"
Height="100%"
Width="100%"
BackColor="Black" ForeColor="White" runat="server"
Font-Size="XX-Large">Contenu de Default.aspx</asp:label>
</asp:content>

Internationaliser une application ASP.NET 2.0


Avons daborder la prsente section, il faut avoir assimil les concepts dinternationalisation
dune application .NET qui font lobjet de la section 2 Internationalisation et assemblages
satellites , page 34.
Dans le cas dune application ASP.NET 2.0, Visual Studio 2005 prsente des facilits pour diter
plusieurs versions localises dune mme page web. Lorsque vous slectionnez une page web
XXX.aspx en mode design, vous pouvez slectionner le menu Tools  Generate Local Resources.
Un fichier ressource XXX.aspx.resx est alors ajout dans le rpertoire /App_LocalResources de
lapplication web. Ce fichier contient les valeurs relatives la culture invariante des ressources
de cette page web. Vous pouvez alors ajouter des fichiers ressources relatifs dautres cultures
en faisant : rpertoire App_LocalResources  Add New Item  Assembly Ressource File  XXX.yyZZ.resx. En ditant ce fichier, vous pouvez modifier les valeurs des ressources de la version de la
page relative la culture yy-ZZ. Notez que tout ceci sapplique aussi aux contrles utilisateurs
(i.e aux fichiers dextension .ascx) et au master pages (i.e aux fichiers dextension .master).
Il est intressant de remarquer que le code ASP.NET dune page est dirent selon que
celle-ci est internationalise ou pas. Notamment, les valeurs des proprits internatio-

954

Chapitre 23 : ASP.NET 2.0

nalisables des contrles serveurs ne sont plus prsentes puisquelles sont contenues dans
les fichiers ressources associs la page. En outre, chaque contrle contient un attribut
meta:resourcekey="XXX" o XXX dsigne une chane de caractres utilise pour identifier le
contrle au niveau des fichiers ressources.
La valeur de la proprit CurrentUICulture du thread qui traite une requte web est dtermine
durant lappel la mthode virtuelle Page.InitializeCulture() au dbut du cycle de vie de la
requte. Par dfaut, la culture du client est envoye par le navigateur chaque requte et elle
adopte par ASP.NET 2.0 pour cette requte. Vous pouvez changer ce mode de fonctionnement
en rcrivant cette mthode InitializeCulture().
Pour tester une page selon direntes cultures soit :

Vous modifiez la culture par dfaut de votre navigateur. Sous Internet Explorer il sut daller
dans le menu Outils Options internet Gnral  Langues  fournir une liste de culture, cellesci seront traites par ordre de priorit par ASP.NET.

Vous positionnez la proprit UICulture de la page la culture souhaite. Attention bien


remettre cette proprit la valeur Auto une fois la page teste.

Vous pouvez fournir une culture par dfaut toutes les pages situes (rcursivement) dans un
rpertoire au moyen des attributs culture et uiCulture de la section <globalization> du fichier
Web.Config. Cette culture sera choisie pour traiter une requte qui ne spcifie pas de culture ou
pour traiter une requte dont la culture nest pas supporte.
Enfin, notez que vous pouvez placer des fichiers de ressources globaux votre application web
dans le rpertoire /App_GlobalResources.

Aides la navigation dans un site


ASP.NET 2.0 prsente une infrastructure extensible permettant dinsrer dans vos pages des
contrles daide la navigation dans un site. Laide la navigation peut se faire principalement
avec trois types de contrles : les TreeView, les Menu et les SiteMapPath. Ils sont illustrs par les
trois captures dcran suivantes :

Figure 23 -24 : Navigation avec un TreeView

Aides la navigation dans un site

955

Figure 23 -25 : Navigation avec un Menu

Figure 23 -26 : Rappel du chemin menant la page courante avec un SiteMapPath


Pour bnficier de laide la navigation, il faut tout dabord ajouter un fichier dextension
.SiteMap votre site web. Ce fichier XML permet de dcrire lorganisation du site grce une
arborescence XML o chaque nud reprsente une page :
Exemple 23-84 :

App.SiteMap

<?xml version="1.0" encoding="utf-8" ?>


<siteMap>
<siteMapNode title="Home" url="~/Default.aspx">
<siteMapNode title="Formations" url="~/Formations.aspx">
<siteMapNode title="Formations Pro" url="~/FormationsPro.aspx">
<siteMapNode title=".NET 2.0" url="~/DotNet2.aspx"/>
<siteMapNode title=".NET avanc
e" url="~/DotNetAdvanced.aspx"/>
<siteMapNode title="ASP.NET 2.0" url="~/ASPDotNet2.aspx"/>
</siteMapNode>
</siteMapNode>
<siteMapNode title="Articles" url="~/Articles.aspx">
<siteMapNode title="Nouveaut
es .NET2.0" url="~/DotNet2News.aspx"/>
</siteMapNode>
<siteMapNode title="Contact" url="~/Contact.aspx"/>
</siteMapNode>
</siteMap>
Ensuite, il faut communiquer ASP.NET notre souhait dexploiter les facilits daide la navigation dans ce site au moyen dune une balise <siteMap> dans le fichier de configuration :

956

Chapitre 23 : ASP.NET 2.0


Web.Config

Exemple :
<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<pages masterPageFile="~/MyMasterPage.master"/>
<siteMap defaultProvider="MySiteMapProvider" enabled="true"
<providers>
<add type="System.Web.XmlSiteMapProvider"
name="MySiteMapProvider"
siteMapFile= "App.SiteMap"/>
</providers>
</siteMap>
...

>

On voit apparatre la notion de fournisseur dorganisation de site (SiteMap provider en anglais). Vous configurez un tel fournisseur au moyen dune classe drivant de System.Web.
SiteMapProvider et dune source de donne contenant lorganisation du site. Ici, nous nous
servons de la classe System.Web.XmlSiteMapProvider et de notre fichier App.SiteMap. Il sagit
bien dune utilisation du design pattern provider prsent en page 902.
Un mme site peut contenir plusieurs fournisseurs dorganisation de site. De plus, lorganisation
dun site peut tre stocke dans plusieurs fichiers .SiteMap. Ces deux possibilits font lobjet de
larticle How to: Configure Multiple Site Maps and Site-Map Provider des MSDN.
Vous pouvez crer votre propre type de fournisseur dorganisation de site au moyen dune classe
propritaire drive de SiteMapProvider. Par exemple, vous pouvez souhaiter stocker lorganisation de votre site dans une base de donnes ou bien intervenir dynamiquement sur larborescence prsente au client. La fabrication dune telle classe est expose dans larticle SiteMapProvider Class (System.Web) des MSDN.
Une fois que vous avez configur un fournisseur dorganisation de site, vous devez ajouter
chaque page susceptible de contenir un contrle daide la navigation, un contrle de type
<asp:SiteMapDataSource> lie ce fournisseur. Vous pouvez alors vous servir de contrles de
type <asp:Menu> <asp:TreeView> et <asp:SiteMapPath> pour aider la navigation. En plaant
de tels contrles dans des master pages, on peut facilement dvelopper un modle puissant de
design de page dun site. Bien entendu, chacun de ces contrles prsente de nombreuses proprits permettant de modifier leur rendu graphique.
Lexemple suivant illustre lutilisation dun contrle <asp:Menu> au sein dune master page :
Exemple 23-85 :

MyMasterPage.master

<%@ master language="C#" %>


<html><head runat="server"/><body>
<form id="Form1" runat="server">
<table id="Tbl1" width="100%" height="30px"
cellspacing="0" cellpadding="0" >
<tr> <td width="100%" bgcolor="silver">Smacchia Site</td> </tr>
</table>
<table id="Tbl2" width="100%" height="50%"
cellspacing="0" cellpadding="0">

Scurit

957

<tr>
<td width="30%" bgcolor="yellow">
<asp:SiteMapDataSource ID="MySiteMapDataSource"
runat="server" />
<asp:Menu ID="Menu1" runat="server" Orientation="Horizontal"
DataSourceID="MySiteMapDataSource" >
</asp:Menu>
</td>
<td align="center">
<asp:contentplaceholder id="MyContentPH" runat="Server">
</asp:contentplaceholder>
</td>
</tr>
</table>
</form></body></html>

Scurit
Il est conseill davoir assimil les concepts prsents dans le chapitre 6 consacr la scurit
avant daborder cette section.
La gestion de la scurit est particulirement importante dans le cas dapplications web. En eet,
lorsque lapplication est accessible partir dinternet, il faut absolument quelle interdise laccs
aux ressources critiques du serveur aux utilisateurs non autoriss. Il faut donc un mcanisme
dauthentification, permettant didentifier lentit lorigine dune requte. Il faut aussi un
mcanisme dautorisation permettant de spcifier quelles sont les ressources accessibles durant
lexcution dune requte, en fonction de lidentit lorigine de la requte.

Authentification des utilisateurs Windows avec IIS


IIS prsente un mcanisme dauthentification indpendant des mcanismes dASP.NET. Vous
pouvez choisir de nactiver lauthentification quau niveau dIIS, quau niveau dASP.NET ou au
niveau dIIS et dASP.NET.
Pour configurer lauthentification IIS pour un rpertoire virtuel donn, il faut eectuer la manipulation suivante avec loutil de configuration dIIS : [click droits sur le rpertoire virtuel concern]
 Proprits  Scurit du rpertoire  Connexion anonyme et contrle didentification  Modifier.
Vous obtenez alors la fentre suivante :
IIS ore quatre modes dauthentification : Connexion Anonyme, Authentification Digest, Authentification de base et Authentification intgre Windows. Ils sont tous bass sur les comptes
utilisateurs Windows.

Le mode dauthentification Connexion anonyme nore aucun contrle. Il implique que tous
les clients peuvent accder lapplication. Dans le cas o ce mode est activ avec dautres
modes dauthentification IIS, tout se passera comme si seul le mode dauthentification anonyme tait activ. Vous avez la possibilit de fournir un compte Windows qui reprsente les
utilisateurs anonymes. Ce compte doit obligatoirement avoir un mot de passe non vide.

Le mode dAuthentification Digest envoie une valeur de hachage du mot de passe dun utilisateur Windows plutt que le mot de passe lui-mme. Cette technique a donc lavantage

958

Chapitre 23 : ASP.NET 2.0

Figure 23 -27 : Mthodes dauthentification sous IIS


dviter de faire circuler le mot de passe sur le rseau. Cependant cette technique nest pas
encore supporte par tous les navigateurs.

Le mode dAuthentification de base nest pas vraiment scuris. En eet, le nom de lutilisateur ainsi que son mot de passe sont envoys au serveur sous une forme encode et non pas
chire. La technique dencodage base-64 est utilise. Vous pouvez cependant avoir recours
au mode HTTPS afin de crypter ces donnes sensibles.

Le mode dAuthentification intgre Windows utilise un systme dchange de donnes chiffres avec le client. Ce mode nest support que par les clients .NET et le navigateur Internet
Explorer.

Au niveau dune application ASP.NET, les mcanismes dauthentifications dIIS ne comptent


que si vous activer le mcanisme demprunt didentit comme ceci :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<identity impersonate = "true" />
...
En page 209, nous expliquons que le mcanisme demprunt didentit permet daecter un utilisateur Windows authentifi au contexte de scurit dun thread non gr. En loccurrence il sagit
de lutilisateur Windows authentifi par IIS et du thread non gr sous-jacent au thread gr qui

Scurit

959

sert la requte dans le processus dASP.NET. Rappelons alors que Windows vrifie la validit de
tout accs aux ressources Windows (fichiers, rpertoires etc) de la part dun thread. Pour cela, il
se base sur le compte utilisateur associ au thread et au descripteur de scurit de la ressource
(voir page 211). Rappelons que par dfaut, le processus dASP.NET et chacun de ses threads
sexcutent dans le contexte de lutilisateur Windows spcifi dans la section <processModel> du
fichier de configuration Machine.Config (voir page 890).
Vous pouvez aussi contrler programmatiquement lappartenance un rle Windows de lutilisateur Windows associ au thread courant comme ceci :
...
IPrincipal p = Thread.CurrentPrincipal ;
if( p.IsInRole(@"BUILTIN\Administrators") ){
// Effectuer ici les operations r
eserv
ees aux administrateurs
}
...
ASP.NET permet de simplifier considrablement cette opration de vrification. En eet,
dans le fichier de configuration de lapplication, vous pouvez dfinir une liste de rgles daccs
quASP.NET doit appliquer chaque requte. Concrtement une rgle est une permission
(allow) ou une interdiction (deny) deectuer la requte, en fonction du principal ou du rle
jou par le principal. Par exemple pour nautoriser que les utilisateurs Mathieu et Julien et les
administrateurs locaux eectuer des requtes, il faut crire ceci dans le fichier de configuration :
Web.Config

Exemple :
<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<authentication mode="Windows" />
<authorization>
<allow users = "Mathieu,Julien" />
<allow roles = "BUILTIN\Administrators" />
<deny users = "*" />
</authorization>
...

Le nom dutilisateur "*" reprsente tous les utilisateurs. Le nom dutilisateur "?" reprsente
lutilisateur anonyme.
Pour chaque rgle, vous pouvez aussi spcifier un verbe qui prcise la mthode HTTP autorise
ou interdite. Par exemple, pour autoriser tous les utilisateurs utiliser la mthode HTTP-GET,
et seulement les administrateurs locaux utiliser la mthode HTTP-POST il faut crire :
Web.Config

Exemple :
...
<authorization>
<allow users = "*" verb = "GET" />
<allow roles = "BUILTIN\Administrators" verb = "POST" />
<deny users = "*" />

960

Chapitre 23 : ASP.NET 2.0


</authorization>
...

Comme vous le voyez, lalgorithme dapplication de ces rgles est trs simple : la liste des rgles
est parcourue linairement et ds quune rgle est satisfaite par le principal courant, elle est
applique. Notez que la liste des rgles est en fait la concatnation des listes de rgles spcifies
dans chaque fichier de configuration. Donc, cette liste commence par les rgles du fichier de
configuration du sous rpertoire courant de lapplication et se finit par les rgles du fichier de
configuration de la machine.

Lauthentification au niveau dASP.NET


ASP.NET supporte lauthentification par lintermdiaire de couches logicielles appeles fournisseurs dauthentification. lheure actuelle, ASP.NET supporte les fournisseurs dauthentification
Windows, Forms et Passport. Vous pouvez dvelopper vos propres fournisseurs dauthentification.
Pour configurer le fournisseur dauthentification ASP.NET quune application web doit utiliser,
il sut de paramtrer lattribut mode de llment <authentication> dans le fichier de configuration de lapplication :
<configuration>
<system.web>
<authentication mode="Windows|Form|Passport|none" />
...
En pratique, on utilise le fournisseur dauthentification Windows dans un environnement intranet o les comptes Windows des utilisateurs sont centraliss sur un serveur. On prfre utiliser
les fournisseurs Passport et Forms dans le cadre dapplications accessibles par internet. Dans le
premier cas lapplication exploite le mcanisme dauthentification Microsoft Passport. Dans le
second cas, lapplication doit grer elle-mme ses propres comptes utilisateurs, en gnral laide
dune base de donnes. Nous allons nous concentrer ici sur le fournisseur dauthentification
Forms. Nous vous invitons consulter larticle The Passport Authentication Provider des
MSDN pour plus dinformation concernant le fournisseur dauthentification Passport.

Le fournisseur dauthentification Forms dASP.NET


Le fournisseur dauthentification Forms permet aux applications de prsenter leur propre interface utilisateur pour saisir les donnes ncessaires lauthentification, sous la forme dune page
.aspx classique. La mthode interne dauthentification partir de ces donnes incombe aussi
lapplication web. Lorsquun client non authentifi tente daccder une page, ASP.NET le
redirige automatiquement vers le formulaire dauthentification. Une fois authentifi, ASP.NET
redirige automatiquement lutilisateur vers la page demande. Pour viter au client de devoir
sauthentifier explicitement chaque requte de page, un cookie est cr par ASP.NET lors de
lauthentification. Ce cookie contient une cl qui nest ventuellement valide que pendant une
certaine dure. Ce cookie est communiqu au navigateur client qui le stocke et qui lutilise alors
pour les prochaines requtes. La page suivante illustre une implmentation minimale de ce
scnario :

Scurit
Exemple 23-86 :

961
Login.aspx

<%@ Page Language="C#" %>


<script runat="server">
void Btn_Click(Object sender, EventArgs e) {
// En general, on acc`ede ici `
a une base de donn
ees
// pour authentifier lutilisateur.
if (Usr.Text == "pat" && Pwd.Text == "pwd")
FormsAuthentication.RedirectFromLoginPage(Usr.Text, true);
else
Msg.Text = "Mauvais login, veuillez r
eessayer." ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="form1" runat="server">
<asp:Label ID="Lbl1" runat="server" Text="Utilisateur:"/>
<asp:TextBox ID="Usr" runat="server"/>
<asp:Label ID="Lbl2" runat="server" Text="Mot de passe:"/>
<asp:TextBox ID="Pwd" runat="server" TextMode="Password"/>
<asp:Button ID="Button1" runat="server" Text="Login"
OnClick="Btn_Click" /><br/>
<asp:Label ID="Msg" runat="server"/>
</form>
</body>
</html>
Il faut bien entendu spcifier ASP.NET que les requtes anonymes (i.e sans cookie dauthentification) doivent tre rediriges vers cette page :
Exemple :

Web.Config

<?xml version="1.0"?>
<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
<authentication mode="Forms">
<forms loginUrl="Login.aspx" />
</authentication>
...
Le simple appel la mthode RedirectFromLoginPage() sut ASP.NET pour crer un cookie,
le communiquer au client et le rediriger vers la page dorigine. On saperoit quASP.NET stocke
la page vers laquelle le client doit tre redirig dans lURL utilise lors de la premire redirection
vers la page dauthentification :
http://localhost:1968/MyWebSite/...
... Login.aspx?ReturnUrl=%2fMyWebSite%2fdefault.aspx

962

Chapitre 23 : ASP.NET 2.0

La section <form> prsente plusieurs attributs pour nommer le cookie, protger les informations qui y sont contenues et lui assigner une dure de validit.
linstar de ce que nous avons vu en page 901 concernant la gestion de session sans cookies, la
gestion de lauthentification peut se faire aussi sans cookies. Tout comme llment <sessionState> llment <forms> prsente un attribut nomm cookieless. Vous pouvez vous servir de
cet attribut pour contraindre ou laisser le choix ASP.NET dutiliser lURI pour stocker la cl
reprsentant la preuve que lutilisateur est authentifi. Voici quoi ressemble une telle URI :
http://localhost:1968/MyWebSite/(F(Xd6tEmGNyNKdZ-Df9B69q4F2JUXxLd7fk8QzQt1qhqWk1FLyUX2_gw31a5XePl8YdR-YzYxEm6kuareBQqgjw2))/default.aspx
Soyez conscient que dans cet exemple, le mot de passe nest pas crypt dans la requte dauthentification. Pour viter ce problme majeur de confidentialit, il faut se servir de HTTPS pour
encrypter cette requte. Comme nous venons de le voir, les autres requtes nont pas besoin
dtre cryptes puisquelles ne contiennent pas le mot de passe.
Nous allons maintenant nous intresser aux possibilits ajoutes par ASP.NET 2.0 au niveau de
la scurit. Vous pouvez maintenant grer les utilisateurs et leur appartenance des rles dune
manire standard au moyen dune base de donnes. En outre, plusieurs nouveaux contrles
serveurs viennent grandement simplifier le dveloppement dapplication ASP.NET supportant
lauthentification.

Gestion des utilisateurs


ASP.NET 2.0 prsente un framework pour standardiser la gestion des comptes utilisateurs par
exemple dans une base de donnes. Ce framework a t conu de faon tre indpendant de
lauthentification par formulaire. Cependant, lintgration de la gestion des utilisateurs et de
lauthentification par formulaire prsente un modle particulirement souple et puissant qui
vous permet de rcrire notre Exemple 23-86 en quatre lignes de code C  :
Exemple 23-87 :

Login.aspx

<%@ Page Language="C#" %>


<script runat="server">
void Btn_Click(Object sender, EventArgs e) {
if ( Membership.ValidateUser( Usr.Text , Pwd.Text ) )
FormsAuthentication.RedirectFromLoginPage(Usr.Text, true) ;
else
Msg.Text = "Mauvais login, veuillez r
eessayer." ;
}
</script>
...
Bien entendu, pour que cet exemple fonctionne, il faut prciser ASP.NET o sont stocks les
donnes relatives aux utilisateurs et comment y accder. Pour cela, il faut avoir recours un
fournisseur dutilisateurs que lon dfini dans le fichier de configuration :
Exemple :
<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

Web.Config

Scurit

963
<connectionStrings>
<add name="MyLocalSqlServer"
connectionString="Data Source=localhost;Integrated
Security=SSPI ;Initial Catalog=MyWebSiteUsers;" />
</connectionStrings>
<system.web>
<membership defaultProvider="MyMembershipSqlProvider" >
<providers>
<add
name="MyMembershipSqlProvider"
type="System.Web.Security.SqlMembershipProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="MyLocalSqlServer" />
</providers>
</membership>

...
Les donnes relatives aux utilisateurs de ce site sont stockes dans une base de donnes nomme
MyWebSiteUsers. Cette base se trouve dans un SGBD local de type SQL Server. Pour construire
une telle base de donnes, il sut dutiliser loutil aspnet_regsql.exe dcrit en page :
>aspnet_regsql.exe -S <host> -U <usr> -P <pwd> -d <database> -ed
Puisque nos donnes utilisateurs sont stockes dans une base de donnes relationnelle,
nous avons prcis ASP.NET dutiliser le fournisseur dutilisateurs System.Web.Security.
SqlMembershipProvider. Un fournisseur dutilisateurs est une classe qui drive de la classe
System.Web.Security.MembershipProvider. Vous pouvez crer votre propre fournisseur en
dveloppant une telle classe. Notez quASP.NET 2.0 fournit aussi par dfaut le fournisseur
System.Web.Security.ActiveDirectoryMembershipProvider. Il sagit bien dune utilisation
du design pattern provider prsent en page 902.
Dans notre Exemple 23-87, la mthode statique bool ValidateUser(string username, string
password) de la classe System.Web.Security.Membership est capable daller vrifier si un utilisateur existe en exploitant le fournisseur dutilisateurs de lapplication. Cette classe prsente
dautres mthodes statiques dont la signification et lutilisation sont particulirement intuitives :
MembershipUser CreateUser(string username, string password) ;
MembershipUser CreateUser(string username, string password, string email) ;
bool DeleteUser(string username) ;
bool DeleteUser(string username, bool deleteAllRelatedData) ;
MembershipUserCollection FindUsersByEmail(string emailToMatch) ;
MembershipUserCollection FindUsersByName(string usernameToMatch) ;
string GeneratePassword(int length, int numOfNonAlphanumericCharacters) ;
MembershipUserCollection GetAllUsers() ;
int GetNumberOfUsersOnline() ;
MembershipUser GetUser() ; // retourne lutilisateur courant
MembershipUser GetUser(string username) ;
string GetUserNameByEmail(string emailToMatch) ;
void UpdateUser(MembershipUser user) ;
bool ValidateUser(string username, string password) ;

964

Chapitre 23 : ASP.NET 2.0

Les instances de la classe MembershipUser reprsentent lexcution les utilisateurs. Cette classe
contient quelques proprits permettant dobtenir le nom, le email, la date de cration, la date
de dernier login, la date de dernire demande de page, la question demander lutilisateur
en cas de perte de mot de passe et quelques mthodes permettant de changer le mot de passe
ou la question associe. Comme vous pouvez le constater, seules les informations relatives
lauthentification sont stockes dans une instance de MembershipUser. Nous verrons dans la prochaine section consacre la personnalisation comment tendre ce mcanisme pour associer
un utilisateur des informations personnelles comme son adresse, son numro de tlphone
voire lhistorique de ses achats.
Sachez quun mcanisme de verrouillage est prvu en cas de trop nombreuses tentatives de
login rates. Dans ce cas, la mthode Membership.ValidateUser() ne peut retourner true
tant que le verrouillage na pas t dsactiv pour lutilisateur concern par lappel la mthode MembershipUser.UnlockOut(). Vous pouvez configurer ce mcanisme avec les attributs
entiers passwordAttemptThreshold et passwordAttemptWindows qui se placent dans llment
de configuration <membership>. Le verrouillage est automatiquement mis en place si plus de
passwordAttemptThreshold logins infructueux ont lieu en moins de passwordAttemptWindows
minutes.

Gestion des rles


En gnral, il est beaucoup plus pratique dutiliser la notion de rle pour valider dans votre
code les permissions des utilisateurs. Aussi, ASP.NET 2.0 prsente un framework permettant de
grer des rles ainsi que lappartenance des comptes utilisateurs ces rles. Pour exploiter ce
framework, il faut avoir recours un fournisseur de rles que lon dfinit dans le fichier de configuration :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<connectionStrings>
<add name="MyLocalSqlServer"
connectionString="Data Source=localhost;Integrated
Security=SSPI ;Initial Catalog=MyWebSiteUsers;" />
</connectionStrings>
<system.web>
<roleManager defaultProvider="MyRoleSqlProvider" enabled="true" >
<providers>
<add
name="MyRoleSqlProvider"
type="System.Web.Security.SqlRoleProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="MyLocalSqlServer" />
</providers>
</roleManager>
...
Comme vous pouvez le constater, la notion de fournisseur de rles se dfinit de la mme
manire que la notion de fournisseur dutilisateurs puisque ici aussi le design pattern provider

Scurit

965

prsent en page 902 est utilis. Un fournisseur de rles est une classe qui drive de la classe
System.Web.Security.RoleProvider. ASP.NET 2.0 prsente les classes SqlRoleProvider,
AuthorizationStoreRoleProvider et WindowsTokenRoleProvider. Vous pouvez crer votre
propre fournisseur de rles au moyen dune classe drivant de RoleProvider.
De la mme manire que lon utilise les mthodes statiques de la classe MembershipUser pour
manipuler programmatiquement les comptes utilisateurs, on utilise les mthodes statiques de
la classe System.Web.Security.Roles pour manipuler programmatiquement les rles. Ici aussi
leurs signatures sont particulirement loquentes :
void AddUsersToRole(string[] usernames,string roleName) ;
void AddUsersToRoles(string[] usernames,string[] roleNames) ;
void AddUserToRole(string username,string roleName) ;
void AddUserToRoles(string username,string[] roleNames) ;
void CreateRole(string roleName) ;
bool DeleteRole(string roleName) ;
bool DeleteRole(string roleName,bool throwOnPopulatedRole) ;
string[] FindUsersInRole(string roleName,string usernameToMatch) ;
string[] GetAllRoles() ;
string[] GetRolesForUser() ;
string[] GetRolesForUser(string username) ;
string[] GetUsersInRole(string roleName) ;
bool IsUserInRole(string roleName) ;
bool IsUserInRole(string username,string roleName) ;
void RemoveUserFromRole(string username,string roleName) ;
void RemoveUserFromRoles(string username,string[] roleNames) ;
void RemoveUsersFromRole(string[] usernames,string roleName) ;
void RemoveUsersFromRoles(string[] usernames,string[] roleNames) ;
bool RoleExists(string roleName) ;
Enfin, remarquez que lon peut dfinir des rgles daccs en se basant sur ce type de rles dans
llment de configuration <authorization> :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<authorization>
<allow roles="admin"/>
<deny roles="simpleUser"/>
</authorization>
...

Contrles serveurs spcialiss dans lauthentification


ASP.NET 2.0 prsente plusieurs contrles serveurs volus qui implmentent les besoins rcurrents concernant la gestion de comptes utilisateurs dun site :

966

Chapitre 23 : ASP.NET 2.0

Le contrle CreateUserWizard prend en charge la cration dutilisateurs avec la vrification du mot de passe et la possibilit de fournir un email et un couple question/rponse
utiliss en cas de perte de mot de passe. Ce contrle est hautement paramtrable avec une
expression rgulire pour le mot de passe, une autre pour le email, des messages derreurs
personnalisables pour tous types derreurs (utilisateur dj existant, champs non remplis
etc.).

Le contrle Login permet un utilisateur de sauthentifier en entrant un nom de compte


et un mot de passe. Cest en gnral la page qui contient ce contrle qui doit tre spcifie
dans lattribut loginUrl.

Le contrle LoginStatus permet dinsrer dans la page HTML en cours de construction un


hyperlien. Cet hyperlien varie selon que lutilisateur courant est authentifi ou non. En
gnral on lutilise pour acher Login dans le cas o lutilisateur nest pas authentifi et
Logout dans le cas contraire.

Le contrle LoginView est similaire au contrle LoginStatus ceci prs quil permet dacher un ensemble de contrles dirents selon que lutilisateur courant est authentifi ou
non.

Le contrle LoginName permet dinsrer dans la page HTML en cours de construction une
chane de caractres contenant le nom de lutilisateur couramment authentifi.

Le contrle PasswordRecovery permet denvoyer un email contenant le mot de passe perdu


ladresse associe avec le compte utilisateur. Il se base sur le systme question/rponse. Si
le mot de passe tait encrypt dune manire irrversible, ASP.NET fabrique un nouveau
mot de passe.

Le contrle ChangePassword permet un utilisateur de changer son mot de passe.

Les contrles tels que CreateUserWizard, ChangePassword ou Login dont les implmentations
exploitent directement le fournisseur dutilisateurs prsentent un attribut MembershipProvider
pour le dfinir.
Signalons que linterface graphique web de ASP.NET 2.0 prsente un onglet scurit qui vous
permet dadministrer lensemble des utilisateurs, des rles et des rgles daccs dune application
web.

Personnalisation
Fournisseur de personnalisation et gestion
des donnes personnelles
ASP.NET 2.0 prsente un framework permettant de stocker et de manipuler dune manire
standard les donnes personnelles de chaque utilisateur. Ici encore, pour prciser le mode de
stockage de ces donnes ASP.NET exploite le design pattern provider. ASP.NET 2.0 ne prsente
quune classe de fournisseur de personnalisation, la classe SqlProfileProvider. Cette classe exploite une base de donnes qui a t prpare au pralable par loutil aspnet_regsql.exe . Bien
entendu, vous pouvez construire votre propre fournisseur de personnalisation au moyen dune
classe drive de System.Web.Profile.ProfileProvider.

Personnalisation
Exemple :

967
Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<connectionStrings>
<add name="MyLocalSqlServer"
connectionString="Data Source=localhost;Integrated
Security=SSPI ;Initial Catalog=MyWebSiteUsers;" />
</connectionStrings>
<system.web>
<profile enabled="true" defaultProvider="MyProfileSqlProvider">
<providers>
<add
name="MyProfileSqlProvider"
type="System.Web.Profile.SqlProfileProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="MyLocalSqlServer" />
</providers>
<properties>
<add name="CompanyName" type="System.String" />
<add name="Birthday" type="System.DateTime" />
<add name="RequestCount" type="System.Int32"
defaultValue="0" />
</properties>
</profile>
...
La section <profile> est consacre la personnalisation. La sous-section <properties> permet
de dfinir les paramtres personnels. Chacun de ces paramtres est nomm, typ et peut avoir
une valeur par dfaut.
Ces paramtres peuvent tre exploits programmatiquement au moyen de la classe Profile.
Le compilateur fait en sorte que la classe ProfileCommon ait une proprit publique dinstance
accessible en criture et en lecture pour chacun des paramtres prciss. Pour accder cette
proprit, il sut dcrire Profile.NomDeLaPropri
et
e:
...
void Page_Load(Object sender, EventArgs e) {
Profile.RequestCount += 1 ;
LabelRequestCount.Text = Profile.RequestCount.ToString() ;
}
...
Le compilateur ASP.NET interprte ceci comme un accs une instance cache de la classe
ProfileCommon. Cette instance cache contient les donnes de lutilisateur couramment logu.
Sil ny a pas dutilisateur couramment authentifi, une exception est leve.
Les donnes personnelles dun utilisateur ne sont charges que lors du premier accs une donne durant le traitement dune requte. Le cas chant, toutes les donnes sont charges. Elles
seront sauvegardes la fin du traitement. Cela signifie que vous navez crire absolument
aucune ligne de code pour prvoir le chargement ou la sauvegarde des donnes.

968

Chapitre 23 : ASP.NET 2.0

Cependant, il peut tre ecace de ne pas charger toutes les donnes personnelles chaque requte par exemple parce quelles reprsentent un gros volume et que lon ne fait quincrmenter
un compteur RequestCount chaque requte. Aussi, ASP.NET 2.0 vous permet de partitionner
vos donnes dans des groupes. Lorsque lon accde une donne dun groupe, seules les donnes
de celui-ci sont charges en mmoire :
Web.Config

Exemple :
...

<properties>
<add name="RequestCount" type="System.Int32" defaultValue="0"/>
<group name="ProfessionalInfo">
<add name="CompanyName" type="System.String"/>
<add name="ProfessionalEmail" type="System.String"/>
</group>
</properties>
...
Un groupe ne peut contenir dautres groupes. Laccs une donne dun groupe se fait par lintermdiaire dune proprit portant le nom du groupe :
...
void Page_Load(Object sender, EventArgs e) {
LblCompanyName.Text = Profile.ProfessionalInfo.CompanyName ;
}
...
Vous pouvez typer une donne utilisateur avec nimporte quel type srialisable en XML
ou en binaire, y compris des types collections tels que System.Collections.Specialized.
StringCollection. Il est intressant daller examiner le contenu des tables cres par aspnet_regsql.exe pour se rendre compte que les donnes personnelles sont sauves sous une
forme srialise. Cependant, vous ne pouvez pas utiliser de types gnriques.

Personnalisation et utilisateurs anonymes


Il peut tre souhaitable de pouvoir sauvegarder des donnes relatives un utilisateur anonyme.
Par exemple, la plupart des sites en ligne de commerce vous autorisent commencer vos achats
avant mme de vous authentifier. Le processus dauthentification na alors lieu en gnral quen
fin dachat, lorsque vous devez imprativement fournir vos paramtres personnels tels que votre
numro de carte bancaire et votre adresse de livraison pour procder lachat.
ASP.NET 2.0 ore des facilits pour implmenter ce scnario. Il faut prciser que vous souhaiter
pouvoir identifier un utilisateur anonyme au moyen de la section <anonymousIdentification>.
Lide est quun nouvel utilisateur est automatiquement cr pour chaque utilisateur anonyme
qui eectue une requte. ASP.NET lui donne pour nom un nouvel identificateur unique. Cet
identificateur unique est connu du navigateur client et est rutilis chaque requte. Cela se fait
soit au moyen dun cookie soit en utilisant un paramtre dURL. Llment <anonymousIdentification> accepte un attribut cookieless dont lensemble des valeurs possibles est expliqu
en page 901.
Chacun des paramtres personnels susceptibles dtre sauvegard pour un utilisateur anonyme
doit tre marqu avec lattribut allowAnonymous positionn true. Voici quoi peut alors ressembler notre fichier de configuration :

Personnalisation
Exemple :

969
Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<connectionStrings>...</connectionStrings>
<anonymousIdentification enabled="true" cookieless="AutoDetect"/>
<system.web>
<profile enabled="true" defaultProvider="MyProfileSqlProvider">
<providers>...</providers>
<properties>
<add name="RequestCount" type="System.Int32"
defaultValue="0" allowAnonymous="true" />
...
</properties>
</profile>
...
Pour implmenter correctement le scnario de lutilisateur anonyme qui va potentiellement
finir par sauthentifier, il faut prvoir la migration des donnes du compte utilisateur anonyme vers le compte utilisateur rel. Cette migration doit tre ralise au moment de lauthentification. Pour cela, il sut dinsrer votre code de migration dans la mthode Profile_MigrationAnonymous() dans le fichier Global.asax :
Exemple 23-88 :

Global.asax

<%@ Application Language="C#" %>


<script runat="server">
protected void Profile_MigrateAnonymous(object sender,
ProfileMigrateEventArgs e) {
ProfileCommon anonymousProfile = Profile.GetProfile(e.AnonymousId);
if (anonymousProfile != null) {
Profile.LoginCount += anonymousProfile.LoginCount;
}
}
</script>

Personnalisation vs. Session


Vous laurez remarqu, les concepts de sessions et de personnalisation sont proches puisque dans
chaque cas il sagit de stocker des donnes relatives un utilisateur de manire ce quelles
survivent entre chaque requte. Aussi, il est important de souligner la dirence smantique
entre session et personnalisation :

Le mcanisme de personnalisation est fait pour stocker des donnes intrinsques un


utilisateur telles que leur date danniversaire, leur adresse ou ventuellement leur numro
de carte bancaire.

Le mcanisme de session est fait pour stocker des donnes temporaires relatives un
utilisateur telles que la liste de ses achats courants.

970

Chapitre 23 : ASP.NET 2.0

En outre, le mcanisme de personnalisation est mieux pens. Il est donc plus simple exploiter
et plus ecace. Notamment :

Les noms et types des paramtres sont vrifis la compilation et non lexcution.
Les donnes personnelles un utilisateur sont charges la demande contrairement aux
donnes de sessions qui sont charges puis sauves chaque requte.

Styles, Thmes et Skins


Style CSS, contrles HTML et contrles serveur
La plupart des navigateurs prennent en compte la proprit style dun contrle HTML pour
modifier sont apparence. Par exemple, la page HTML suivante contient une zone ddition de
texte style :
<html><body>
<input type="text" style="font: 18pt times ; background-color:yellow;
border-style:dashed ; width:500 ;
border-color:blue;"
value="Entrez votre texte !" />
</body></html>

Figure 23 -28 : Style appliqu une zone ddition de texte


La proprit style contient une liste dattributs CSS (Cascading Style Sheet) spars par des
points virgules. Une autre syntaxe peut tre utilise pour factoriser un mme style sur plusieurs
contrles :
<html><head><style>
.inputstyle { font: 34pt times ; background-color:yellow;
border-style:dashed ; width:500 ; border-color:blue ; }
</style></head>
<body>
<input type="text" class="inputstyle" value="Entrez votre texte !" />
</body></html>
La classe HtmlControl, et par consquent tous les contrles serveurs HTML, prsentent la proprit CssStyleCollection Style{get} qui permet de stocker un dictionnaire o les cls sont
les noms des attributs CSS.
La classe de base commune tous les contrles serveurs web, savoir la classe WebControl,
prsente la proprit string CssClass{get;set;} qui permet de spcifier le nom dun style

Styles, Thmes et Skins

971

appliquer (tel que inputstyle). Elle prsente aussi quelques attributs fortement typs commun tous les contrles serveurs web tels que BackColor ou BorderWidth. Lespace de noms
System.Web.UI.Controls ainsi que ses sous espaces de noms contiennent plusieurs classes de
dfinition de style qui drivent de la classe System.Web.UI.Controls.Style. Ces classes telles
que TableStyle, PanelStyle, TitleStyle ou TreeNodeStyle sont spcifiques au style de chaque
contrle web ou de chaque partie dun contrle web. Les proprits de ces styles peuvent tre
initialises dans la dfinition du contrle comme ceci :
<asp:Calendar ...>
<TitleStyle BorderColor="green" BorderWidth="3" ... />
</asp:Calendar>
...ou comme cela :
<asp:Calendar ...
TitleStyle-BorderColor="green"
TitleStyle-BorderWidth="3" ... />

Notion de thme
Malgr la possibilit de factoriser les styles dans un lment <header>, la ncessit dun mcanisme puissant permettant de configurer simplement lapparence de tout un site pousser les
concepteurs dASP.NET 2.0 crer la notion de thme. Un thme est la dfinition dun ensemble
de styles qui peuvent tre appliqus statiquement ou dynamiquement aux contrles des pages
dun site. La modification des styles dun thme entrane automatiquement la modification de
lapparence des pages qui lexploitent.
Un thme est matrialis par le contenu dun rpertoire. Le nom du thme est le nom du rpertoire. Un thme peut tre local une application ou partag par toutes les applications dune
machine. Dans le premier cas, le rpertoire du thme doit tre un sous rpertoire du rpertoire
/App_Theme de lapplication. Dans le second cas le rpertoire du thme est soit un sous rpertoire
du rpertoire dinstallation dASP.NET ( savoir %WINDIR%\Microsoft.NET\Framework\<version>\ASP.NETClientFiles\Themes) soit un sous rpertoire du rpertoire Inetpub\wwwroot\
aspnet_client\system_web\<version>\Themes dans le cas dapplication web hberges par IIS.
Il y a plusieurs faons dappliquer un thme aux pages dune application :

En utilisant lattribut theme de la section <pages> dans le fichier de configuration. Dans ce


cas le thme est appliqu toutes les pages impactes par ce fichier de configuration :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<pages theme="NomDuTheme" >
...

En utilisant la sous directive Theme de la directive <@ Page> des pages .aspx concernes.
Cette possibilit permet de rcrire pour une page un thme global spcifi dans la section
de configuration <page> :

972

Chapitre 23 : ASP.NET 2.0


PagesXXX.aspx

Exemple :
<%@ Page Theme="NomDuTheme"
...

%>

En utilisant la sous directive StyleSheetTheme de la directive <@ Page> des pages .aspx
concernes. Il faut savoir que pour les contrles dune page, les styles sont choisis dans
cet ordre croissant de priorit : styles dfinis par le thme spcifi avec la sous directive
StyleSheetTheme ; styles dfinis directement dans les dclarations des contrles ; styles dfinis dans llment <page> du fichier de configuration ; styles dfinis par le thme spcifi
avec la sous directive Theme.
PagesXXX.aspx

Exemple :
<%@ Page StyleSheetTheme="NomDuTheme"
...

%>

Dynamiquement en spcifiant le thme dans la mthode abonne lvnement PreInit


de la page. Typiquement, cette possibilit est utilise lorsque lon souhaite permettre aux
utilisateurs authentifis de personnaliser le thme dfinissant lapparence du site :
Exemple :

PagesXXX.aspx

<%@ Page Language="C#" %>


<script runat="server">
protected void Page_PreInit(){
if (Profile.IsAnonymous == false)
Page.Theme = Profile.Theme;
}
</script>
...
Enfin, vous pouvez dsactiver lapplication des styles dun thme pour un contrle simplement
en positionnant sa proprit EnableTheming false. Cette proprit est initialement prsente
par la classe Control.

Notion de Skin
Un rpertoire dun thme contient un ou plusieurs fichiers dextension .skin, zro, un ou plusieurs fichiers dextension .css et ventuellement des sous rpertoires contenant des ressources
rfrences par les styles, telles que des images. Les stylesheets dun thme, dfinis dans ses fichiers dextension .css seront appliqus aux pages concernes.
Les fichiers dextension .skin contiennent les dfinitions des skins des types de contrles (que lon
pourrait traduire par peau/apparence dun type de contrle). La dfinition dun skin ressemble
la dfinition dun contrle mis par que seules certaines proprits peuvent tre positionnes. Ce sont les proprits qui se rapportent au style du type de contrle. Vous pouvez les
reconnatre en regardant la dfinition dun contrle car elles sont marques avec lattribut System.Web.UI.ThemeableAttribute(true). Voici par exemple la dfinition dun fichier .skin :
Exemple 23-89 :
<asp:Label Font-Bold="true" Font-Size="20" runat="server" />
<asp:TextBox Font-Bold="true" Font-Size="20" runat="server" />

BigText.skin

WebParts

973

Voici une page sur laquelle nous appliquons le thme ThemeBig qui ne contient que le fichier
BigText.skin :
Default.aspx

Exemple 23-90 :
<%@ Page Language="C#" Theme="ThemeBig" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
<asp:Label ID="Lbl1" runat="server" Text="Label 1" /><br/>
<asp:Label ID="Lbl2" runat="server" Text="Label 2"
Font-Size="10"/><br/>
<asp:Label ID="Lbl3" runat="server" Text="Label 3"
EnableTheming=false /><br/>
<asp:TextBox ID="TextBox1" runat="server" Text="TextBox"/>
</form>
</body>
</html>

Enfin, voici une copie dcran de cette page. On voit bien que seul le Label numro 3 na pas t
impact par le thme du fait que lon a positionn la proprit EnableTheming false :

Figure 23 -29 : Page laquelle on applique un thme

Skin nomm
Lors de la dfinition dun skin, vous avez la possibilit de le nommer avec lattribut SkinId.
On peut ainsi dfinir plusieurs skins relatifs un mme type de contrle dans un thme. Bien
entendu, un thme ne peut contenir des skins homonymes ni plus dun skin anonyme.
Comme lon peut sans douter, on peut aecter un skin nomm dans la dfinition dun contrle
avec lattribut SkinId. Si lexcution un skin nomm est utilis par un contrle mais nexiste
pas dans le thme courant, ASP.NET napplique aucun skin au contrle concern. Aussi, il est
prfrable de redfinir la totalit de vos skins nomms dans la totalit de vos thmes.

WebParts
ASP.NET 2.0 prsente un framework ddi la cration de webParts. Cette notion de webParts
permet un utilisateur de personnaliser lensemble des services que peut lui rendre une page.

974

Chapitre 23 : ASP.NET 2.0

Par exemple, on pourrait imaginer que lutilisateur ait le choix entre les services : dernire
nouvelles concernant le milieu des tlcommunications ; les valeurs du jour de la bourse ; la
mto locale ; le programme TV du soir etc. Chacun de ces services est matrialis sur la page
par un contrle particulier que lon nomme une webPart. Une application web qui supporte
un tel niveau de personnalisation est nomm portail web. Jusquici, dans le monde Microsoft
seule la technologie SharePoint tait ddie la cration de portails. Les webParts dASP.NET
2.0 permettent essentiellement :

De prsenter une ou plusieurs zones sur une mme page qui contiennent chacune une
ou plusieurs webParts. Chacune de ces zones est en fait un contrle serveurs de type
WebPartZone. Les webParts contenues dans une zone peuvent tre alignes verticalement
ou horizontalement.

Figure 23 -30 : Une page avec trois webParts

De dfinir lordre dans lequel sont aches les webParts dune zone.

Figure 23 -31 : Changement de lordre des webParts dans une zone.

WebParts

975

De choisir les webParts qui sont contenues dans une zone au moyen dun catalogue de webParts. Un catalogue par dfaut peut tre fourni par le site mais lutilisateur peut importer
ses propres webParts stockes dans des fichiers XML sur son disque dur.

Figure 23 -32 : Slection de webParts dans le catalogue

Le contenu dune webPart est ach dans une fentre spciale que lon nomme chrome.
Pour chaque webPart, lutilisateur peut dfinir lapparence du chrome, les actions prsentes dans le chrome (minimise, close etc.), le titre crit dans le chrome etc. Notez que les
actions prsentes par un chrome sont nommes verbes.

Figure 23 -33 : Menu du chrome

De connecter deux webParts. Dans ce cas une webPart joue le rle du producteur dinformation un peu comme une source de donnes tandis que lautre joue le rle de consommateur.

976

Chapitre 23 : ASP.NET 2.0

Figure 23 -34 : dition du chrome

Figure 23 -35 : Connexion entre deux webParts

Un utilisateur ne peut modifier les webParts dune page que sil est couramment authentifi. En
eet, chaque modification entrane une requte POST, qui son tour entrane du ct serveur
la sauvegarde de ltat courant des webParts pour lutilisateur couramment authentifi.

WebParts

977

Cration dune page avec des webParts


Une webPart peut contenir un contrle serveur standard ou un contrle serveur utilisateur.
Une webPart est matrialise du ct serveur par une instance dune classe qui drive de
System.Web.UI.WebControls.WebParts.WebPart. ASP.NET 2.0 utilise automatiquement et
implicitement une instance de la classe GenericWebPart pour contenir un contrle standard ou
un contrle utilisateur dfini dans un fichier .ascx. Un contrle utilisateur dfini directement
dans un fichier C  (comme celui de lExemple 23-31) doit lui-mme tre une webPart. En
consquence, pour pouvoir tre utilis comme webPart sa classe doit driver de la classe WebPart
plutt que de la classe Control.
Comme nous lavons vu, pour pouvoir modifier les webParts sur une page il faut quun utilisateur soit authentifi. Cela ne sut pas cependant pour sauver ltat des webParts. Il faut prvoir
un fournisseur de webParts dans le fichier de configuration comme ceci :
Exemple :

Web.Config

<?xml version="1.0"?>
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<connectionStrings>
<add name="MyLocalSqlServer"
connectionString="Data Source=localhost;Integrated
Security=SSPI ;Initial Catalog=MyWebSiteUsers;" />
</connectionStrings>
<system.web>
<webParts>
<personalization defaultProvider="MyWebPartsSqlProvider">
<providers>
<add
name=" MyWebPartsSqlProvider "
type="System.Web.UI.WebControls.WebParts.SqlPersonalizationProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="MyLocalSqlServer" />
</providers>
</personalization>
</webParts>
...
Puisque le design pattern provider est utilis vous pouvez bien entendu prvoir vos propres
fournisseurs de webParts avec des classes qui drivent de la classe System.Web.UI.WebControls.
WebParts.PersonalizationProvider. Ainsi les donnes relatives aux tats des webParts des
pages accessibles un utilisateur donn peuvent tre sauves un autre endroit que les donnes
personnelles de cet utilisateur.
Pour quune page puisse prsenter des webParts, il faut quelle contienne un contrle serveur de
type WebPartManager avant la dclaration de tous contrles serveurs relatifs aux webParts. Vient
alors la notion de webPartsZone. Une webPartsZone est un contrle serveur qui la particularit de pouvoir contenir des webParts. Prcisons quune webParts est forcment contenu dans
une webPartsZone. La page suivante contient une webPartsZone qui elle-mme contient trois
webParts : une webPart qui contient un contrle utilisateur (celui dfini par lExemple 23-33),

978

Chapitre 23 : ASP.NET 2.0

une webPart qui contient un contrle serveur de type GridView et une webPart qui contient un
contrle serveur de type Calendar :
Exemple 23-91 :
<%@ Page Language="C#" %>
<%@ Register TagPrefix="PRATIQUE" Src="~/Securized/MyUserCtrl.ascx"
TagName=UserCtrl %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd= ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES" />
<asp:WebPartManager ID="WebPartManager1" runat="server"/>
<asp:WebPartZone ID="WebPartZone1" runat="server"
LayoutOrientation="Horizontal">
<ZoneTemplate>
<PRATIQUE:UserCtrl ID="UserCtrl1" runat="server" />
<asp:GridView ID="Grid" DataSourceID="DataSrc" runat="server"/>
<asp:Calendar ID="Calendar1" runat="server"></asp:Calendar>
</ZoneTemplate>
</asp:WebPartZone>
</form>
</body>
</html>
La Figure 23 -30 reprsente une copie dcran de cette page. Notez que nous avons choisi un
style professionnel pour acher les webParts. Voici les changements quun tel style implique
au code de notre page. Pour plus de clart, nous omettrons ce code ncessaire aux styles dans
nos prochains listings. En outre, vous aurez rarement le besoin daccder au code des pages qui
contiennent des webParts puisque le mode design de Visual Studio est particulirement ecace :
Exemple 23-92 :
<%@ Page Language="C#" %>
...
<body>
...
<asp:WebPartZone ID="WebPartZone1" runat="server"
LayoutOrientation="Horizontal" BorderColor="#CCCCCC"
Font-Names="Verdana" Padding="6">
<ZoneTemplate> ... </ZoneTemplate>
<PartChromeStyle BackColor="#F7F6F3" BorderColor="#E2DED6"
Font-Names="Verdana" ForeColor="White" />
<MenuLabelHoverStyle ForeColor="#E2DED6" />
<EmptyZoneTextStyle Font-Size="0.8em" />
<MenuLabelStyle ForeColor="White" />
<MenuVerbHoverStyle BackColor="#F7F6F3" BorderColor="#CCCCCC"
BorderStyle="Solid" ForeColor="#333333" BorderWidth="1px" />
<HeaderStyle Font-Size="0.7em" ForeColor="#CCCCCC"

WebParts

979
HorizontalAlign="Center" />
<MenuVerbStyle BorderColor="#5D7B9D" BorderStyle="Solid"
BorderWidth="1px" ForeColor="White" />
<PartStyle Font-Size="0.8em" ForeColor="#333333" />
<TitleBarVerbStyle Font-Size="0.6em" Font-Underline="False"
ForeColor="White" />
<MenuPopupStyle BackColor="#5D7B9D" BorderColor="#CCCCCC"
BorderWidth="1px" Font-Names="Verdana"
Font-Size="0.6em" />
<PartTitleStyle BackColor="#5D7B9D" Font-Bold="True"
Font-Size="0.8em" ForeColor="White" />
</asp:WebPartZone>

...
</body>
La copie dcran de la Figure 23 -33 montre qu ce stade on peut dj minimiser/restaurer ou
fermer une webPart. Ltat du chrome de chaque webPart (minimis/restaur, ouvert/ferm)
est sauv pour lutilisateur authentifi dans la base de donnes spcifie par le fournisseur de
webParts. son retour sur cette page, lutilisateur retrouvera les chromes des webParts dans
ltat dans lesquels il les a laiss. Comprenez bien que seul ltat dune webPart est sauv. Pour
vous en convaincre, modifier ltat dun contrle et vrifier que cet tat est perdu lorsque vous
vous r authentifiez.
Une question se pose : comment faire pour dfinir les attributs dune webPart tels que son
titre ou la description qui doit tre ache dans le tooltip ? En eet, dans notre page .aspx
nos trois contrles sont implicitement contenus dans des contrles de type GenericWebPart.
Puisque nous navons pas accs ces contrles, nous ne pouvons pas les paramtrer. Deux solutions existent ce problme :

Dans le cas de contrles standard ou .aspx nous sommes oblig de fixer des attributs qui
seront exploits par le GenericWebPart par exemple comme ceci :
...
<ZoneTemplate>
<PRATIQUE:UserCtrl title="Couleurs" ID="UserCtrl1" runat="server" />
<asp:GridView title="Employ
es" ID="Grid" DataSourceID="DataSrc"
runat="server"/>
<asp:Calendar title="Calendrier" ID="Calendar1"
runat="server"></asp:Calendar>
</ZoneTemplate>
...

On note que lintellisense ne connat pas ces attributs et que le compilateur gnre un avertissement.
Dans le cas dun contrle utilisateur vous pouvez implmenter linterface IWebPart qui
dfinit les attributs sous forme de proprits.

Le mode design
Le mode dune page contenant des webParts est dfini par la proprit DisplayMode{get;set;}
du contrle WebPartManager de la page. Par dfaut ce mode est BrowseDisplayMode et lutilisateur ne peut que minimiser/restaurer et fermer les webParts. Si vous passez en mode

980

Chapitre 23 : ASP.NET 2.0

DesignDisplayMode, il devient possible de changer lordre des webParts (comme illustr par
la Figure 23 -31). Ici aussi, chaque changement gnre un vnement postback qui permet de
sauver le nouvel tat des webParts.
Exemple 23-93 :
<%@ Page Language="C#" %>
...
<script runat="server">
protected void ButtonBrowse_Click(object sender, EventArgs e) {
WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode;
}
protected void ButtonDesign_Click(object sender, EventArgs e) {
WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
...
<asp:Button ID="ButtonBrowse" runat="server"
OnClick="ButtonBrowse_Click" Text="Browse Mode" />
<asp:Button ID="ButtonDesign" runat="server"
OnClick="ButtonDesign_Click" Text="Design Mode" />
</form>
</body>
</html>

Catalogue de webParts
Nous avons vu que le chrome permet de rendre invisible une webPart aprs avoir slectionn le
verbe Close. Vous pouvez vous servir dun contrle de type PageCatalogPart pour rendre visible
une ou plusieurs webParts qui ont t fermes. Un tel contrle ache la liste des webParts fermes que contient la page courante. Il doit tre contenu dans un contrle de type CatalogZone.
Un CatalogZone nest ach que lorsque lon est en mode CatalogDisplayMode :
Exemple 23-94 :
<%@ Page Language="C#" %>
...
<script runat="server">
protected void ButtonCatalog_Click(object sender, EventArgs e) {
WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
...
<asp:CatalogZone ID="CatalogZone1" runat="server">

WebParts

981

<ZoneTemplate>
<asp:PageCatalogPart ID="part1" runat="server"/>
</ZoneTemplate>
</asp:CatalogZone>
<asp:Button ID="ButtonCatalog" runat="server"
OnClick="ButtonCatalog_Click" Text="Catalog Mode" />
</form>
</body>
</html>
La notion de catalogue va beaucoup plus loin que la simple rapparition des webParts fermes.
Vous pouvez dclarer un catalogue de contrles que lutilisateur peut potentiellement ajouter
dans votre page au moyen dun contrle de type DeclarativeCatalogPart. Ce type de contrle
contient des dfinitions de webParts, un peu comme un WebPartZone.
Grce au type de contrles ImportCatalogPart, vous pouvez mme autoriser un utilisateur
importer des webParts dfinies dans un document XML. Veuillez vous rfrer la documentation de ce contrle dans les MSDN pour connatre le format dun tel fichier.
Ces deux contrles permettent donc dajouter dynamiquement des webParts. Notons quen interne, du ct serveur, un identificateur unique est assign chaque webPart. Les webParts ajoutes dynamiquement prsentent le verbe Delete qui permet de les dtruire. Comme le montre
la page suivante, il est aussi possible dajouter dynamiquement des webParts partir du code :
Exemple 23-95 :
<%@ Page Language="C#" %>
<script runat="server">
private static int calendarID = 0 ;
protected void ButtonAdd_Click(object sender, EventArgs e) {
Calendar calendar = new Calendar();
calendar.ID = "Calendar_" + calendarID++;
GenericWebPart wrapper = WebPartManager1.CreateWebPart(calendar);
WebPartManager1.AddWebPart(wrapper, WebPartZone1, 0);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form2" runat="server">
<asp:SqlDataSource ID="DataSrc" runat="server" ConnectionString=
"server = localhost ; uid=sa ; pwd= ; database = ORGANISATION"
SelectCommand="SELECT * FROM EMPLOYES" />
<asp:WebPartManager ID="WebPartManager1" runat="server"/>
<asp:WebPartZone ID="WebPartZone1" runat="server"
LayoutOrientation="Horizontal"/>
<asp:Button id="ButtonAdd" runat="server"
Text="Ajout dun calendrier" OnClick="ButtonAdd_Click"/>
</form>
</body>
</html>

982

Chapitre 23 : ASP.NET 2.0

Le mode dition
Le mode ddition permet dditer tous les attributs des webParts dune page. Pour cela il faut
passer en mode EditDisplayMode. Les contrles contenus dans les contrles de type EditorZone
sont alors visibles. Ils peuvent tre de trois types :

AppearanceEditorPart (illustr par la Figure 23-34) : Permet dditer les proprits visuelles dune webPart telles que sont titre ou sa taille.

BehaviorEditorPart : Permet dditer les proprits comportementales dune webPart


telles que les verbes disponibles partir de son chrome.

LayoutEditorPart : Permet dditer lorganisation des webParts comme par exemple


lordre dachage au sein dune zone. Cet diteur est utile car certains navigateurs ne
prsentent pas de facilits de type glisser-dposer comparables celles de IE pour organiser
les webParts.

La page suivante montre comment dclarer un contrle de type EditorZone :


Exemple 23-96 :
<%@ Page Language="C#" %>
...
<script runat="server">
protected void ButtonEdit_Click(object sender, EventArgs e) {
WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
...
<asp:EditorZone ID="EditorZone1" runat="server">
<ZoneTemplate>
<asp:AppearanceEditorPart ID="AppearanceEditorPart1"
runat="server" />
</ZoneTemplate>
</asp:EditorZone>
<asp:Button ID="ButtonEdit" runat="server"
OnClick="ButtonEdit_Click" Text="Edition Mode" />
</form>
</body>
</html>

Connexion entre webParts


Vous avez la possibilit de crer une connexion entre deux webParts. Ce type de connexion est
dissymtrique : tandis quune webPart joue le rle de producteur lautre joue le rle de consommateur. Illustrons cette possibilit laide dune page qui utilise un contrle serveur utilisateur
producteur permettant de choisir une couleur et un contrle serveur utilisateur consommateur
qui ache la couleur slectionne. Cette page est illustre par la Figure 23 -35 :

WebParts
Exemple 23-97 :

983
MyProviderCtrl.ascx

<%@ Control Language=C# CodeFile="~/Securized/MyProviderCtrl.ascx.cs"


Inherits="MyUserCtrl" %>
Couleur : <asp:dropdownlist id="Couleur" runat="server">
<asp:listitem>blanc</asp:listitem>
<asp:listitem>noir</asp:listitem>
</asp:dropdownlist>
<asp:button id="Button1" text="Soumettre" OnClick="Btn_Click"
runat="server"/>
Exemple 23-98 :

MyProviderCtrl.ascx.cs

using System.Web.UI ;
using System.Web.UI.WebControls.WebParts ;
public interface IColor { string SelectedColor { get;set ; } }
public partial class MyProviderCtrl : UserControl, IColor {
private string m_SelectedColor ;
public string SelectedColor {
get { return m_SelectedColor ; }
set { m_SelectedColor = value ; }
}
protected void Btn_Click(System.Object sender, System.EventArgs e){
m_SelectedColor = Couleur.SelectedItem.Value;
}
[ConnectionProvider("TestProviderConsumer")]
public IColor ProvideColor() { return this ; }
}
Le contrle serveur qui reprsente la webPart producteur (en loccurrence MyProviderCtrl)
doit supporter une interface connue du contrle serveur qui reprsente le consommateur (en
loccurrence MyProviderCtrl). Linterface IColor joue ici ce rle dintermdiaire :
Exemple 23-99 :

MyConsumerCtrl.ascx

<%@ Control Language="C#" ClassName="MyConsumerCtrl" %>


<script runat="server">
[ConnectionConsumer("TestProviderConsumer")]
public void Consume(IColor colorProvider){
Msg.Text = "Vous avez selection
e : " + color.SelectedColor ;
}
</script>
<asp:Label ID="Msg" runat="server" />
On voit quil faut utiliser lattribut ConnectionProvider pour indiquer la mthode quASP.NET
doit utiliser pour obtenir lobjet reprsentant la webPart producteur. De mme, on indique
la mthode du contrle consommateur quASP.NET doit invoquer au moyen de lattribut
ConnectionConsumer. ce stade, si vous construisez une page avec ces deux contrles contenus
dans deux webParts rien ne se passe. En eet, pour que deux webParts puissent communiquer,
il faut crer une connexion entre elles. Vous pouvez crer une telle connexion au moyen dun
contrle WebPartConnection comme ceci :

984

Chapitre 23 : ASP.NET 2.0

Exemple 23-100 :
<%@ Page Language="C#" %>
<%@ Register TagPrefix="PRATIQUE" Src="~/Securized/MyProviderCtrl.ascx"
TagName="ProviderCtrl" %>
<%@ Register TagPrefix="PRATIQUE" Src="~/Securized/MyConsumerCtrl.ascx"
TagName="ConsumerCtrl" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:WebPartManager ID="WebPartManager1" runat="server">
<StaticConnections>
<asp:WebPartConnection ID="MyCnx" ConsumerID="ConsumerCtrl1"
ProviderID="ProviderCtrl1" />
</StaticConnections>
</asp:WebPartManager>
<asp:WebPartZone ID="WebPartZone1" runat="server"
LayoutOrientation="Horizontal">
<ZoneTemplate>
<PRATIQUE:ProviderCtrl ID="ProviderCtrl1" runat="server" />
<PRATIQUE:ConsumerCtrl ID="ConsumerCtrl1" runat="server" />
</ZoneTemplate>
</asp:WebPartZone>
</form>
</body>
</html>
Cette page illustre une connexion construite statiquement. Vous pouvez aussi permettre
vos utilisateurs de crer dynamiquement des connexions. Pour cela, il faut passer en mode
ConnectDisplayMode et fournir un contrle de type ConnectionZone :
Exemple 23-101 :
<%@ Page Language="C#" %>
...
<script runat="server">
protected void ButtonBrowse_Click(object sender, EventArgs e) {
WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode ;
}
protected void ButtonCnx_Click(object sender, EventArgs e) {
WebPartManager1.DisplayMode = WebPartManager.ConnectDisplayMode ;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
<form id="Form1" runat="server">
<asp:WebPartManager ID="WebPartManager1" runat="server"/>
...
<asp:ConnectionsZone ID="ConnectionsZone1" runat="server"/>
<asp:Button ID="ButtonBrowse" runat="server"
OnClick="ButtonBrowse_Click" Text="Browse Mode" />

WebParts

985

<asp:Button ID="ButtonDesign" runat="server"


OnClick="ButtonCnx_Click" Text="Connexion Mode" />
</form>
</body>
</html>
Si lutilisateur souhaite crer une connexion impliquant une webPart, il lui sut de slectionner le verbe Connect sur cette webPart. Du fait de la prsence dun des attributs
ConnectionProvider ou ConnectionConsumer, ASP.NET sait reconnatre si la webPart est de
type producteur ou consommateur. Le contrle ConnectionZone est alors ach et propose
lutilisateur de slectionner la webPart avec laquelle notre premire webPart va tre connecte.
Ici encore, une grande part de lintrt de cette technique rside dans le fait que lexistence de
connexions cres dynamiquement est sauve du ct serveur pour chaque utilisateur.

24
Introduction au dveloppement
de Services Web avec .NET

Introduction
SOA : Architecture Oriente Service
La programmation et larchitecture oriente service (SOA pour Service Oriented Architecture) est
un complment rcent de la POO (Programmation Oriente Objet). La notion de service est
simple : un service est une application avec laquelle on interagit avec des messages. Un service
a donc des clients qui lui envoient des messages et auxquels il peut retourner des messages. Un
service peut aussi tre le client dun autre service. SOA est fond sur quatre principes cls :

Les frontires dun service sont explicites :


Chaque message envoy ou reu dun service reprsente un cot en terme de performance
puisque cela ncessite lutilisation dun rseaux. Les direntes technologies dobjets distribus (telle que .NET Remoting ou Java RMI) cachent au dveloppeur le fait quun objet
est distant en convertissant implicitement un appel une de ses mthodes en un message
envoy sur le rseau. Lapproche SOA est diamtralement oppose. Il est ncessaire que le
dveloppeur soit conscient de lenvoi ou de la rception dun message. Nous verrons que
cette approche permet notamment dautres modles dchange de messages que le traditionnel requte/rponse synchrone utilis pour simuler un appel de mthode sur un objet
distant.

Un service est une entit autonome :


La notion dautonomie dun service dcoule de rgles radicalement direntes de ce qui se
pratique traditionnellement en POO. Le dploiement et lvolution dun service se font
indpendamment de ses clients. Il est de la responsabilit des clients de sadapter une

988

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET


nouvelle version dun service. De cette faon la topologie dun ensemble de services interagissant sadapte naturellement aux nouveaux besoins. Cela permet aussi de mettre en vidence limportance de la gestion des erreurs puisque celles-ci surviendront certainement.
De mme, on ne peut pas se passer dune gestion srieuse de la scurit puisque les services
sont en gnral accessibles partir dun rseau public et donc peu sr, tel que Internet.

La structure dutilisation dun service est dfinie sans ambigut par un unique contrat
prsent par le service ses clients :
Lvolution des langages objets illustre limportance croissante de la dissociation entre abstraction et implmentation. En eet, la notion dinterface est maintenant une composante
privilgie des langages modernes tels que C  ou Java, ce qui nest pas le cas par exemple en
C++. SOA va plus loin en formalisant la structure dutilisation dun service dans un unique
contrat matrialis par un document XML. Ce contrat spcifie entre autres la structure des
informations contenues dans les messages entrant et sortant au moyen de schmas XSD.
Ce contrat peut contenir des commentaires et ainsi renseigner un humain sur la structure
dutilisation du service. Ce contrat peut aussi tre utilis par un programme pour gnrer
par exemple, des classes qui seront utilises ct client pour fabriquer les messages et les
envoyer au service. Le fait que la rception dun message ct service dclenche en interne
lappel une certaine mthode dune certaine classe est un dtail dimplmentation du
service et ne fait pas partie de son contrat.

La smantique dutilisation dun service, aussi appel stratgie (policy en anglais) est dfinie sans ambigut dans le contrat prsent par le service ses clients :
La plupart des langages objets ne prsentent pas de mcanismes simples pour signifier aux
clients dune interface les prconditions et les postconditions dutilisation. Ces conditions
sont vrifies au sein de limplmentation de linterface et si elles ne sont pas rigoureusement documentes, le client na aucun moyen de les connatre avec exactitude. En SOA,
cette smantique dutilisation fait partie intgrante du contrat prsent par le service ses
clients. Par exemple un service peut dclarer avec une stratgie quil naccepte de traiter
un message entrant qu la condition que celui-ci soit encrypt dune certaine manire.
Un service peut dclarer avec une stratgie quil nest accessible que de 8h 22h GMT ou
quil supporte un certain protocole transactionnel. linstar de la structure dutilisation,
les stratgies sont rdiges dans un langage XML non ambigu. Elles peuvent donc tre
consommes par un framework pour viter au dveloppeur de coder la logique spcifie (par
exemple un message sera automatiquement crypt dune certaine manire ct client par
un tel framework si telle est la stratgie du service cibl).

Services Web : les langages SOAP et WSDL


Les services web reprsentent la manire privilgie lheure actuelle pour implmenter la notion de service telle que nous venons de la dfinir. Les services web ont deux caractristiques
essentielles :

Indpendance de la plateforme dexcution du client et du service web : Tout a t


fait pour que les messages changs entre un service web et ses clients soient compltement indpendants des plateformes dexcutions impliques. Les services web constituent
le moyen privilgi pour faire circuler linformation dans un environnement htrogne.
Concrtement, un service web implment en Java fonctionne de la mme manire avec
un client crit en C  ou un client crit en Java. On parle dinteroprabilit.

Introduction

989

Indpendance du protocole de transport des messages : Concrtement, un service web


se comportera de la mme faon indpendamment du fait que les messages soient achemins par un des protocoles HTTP, HTTPS, TCP/IP, UDP/IP ou SMTP. En consquence, les
services web ne font aucune hypothse sur les caractristiques propres du protocole sousjacent (comme lencryptions des donnes avec HTTPS ou la fiabilit avec TCP). Nous verrons comment la plupart des caractristiques de transport peuvent tre obtenues au niveau
de lchange de message, indpendamment du protocole de transport sous-jacent. Enfin,
ne vous y trompez pas, lexpression service web contient le mot web parce que le protocole
HTTP (i.e le protocole du web), est le plus utilis au monde. Le fait de pouvoir se placer
dans la continuit du succs phnomnal du web constitue certainement un atout dcisif
pour ladoption des services web.

Pour pouvoir obtenir linteroprabilit, les services web reposent dabord sur deux langages
XML simples et normaliss. En eet, en page 759 nous expliquons quun atout majeur de XML
est de pouvoir coder des donnes indpendamment dune plateforme dexcution :

Le langage XML SOAP (Simple Object Access Protocol) est utilis pour la rdaction des messages changs entre services. Des spcifications existent pour chaque protocole rseaux
pour dcrire comment acheminer un message SOAP.

Le langage XML WSDL (Web Service Definition Language) est utilis pour rdiger les contrats
prsents par les services leurs clients. Nous verrons que ce langage permet aussi de dfinir les liens entre le contrat dun service et son implmentation (autrement dit, il indique
quelle mthode de quelle classe doit tre invoque pour traiter tel type de message). Ainsi,
le contrat que prsente un service ses clients ne constitue quune partie dun document
WSDL puisque nous avons vu que ce dernier type dinformation na pas tre consomm
par les clients.

WS-I Basic profiles


La caractristique principale des langages SOAP et de WSDL est quils sont extensibles. Concrtement, les plus gros acteurs du march tel que Microsoft, IBM ou BEA se sont runis au sein
dune organisation nomme WS-I (Web Service Interoperabilityy) pour produire un ensemble de
spcifications qui tendent ces langages. Cet ensemble est nomm WS-* (prononc WS Star) car
les noms de ces spcifications commencent par WS- puis se termine par une expression illustrant
le dessein. Par exemple, on comprend que la spcification WS-Security permet dintroduire un
certain niveau de scurit dans lchange des messages. Voici les grands domaines couverts
lheure actuels (fin 2005) par les spcifications WS-* :

Scurit : Plusieurs spcifications WS-* permettent dimplmenter direntes facettes de la


scurit telles que lauthentification, la confidentialit des donnes ou les sessions scurises
dchange de messages.

Description : Le langage WSDL est tendu par plusieurs spcifications WS-* qui permettent
de rdiger des contrats plus prcis. Par exemple, la spcification WS-SecurityPolicy permet au
contrat dun service de spcifier quil naccepte de traiter un message reu que si ce dernier
a t crypt dune certaine manire.

Dcouverte : Des spcifications dcrivent des implmentations de mcanismes de recherche selon des critres, de services web au travers dun rseau. Ainsi un client peut
dcouvrir lensemble des services qui rpondent ses besoins. Lorsque que le client dcide

990

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET


dexploiter un service, il na plus qu examiner son contrat afin de comprendre comment
lutiliser.

Livraison de messages : Des spcifications WS-* permettent de fournir certaines garanties


quant la livraison des messages. Cela permet de pallier le fait que le protocole rseau de
transport des messages sous-jacent nest pas ncessairement fiable. Par exemple le protocole
UDP ne permet pas une source de savoir quun destinataire a bien reu un message. Le
domaine de la livraison de messages couvre aussi les problmatiques lies un envoi de
messages vers plusieurs destinataires (broadcasting) ainsi que les problmatiques lies la
consommation dun mme message par plusieurs services qui sont destins travailler
la chane (en pipeline).

Coordination et transaction : Des spcifications WS-* permettent de coordonner les activits de plusieurs services afin dobtenir un certain comportement global. La notion de
transaction distribue entre plusieurs services constitue un exemple dune telle coordination.

Un aspect trs intressant des spcifications WS-* est quelles permettent de composer les protocoles. Si deux protocoles ont des desseins orthogonaux, alors ils peuvent tre utiliss conjointement dune manire indpendante. Par exemple, un protocole de scurit destin rendre les
messages confidentiels en les cryptant peu optionnellement tre utilis conjointement avec un
protocole de livraison de message permettant de garantir au client que les messages ont bien
t reus par le service.
Cependant comprenez bien que lindpendance dans la composition des protocoles se fait dans
la mesure du possible. Les spcifications WS-* agissent dirents niveaux de larchitecture. Il
nest pas rare quune spcification WS-* compte sur une autre spcification dun niveau infrieur. Par exemple, les algorithmes de transaction distribue sur plusieurs services prsupposent
que la livraison des messages se fait dans lordre dans lequel ils sont envoys.

Modle dchange de messages


Du fait quils permettent linteroprabilit entre plateformes et saranchissent des caractristiques des protocoles rseaux sous-jacents, nous avons vu que les services web reprsentent une
volution majeure par rapport aux autres technologies dchange dinformation entre application. Contrairement la plupart des ces technologies, la notion de service rend possible aussi
dirents modles dchanges de messages (en anglais Message Exchange Pattern ou MEP). En
eet, jusquici les dveloppeurs taient contraints par ces technologies utiliser principalement
le modle dchange requte/rponse synchrone. Ce modle a t popularis par la technologie
RPC (Remote Procedure Call). Il consiste simuler du ct client lappel dune procdure en local
en encapsulant les donnes entrantes dans un message requte envoy au travers du rseaux,
puis en attendant un message rponse contenant les donnes sortantes. Les langages SOAP et
WSDL des services web permettent dimplmenter tous les modles dchanges de messages
imaginables tels que :

Le modle style RPC requte/rponse synchrone que nous venons de dcrire.

Le modle requte/rponse asynchrone. La dirence avec le modle prcdent est que le


thread du client responsable de lenvoi du message requte reprend sa course directement
aprs lenvoi. Le client doit mobiliser des ressources afin dassurer la rception du message
rponse. Ce modle est adapt lorsque le traitement du message requte par le service peut
prendre un certain temps.

Dveloppement dun service web simple

991

Le modle requte/polling de la rponse. La dirence avec le modle prcdent est


quaprs lenvoi du message requte, le client demande priodiquement la rponse au
service au moyen dun change de message requte/rponse jusqu ce quil obtienne la
rponse ou une erreur.

Le modle oneway. Dans ce modle, le client nattend aucune information du service. Il


ne fait quenvoyer son message. Ce modle est adapt lenvoi dinformation non cruciale
telles que des informations de log.

Le modle multi-diusion (dit aussi broadcast) permet denvoyer un mme message simultanment plusieurs services. Des variantes de ce modle permettent au client de sattendre une ou plusieurs rponses de la part des services contacts.

Le modle abonnement/notification (dit aussi dvnement) permet un client de


sabonner un service. Il recevra alors les messages fabriqus par le service qui correspondent ses besoins. Plusieurs variantes de ce modle existent pour spcifier comment
le service obtient la notification de dsabonnement du client.

Ces modles reprsentent la plupart des changes de messages rels mais bien dautres modles
dchange plus ou moins compliqus sont imaginables et implmentables.

Dveloppement dun service web simple


Dveloppement dun service web simple
sans utiliser Visual Studio .NET
Un service web simple, est un service web qui ne supporte que le modle dchange de messages
style RPC au dessus du protocole HTTP et qui nexploite aucune des spcifications WS-*. Il nest
pas ncessaire davoir recours la plateforme de dveloppement WSE pour fabriquer de tels
services web. Il est prfrable davoir assimil les principales notions du dveloppement ASP
.NET avant de continuer.
En .NET, le code source dun service web est un fichier dextension .asmx stock dans un rpertoire hbergeant le service. Pour notre exemple ce rpertoire se nommera localizationcorp. Le
code source contenu dans une page asmx sera automatiquement compil avant son excution,
grce ASP.NET. Un assemblage sera produit dans le sous rpertoire bin.
Dans ce chapitre nous nous baserons sur un service web de go localisation qui prsente une
opration unique : la possibilit dobtenir un couple ville/pays partir dun couple de coordonnes latitude/longitude. Cette opration na clairement besoin que du modle dchanges de
messages style RPC synchrone. Aussi, le concept de mthode dune classe convient parfaitement
son implmentation. Une mthode qui reprsente limplmentation dune telle opration
dun service est en gnral qualifie de web mthode. Comprenez bien que les implmentations
des autres modles dchanges de messages ne peuvent pas tre aussi simples.
Par souci de simplicit nous ne nous limiterons qu la localisation de la ville de Nice. Voici le
code de ce service web :
Exemple 24-1 :

Localizer.asmx

<%@ WebService language="C#" class="LocalizationCorp.Localizer" %>

992

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET


using System ;
using System.Web.Services ;
using System.Xml.Serialization ;
namespace LocalizationCorp {
public class Localizer : WebService {
[XmlRoot(Namespace="http://localizationcorp.com/documents/data/")]
public class Town {
public string Name ;
public string Country ;
}
[WebMethod]
public Town GetTownFromLatLon(double lat, double lon) {
Town town = new Town() ;
if (lat < 43.44 && lat > 43.39 && lon < 7.18 && lon > 7.10) {
town.Name = "Nice" ; town.Country = "France" ;
} else {
town.Name = "Unkown" ; town.Country = "Unkown" ;
}
return town ;
}
}
}

La premire ligne indique ASP.NET que ce fichier dcrit un service web. lexcution, un service web est en fait une instance de la classe spcifie en premire ligne avec lattribut class, en
loccurrence, la classe LocalizationCorp.Localizer. Une telle instance est cre par ASP.NET
pour traiter chaque requte. On parle denvironnement sans tat puisquun mme objet ne peut
servir plusieurs requtes. Comprenez bien que selon vos besoin vous pouvez toujours stocker
des tats persistant entre les requtes du ct serveur, par exemple au moyen dune base de
donnes.
Dans cette premire ligne, nous indiquons ASP.NET le langage .NET utilis pour rdiger cette
classe, avec lattribut Language, en loccurrence le langage C  . Tous comme une page ASPX, on
peut sparer le code source C  des pages ASMX. Nous expliquons ceci en page 868.
On remarque que notre classe drive de la classe System.Web.Services.WebService. Une classe
reprsentant un service web ne drive pas ncessairement de la classe WebService. Cependant,
cela permet laccs de nombreuses fonctionnalits, comme la gestion des tats dune page sur
lautre. Une classe reprsentant un service web doit satisfaire les deux contraintes suivantes :

La classe doit tre publique et doit avoir un constructeur par dfaut public.
Toutes les web mthodes doivent tre marques avec lattribut System.Web.Services.
WebMethod. Les proprits de cet attribut configurent les fonctionnalits accessibles cette
mthode, comme son comportement relatif aux sessions. La proprit Description permet
de fournir une description pour la mthode. Veuillez consulter larticle WebMethodAttribute Members des MSDN pour plus de dtails.

La proprit Namespace de la classe WebService permet de nommer dune manire unique votre
service web. En gnral, on fournit une URL pour cette proprit mais ceci nest pas une obligation. Si on ne fournit pas explicitement dURL, lURL http://tempuri.org/ est prise par dfaut.

Tester et dboguer un service web

993

Dans tous les cas, la ressource localise lURL (si elle existe) ne sera jamais accde. Le fait
dutiliser une URL qui vous appartient permet de garantir quaucun autre service web au monde
ne portera le mme nom que le vtre. Voici comment nous aurions pu crire notre service web
pour utiliser cette proprit :
Exemple 24-2 :
...
namespace LocalizationCorp {
[WebService(Namespace="http://localizationcorp.com/localizer")]
public class Localizer : WebService {
...
Vous pouvez remarquer quil ny a pas de fichier dextension WSDL prsent dans le rpertoire hbergeant le service web. Pour obtenir ce document, il sut de taper lURL de
notre service web dans un navigateur, suivie de ?wsdl. Dans notre exemple cela donne :
http://localhost/localizationcorp/localizer.asmx?wsdl.
En fin de chapitre nous consacrons une section la description des documents WSDL.
Vous pouvez dj remarquer que notre classe de donnes LocalizationCorp.Town a t
convertie en un schma XSD inclus dans le document WSDL et dfini avec lespace de noms
http://localizationcorp.com/documents/data/.

Dveloppement dun service web simple avec Visual Studio


Visual Studio prsente de nombreuses facilits pour construire un service web ASP.NET simple.
Pour crer un service web avec Visual Studio il sut de faire : Fichier  Nouveau  Site Web  Service Web ASP.NET  Emplacement = http://localhost/localizationcorp  OK. Notre service
web est hberg sur la machine locale puisque nous avons prcis localhost.
Vous pouvez maintenant renommer le ficher Service1.asmx en localizer.asmx et recopier le
code C  de lExemple 24-1 dans le fichier localizer.asmx.cs (accessible par click droit sur le
fichier localizer.asmx  Acher le code).

Tester et dboguer un service web


Tester un service web
Grce ASP.NET, nous pouvons tester un service web sans avoir crire un client. Il sut douvrir un navigateur et daccder au fichier asmx avec lURL http://localhost/localizationcorp/
localizer.asmx. Une page HTML fabrique automatiquement partir du document WSDL
prsente les oprations du service. Vous pouvez slectionner lopration GetTownFromLatLon.
Une nouvelle page HTML apparat et vous permet de saisir les informations consommes
par lopration, en loccurrence la latitude et la longitude. Si vous tapez des coordonnes
gographiques correspondants la ville de Nice (telles que latitude=43.42 et longitude=7.15)
vous obtiendrez une page HTML contenant le document XML suivant :
<?xml version="1.0" encoding="utf-8" ?>
<Town xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

994

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET


xmlns="http://localizationcorp.com/documents/data/">
<Name>Nice</Name>
<Country>France</Country>
</Town>

Cette possibilit de test utilise la mthode Documentation pour gnrer les pages HTML. Si
vous dsactivez cette mthode vous navez plus accs aux tests partir dun navigateur. Cette
remarque se rvlera pertinente lorsque lon expliquera que la plateforme WSE compte sur la
mthode HttpSoap pour eectuer son travail. Ainsi, les traitements de WSE ne peuvent tre
tests partir de la mthode Documentation.
En production, pour plus de scurit il est conseill de dsactiver les mthodes autres que
HttpSoap. Cette dsactivation peut seectuer en ajoutant les lignes suivantes dans le fichier
web.config de votre service ou mme mieux, dans votre fichier machine.config (qui peut tre
trouv dans votre rpertoire dinstallation de .NET C:\WINDOWS\Microsoft.NET\Framework
\v2.0.XXXXX\CONFIG) :
<configuration>
<system.web>
<webServices>
<protocols>
<add name="HttpSoap1.2" />
<add name="HttpSoap" />
<remove name="HttpPost" />
<remove name="HttpGet" />
<remove name="HttpPostLocalhost"/>
<remove name="Documentation"/>
...

Dboguer un service web


Visual Studio vous permet de dboguer un service web comme nimporte quelle application
.NET. Si vous faite dboguer  dmarrer vous obtiendrez la page HTML de test. Vous pouvez
maintenant dboguer votre service en lui envoyant des messages soit en utilisant les pages
HTML de test, soit en utilisant nimporte quel autre client.

Crer un client .NET dun service web


Crer un client .NET dun service web sans Visual Studio
En gnral, pour dvelopper un client dun service web dont les oprations sont modlisables
par un change de messages style RPC, on se sert dune classe appele classe proxy du service.
Cette classe est directement accde par le code source du client. Pour chaque web mthode du
service web, la classe proxy prsente une mthode de mme nom et qui accepte et retourne les
mmes arguments. La classe proxy soccupe compltement de la construction et de lenvoi du
message SOAP, et de la rception de la rponse.
Si votre client est dvelopp avec .NET, vous vous servirez certainement de loutil wsdl.exe,
fourni avec Visual Studio pour gnrer votre classe proxy. Cet outil est capable de gnrer la classe

Crer un client .NET dun service web

995

proxy dans un fichier source en langage C  , VB.NET ou JScript, directement partir du contrat
WSDL du service. Par exemple cette commande gnre ce fichier :
wsdl.exe /out:LocalizerProxy.cs /language:C#
http://localhost/localizationcorp/Localizer.asmx?wsdl
Exemple :

LocalizerProxy.cs

//---------------------------------------------------------------------// <auto-generated>
//
This code was generated by a tool.
//
Runtime Version:2.0.XXXXX
//
//
Changes to this file may cause incorrect behavior and will be lost
//
if the code is regenerated.
// </auto-generated>
//---------------------------------------------------------------------using System ;
using System.ComponentModel ;
using System.Diagnostics ;
using System.Web.Services ;
using System.Web.Services.Protocols ;
using System.Xml.Serialization ;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(
Name="LocalizerSoap", Namespace="http://tempuri.org/")]
public partial class Localizer :
System.Web.Services.Protocols.SoapHttpClientProtocol {
...
public Localizer() {
this.Url = "http://localhost/LocalizationCorp/Localizer.asmx" ;
}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(
"http://tempuri.org/GetTownFromLatLon",
...)]
[return: System.Xml.Serialization.XmlElementAttribute(
Namespace="http://localizationcorp.com/documents/data/",
IsNullable=true)]
public Town GetTownFromLatLon(double lat, double lon) {
object[] results = this.Invoke("GetTownFromLatLon", new object[] {
lat,
lon}) ;
return ((Town)(results[0])) ;
}
public System.IAsyncResult BeginGetTownFromLatLon(
double lat, double lon,

996

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET


System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("GetTownFromLatLon", new object[] {
lat, lon}, callback, asyncState) ;
}
public Town EndGetTownFromLatLon(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult) ;
return ((Town)(results[0])) ;
}
public void GetTownFromLatLonAsync(double lat, double lon) {
this.GetTownFromLatLonAsync(lat, lon, null) ;
}
public void GetTownFromLatLonAsync(double lat, double lon,
object userState) {
if ((this.GetTownFromLatLonOperationCompleted == null)) {
this.GetTownFromLatLonOperationCompleted =
new System.Threading.SendOrPostCallback(
this.OnGetTownFromLatLonOperationCompleted) ;
}
this.InvokeAsync("GetTownFromLatLon", new object[] {
lat,
lon}, this.GetTownFromLatLonOperationCompleted,
userState) ;
}
private void OnGetTownFromLatLonOperationCompleted(object arg) {
if ((this.GetTownFromLatLonCompleted != null)) {
System.Web.Services.Protocols.InvokeCompletedEventArgs
invokeArgs =
((System.Web.Services.Protocols.InvokeCompletedEventArgs)
(arg)) ;
this.GetTownFromLatLonCompleted(this,
new GetTownFromLatLonCompletedEventArgs(
invokeArgs.Results, invokeArgs.Error,
invokeArgs.Cancelled, invokeArgs.UserState)) ;
}
}
public new void CancelAsync(object userState) {
base.CancelAsync(userState) ;
}
public event GetTownFromLatLonCompletedEventHandler
GetTownFromLatLonCompleted;
}

Crer un client .NET dun service web

997

[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(
Namespace="http://localizationcorp.com/documents/data/")]
public partial class Town {
private string nameField ;
private string countryField ;
public string Name {
get { returnthis.nameField;} set {this.nameField = value;}
}
public string Country {
get {return this.countryField;} set {this.countryField = value;}
}
}
...
Il est intressant de remarquer que la classe Town a t rgnre partir du schma XSD prsent
dans le fichier WSDL du service. Certaine documentions conseillent de supprimer cette dfinition du fichier gnr et dencapsuler ces classes de donnes dans des bibliothques rfrences
la fois par le service et ses clients. Nous ne sommes pas daccord avec cette pratique qui va compltement lencontre dun des principes fondamentaux de lapproche SOA : un service et ses
clients ne partagent quun contrat. Aussi dans la suite nous nous contenterons parfaitement
de cette classe Town gnre.
Notez que la classe proxy drive de la classe SoapHttpClientProtocol. Cela lui confre un certains nombre de possibilits comme la configuration des mthodes dauthentification utiliser.
Vous pouvez maintenant crer un assemblage qui rfrence lassemblage System.Web.Services.
dll et qui contient le fichier source LocalizerProxy.cs ainsi que le fichier source suivant :
Exemple 24-3 :
using System ;
class Program {
static void Main() {
Localizer proxy = new Localizer();
Town town = proxy.GetTownFromLatLon(43.42, 7.15);
Console.WriteLine("Ville:" + town.Name + " Pays:" + town.Country) ;
}
}

Crer un client .NET dun service web avec Visual Studio


Pour utiliser un service web partir dune application dveloppe avec Visual Studio, il sut
dajouter une rfrence web au projet vers le service web concern. Pour cela, il faut cliquer droit
sur le projet et slectionner Add Web Reference.... Une fentre de recherche de service apparat.
partir de cette fentre vous pouvez slectionner un service web distant ou local. Lorsque la
rfrence est ajoute, Visual Studio utilise automatiquement et implicitement loutil wsdl.exe
pour construire le fichier source C  contenant la classe proxy. Ce nouveau fichier source vous
est cach au niveau de lexplorateur de solution pour vous vitez de le modifier. Vous pouvez
vrifier cependant quil est bien prsent dans le rpertoire du projet du client.

998

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

Notez que Visual Studio utilise la technologie UDDI pour rechercher les services web distants.
Nous dtaillons cette technologie un peu plus loin dans ce chapitre.

Appels asynchrones et modle dchange de message


Remarquez que pour chaque web mthode web qui peut tre invoque, la classe proxy prsente
aussi une mthode pour les appels asynchrones (nomme Begin[method]) et une mthode
(nomme End[method]) pour rcuprer les arguments de retour dun appel asynchrone la web
mthode concerne. Ce type dappel asynchrone est dcrit page 171.
Pour chaque web mthode, un autre modle dappel asynchrone est fourni grce aux mthodes
[method]Async(), On[method]OperationCompleted() et lvnement [method]CompletedEventHandler. Ce modle supporte notamment lannulation de toutes les oprations asynchrones de ce type en cours au moyen de la mthode CancelAsync().
Comprenez bien que cette faon de programmer ne correspond pas lesprit SOA. En SOA,
le paradigme client/serveur est plutt remplac par lide dassociation expditeur/destinataire
propre chaque message. Aussi, dans un change de messages type requte/rponse (synchrone
ou asynchrone) les rles dexpditeur/destinataire sont inverss selon le point de vue que lon
prend :
change de messages style requte rponse
(synchrone ou asynchrone)
Client du
service
(i.e. le client)

Message requte
Expditeur = Client
Destinataire = Serveur

Service
(i.e. le serveur)

Message rponse
Expditeur = Serveur
Destinataire = Client

Figure 24 -1 : Client/Serveur vs. Expditeur/Destinataire


Autrement dit, pour tre habilit recevoir un message rponse, et plus gnralement pour
pouvoir supporter nimporte quel modle dchange de message, il faut que le client soit luimme un service. La suite de notre expos naura pas besoin de modles dchange de messages
autres que le traditionnel style RPC.

Utiliser un service web partir dun client .NET Remoting


Vous pouvez consommer un service web simple avec la technologie .NET Remoting. Pour cela,
il sut dutiliser les mtadonnes de type produites par loutil soapsuds.exe (dcrit page 804).
Pour que soapsuds.exe puisse consommer un service web, il faut que celui-ci supporte le formatage RPC qui est dconseill. Ce formatage ainsi que ses dsavantages sont dcrits dans la
prochaine section consacre SOAP. Il faut alors utiliser lattribut SoapRpcService sur la classe
contenant les web mthodes de votre service. Le service web de lExemple 24-1 doit donc tre
rcrit comme ceci pour tre consommable par soapsuds.exe :

Utiliser un service web partir dun client .NET Remoting


Exemple 24-4 :

999
localizer.asmx

...
namespace LocalizationCorp {
[System.Web.Services.Protocols.SoapRpcService()]
public class Localizer : WebService {
...
Vous pouvez maintenant obtenir des mtadonnes exploitables par .NET Remoting dans lassemblage ProxyLocalizationCorp.dll en tapant la ligne de commande suivante. Notez que
pour excuter cette commande sur un service web hberg par IIS, il faut dsactiver lauthentification intgre de Windows sur le rpertoire virtuel concern. Si vous excutez votre service web
avec le serveur web de Visual Studio 2005, l aussi il faut dsactiver lauthentification intgre
avec le menu Proprit du projet  Start Options  Dcochez NTLM Authentication :
soapsuds.exe /url:http://localhost:80/LocalizationCorp/
Localizer.asmx?wsdl/oa:ProxyLocalizationCorp.dll
Le programme suivant, qui doit tre compil en rfrenant les assemblages ProxyLocalizationCorp.dll et System.Runtime.Remoting.dll utilise le service web partir de .NET Remoting. Un client .NET Remoting na pas besoin que le service web supporte le formatage RPC.
Vous pouvez donc maintenant enlever lattribut SoapRpcService en revenant lExemple 24-1.
Notez que les classes LocalizerSoap et Town sont dans lespace de noms InteropNS :
Exemple 24-5 :
using System ;
using System.Runtime.Remoting ;
using System.Runtime.Remoting.Channels ;
using System.Runtime.Remoting.Channels.Http ;
class Program {
static void Main() {
HttpChannel canalHttp = new HttpChannel(0) ;
ChannelServices.RegisterChannel( canalHttp, false ) ;
MarshalByRefObject obj = (MarshalByRefObject)
RemotingServices.Connect(
typeof(InteropNS.LocalizerSoap),
"http://localhost:80/LocalizationCorp/Localizer.asmx") ;
InteropNS.LocalizerSoap proxy = obj as InteropNS.LocalizerSoap;
InteropNS.Town town = proxy.GetTownFromLatLon(43.42, 7.15) ;
Console.WriteLine("Ville:" + town.Name + " Pays:" + town.Country) ;
}
}
Aprs cette incursion dans le monde de la pratique il est temps dapporter un peu plus de thorie
quant aux langages SOAP et WSDL.

1000

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

Encoder les messages au format SOAP


Avant daborder cette section, rappelons que les trois caractristiques centrales de SOAP vues
dans lintroduction sont :

La possibilit dextension du langage SOAP pour implmenter des protocoles de communications (dencryption, dauthentification, transactionnel etc).

Les messages cods en langage SOAP peuvent tre achemins par tous types de protocoles
rseaux tels que HTTP, TCP, UDP ou SMTP.

SOAP permet tous types de modle dchange de messages.

Introduction SOAP
La version 1.0 de SOAP tait destine tre utilise par des technologies dobjets distribus. La
version 1.1 de SOAP est utilise depuis plusieurs annes principalement par les services web. Le
mot Object de lacronyme SOAP nest donc plus significatif. Aussi depuis la version 1.2 de SOAP,
le terme SOAP nest plus considr comme un acronyme.
WSE 3.0 travaille maintenant par dfaut avec la version 1.2. Dans la suite nous nous baserons donc sur la version 1.2. Vous pouvez obtenir une liste exhaustive des volutions de SOAP
1.1 vers SOAP 1.2 dans cet article http://www.idealliance.org/papers/xmle02/dx_xmle02/
papers/02-02-02/02-02-02.html. La spcification complte de SOAP 1.2 est divise en trois
documents accessibles aux URL http://www.w3.org/TR/soap12-part0/, http://www.w3.org/
TR/soap12-part1/ et http://www.w3.org/TR/soap12-part2/.
Un message SOAP contient un lment racine <Envelope>. Cet lment contient optionnellement un lment <Header> au dbut et obligatoirement un lment <Body> en dernire position. Donc un message SOAP ressemble ceci :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header> <!-- optionel -->
<!-- Contient les donnees relatives au transport du message. -->
</soap:Header>
<soap:Body> <!-- obligatoire -->
<!-- Contient les donnees m
etiers. -->
</soap:Body>
</soap:Envelope>
Llment <body> contient les donnes utiles du message codes en XML. Par exemple, voici
quoi ressemble un message SOAP requte envoy par notre client au service localizer :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetTownFromLatLon xmlns="http://tempuri.org/">
<lat>43.42</lat>
<lon>7.15</lon>
</GetTownFromLatLon>
</soap:Body>
</soap:Envelope>

Encoder les messages au format SOAP

1001

Llment <Header> contient des donnes codes en XML, fabriques et consommes par des
extensions de SOAP telles que les implmentations des spcifications WS-*. Le langage SOAP
ne dfinit pas de sous lments de llment <Header>. Seules les spcifications WS-* dfinissent
des sous lments de llment <Header>. Chaque spcification implique dans lacheminement
dun message ajoute ses sous lments. Cest grce ce mcanisme que lon obtient la fois
lextensibilit du langage SOAP et la possibilit de composer les protocoles.
Voici un message SOAP qui contient un jeton de scurit sous la forme utilisateur/mot de passe
ajout par une implmentation de la spcification WS-Security. Il contient aussi des informations sur la source du message, qui ont t ajoutes par une implmentation de la spcification
WS-Addressing :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2003/03/addressing"
xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext/">
<soap:Header>
<!-- Les donnees relatives `
a WS-Addressing. -->
<wsa:From>
<wsa:Address>http://smacchia.com/client</wsa:Address>
</wsa:From>
...
<!-- Les donnees relatives `
a WS-Security. -->
<wsse:Security soap:mustUnderstand="1">
<wsse:UserName>psmacchia</wsse:UserName>
<wsse:Password wsse:Type="wsse:PasswordDigest">
yH/*kiGGdsdujg5%16?LHRVhbg...
</wsse:Password>
...
</wsse:Security>
...
</soap:Header>
<soap:Body>
...
</soap:Body>
</soap:Envelope>

Traitement de lentte dun message SOAP


Bien que le langage SOAP ne dfinisse pas de sous lments de llment <header>, il dfinit un
ensemble de rgles pour traiter ces lments de lentte. Les spcifications WS-* doivent ainsi
suivre ces rgles. Pour expliquer ces rgles, il faut dabord aller plus loin dans la terminologie
SOAP.
Un nud SOAP est un agent logiciel qui met ou (non exclusif) reoit des messages SOAP. Pour
un message SOAP donn, on distingue le nud dorigine (qualifi aussi de nud expditeur,
cest le client du service) et le nud final (qualifi aussi de nud destinataire, cest le service).
Comme illustr par la figure suivante, entre ces deux nuds, un mme message peut tre reu
puis renvoy par plusieurs nuds intermdiaires. Les nuds intermdiaires ainsi que le nud
final sont qualifis de nuds du chemin. Naturellement toutes les topologies de nuds SOAP
ne sont pas ncessairement aussi sophistiques et nutilisent pas forcment plusieurs protocoles
rseaux entre les nuds :

1002

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET


http
Nud
dorigine

tcp
Nud
intermdiaire

udp
Nud
intermdiaire

Nud final

Nuds intermdiaires
Nuds du chemin

Figure 24 -2 : Une topologie sophistique de nuds SOAP


Seul le nud final est habilit traiter les donnes de llment <body>. En revanche un lment de lentte peut tre trait par nimporte quel nud du chemin. Ces traitements sont
dfinis par les spcifications WS-*. Cependant SOAP fournit un mcanisme permettant de dterminer quel nud du chemin doit traiter quel lment dentte au moyen des trois attributs
XML role, mustUnderstand et relay.
Chaque nud du chemin joue un ou plusieurs rles SOAP. Un lment dentte informe un
nud SOAP si il peut le traiter au moyen de lattribut role. Si lURI prcise par cet attribut
dfinit un des rles jou par le nud SOAP alors ce dernier peut potentiellement le traiter. En
fait, le nud SOAP nest oblig de le traiter que si llment de lentte contient aussi lattribut
mustUnderstand="1" (ou mustUnderstand=true). Si un nud intermdiaire traite un ou plusieurs lments de lentte, il devra les enlever du message avant de le renvoyer vers le prochain
noeud. Un lment de lentte qui correspond au rle dun nud intermdiaire mais qui ne
doit pas tre obligatoirement trait peut tre soit enlev du message, soit laiss si il contient
lattribut relay="1" (ou relay=true).
SOAP 1.2 ne dfinit que trois rles SOAP : le rle Next est jou par tous les nuds intermdiaires tandis que le rle UltimateReceiver nest jou que par le nud final. Le rle None nest
jou par aucun nud. Les donnes dun lment dentte qui a ce rle sont donc purement
informatives. Ces trois rles sont reprsents respectivement par les URIs http://www.w3.
org/2003/05/soap-envelope/role/next, http://www.w3.org/2003/05/soap-envelope/role/
ultimateReceiver et http://www.w3.org/2003/05/soap-envelope/role/none. Les spcifications WS-* sont libres dtendre la langage SOAP en dfinissant de nouveaux rles.
Il est intressant de noter que seuls les lments de lentte qui doivent tre obligatoirement
traits (i.e ceux avec un attribut mustUnderstand positif) peuvent entraner une condition de
rupture. Autrement dit, si un nud du chemin rencontre un problme lors du traitement dun
tel lment de lentte, il est oblig de produire une erreur qui empchera le traitement des donnes du corps du message par le nud final. Cette erreur peut ventuellement tre achemine
jusquau nud dorigine par lintermdiaire dun nouveau message SOAP derreur.

Encodage du corps dun message SOAP


Les donnes contenues dans le corps dun message SOAP (i.e dans llment <body>) se prsentent en gnral sous la forme dun document XML dont le schma est prcis dans le contrat
du service concern. Cette faon de procder est connue sous le nom de formatage document
/ encodage littral puisque les donnes sont formates dans un document XML qui satisfait
littralement un schma XSD.
Lhistorique oriente objet du langage SOAP que nous avons mentionne dans les pages
prcdentes fait quil existe une alternative pour prsenter les donnes du corps dun message

Dfinir des contrats avec le langage WSDL

1003

SOAP. Cette autre faon de faire est connue sous le nom de formatage RPC / encodage encoded . Cette technique est adapte aux changes de messages requte/rponse synchrone modlisant un appel de mthode (do RPC). Lencodage encoded fait partie de la spcification de
SOAP et dcrit comment srialiser en XML des arguments entrant et sortant dune mthode en
se basant sur un ensemble de rgles. Ces rgles spcifient la faon dont les objets, les tableaux,
les structures et les graphes dobjets sont srialiss.
SOAP est maintenant principalement utilis pour coder des messages changs entre services.
Comme nous lavons mentionn le modle dchange de messages RPC style requte/rponse
synchrone nest plus quun modle dchange parmi dautres. Il ny a donc plus de raison de
le privilgier. Aussi les direntes implmentations des services web telles que ASP.NET/WSE
utilisent par dfaut le modle dencodage document/literal plutt que le modle RPC/encoded.

Messages derreur SOAP


Nous avons vu que le traitement dun lment dentte par un nud SOAP du chemin peut engendrer une erreur qui peut tre retourne au nud dorigine par lintermdiaire dun nouveau
message SOAP. Ceci est possible car le langage SOAP permet de dfinir des messages SOAP derreur. Un message SOAP derreur est un message SOAP dont le corps ne contient quun lment
<Fault>. La spcification du langage SOAP explique comment encoder au sein de cet lment la
raison de lerreur, le rle SOAP qui a provoqu lerreur, le nud qui a provoqu lerreur etc. Elle
spcifie aussi certains codes derreur tel que le code MustUnderstand qui signifie quun lment
de lentte avec un attribut mustUnderstand positif na pas pu tre trait.

Lien entre SOAP et les protocoles de transport sous-jacents


Pour chaque protocole rseau pouvant tre utilis pour transporter un message SOAP il existe
une spcification expliquant en dtail comment le message est achemin. En gnral cela ne
pose pas de problmes puisque la plupart des protocoles utiliss ont une notion de ce quest au
sens large un message.
La spcification SOAP 1.2 ne dcrit que le lien avec le protocole HTTP. Elle tablit notamment
une correspondance naturelle avec le modle dchange de messages style requte/rponse synchrone de HTTP.

Dfinir des contrats avec le langage WSDL


Nous avons introduit WSDL (Web Service Description Language prononc Wizdil) comme un langage XML extensible servant rdiger les contrats des services. Nous allons maintenant nous
intresser en dtail ce langage.

Domaine de couverture de WSDL


Le langage WSDL est constitu dun lment racine <definitions> et de sept lments XML
que lon peut classer en deux catgories :

Les lments <types>, <message>, <operation> et <portType> utiliss pour dcrire le


contrat que prsente un service web.

1004

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

Les lments <binding>, <port> et <service> utiliss pour prciser les liens entre un contrat
et une implmentation. En plus de lier les web mthodes aux oprations du service, ces liens
dcrivent dautres aspects de limplmentation tels que le protocole rseau ou lencodage
des donnes dans les messages SOAP.

Ainsi, certaines informations dcrites par la seconde catgorie dlments ne sont pas utiles aux
clients des services web. Elles ne font donc pas partie du contrat.
La spcification WS-Policy dcrit comment tendre WSDL pour rdiger des stratgies. Les stratgies sont des clauses du contrat permettant daugmenter sa prcision. Par exemple, le langage
WSDL ne spcifie pas comment expliciter le fait que les messages envoys un service doivent
tre encrypts. Il faut pour cela ajouter des stratgies de scurit dans le contrat. Ainsi, seule une
partie du contrat est eectivement rdige en WSDL :
Domaine de couverture des stratgies

Domaine de couverture de WSDL

Rdaction du contrat dun service Web

Dfinition du lien entre


le contrat et une
implmentation

Figure 24 -3 : WSDL et contrat

Analyse dun document WSDL


Voici le document WSDL de notre service localizer :
<?xml version="1.0" encoding="utf-8" ?>
<definitions
xmlns:s1="http://localizationcorp.com/documents/data/"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:s0="http://tempuri.org/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
targetNamespace="http://tempuri.org/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<s:schema elementFormDefault="qualified"
targetNamespace="http://tempuri.org/">
<s:import namespace="http://localizationcorp.com/documents/data/" />
<s:element name="GetTownFromLatLon">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="lat"
type="s:double" />
<s:element minOccurs="1" maxOccurs="1" name="lon"
type="s:double" />
</s:sequence>

Dfinir des contrats avec le langage WSDL


</s:complexType>
</s:element>
<s:element name="GetTownFromLatLonResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
ref="s1:GetTownFromLatLonResult" />
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
<s:schema elementFormDefault="qualified"
targetNamespace="http://localizationcorp.com/documents/data/">
<s:element name="GetTownFromLatLonResult" nillable="true"
type="s1:Town" />
<s:complexType name="Town">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="Name"
type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Country"
type="s:string" />
</s:sequence>
</s:complexType>
</s:schema>
</types>
<message name="GetTownFromLatLonSoapIn">
<part name="parameters" element="s0:GetTownFromLatLon" />
</message>
<message name="GetTownFromLatLonSoapOut">
<part name="parameters" element="s0:GetTownFromLatLonResponse" />
</message>
<portType name="LocalizerSoap">
<operation name="GetTownFromLatLon">
<input message="s0:GetTownFromLatLonSoapIn" />
<output message="s0:GetTownFromLatLonSoapOut" />
</operation>
</portType>
<binding name="LocalizerSoap" type="s0:LocalizerSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document" />
<operation name="GetTownFromLatLon">
<soap:operation soapAction="http://tempuri.org/GetTownFromLatLon"
style="document" />
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>

1005

1006

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

</operation>
</binding>
<service name="Localizer">
<port name="LocalizerSoap" binding="s0:LocalizerSoap">
<soap:address
location="http://localhost/localizationcorp/localizer.asmx" />
</port>
</service>
</definitions>

Llment <definitions>
Llment <definitions> est llment racine de tous document WSDL. Cet lment inclut les
espaces de noms standard utiliss (SOAP, XSD etc) et ventuellement des espaces de noms propritaires.

Llment <Types>
On voit que llment <types> contient les schmas XSD des donnes qui sont contenues dans
la partie <Body> des messages SOAP changs. Notez que llment <types> est obligatoire mais
quil peut tre vide si vous importez les schmas XSD dans un lment racine <import> :
<?xml version="1.0" encoding="utf-8" ?>
<import namespace="http://localizationcorp.com/documents/data/"
location="http://localizationcorp.com/documents/data/schema.xsd" >
<definitions>
<types />
...
</definitions>

Llment <message>
Chaque lment <message> dfinit un message SOAP entrant, sortant ou derreur. Chaque lment <part> dun lment <message> rfrence un schma de donnes. Puisquun message
SOAP peut contenir plusieurs parties, un lment <message> peut contenir plusieurs lments
<part>.

Llment <operation>
Chaque lment <operation> dfinit les messages SOAP supports par chaque opration du
service web. Dans notre service web simple localizer, la seule opration est constitue dun
change de message synchrone style RPC aussi elle prsente un message entrant et un message
sortant. Une opration supportant un autre modle dchange de message pourrait ne dfinir
quun message dentre ou de sortie. Une opration a aussi la possibilit de prsenter un message
derreur en contenant un lment <fault>.
Le nom dune opration dfinie dans llment soap:operation est prfix par dfaut par lespace de nom http://tempuri.org/. LExemple 24-2 montre comment personnaliser cet espace
de nom.

Dfinir des contrats avec le langage WSDL

1007

Llment <portType>
Un lment <portType> dfinit lensemble des oprations prsentes par un service. linstar de
note exemple les oprations ont la possibilit dtre dfinies directement dans llment <portType>. Elles peuvent tre aussi dfinies hors de cet lment puis rfrences comme ceci :
...
<operation name="GetTownFromLatLon">
<input message="s0:GetTownFromLatLonSoapIn" />
<output message="s0:GetTownFromLatLonSoapOut" />
</operation>
<portType name="LocalizerSoap">
<operation name="GetTownFromLatLon" />
</portType>
...
Si lon raisonne en termes de langage objet les types de llment <types> sapparentent
aux structures/classes de donnes, les lments <message> sapparentent aux signatures des
mthodes, les lments <operation> sapparentent aux mthodes et les lments <portType>
sapparentent aux interfaces. Intressons nous maintenant aux lments WSDL permettant
dtablir un lien entre un contrat et une implmentation.

Llment <binding>
Llment <binding> permet de prciser des informations sur limplmentation dun service.
Ces informations sont essentiellement :

Les protocoles dencodage des messages SOAP avec les lments <soap:body>.

Les noms des web mthodes utiliser pour chaque opration avec les lments <soap:operation>.

Le protocole rseaux sous-jacent avec llment <soap:binding>.

Comprenez bien que par exemple les lments <soap:operation> et <operation> sont dirents puisquils ne font pas parties du mme espace de noms. En outre, bien que certaines de
ces informations soient utiles aux clients pour consommer le service, elles ne contiennent pas
dinformation relative la smantique du service.

Llment <port>
Llment <port> permet de faire le lien entre un lment <binding> et une implmentation
dun service au moyen dun lment <soap:address>. Il est intressant que cette information
ne fasse pas partie de llment <binding> car cela donne un degr de libert supplmentaire
pour rediriger les messages SOAP entrant vers une autre implmentation.

Llment <service>
Llment <service> est une collection dlment <port>.

Squelette dun document WSDL


En rsum, voici le squelette dun document WSDL :

1008

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

<definitions>
<types />
<message />
<operation>
<message />
</operation>
<portType>
<operation />
</portType>
<binding>
<operation />
</binding>
<service>
<port>
<binding />
</port>
</service>
</definitions>

Introduction WSE et aux spcifications WS-*


Introduction WSE
Les facilits prsentes par Visual Studio et ASP.NET pour le dveloppement dun service web
ne permettent pas dimplmenter des possibilits basiques telles que lencryptions des messages
ou lutilisation dun protocole de transport autre que HTTP. Dun autre cot seules certaines
spcifications WS-* sont finalises. Depuis 2003, pour permettrent aux dveloppeurs .NET de
construire des services web exploitants ces spcifications finalises Microsoft fournit gratuitement la plateforme de dveloppement nomm WSE (Web Service Enhancement prononcez Wizi).
Concrtement WSE est principalement constitu dun diteur de configuration intgr Visual
Studio et de lassemblage Microsoft.Web.ServiceXX.dll (XX dsigne la version de WSE). Lditeur de configuration WSE permet de fixer simplement les paramtres dun projet qui exploite
WSE. Cet diteur travaille en parsant et en mettrant jour le code du fichier de configuration
de lapplication (web.config dans le cas dun service, app.config dans le cas dun client dun
service). Bien entendu vous pouvez dcider de ne pas utiliser cet diteur et prfrer modifier
vos fichiers la main.
Lassemblage Microsoft.Web.ServiceXX.dll doit tre rfrenc par chaque assemblage qui exploite WSE et distribu avec chaque application base sur WSE. Il contient du code qui modifit
les messages SOAP envoys ou reus selon les spcifications WS-* supportes. Cet assemblage
peut tre utilis du ct du client dun service et du ct du service.
La plateforme .NET 2.0 nest compatible quavec les versions 3.0+ de WSE. Il faut savoir que les
versions majeures de WSE (1.0, 2.0, 3.0 etc) ne sont pas compatibles entre elles. En outre, les
livraisons de WSE ne sont pas calques sur les livraisons du framework. Ces deux liberts prisent
par Microsoft confrent une certaine souplesse lvolution de WSE.
Vous pouvez tre rticent investir dans une technologie qui naura pas de compatibilit ascendante. Cependant sachez que WSE constitue lalternative la plus ecace pour mettre des services

Introduction WSE et aux spcifications WS-*

1009

en production avec .NET ds aujourdhui. Il est clair que vous bnficierez de vos acquis lorsque
vous devrez aborder les futures plateformes de dveloppement plus volues telles que Windows
Communication Foundation.

Les spcifications supportes par WSE 3.0


WSE 3.0 supporte principalement les spcifications suivantes :

WS-Policy : pour le dveloppement de stratgies.


WS-Security pour lauthentification, et lencryptions des messages.
WS-SecureConversation pour tablir des sessions dchange de messages scuriss (dans le
mme esprit que le protocole SSL).
WS-SecurityPolicy pour la description des stratgies de scurit.
WS-Trust pour lobtention de jeton de scurit.
WS-Addressing pour supporter des modles dchange de messages autres que le style RPC.
MTOM (Message Transmission Optimization Mechanism) pour lenvoi de donnes binaires volumineuses.

En plus de HTTP, WSE 3.0 permet dexploiter le protocole RPC ou un mode de transport optimis lorsquun service et son client sont dans le mme processus. Dans ces deux cas il nest bien
videmment pas utile dexploiter la technologie ASP.NET ni un serveur web tel que IIS.

Linstallation de WSE
WSE est tlchargeable gratuitement sur le site de Microsoft. En plus de lditeur de configuration WSE et de lassemblage Microsoft.Web.ServiceXX.dll vous trouverez dans le rpertoire
dinstallation des outils, de la documentation et des exemples de projets exploitant WSE.
La technologie de certification X509 (dcrite dans la section page 230) est largement exploite
dans le domaine de la scurit des services dvelopps avec WSE. Loutil X509 Certificate Tool
fourni avec WSE permet de faciliter la manipulation des certificats X509. WSE est livr avec deux
certificats X509 que vous pouvez utiliser pour tester les exemples fournis ainsi que vos propres
prototypes.
WSE est aussi livr avec loutil Policy Wizard qui permet de gnrer plus simplement vos stratgies.
Une fois que WSE est install sur votre machine de dveloppement vous pouvez lactiver trs
simplement sur vos projets Visual Studio (ASP.NET ou non). Il vous sut de cliquer droit sur
votre projet dans lexplorateur de solution et de slectionner WSE Settings 3.0.... Lditeur de WSE
sache alors et vous navez plus qu slectionner Enable this project for Web Services Enhancements. Dans le cas dun projet ASP.NET il faut aussi slectionner Enable Microsoft Web Services
Enhancements Soap Extensions.

Comment WSE est implment grce aux extensions SOAP ?


Dans le cas dun projet ASP.NET, WSE exploite les extensions SOAP. Une extension SOAP est une
classe capable davoir accs aux messages sortants avant quils soient envoys et aux messages entrants avant quils soient exploits par le service. Pour pouvoir tre reconnue comme une extension SOAP, une classe doit driver de la classe abstraite System.Web.Services.SoapExtension.

1010

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

La description dtaille des extensions SOAP dpasse le cadre de cet ouvrage. Nanmoins vous
pouvez vous rfrer la documentation des MSDN concernant la classe SoapExtension. En
eet, celle-ci contient un exemple dextension SOAP particulirement instructif qui permet de
sauver dans un fichier de log les messages SOAP entrants et sortants. Pour faire fonctionner cet
exemple vous devez prendre trois prcautions :

Faire en sorte que lutilisateur Windows qui excute le processus dASP.NET (par dfaut
ASPNET) ait tous les droits daccs sur le rpertoire dans lequel lapplication web est stocke
afin de pouvoir crer et modifier les fichiers de log.

Utiliser un client dvelopp avec une classe proxy. En eet, si vous utilisez un navigateur
pour tester votre service vous nutiliserez pas la mthode HttpSoap et dans ce cas les extensions SOAP ne sont pas utilises.

Ajoutez ceci dans votre fichier web.config afin dactiver votre extension SOAP.
<configuration>
<system.web>
<webServices>
<soapExtensionTypes>
<add type="NomDeLaClasseAvecEspaceDeNoms,
NomDeLAssemblageContenantLaClasse",
priority="1" group="0" />
</soapExtensionTypes>
...

Notez que vous pouvez dcider de nappliquer une extension SOAP que sur certaines web
mthodes. Dans ce cas il ne faut pas rajouter ceci votre fichier web.config mais il faut
marquer chaque mthode concerne avec un attribut dextension SOAP. Plus dinformations ce sujet sont disponibles dans la documentation des MSDN concernant la classe
SoapExtensionAttribute.

Dans le cas dun projet non ASP.NET lditeur de configuration de WSE cre une nouvelle
classe proxy dont le nom se termine par Wse. Dans notre exemple il y a donc deux classes
proxy : Localizer et LocalizerWse. La classe Localizer drive de System.Web.Services.
Protocols.SoapHttpClientProtocol tandis que la classe LocalizerWse drive de Microsoft.
Web.Services2.WebServicesClientProtocol. Bien entendu vous nobtenez les services de WSE
que si vous utilisez la classe proxy LocalizerWse pour accder votre service.

Un test de WSE avec les diagnostics


Vous pouvez trs simplement tester WSE sur un projet (ASP.NET ou non) en activant le log des
messages SOAP dans longlet Diagnostics de lditeur de configuration WSE Il est instructif que
vous regardiez les modifications qui sont alors apportes au fichier web.config (ou app.config
dans le cas dun projet non ASP.NET). Ici aussi, le test ne sexcute correctement que si vous
prenez les prcautions de la section prcdente (droits daccs de lutilisateur Windows ASPNET
+ client avec une classe proxy).

Les spcifications WS-* non encore supportes par WSE

1011

Les spcifications WS-* non encore supportes par WSE


WS-PolicyAttachment et WS-MetadataExchange
Il nest pas encore possible quun client obtienne automatiquement les expressions WS-Policy qui
sappliquent sur les oprations dun service dvelopp avec WSE. Le responsable du service peut
toujours communiquer le fichier contenant les expressions dune manire ou dune autre aux
clients mais ce type de solution nest pas satisfaisant car non standardis. Aussi, il existe la spcification WS-PolicyAttachment qui dcrit comment assigner des expressions aux oprations dun service directement dans le contrat WSDL. Il existe aussi la spcification WS-MetadataExchange qui
dcrit comment un client peut rcuprer les expressions rfrences par WS-PolicyAttachment.

WS-ReliableMessage
La spcification WS-ReliableMessage permet de pallier labsence de garanties de certains protocoles rseaux utiliss pour acheminer les messages SOAP. Par exemple, le protocole rseau UDP
ne fournit ni mcanisme daccus rception pour informer lenvoyeur dun message quil a bien
t reu par son destinataire, ni mcanisme permettant de garantir lordre de rception dune
squence de messages par un destinataire, ni mcanisme pour permettre au destinataire dliminer des messages dupliqus. Bien entendu la spcification WS-ReliableMessage se place au
niveau le plus bas dans lempilement des spcifications WS-* et sera gre implicitement par les
environnements dexcutions.

UDDI et WS-Discovery
Les spcifications UDDI (Universal Description Discovery and Integration) et WS-Discovery permettent de dcouvrir des services web. Tandis que UDDI dfinit un systme centralis bas sur
des inscriptions et des recherches dans des annuaires, WS-Discovery exploite des algorithmes
types multi diusion o lon peut se passer dannuaires centraliss.
Les annuaires UDDI peuvent tre publiques, inter entreprise ou intra entreprise. Chaque
entre dans un annuaire reprsente un fournisseur de service. Une partie de cette entre est
consacre la prsentation du fournisseur de service (domaines dactivit, contacts, tarifs etc).
Une deuxime partie est ddie la description abstraite des services web prsents (lments
<types>, <import>, <message>, <portType> et <binding> du contrat WSDL). Les documents
reprsentant les descriptions abstraites des services sont nomms Types Models (tModel). La
recherche dun service se fait essentiellement en demandant lannuaire quels sont les services
qui supportent un certains tModel. Enfin chaque entre de lannuaire contient une troisime
partie qui stocke les liens vers les implmentations des services (lments <import> et <service>
du contrat WSDL).
WS-Discovery dfinit deux messages hello et bye permettant un service de se dclarer ou de
se retirer lorsquil se connecte ou se dconnecte un rseau. Ces messages sont relays par multi
diusion pour atteindre les clients potentiels du service. Ces derniers ont aussi leur disposition deux messages probe et resolve pour lancer une recherche dun service sur le rseau.
Pour limiter la consommation excessive de bande passante des protocoles multi diusion, WSDiscovery permet dimplmenter des services spciaux dit proxy de dcouverte pour sappuyer sur
une certaine centralisation de la liste des services disponibles sur le rseau.

1012

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

WS-Federation
La spcification WS-Federation tend la spcification WS-Trust pour aner le modle dauthentification didentits entre dirents domaines de confiance. WS-Federation permet un mme
utilisateur de naviguer entre dirents domaines fdrs, sans avoir sauthentifier explicitement chaque fois. WS-Federation dfinit notamment les messages SOAP que schangent les
domaines pour rendre une telle navigation possible. Dun point de vue pratique, cette spcification vise pallier plusieurs problmes couramment rencontrs dans les organisations et les
entreprises tels que :

La dicult pour obtenir les autorisations quun agent dune organisation avec laquelle on
collabore a dans le cadre dune opration donne.
La dicult pour authentifier une mme personne au travers de ses direntes adresses email (professionnelles, personnelles etc).
La dicult pour dfinir le degr de sensibilit des donnes prives dune identit en fonction des contextes des oprations quelle eectue.

WS-Coordination
La spcification WS-Coordination permet plusieurs services web de coordonner leurs oprations
dans le cadre dun travail quils ont raliser en commun. Chaque travail est identifi par un
identificateur unique nomm contexte de coordination. Cet identificateur se retrouve dans des
enttes SOAP de chaque message SOAP chang dans le cadre de ce travail. WS-Coordination
dfinit ces enttes ainsi que leurs significations (requte pour demander deectuer une opration, refus ou incapacit de faire une opration, terminaison dune opration, time out dune
opration etc). Remarquez que certains travaux en commun tels quune transaction 2PC ncessitent la prsence dun service qui joue le rle particulier de coordinateur. Dautres travaux
tels que llection dun service parmi plusieurs services nont pas besoin de coordinateurs. WSCoordination gre ces deux types de travaux et lensemble des travaux possibles est ouvert. Cette
extensibilit se traduit par dautres spcifications WS-* telles que WS-AtomicTransaction qui se
basent sur les possibilits de WS-Coordination pour dfinir des travaux prcis.

WS-AtomicTransaction et WS-BusinessActivity
La spcification WS-AtomicTransaction se sert des possibilits de WS-Coordination pour permettre deectuer des transactions ACID volatiles ou durables, distribues sur plusieurs services.
Pour cela WS-AtomicTransaction exploite les algorithmes styles 2PC. Cette spcification sera
implmente par le DTC (Distributed Transaction Coordinator) de Windows Vista. Elle supporte
notamment les notions de transactions volatiles et durables.
La spcification WS-BusinessActivity se sert des possibilits de WS-Coordination pour permettre
deectuer des transactions longues distribues sur plusieurs services. Les transactions longues
viennent pallier limpossibilit de verrouiller certaines ressources pendant la dure dune transaction ACID. Aussi, une telle ressource est mise jour ds le dbut de la transaction longue.
Sil savre que la transaction choue, le coordinateur en informe chaque participant. Libre
eux alors deectuer une action compensatoire pour remettre leurs ressources dans leurs tats
initiales. Une consquence de cette faon de procder est que lon na pas de notion disolation
entres plusieurs transactions longues. Ainsi, il faut tre conscient quun participant ne peut pas
forcment remettre une ressource dans son tat initial puisque cette dernire a pu tre modifie
entre temps dans le cadre dune autre transaction.

Introduction WCF (Windows Communication Framework)

1013

WS-Enumeration
La spcification WS-Enumeration permet de demander une grosse quantit dinformation un
service. Une telle demande se traduit par ltablissement dune session WS-Enumeration qui
se charge de grer la squence des messages envoys du service vers le demandeur. Le suivit
de lavancement du curseur sur les informations transmettre peut se faire aussi bien du ct
demandeur que du ct service. Ce suivi peut mme ventuellement tre bascul durant une
mme session, par exemple du serveur vers le demandeur pour librer le serveur de la gestion
de lavancement lorsquil dtecte que le temps de latence entre les messages devient prohibitif.

WS-Eventing
La spcification WS-Enumeration permet dimplmenter dune manire standard le modle
dchange de message abonnement/notification dcrit en page 991.

WS-Management
La spcification WS-Management a pour but duniformiser la supervision des systmes dinformation. Le terme systme dinformation doit ici tre pris au sens large et englobe la fois le software (tels quun serveur IIS ou un systme dexploitation) et le hardware (tels quun SmartPhone
ou un Pocket PC). WS-Management dfinit des messages SOAP modlisant les oprations qui
sont exiges par toutes les solutions de supervision. Parmi ces oprations, citons la dcouverte
de la prsence de ressources de supervision, le paramtrage de ces ressources et lobtention de
renseignements sur ltat courant du systme. WS-Management est supporte par les acteurs
majeurs du march tels que Microsoft, Intel, AMD, Dell ou Sun.

Introduction WCF
(Windows Communication Framework)
WCF (Windows Communication Framework anciennement nomm Indigo) est un framework qui
sera une partie intgrante de Windows Vista. Il sera aussi disponible pour Windows XP. Ce framework vise unifier les direntes technologies de communication proposes par Microsoft jusquici savoir : ASMX, .NET Remoting, COM+/Enterprise Services, System.Messaging, MSMQ
et WSE. La compatibilit avec ces technologies permettra chaque entreprise de migrer son
rythme vers WCF.
WCF permet dtablir des communications entre des endpoints WCF (que lon pourrait traduire
par point de terminaison). Chaque endpoint est caractris par trois composantes :

Son adresse qui permet de le localiser.

Ses informations de liaison (binding) qui dfinissent le type de protocole avec lequel il sait
communiquer.

Ses contrats au sens o ce que lon a pu voir dans le prsent chapitre (structure et smantique dutilisation).

Larchitecture de WCF fait en sorte de bien sparer chacune de ces composantes. En tant que
dveloppeur, vous serez surtout concern par la notion de contrat puisque les notions dadresse
et dinformation de liaison ne sont significatives que durant le dploiement et la maintenance.

1014

Chapitre 24 : Introduction au dveloppement de Services Web avec .NET

WCF supportera la plupart des spcifications WS-* non encore implmentes par WSE que nous
venons de prsenter. La conception de WCF fait en sorte de masquer la complexit de ces spcifications aux dveloppeurs. Aussi, vous aurez accs toutes ces fonctionnalits au travers de
simples paramtres de fichiers de configuration et il est probable que vous nentendrez parler
des spcifications WS-* sous jacente que trs rarement.

A
Les mots-cls du langage C  2.0

Voici la liste des mots-cls :

Les mots-cls en gras nexistent pas dans le langage C++.

Les mots-cls en italique existent dans le langage C++ mais ont une utilisation ou une signification dirente.
Le terme C  2 est prcis pour un mot-cl introduit avec cette version du langage. Trs peu

de mots-cls ont t ajouts en comparaison du nombre de nouvelles possibilits.


Remarquez que plusieurs mots-cls ont deux ou trois significations (using, delegate, new, fixed
etc).
Un mot-cl ne peut pas tre utilis comme identificateur. En revanche un mot-cl prfix du
caractre @ est un identificateur, mais il vaut mieux viter cette pratique.
Mot-cl

Rfrence

abstract

La solution ! les classes abstraites et les mthodes abstraites, page


453

as

Loprateur as, page 465

base

Lhritage dimplmentation, page 445

bool

Le type boolen, page 356

break

Les instructions break et continue, page 332. Linstruction switch,


page 329

byte

Les types concernant la reprsentation des nombres entiers, page


354

1016

Annexe A : Les mots-cls du langage C  2.0

case

Linstruction switch, page 329

catch

Principe de la gestion des exceptions, page 509

char

Le type reprsentant un caractre, page 357

checked

Gestion des dpassements de capacit, page 359

class

Dfinition dune classe, page 396. La contrainte type valeur/type rfrence, page 477

const

Champs constants, page 398

continue

Les instructions break et continue, page 332

decimal

Les types concernant la reprsentation des nombres rels, page 355

default

Linstruction switch, page 329. Loprateur default, page 484

delegate

Les dlgations et les dlgus, page 378. Introduction aux mthodes anonymes de C  2, page 524

do

Les boucles de type while while (mot-cl), et do/while, page 332

double

Les types concernant la reprsentation des nombres rels, page 355

else

Utilisation de if/else, page 327

enum

Les numrations, page 366

event

Les vnements, page 411

explicit

Oprateurs de conversion de type (de transtypage), page 436

extern

Lattribut DllImport, page 265

extern alias C  2

Les alias externes, page 322

false

Le type boolen, page 356

finally

Le gestionnaire dexceptions et la clause finally, page 515

fixed

Ncessit dpingler les objets en mmoire, page 505. Les tableaux


fixs de taille fixe, page 507

float

Les types concernant la reprsentation des nombres rels, page 355

for

Les boucles for, page 332

foreach

Parcours des lments dune collection avec foreach et in, page


563

get

Les proprits, page 407

1017
global C  2

Le qualificateur global, page 321

goto

Les branchements (goto), page 334

if

Utilisation de if/else, page 327

implicit

Oprateurs de conversion de type (de transtypage), page 436

in

Parcours des lments dune collection avec foreach et in, page


563

int

Les types concernant la reprsentation des nombres entiers, page


354

interface

Les interfaces, page 456

internal

Les niveaux de visibilit des membres, page 417. Les niveaux de visibilit protg et interne protg, page 445

is

Loprateur is, page 464

lock

Le mot cl lock de C  , page 148

long

Les types concernant la reprsentation des nombres entiers, page


354

namespace

Les espaces de noms, page 310

new

Accs un constructeur lors de la construction dun objet, page 422.


Redfinition dune mthode et dsactivation du polymorphisme,
page 451. La contrainte du constructeur par dfaut, page 474

null

Notion de rfrence sur une instance de classe, page 341. volution


de la syntaxe C  : Nullable<T> et le mot cl null page 386

object

La classe System.Object, page 344

operator

Surcharge des oprateurs, page 433

out

Rcupration dinformation partir dune mthode (les paramtres out), page 404

override

La solution ! les mthodes virtuelles et le polymorphisme, page 449

params

La possibilit davoir des arguments variables en nombre et en type,


page 405

partial C  2

Dfinir un type sur plusieurs fichiers sources, page 392

private

Les niveaux de visibilit des membres, page 417

protected

Les niveaux de visibilit des membres, page 417. Les niveaux de visibilit protg et interne protg, page 445

1018

Annexe A : Les mots-cls du langage C  2.0

public

Les niveaux de visibilit des membres, page 417

readonly

Champs constants, page 398

ref

La possibilit de forcer le passage dargument par rfrence, page


401

return

Rcupration dinformation partir dune mthode (les paramtres out), page 404

sbyte

Les types concernant la reprsentation des nombres entiers, page


354

sealed

Les classes dont on ne peut driver (sealed), page 448

set

Les proprits, page 407

short

Les types concernant la reprsentation des nombres entiers, page


354

sizeof

Loprateur sizeof, page 504

stackalloc

Rservation de mmoire sur la pile avec stackalloc, page 508

static

Les membres statiques, page 430

string

Les chanes de caractres, page 370

struct

Les structures, page 364. La contrainte type valeur/type rfrence,


page 477

switch

Linstruction switch, page 329

this

Les indexeurs, page 409. Le mot-cl this, page 420

throw

Principe de la gestion des exceptions, page 509

true

Le type boolen, page 356

try

Principe de la gestion des exceptions, page 509

typeof

Prciser un type, page 240

uint

Les types concernant la reprsentation des nombres entiers, page


354

ulong

Les types concernant la reprsentation des nombres entiers, page


354

unchecked

Gestion des dpassements de capacit, page 359

unsafe

Dclaration dune zone de code non vrifiable, page 502

1019

ushort

Les types concernant la reprsentation des nombres entiers, page


354

using

Utilisation des ressources contenue dans un espace de noms, page


310. Linterface IDisposable et sa mthode Dispose(), page 425.
Alias sur les espaces de noms et sur les types, page 320

value

Accesseur set, page 408

virtual

La solution ! les mthodes virtuelles et le polymorphisme, page 449

volatile

Les champs volatiles, page 145

void

Casting de pointeurs, page 505

where

Contraintes de drivation, page 475

while

Les boucles de type while while (mot-cl), et do/while page 332

yield break C  2

Le mot-cl yield break, page 546

yield return C  2

Un premier exemple avec le mot-cl yield return, page 542

B
Nouveauts .NET 2.0

Assemblage
Lutilisation de lattribut AssemblyKeyFile pour signer un assemblage est maintenant viter.
Il est prfrable dutiliser les options /keycontainer et /keyfile du compilateur csc.exe ou les
nouvelles proprits du projet de Visual Studio 2005 (page 30).
Le nouvel attribut System.Runtime.CompilerServices.InternalsVisibleToAttribute permet de spcifier des assemblages qui ont accs aux types non publics de lassemblage sur lequel
il sapplique. On parle alors dassemblages amis (page 27).
Loutil ildasm.exe 2.0 prsente par dfaut la possibilit dobtenir des statistiques quant la
taille en octets de chaque section dun assemblage et quant lachage des informations sur les
mtadonnes. Avec ildasm.exe 1.x il fallait utiliser le commutateur /adv en ligne de commande
afin de les obtenir (page 24).

Linternationalisation des applications


Loutil resgen.exe peut maintenant gnrer le code source de classes C  ou VB.NET qui encapsulent laccs aux ressources dune manire fortement type (page 35).

Construction des applications


La plateforme .NET 2.0 est livre avec un nouvel outil nomm msbuild.exe. Cet outil sert
construire les applications .NET. Cet outil est notamment exploit par Visual Studio 2005 mais
vous pouvez lutiliser pour lancer vos propres scripts de construction (page 49).

Configuration des applications


La plateforme .NET 2.0 prsente une nouvelle gestion fortement type de vos paramtres de
configuration (page 60). Visual Studio 2005 contient un diteur de paramtres de configuration
qui prend compltement en charge la gnration des lignes de code ncessaires pour exploiter
cette technique (page 62).

1022

Annexe B : Nouveauts .NET 2.0

Dploiement des applications


La nouvelle technologie de dploiement dapplication nomme ClickOnce permet une gestion
fine de la scurit ainsi que des mises jour et une gestion du dploiement dune application
en plusieurs temps (page 76). En outre, Visual Studio 2005 prsente des facilits trs pratiques
pour utiliser cette technologie (page 81).

CLR
Un bug majeur du CLR version 1.x rendant possible la modification dun assemblage sign
numriquement a t corrig en version 2.0 (page 28).
La classe System.GC prsente les nouvelles mthodes AddMemoryPressure() et RemoveMemoryPressure() permettant de fournir au GC une indication quant au volume des ressources non
gres dtenues (page 123). Une autre mthode CollectionCount(int generation) permet
dobtenir le nombre de collectes eectues pour une gnration donne (page 123).
De nouvelles possibilits ont t ajoutes loutil ngen.exe pour supporter les assemblages exploitant la rflexion et pour automatiser la mise jour de la version compile dun assemblage
lorsquune de ses dpendances volue (page 113).
Linterface ICLRRuntimeHost utilis partir du code non gr pour hberger le CLR vient remplacer linterface ICorRuntimeHost. Elle permet davoir accs une nouvelle API permettant au
CLR de dlguer un certains nombre de responsabilits cruciales telles que le chargement des
assemblages, la gestion des threads ou la gestion des allocations mmoire. Cette API nest pour
linstant quutilise par lhte du moteur dexcution de SQL Server 2005 (page 100).
Trois nouveaux mcanismes dnomms rgion dexcution contrainte (CER), finaliseur critique et
rgion critique (CR) permettent daccrotre la fiabilit des applications telles que SQL Server 2005
susceptibles de faire face des pnuries de ressources (page 125, page 129 et page 129).
Un mcanisme dit de portail de mmoire est exploitable pour valuer avant un traitement si
lon disposera dassez de mmoire (page 127).
Vous pouvez maintenant terminer rapidement un processus en appelant la mthode statique
FailFast() de la classe System.Environment. Cette mthode ne prend pas de prcautions telles
que lexcution des finaliseurs ou des blocs finally en attente (page 136).

Dlgu
Un dlgu peut rfrencer une mthode gnrique ou une mthode dun type gnrique. On
voit alors apparatre la notion de dlgu gnrique (page 491).
Grce aux nouvelles surcharges de la mthode Delegate.CreateDelegate(Type, Object, MethodInfo) il est possible de rfrencer une mthode statique et son premier argument partir
dun dlgu. Les appels aux dlgus nont alors pas besoin de ce premier argument et cette
pratique sapparente un appel de mthode dinstance (page 538).
Linvocation de mthodes au travers de dlgus est maintenant beaucoup plus performante.

Synchronisation
Vous pouvez passer simplement des informations un thread que vous crez grce la nouvelle
dlgation ParametrizedThreadStart. En outre, de nouveaux constructeurs de la classe Thread
vous permettent de fixer la taille maximale en octets de la pile dun thread (page 140).

1023
La classe Interlocked prsente de nouvelles mthodes et permet de traiter dautres types tels
que IntPtr ou double (page 145).
La classe WaitHandle prsente une nouvelle mthode statique SignalAndWait(). En outre,
toutes les classes drivant de WaitHandle prsentent une nouvelle mthode statique OpenExisting() (page 153).
La classe EventWaitHandle permet de se passer des deux classes AutoResetEvent et ManualResetEvent qui en drivent. De plus, elle permet de nommer un vnement et donc, de le partager
entre plusieurs processus (page 155).
La nouvelle classe Semaphore permet dexploiter des smaphores Windows partir de votre code
gr (page 157).
La nouvelle mthode SetMaxThreads() de la classe ThreadPool permet de modifier le nombre
de threads du pool de threads partir du code gr (page 167).
Le framework .NET 2. 0 propose des nouvelles classes qui permettent de capturer et de propager
le contexte dexcution du thread courant un autre thread (page 180).

Scurit
La classe System.Security.Policy.Gac permet de reprsenter un nouveau type de preuve bas
sur la prsence dun assemblage dans le rpertoire GAC. (page 188).
Les nouvelles classes de permissions suivantes ont t ajoutes : System.Security.Permissions.KeyContainerPermission,
System.Net.NetworkInformation.NetworkInformationPermission, System.Security.Permissions.DataProtectionPermission, System.Net.Mail.
SmtpPermission, System.Data.SqlClient.SqlNotificationPermission, System.Security.
Permissions.StorePermission, System.Configuration.UserSettingsPermission, System.
Transactions.DistributedTransactionPermission et System.Security.Permissions.GacIdentityPermission.
La classe IsolatedStorageFile prsente les nouvelles mthodes suivantes : GetUserStoreForApplication(), GetMachineStoreForAssembly(), GetMachineStoreForDomain() et GetMachineStoreForApplication() (page 205).
Le framework .NET 2005 permet de lancer un processus fils dans un contexte de scurit dirent
de celui de son processus parent (page 135).
Le framework .NET 2005 prsente de nouveaux types dans lespace de nom System.Security.
Principal permettant de reprsenter et de manipuler des identificateurs de scurit (page 209).
Le framework .NET 2005 prsente de nouveaux types dans lespace de nom System.Security.
AccessControl pour manipuler les droits daccs Windows (page 211).
Le framework .NET 2005 prsente de nouvelles implmentations dalgorithme de hachage disponibles dans lespace de noms System.Security.Cryptography.
Le framework .NET 2005 prsente plusieurs classes permettant davoir accs aux fonctionnalits
prsentes par lAPI de protection de donnes (DPAPI) de Windows (page 225).
La classe System.Configuration.Configuration permet de grer trs simplement le fichier de
configuration dune application. Notamment, vous pouvez lexploiter pour encrypter vos donnes de configuration (page 718).
Le framework .NET 2005 prsente de nouveaux types dans les espaces de noms System.
Security.Cryptography.X509Certificates et System.Security.Cryptography.Pkcs qui sont

1024

Annexe B : Nouveauts .NET 2.0

spcialiss dans la manipulation des standards de certification X.509 et CMS/Pkcs7 ainsi que dans
la manipulation des listes de certificats (page 230).
Le nouvel espace de noms System.Net.Security prsente les nouvelles classes SslStream et
NegociateStream qui permettent dutiliser les protocoles SSL, NTLM et Kerberos de scurisation de flots de donnes (page 660).

Rflexion/Attribut
Vous avez maintenant la possibilit de charger un assemblage en mode reflection only (page
237). Dailleurs, la classe AppDomain prsente le nouvel vnement ReflectionOnlyPreBindAssemblyResolve dclench juste avant le chargement dun assemblage destin tre utilis par
le mcanisme de rflexion (page 92).
Le framework .NET 2005 introduit la notion dattribut conditionnel. Un tel attribut a la particularit dtre pris en compte par le compilateur C  2 que si un certain symbole est dfini (page
255).

Interoprabilit
Les notions de pointeur sur fonction et de dlgu sont maintenant interchangeables grce
aux nouvelles mthodes GetDelegateForFunctionPointer() et GetFunctionPointerForDelegate() de la classe Marshal (page 272).
La classe HandleCollector permet de fournir au ramasse-miettes une estimation de la quantit
de ressources non gres couramment dtenues (page 277).
Les nouvelles classes SafeHandle et CriticalHandle permettent dexploiter les handles Windows
plus ecacement quavec la classe IntPtr (page 278).
Les outils tlbimp.exe et tlbexp.exe prsentent la nouvelle option /tlbreference permettant
de fournir explicitement une libraire de type sans passer par la base des registres. Cela permet
la cration denvironnements de compilation moins fragiles.
Visual Studio 2005 prsente des facilits pour exploiter partir dune application .NET la technologie reg free COM de Windows XP qui permet dutiliser une classe COM sans lenregistrer dans
la base des registres (page 287).
Les structures relatives la technologie COM telles que BINDPTR, ELEMDESC ou STATDATA ont
t dplaces de lespace de noms System.Runtime.InteropServices vers le nouvel espace de
noms System.Runtime.InteropServices.ComTypes. En outre, cet espace de noms contient de
nouvelles interfaces qui redfinissent certaines interfaces standard COM telles que IAdviseSink
ou IConnectionPoint.
Lespace de noms System.Runtime.InteropServices contient de nouvelles interfaces telles que
_Type, _MemberInfo ou _ConstructorInfo qui permettent au code non gr davoir accs au mcanisme de rflexion sur les lments du code gr. Bien entendu, les classes gres concernes
(Type, MemberInfo, ConstructorInfo etc) implmentent ces interfaces.

C  2.0
Le chapitre 13 est consacr la fonctionnalit phare de .NET 2.0 : la gnricit.
Le compilateur csc.exe prsente les nouvelles options /keycontainer, /keyfile, /delaysign
(page 33), /errorreport et /langversion.

1025
C  2.0 apporte les notions de qualificateur dalias despaces de noms (page 320) de qualificateur
global: : (page 321) et dalias externe (page 322) pour viter certains problmes de conflits
didentificateurs.
C  2.0 prsente les nouvelles directives de compilation #pragma warning disable et #pragma
warning restore (page 316).
Le compilateur C  2.0 est capable dinfrer la dlgation adquate lors de la cration dun dlgu (page 380). Ceci rend le code plus lisible.
Le framework .NET 2005 introduit la notion de type nullable exploitable partir dune syntaxe
spciale en C  2 (page 385).
C  2.0 permet dtaler la dfinition dun mme type sur plusieurs fichiers sources dun mme
module (page 392).
C  2.0 permet dassigner une visibilit dirente aux accesseurs dune proprit ou dun indexeur (page 419).
C  2.0 permet la dfinition de classes statiques (page 432).
C  2.0 permet de dclarer un champ de type tableau de taille fixe dlments de types primitifs
au sein dune structure (page 507).
Lintellisense de Visual Studio 2005 vous propose les balises XML des commentaires de types ///
(page 324).

Exceptions
La classe SecurityException et Visual Studio 2005 ont t amliors de faon vous permettre
de tester et de dboguer plus facilement votre code mobile (page 205).
Visual Studio 2005 prsente un assistant trs pratique pour obtenir la totalit des donnes relatives une exception qui survient durant le dboguage (page 510).
Visual Studio 2005 vous permet dtre inform lorsquun vnement problmatique connu du
CLR survient. Ces vnements se traduisent parfois par une exception gre (page 522).

Collection
Lensemble des types de collection du framework .NET a t compltement revu afin de tenir
compte des bnfices du support de la gnricit. Ces types font lobjet du chapitre 15. En page
595 nous exposons un tableau de correspondance entre les types des deux espaces de noms
System.Collections et System.Collections.Generic.

Dbogage
Lespace de noms System.Diagnostics prsente les nouveaux attributs DebuggerDisplayAttribute, DebuggerBrowsable, DebuggerTypeProxyAttribute et DebuggerVisualizerAttribute qui
vous permettent de personnaliser lachage des tats de vos objets durant le dbogage (page
617).
Vous avez maintenant accs la fonctionnalit Edit And Continue permettant de modifier le code
en cours de dbogage.

1026

Annexe B : Nouveauts .NET 2.0

Classes de bases
Les types primitifs (entier, boolen, nombre virgule flottante, dcimaux) prsentent la mthode TryParse() qui permet de parser une valeur dans une chane de caractres sans lancer
dexception en cas dchec (page 358).
Le framework .NET 2005 prsente plusieurs implmentations drives de la classe abstraite
System.StringComparer qui permettent de comparer des chanes de caractres en tenant
compte ou non de la culture et de la casse (page 585).
La nouvelle classe DriveInfo permet de reprsenter et de manipuler des volumes (page 607).
Le framework .NET 2005 introduit la notion de source de traage permettant un paramtrage
plus fin des traces (page 622). En outre, les nouvelles classes suivantes de listeners ont t
ajoutes : ConsoleTraceListener, DelimitedListTraceListener, XmlWriterTraceListener
et WebPageTraceListener. (page 621).
De nombreuses fonctionnalits ont t ajoutes la classe System.Console afin de rendre laffichage des donnes plus convivial (page 629).

Entres/Sorties
Le framework .NET 2005 prsente la nouvelle classe System.Net.HttpListener qui permet dexploiter la couche HTTP.SYS de Windows XP SP2 et Windows Server 2003 pour dvelopper un
serveur HTTP (page 654).
En .NET 2005, les classes de lespace de noms System.Web.Mail sont maintenant obsoltes. Pour
envoyer des mails, il faut utiliser les classes de lespace de noms System.Net.Mail. En outre,
le nouvel espace de noms System.Net.Mime contient des classes pour le support de la norme
MIME (page 656).
Des nouvelles mthodes permettent de lire ou dcrire dans un fichier en un seul appel (page
636).
De nouvelles classes sont disponibles pour compresser/dcompresser un flot de donnes (page
659).
Une nouvelle version non gre System.IO.UnmanagedMemoryStream de la classe MemoryStream
permet dviter la copie des donnes sur le tas des objets du CLR. Elle est donc plus ecace (page
659).
La nouvelle classe System.Net.FtpWebRequest implmente un client FTP (page 653).
Le nouvel espace de noms System.Net.NetworkInformation contient des types qui permettent
de connatre les interfaces rseaux disponibles sur la machine, dinterroger leurs tats, dtre
averti des changements dtats et dobtenir des statistiques sur le trafic (page 649).
Des services de cache de ressources web sont disponibles dans le nouvel espace de noms System.
Net.Cache (page 653).
La nouvelle classe System.IO.Ports.SerialPort permet dexploiter un port srie dune manire synchrone ou par vnement (page 660).

Windows Forms 2.0


Visual Studio 2005 exploite la notion de classe partielle au niveau de la gestion des formulaires
Windows Forms. Ainsi il ne mlange pas le code quil gnre et votre propre code dans le mme
fichier (page 669).

1027
Windows Forms 2.0 prsente la classe BackgroundWorker qui permet de standardiser le dveloppement dopration asynchrone au sein dun formulaire (page 676).
Lapparence (i.e le style visuel) des contrles est mieux gre avec Windows Forms 2.0 car il ny a
plus besoin davoir recours la DLL comctl32.dll pour obtenir un style la Windows XP (page
679).
Un rapide aperu des nouveaux contrles Windows Forms 2.0 est disponible en page 681.
Windows Forms 2.0 et Visual Studio 2005 contiennent un framework et des outils de dveloppement rapide de fentres de prsentation et ddition de donnes (page 688).
Windows Forms 2.0 prsente les nouvelles classes BufferedGraphicsContext et BufferedGraphics
qui permettent de grer finement un mcanisme de double buering (page 703).

ADO.NET 2.0
ASO.NET 2.0 prsente de nouvelles classes abstraites telles que DbConnection ou DbCommand
dans le nouvel espace de noms System.Data.Common qui implmentent les interfaces IDbConnection ou IDbCommand. Lutilisation de ces nouvelles classes pour sabstraire dun fournisseur
de donnes sous-jacent est maintenant prfrer lutilisation des interfaces (page 712).
ADO.NET 2.0 prsente une architecture de classes volues de type fabrique abstraite qui permet de produire du code daccs aux donnes compltement indpendant du fournisseur de
donnes sous-jacent (page 712).
Les quatre fournisseurs de donnes fournis avec le framework prsentent de nouvelles facilits
permettant de construire une chane de connexion indpendamment du fournisseur de donnes sous-jacent (page 716).
ADO.NET 2.0 prsente un framework permettant de naviguer programmatiquement dans le
schma dun SGBD (page 718).
Le moteur dindexation exploit en interne par le framework lorsque vous utilisez des instances
des classes DataSet et DataTable a t refait de faon tre plus performant lors du chargement
et de la manipulation des donnes.
Les instances des classes DataSet et DataTable sont maintenant srialisables au format binaire
grce la nouvelle proprit SerializationFormat RemotingFormat{get;set;}. Vous pouvez
vous attendre un gain dun facteur de 3 8 par rapport la srialisation XML.
La classe DataTable est moins dpendante de la classe DataSet car les possibilits XML de cette
dernire (dcrites en page 775) lui ont t ajoutes.
La nouvelle mthode DataTable DataView.ToTable() permet de construire une DataTable
ayant une copie du contenu dune vue (page 730).
ADO.NET 2.0 ore un pont entre le mode connect et le mode dconnect en permettant aux
classes DataSet/DataTable et DataReader de travailler ensembles (page 735).
Les DataSet typs de ADO.NET 2.0 prennent en compte directement la notion de relation entre
tables. En outre, grce aux types partiels, le code gnr et votre propre code sont spars dans
deux fichiers distincts (page 731). Enfin, vous pouvez coder des requtes SQL types avec la
nouvelle notion de TableAdapter (page 733).
ADO.NET 2.0 ore la possibilit doptimiser la sauvegarde dans une base des modifications
eectues sur un cache de donnes en permettant la mise jour par lot des modifications (page
727).

1028

Annexe B : Nouveauts .NET 2.0

ADO.NET 2.0 : Fournisseur de donnes de SQL Server


Vous avez la possibilit dnumrer les sources de donnes de type SQL Server (page 716).
Vous avez la possibilit dobtenir programmatiquement un certain contrle sur la faon dont
le pooling de connexions seectue (page 718).
Le fournisseur de donnes SqlClient de ADO.NET 2.0 permet deectuer des commandes
dune manire asynchrone (page 738).
Vous pouvez utiliser les services de copie en masse de loutil SQL Server bcp.exe grce la nouvelle classe SqlBulkCopy (page 739).
Vous pouvez obtenir des statistiques concernant lactivit dune connexion (page 740).
Il existe une version simplifie, gratuite et librement distribuable de SQL Server 2005 qui prsente de nombreux avantages par rapport aux anciens produits MSDE et Jet (page 740).

Transaction
Le nouvel espace de noms System.Transactions (contenu dans la DLL Systems.Transactions.
dll) propose la fois un modle de programmation transactionnelle unifi et un nouveau moteur transactionnel dont la particularit est dtre trs performant sur certains types de transactions lgres (page 741).

XML
Les performances de toutes les implmentations utilises pour manipuler des donnes stockes
dans un format XML ont t amliores significativement (dun facteur 2 4 lors des scnarios
dutilisation classiques selon Microsoft).
La nouvelle classe System.Xml.XmlReaderSettings permet de prciser les vrifications qui
doivent tre faites lorsque lon lit des donnes XML au moyen dune classe drive de XmlReader
(page 766).
Il est possible de valider partiellement un arbre DOM charg dans une instance de XmlDocument
(page 771).
Il est maintenant possible de modifier un arbre DOM stock dans un XmlDocument au moyen
de lAPI type curseur de XPathNavigator (page 774).
La classe XslCompiledTransform remplace la classe XslTransform maintenant devenue obsolte. Son atout principal et de compiler les programmes XSLT en code MSIL avant dappliquer
une transformation. Selon Microsoft, cette nouvelle implmentation amliore les performances
dun facteur de 3 4 (page 774). En outre Visual Studio 2005 permet maintenant de dboguer
un programme XSLT (page 784).
Le support de la classe DataSet pour XML a t amlior. Vous pouvez maintenant charger des
schmas XSD avec des noms rpts dans dirents espaces de noms et charger du XML avec
plusieurs schmas en ligne. En outre, les mthodes de chargement et de sauvegarde des donnes
stockes en XML ont t ajoutes la classe DataTable (page 775).
La version 2005 de SQL Server apporte des nouvelles facilits quant lintgration de donnes
XML dans une base de donnes relationnelle (page 779).
La srialisation XML permet maintenant de srialiser les informations de type nullable et les
instances de types gnriques. En outre, il existe un nouvel outil sgen.exe permettant de pr
gnrer un assemblage contenant le code pour srialiser un type (page 779).

1029

.NET Remoting
Le nouveau canal IpcChannel est ddi la communication entre processus tournants sur la
mme machine. Son implmentation est base sur la notion de pipe nomm Windows (page
830).
Si vous utilisez un canal de type TCP, vous avez maintenant la possibilit dutiliser les protocoles
NTLM et Kerberos pour authentifier lutilisateur Windows sous lequel sexcute le client, pour
crypter les donnes changes et pour impersonifier vos requtes (page 816).
De nouveaux attributs de lespace de noms System.Runtime.Serialization permettent de grer les problmes inhrents lvolution dune classe dont les instances sont srialises (page
791).
Il est possible de consommer une instance dun type gnrique ferm avec la technologie .NET
Remoting, que vous soyez en mode CAO ou WKO (page 499).

ASP.NET 2.0
Visual Studio .NET 2005 est fourni avec un serveur web permettant de tester et de dboguer vos
applications web en cours de dveloppement (page 862).
Il est ais de se servir de la couche HTTP.SYS pour construire un serveur web qui hberge
ASP.NET sans aucunement avoir recours IIS (page 865).
ASP.NET 2.0 prsente un nouveau modle de construction des classes reprsentant les pages.
Ce modle est bas sur les classes partielles et est dirent de celui dASP.NET 1.x (page 869).
La directive CodeBehind dASP.NET v1.x nest plus supporte (page 871).
En ASP.NET 2.0, le modle de compilation dynamique a considrablement volu et se base
maintenant sur plusieurs nouveaux rpertoires standard (page 871). De plus, ASP.NET 2.0 prsente deux nouveaux modes de pr compilation (page 872 et page 872).
Pour pallier les eets des viewstate volumineux de ASP.NET 1.x, ASP.NET 2.0 stocke les informations dans la chane base64 plus ecacement et introduit la notion de controlstate (page 879).
ASP.NET 2.0 prsente une nouvelle technique pour permettre une page denvoyer un vnement postback une autre page (page 880).
Certains vnements ont t rajouts au cycle de vie dune page (page 883).
ASP.NET 2.0 prsente une infrastructure pour permettre deectuer le traitement dune mme
requte sur plusieurs threads du pool. Cela permet notamment dviter la pnurie de threads du
pool qui survient lorsque plusieurs traitements asynchrones longs sont raliss simultanment
(page 886).
De nouveaux vnements ont t ajouts la classe HttpApplication (voir les MSDN).
La manipulation des fichiers de configuration est simplifie grce lintellisense de Visual Studio
2005, une nouvelle interface web, une nouvelle interface graphique intgre dans la console de
configuration dIIS et grce des nouvelles classes de base permettant de manipuler programmatiquement et surtout, dune manire fortement type, les donnes XML de ces fichiers (page
890).
ASP.NET 2.0 prsente un framework permettant de grer dune manire standard les vnements qui surviennent dans la vie dune application web (page 906).

1030

Annexe B : Nouveauts .NET 2.0

Vous pouvez configurer ASP.NET 2.0 pour quil dtecte sil peut stocker lidentifiant de session
dans un cookie cot client et pour se mettre automatiquement en mode URI si le navigateur du
client ne permet pas les cookies (page 901).
ASP.NET 2.0 vous permet de fournir votre propre mcanisme de gestion de sessions ou de gestion dIDs de sessions (page 902).
Le moteur de cache dASP.NET 2.0 prsente plusieurs nouvelles possibilits intressantes. Vous
pouvez maintenant utiliser la sous directive VaryByControl dans vos pages (page 920). Vous
pouvez substituer des fragments de page dynamiques dans vos pages caches (page 923). Vous
pouvez associer des donnes caches des dpendances vers des tables dun SGBD type SQL
Server (page 926). Enfin, vous pouvez crer vos propres types de dpendances (page 928).
ASP.NET 2.0 prsente plusieurs nouveaux contrles serveur permettant de se lier dclarativement une source de donne (page 930).
ASP.NET 2.0 prsente une nouvelle hirarchie de contrles serveurs de prsentation et ddition
de donnes. Ces contrles ont tous la particularit de pouvoir exploiter un contrle de source
de donnes pour laccs en lecture et en criture (page 935).
ASP.NET 2.0 prsente une syntaxe simplifie pour les templates (page 939).
ASP.NET 2.0 prsente la notion de master page qui permet de rutiliser simplement un modle
de design de page sur les pages dun site (page . 947).
ASP.NET 2.0 prsente une infrastructure extensible permettant dinsrer dans vos pages des
contrles daide la navigation dans un site (page 954).
Avec ASP.NET 2.0 vous pouvez utiliser le mode dauthentification Forms sans avoir recours aux
cookies (page 962).
ASP.NET 2.0 permet de grer les donnes dauthentification des utilisateurs ainsi que les rles
auxquels ils peuvent appartenir dune manire standard au moyen dune base de donnes
(pages 962 et 964). En outre, plusieurs nouveaux contrles serveurs viennent grandement
simplifier le dveloppement dapplication ASP.NET supportant lauthentification (page 965).
ASP.NET 2.0 prsente un framework permettant de stocker et de manipuler dune manire standard les donnes personnelles chaque utilisateur (page 966).
ASP.NET 2.0 prsente un framework permettant de faciliter la maintenance de lapparence dun
site (page 971).
ASP.NET 2.0 prsente un framework permettant de crer des portails webs au moyen de ce que
lon nomme les webParts (page (page 973).
ASP.NET 2.0 prsente un framework permettant de modifier le rendu dune page HTML si la
requte HTTP initiatrice a t mise partir dun systme avec un cran de petite taille, type
systme mobile. Concrtement, le rendu de la plupart des contrles serveurs est modifi pour
tenir moins de place. Cette modification se fait grce des objets que lon appelle adaptateurs.
Les adaptateurs sont sollicits automatiquement et implicitement par ASP.NET au moment de
la phase du rendu. Larticle Inside the ASP.NET Mobile Controls des MSDN constitue un bon
point de dpart pour aborder cette facette dASP.NET 2.0.

Web Service
Les classes proxys gnres avec loutil wsdl.exe prsentent un nouveau modle dappel asynchrone annulable (page 998).

C
Introduction aux design patterns

Les design patterns objets sont des motifs dinteraction entre classes, des motifs dinteraction
entre objets, et des motifs de dfinition de classes. La notion de pattern est apparue dans les
annes 1970, dans le domaine de larchitecture classique (btiment etc). Elle a t popularise
dans le domaine de la programmation oriente objet la fin des annes 90 grce cet ouvrage
de rfrence :
Design Patterns, Elements of Reusable Object-Oriented Software
ADDISON-WESLEY 1994
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
ISBN : 0-201-63361-2
La notion de pattern sest alors invite dans dautres domaines de la conception logicielle, notamment en architecture des applications distribues. Louvrage suivant, qui lui aussi fait rfrence,
met en vidence des motifs rcurrents (i.e des patterns) concernant la gestion de la persistance
et lchange de donnes entres les tiers dune application distribue :
Patterns of Enterprise Application Architecture
ADDISON-WESLEY 2002
Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Staord
ISBN : 0-321-12742-0
Microsoft sintresse et communique depuis quelques annes sur les patterns de conception logicielle avec ses technologies au travers de son groupe de travail Pattern & Practices (PAG). Plus
dinformations ce sujet sont disponibles lURL http://msdn.microsoft.com/practices/.
Les initis qui pensent en terme de patterns parviennent concevoir rapidement des architectures de meilleure qualit. De plus leurs capacits de communication et dabstraction sont bonifies grce aux noms des patterns. Le dveloppeur a tout intrt les assimiler et les utiliser.
Chaque pattern dfinit un comportement plus ou moins souple, qui rpond une ou plusieurs
problmatiques rcurrentes de conception logiciel. Voici quelques exemples de problmatiques
introduites dans le prsent ouvrage suivies du nom du pattern utilis pour les rsoudre :

1032

Annexe C : Introduction aux design patterns

La cration des objets sans spcifier explicitement leur classe concrte (Fabrique abstraite).
Ce pattern est exploit par la technologie ADO.NET notamment pour viter une application de se coupler avec un fournisseur de donnes particulier (voir page 715). Il est aussi
utilis par la technologie .NET Remoting pour viter dexposer les implmentations des
objets serveurs aux clients (voir page 802).

Linterception des appels un objet (Proxy). Plus de dtails sont disponibles page 817.

La dfinition dune relation one-to-many entre un objet sujet et des objets de faon ce
que chacun des objets dpendants soient notifis des changements dtat de lobjet sujet
(Observateur). La notion dvnement de C  constitue une excellente alternative limplmentation de ce pattern (voir page 411).

La possibilit de pouvoir choisir limplmentation dune fonctionnalit aprs que le code


ait t compil grce un fichier de configuration (Provider). Ce pattern est trs exploit par
ASP.NET, notamment pour permettre de fournir vos propres extensions aux fonctionnalits
de ce framework (voir page 902).

Dlguer le choix dune implmentation jusquau moment de lexcution en ayant recours


des liens tardifs (Plugin). En page 247 nous exposons un exemple dutilisation.

La possibilit de pouvoir accder aux lments dune collection dune manire squentielle
sans se proccuper de la reprsentation physique de ces lments (Itrateur). C  2.0 prsente
des facilits pour implmenter ce pattern (voir page 542).

Permettre des objets qui ne se connaissent pas dinteragir grce une classe qui encapsule
ces interactions (Mdiateur). Ce pattern est exploit par la technologie Windows Form pour
faciliter le dveloppement des formulaires (voir page 667).

Le contrle du nombre dinstance dune classe (Singleton). Les constructeurs de classe de


.NET permettent de faciliter limplmentation de ce pattern (voir page 431).

Lajout de responsabilits un objet durant lexcution (Dcorateur). Ce pattern est exploit


par le framework .NET notamment pour ajouter des services un flot de donnes (voir page
657).

Crer volont des objets partir dun objet prototype connu par une rfrence de type une
interface qui ne contient quune mthode de clonage (Prototype). Linterface du framework
System.ICloneable prsente page 347 est dans une certaine mesure adapte ce rle.

Maintenir la liste de changements eectus durant une opration et coordonner la persistance de ces changements en grant les ventuels problmes dus la concurrence (Unit de
travail). Ce pattern est utilis pour sauver les changements eectus sur les donnes dun
DataSet dans la technologie ADO.NET (voir page 725).

Obtenir une implmentation thread-safe dune classe (SyncRoot). Plus de dtails sont disponibles en page 149.

Vous pouvez complter cette lecture par larticle Discover the Design Patterns Youre Already Using in the .NET Framework de Rob Pierry disponible dans le numro de Juillet
2005 de MSDN Magazine lURL : http://msdn.microsoft.com/msdnmag/issues/05/07/
DesignPatterns/default.aspx.

D
Les outils

Vous pouvez consulter le site http://sharptoolbox.com qui maintient une liste trs complte
des outils disponibles autour du dveloppement pour la plateforme .NET. Voici la liste des outils dont nous parlons dans le prsent ouvrage :

Assemblages

Scurit

ildasm.exe page 21

caspol.exe page 195

Reflector page 24

mscorcfg.msc page 195

resgen.exe page 35

permview.exe page 204

al.exe page 37 et page 67

certmgr.exe page 231

ilasm.exe page 41

permcalc.exe page 82

ILMerge page 17

Interoprabilit

Construction/Dploiement

regsvr32.exe page 292

msbuild.exe page 49

regasm.exe page 292

GACUtil.exe page 65

tlbimp.exe page 279

mage.exe page 77

tlbexp.exe page 289

mageui.exe page 77

guidgen.exe page 20

fuslogvw.exe page 106

regsvcs.exe page 304

ngen.exe page 113

C

Profiling

csc.exe page 317

CLR Profiler page 119

NDoc page 326

1034

Annexe D : Les outils

Windows Forms

ASP.NET 2.0

HTML Help WorkShop page 679

aspnet_regiis.exe page 863

Srialisation XML et datasets typs

aspnet_compiler.exe page 872

xsd.exe et les datasets typs page 731

aspnet_regsql.exe page 963

xsd.exe et la srialisation XML page 782

Service web

sgen.exe page 782

wsdl.exe page 994

Mapping Objet/Relationnel

X509 Certificate Tool page 1009

Des liens vers les outils les plus populaires sont


disponibles en page 737

Policy Wizard page 1009

Sql Server

Diagnostic

bcp.exe page 739

perfmon.exe page 906 et page 116

.NET Remoting

Programmation Oriente Aspect

soapsuds.exe page 804

AspectDNG page 466

Enfin, nous signalons lexistence de loutil NDepend. Cet outil analyse statiquement le code
compil de nimporte quelle application .NET et nest aucunement intrusif. En plus de calculer
la plupart des mtriques de code partir de cette analyse il sait dtecter un certain nombre de
problmes de design.

Index
2 Phase Commit, 742
2PC, voir 2 Phase Commit, 742
abonn ( un vnement), 411
abstract factory, voir fabrique abstraite, 715
abstraction, 453
Access Control Element, 211
Access Control List, 211
accesseur (vnement), 412
accesseur (proprit), 407
ACE, voir Access Control Element, 211
ACID (proprits), 741
ACL, voir Access Control List, 211
activation dun objet, 795
Activator, voir System.Activator, 242
ActiveX, 283
activit COM+, 298
adaptateur, 280
add (accesseur dvnement), 412
administrateur de baux, 806
ADO, 707
ADO.NET, 707
adresse IP, 641
agrgation (dun objet sur un autre), 398
al.exe, 37
alatoire, 600
algbre relationnelle, 706
algorithme symtrique, 219, 221
alias, 320
alias externes, 322
allocation, 337
allocation dynamique, 338
allocation statique, 338
AllowPartiallyTrustedCallersAttribute, 198
amies (notion C++), 396

AOP, voir programmation oriente aspect,


466
apartment COM, 285
AppDomain, voir domaine dapplication, 87
AppearanceEditorPart, 982
appel asynchrone, 171, 297
appel asynchrone (.NET Remoting), 787, 998
appel asynchrone sans retour, 176
AppLaunch.exe, 83
application, 134
application domain, voir domaine
dapplication, 87
application web, 861
ApplicationDirectory, voir System.Security.
Policy.ApplicationDirectory, 188
ApplicationScopedSettingAttribute, 61
arnes, 120
array, 344
as (mot-cl), 465
ASCII, 635
Aspect Oriented Programming, voir
programmation oriente aspect, 466
aspnet_isapi.dll, 861
aspnet_regiis.exe, 863
aspnet_regsql.exe, 927, 963, 966
aspnet_wp.exe, 861
assemblage, 15
assemblage amis, 27, 419
assemblage de stratgie dditeur, 67
assemblage domain neutral, 88
assemblage interoprable, 279
assemblage partag, 64
assemblage satellite, 34
assembly loader, voir chargeur dassemblage,
103

1036
AssemblyDef (table du manifeste), 18
AssemblyInfo.cs, 25
AssemblyRef (table de mtadonnes de type),
19
atomique (opration), 146
attribut, 248
attribut
AttributeTargets, 250
ConditionalAttribute, 314, 315
attribut conditionnel, 255
attribut de contexte, 845
attribut personnalis, 251
attribut XML, 761
Attribute, voir System.Attribute, 251
Authenticode, 73, 230
auto-descriptif (format), 759
AutoEventWireUp, 884
Automation, 109
AutoResetEvent, 153
autorisation, voir permission, 191
bac sable, 205
BackgroundWorker, 676
bail, 805
base (mot-cl), 447
Base Class Library, voir BCL, 6
base des registres, 613
Base64 (encodage), 35
Basic String, voir BSTR, 284
BCL, 6
bcp.exe, 739
BehaviorEditorPart, 982
bibliothque de types, 279
bien connu (objet), voir WKO, 796
big-endian, 635
BindingList<T>, voir System.
ComponentModel.BindingList<T>,
579
BindingNavigator, 689
bindingRedirect, 106
BindingSource, 691
bits par pixel, 698
blob, 189
bloc de restitution de code, 867
bool (mot-cl), 356
bootstrapper, 78
boucles, 327
boucles, 331
boxing, 350

Index
bpp, voir bits par pixel, 698
branchement, 327
break (mot-cl), 333
browser, voir navigateur, 859
BSTR, 284
buer, voir mmoire tampon, 637
BueredWebEventProvider, 907
BYOT, 296
byte (mot-cl), 354
bytecode, 41
Cw, 737
C++/CLI, 272
cte cte, 65
CA, voir Certificate Authorities, 231
cache (ASP.NET), 918
cache de tlchargement, 85
cache des images natives, 66, 113
cache global des assemblages, 64, 113
cale, 96
call context, 856
callback procedure, voir Procdure de rappel,
666
CamelCase, 326
canal, 830
canal metteur, 830
canal rcepteur, 830
CAO, 799
capacit (classe List<T>), 578
capacit (classe StringBuilder), 376
capture dune variable, 531
CAS, 186
Cascading Style Sheet, 970
caspol.exe, 196
Cassini, 862
catalogue COM+, 302
catch (mot-cl), 511
CategoryAttribute, 685
cctor, voir constructeur de classe, 431
CCW, 288
Cdecl, 266
CER, voir rgions dexcution contraintes, 126
certificat, 231
certificat racine, 231
Certificate Authorities, 231
Certificate Store, 231
certmgr.exe, 231
CGI, 860
ChangePassword, 966

Index
channel, voir canal, 830
channel sink providers, voir fournisseur
dintercepteurs de messages, 833
char (mot-cl), 357
chargement explicitement, 108
chargement implicite, 108
chargeur dassemblages, 103
chargeur de classe, 109, 191
checked, 359
checked exception, voir exception contrle,
515
chemin
chemin relatif, 611
chemin, 611
chemin
chemin absolu, 611
chrome (WebParts), 975
CICS, 297
CIL, voir IL, 41
class (mot-cl), 396
class constructor, voir constructeur de classe,
431
class interface, voir interface de classe, 291
class loader, voir chargeur de classe, 109
classe, 395
classe abstraite, 453
classe de base, 444
classe drive, 444
classe finalise, 448
classe polymorphe, 449
classe statique, 432
clavier (gestion du), 675
cl de session, 222
cl trangre (DB), 706
cl primaire(DB), 706
cl prive, 222
cl publique, 222
CLI, 129
Client Activated Object, voir CAO, 799
clipboard, 679
closure, voir fermeture, 536
CLR Profiler, 120
CLS, 130
CMS/Pkcs7, 231
Code Access Security, voir CAS, 186
code groups, voir groupes de code, 194
code mobile, 185
code natif, 265
code-behind, 868

1037
code-bloat, 472
code-inline, 866
CodeAccessPermission, voir System.Security.
CodeAccessPermission, 191
codebase, 106
codepage, voir page de code, 635
COFF, voir PE/COFF, 17
collecte, 117
collecte partielle, 118
COM+ application, 300
commit, 741
Common Langage Infrastructure, voir CLI,
129
Common Langage Runtime (CLR), 87
Common Type System, voir CTS, 342
comparaison selon lquivalence, 345
comparaison selon lidentit., 345
Comparer<T>, voir System.Collections.
Generic.Comparer<T>, 588
Comparison<T>, voir System.
Comparison<T>, 588
compartimentation, voir Boxing, 350
compatibilit consommateur, 130
compatibilit extenseur, 130
Compilation, 313
component services, voir Services de
composants, 305
composant (graphique), 667
composant COM, 279
composant logiciel enfichable, 305
composant servi, 299
composants, 15
COMTI, 297
conditions, 327
confiance aveugle (CAS), 198
confiance partielle (CAS), 198
Console, 629
Console Application, 666
ConsoleColor, 630
const (mot-cl), 398
Constrained Execution Regions, voir rgions
dexcution contraintes, 126
constructeur (dune classe), 422
constructeur de classe, 431
constructeur de copie, 349
constructeur de pile, 818
constructeur par dfaut, 422
constructeur statique, 431, 792
construction incrmentale, 54

1038
ConstructorInfo, voir System.Reflection.
ConstructorInfo, 243
container, voir conteneur, 678
conteneur, 678
Context, voir Context, 844
context-agile, 843
context-bound, 843
contexte (scurit), 168
contexte .NET, 842
contexte COM+, 300
contexte dexcution, voir Contexte .NET, 842
contexte gr, voir Contexte .NET, 842
contexte non gr, voir Contexte COM+, 842
contexte par dfaut, 842
continuation, 552
continue (mot-cl C  ), 332
contrle (graphique), 667
contrle ActiveX, voir ActiveX, 283
contrle serveur web, 882
contrle serveurs HTML, 882
contrle utilisateur (ASP.NET), 912
contrainte (gnricit), 474
contrainte dintgrit (DB), 706
contraintes (DB), 728
contravariance, 485, 492
controlstate, 879, 917
cookies, 653
copie en profondeur, 348
copie superficielle, 348
coroutine, 552
covariance, 485, 492, 570
CR, voir rgion critique, 129
CreateUserWizard, 966
crible dEratosthne, 558
Critical Region, voir rgion critique, 129
CRMs, 297
CrossAppDomain, 831
CrossContextChannel, 849
csc.exe, 21
csc.exe, 317
CSS, voir Cascading Style Sheet, 970
ctor, voir constructeur, 422
CTS, 342
culture, 34
CurrentCulture, 40
CurrentUICulture, 38
curseur (flot de donnes), 633
custom attribute, voir attribut personnalis,
251

Index
DACL, voir Discretionary Access Control List,
211
Data Control Langage, 706
Data Definition Langage, 706
Data Manipulation Language, 706
Data Protection API, voir DPAPI, 225
Data Source Name, voir DSN, 717
DataGrid, 935
DataList, 940
DataSet typ, 731
DataView, 729
DataViewManager, 731
DbCommandBuilder, 726
DbConnectionStringBuilder, 716
DbProviderSupportedClasses, 716
DCL, voir Data Control Langage, 706
DCOM, 786
DDL, voir Data Definition Langage, 706
deadlocks, 144
decimal (mot-cl), 355
DeclarativeCatalogPart, 981
dcompartimentation, voir UnBoxing, 352
dcorateur, 657
deep copy, voir copie en profondeur, 348
default (mot-cl), 484
DeflateStream, 659
delaysign (option csc.exe), 34
delegate (mot-cl), 379
dlgation, 379
dlgu, 168, 344, 379
dlgu asynchrone, 172
dpassement de capacit, 359
dependentAssembly, 106
dployer des applications, 63
deployment pre-compilation, 871
DES, voir Digital Encryption Standard, 220
dsallocation, 337
descripteur de scurit, 211
dsrialisation, voir srialisation, 790
design pattern provider, 902, 956, 963, 964,
966
design-time, 678
destructeur, 423
DetailsView, 941
dterminisme, 146
devenv.exe, 49
Device Context, 695
dfsvc.exe, 83

Index
Digital Encryption Standard, 220
directives prprocesseur, 313
directory, voir rpertoire, 608
Discretionary Access Control List, 211
DISPID, 286
Dispose(), voir System.IDisposable, 425
Distributed Transaction Coordinator, 745
division par zro, 359
DLL hell, voir enfer des DLLs, 65
dllhost.exe, 303
DML, voir Data Manipulation Langage, 706
document XML, 759
DOM (Document Object Model), 769
Domain Specific Language, 8
domaine (DB), 705
domaine dapplication, 87
chargement des assemblages dune
manire neutre par rapport aux
domaines, 97
domaine dapplication par dfaut, 88
DOS (Disk Operating System), 665
dotnetfx.exe, 86
double (mot-cl), 355
double buering, 702
double pointeur, 505
double pointeur, 402
downcast, 452
download cache, voir cache de
tlchargement, 85
DPAPI, 225
drag & drop, 678
drapeau, 369
drive, voir volume, 607
droit daccs, 211
droits, voir permission, 191
droits daccs exclusifs (domaine de
synchronisation), 162
DSL, voir Domain Specific Language, 8
DSN, 717
DTC, voir Distributed Transaction
Coordinator, 745
DTP, 297
due time, voir chance de dmarrage, 171
durable (RM), 746
dynamic bind, voir lien dynamique, 238
early bind, voir lien prcoce, 238
cart type, 721
chance de dmarrage, 171

1039
ECMA, 9
ECMA, 29
lment XML, 761
EMF+ (Enhanced Meta File), 699
emprunt didentit, 210, 958
encapsulation, 418
encodage des caractres, 635
end point, voir point terminal, 796
endpoint WCF, 1013
enfer des DLLs, 65
Enregistration, 113
enregistrements (DB), 705
ensembles de permissions, 194
entte CLR, 17
entropie, 226
enum (mot-cl), 366
numrable, 539
numrateur, 539
numrations, 343
environnement lexical, 536
escalation policy, voir politique de lescalade,
129
espace de noms, 310
espace de noms
alias despace de noms, 311
tat dun objet, 396, 790
European Computer Manufacturers
Association, voir ECMA, 9
vnement, 155
vnement repositionnement
automatique, 156
vnement repositionnement manuel,
156
vnement, 411
vnement postback, 877
event (mot-cl), 412
EventDef, 19
EventLogWebEventProvider, 907
Evidence, voir System.Security.Policy.
Evidence, 190
evidence, voir preuve, 191
exception applicative, 125, 522
exception asynchrone, 125, 522
exception contrle, 515
exceptions
gestionnaire dexception, 519
exceptions, 509
exceptions vrifies, voir exception controle,
515

1040
Exit(), 136
explicit (mot-cl), 436
ExportedTypeDef (table du manifeste), 18
expression rgulire, 625
extension dinterface, 456
extension SOAP, 1009
extern (mot-cl), 266
eXtreme Programming, 7
fabrique abstraite, 715
facteur dexpansion, 579
facteur de chargement, 585
factory (design pattern), 802
FailFast(), 136
fentres de pile, 43, 267
fermeture, 536
fermeture, 552
fiber, voir fibre, 101
Fibonacci, 557
fibre, 101
fichier, 610
fichier de ressources, 16, 34
fichier PE, 17
fichiers cab, 71
fichiers cab, 71
fichiers image, 17
fichiers objets, 17
FieldPtr (table de mtadonnes de type), 20
FIFO, 580
file, voir fichier, 610
File (classe), voir System.IO.File, 610
file dattente, 580
FileDef (table du manifeste), 18
finaliseur, 118, 423
finaliseur critique, 129
finally (mot-cl), 515
firewall, voir pare feu, 805
fixed, 505
fixed (mot-cl), 505
flag, voir drapeau, 369
float (mot-cl), 355
focus, 675
foncteur, 536
foncteur, 592
foncteurs (notion C++), 433
fonction-objet, voir foncteur, 536
fonction-objet, voir foncteur, 592
fonctor, voir foncteur, 592
for (mot-cl), 332

Index
foreach (mot-cl), 332, 563
foreign key, voir cl trangre, 706
FormsIdentity, voir System.Web.Security.
FormsIdentity, 208
formulaire, 667
FormView, 944
forward-only (flot de donnes), 633
fournisseur dauthentification, 960
fournisseur dintercepteurs de messages, 833
fournisseur dorganisation de site, 956
fournisseur dutilisateurs, 962
fournisseur de donnes, 708
fournisseur de rles, 964
fournisseur de traitement dvnements, 906
fragmentation du tas, 117
Framework Configuration, 108
FreeBSD Unix, 10
FTP, 652
fuite de mmoire, 339
fuite de mmoire, 502
full runtime compilation, 871
Full trusted assemblies, 198
fully trusted assemblies, 194
fusion, 103
fuslogvw.exe, 106
GAC, 64
Gac (classe), voir System.Security.Policy.Gac,
188
GACUtil.exe, 66
GDI+, 695
gnration (ramasse-miettes), 117
Generic.Dictionary<K,V>, voir System.
Collections.Generic.Dictionary<K,V>,
583
gnricit, 467
GenericWebPart, 977
gestionnaire des tches, 134
global (mot-cl), 322
Global Assembly Cache, voir GAC, 64
Global.asax, 892
globalisation (culture), 34
goto (mot-cl), 334
Graphical Device Interface, voir GDI+, 695
groupe dutilisateurs, 207
groupe de validation, 910
groupes de code, 194
grow factor, voir facteur dexpension, 579
GUID, 821

Index
guidgen.exe, 20
GZipStream, 659
hte dexcution, 94
hte dexcution, 125
hte dun domaine dapplication, 191
hachage, 223
handle (C++/CLI), 275
handle (win32), 277
handle-recycling attack, 278
handler HTTP, 891
Hash, voir System.Security.Policy.Hash, 190
heap, voir tas, 338
hritage dimplmentation multiple, 445
hritage dimplmentation simple, 445
hritage de classe, 444
HRESULT, 285
HTTP.SYS, 654
IBindingList, 693
ICollection<T>, voir System.Collections.
Generic.ICollection<T>, 575
IComparable<T>, voir System.Collections.
Generic.IComparable<T>, 588
IComparer<T>, voir System.Collections.
Generic.IComparer<T>, 588
identificateurs de scurit, 209
identity permission, voir permission
didentit, 192
IDictionary<K,V>, voir System.Collections.
Generic.IDictionary<K,T>, 582
IDispatch, 109, 286
IDL, 279
idle, voir processus dinactivit, 139
IIS, 815
IJW, 272
IL, 41
ilasm.exe, 41
ildasm.exe, 21, 110
IList<T>, voir System.Collections.Generic.
IList<T>, 576
images natives, 113
immuable (objet), 370
immutable, voir immuable, 370
impersonation, voir emprunt didentit, 210
implicit (mot-cl), 436
ImportCatalogPart, 981
IMS, 297
in (mot-cl), 563

1041
in-place pre-compilation, 871
indexeur, 409
indicateur binaire, 143, 369
Indigo, voir Windows Communication
Framework, 1013
INETINFO.EXE, 861
InetInfo.exe, 861
Initialization Vector, voir vecteur
dinitialisation, 220
inlining, 112, 333
instance dune classe, 395
InsucientMemoryException, 128
int (mot-cl), 354
interblocage, voir deadlocks, 144
intercepteur de messages, 822
interface (mot-cl), 456
interface de classe, 291
Intermediate Language, voir IL, 41
internal, 418
internal call, 265
internal protected, 418, 445
InternalsVisibleToAttribute, voir System.
Runtime.CompilerServices.
InternalsVisibleToAttribute, 27
internationalisation, 34
interop assembly, voir assemblage
interoprable, 279
interruption software, 138
IP, 641
IPC, voir Inter Processus Communication,
830
IPermission, voir System.Security.
IPermission, 202
is (mot-cl), 464
isolated storage, voir stockage isol, 205
ISynchronizeInvoke, 180
It Just Works, voir IJW, 272
itrateur (design pattern), 539
IXPathNavigable, 772
jagged array, voir tableau irrgulier, 567
Jet, 740
jeton de cl publique, 30
jeton de mtadonnes, 46
jeton de scurit, 207
JIT, 112
Just In Time, voir JIT, 112
Juste temps, voir JIT, 112

1042
Kerberos, 660
keycontainer (option csc.exe), 30
keyfile (option csc.exe), 30
KeyValuePair<K,V>, voir System.Collections.
Generic.KeyValuePair<K,T>, 582
langage fonctionnel, 536
langage impratif, 536
late binding, voir lien tardif, 238
LayoutEditorPart, 982
lease, voir bail, 805
lease manager, voir administrateur de baux,
806
lien dynamique, 238
lien dynamique, 238
lien prcoce, 238
lien tardif, 233, 238, 239
lien tardif explicite, voir lien tardif, 239
lien tardif implicite, voir lien dynamique, 238
LIFO, 581
Lightweight Transaction Manager, 746
LinkedList<T>, voir System.Collections.
Generic.LinkedList<T>, 579
LinkedListNode<T>, voir System.Collections.
Generic.LinkedListNode<T>, 579
List<T>, voir System.Collections.Generic.
List<T>, 576
liste, 577
liste doublement lie, 579
little-endian, 635
load balancing, voir rpartition de charge, 824
load factor, voir facteur de chargement, 585
LocalDataStoreSlot, 177
localisation (culture), 34
log, 620
Login, 966
LoginName, 966
LoginStatus, 966
LoginView, 966
long (mot-cl), 354
loop, voir boucles, 331
LTM, voir Lightweight Transaction Manager,
746
machine tat, 334
machine virtuelle, 87
machine.config, 715
machine.config, 888
mage.exe, 77

Index
mageui.exe, 77
Main, 334
main thread, voir thread principal, 133
manifeste, 17
ManifestResourceDef (table du manifeste), 18
ManualResetEvent, 153
MarshalAs, voir System.Runtime.
InteropServices.MarshalAs, 271
Marshalling By Reference, voir MBR, 787
Marshalling By Value, voir MBV, 790
master page, 947
mathmatique (fonctions), 597
MBR, 787
MBV, 790
MD5 (algorithme de hachage), 30
MemberRef (table de mtadonnes de type),
19
MembershipUser, 964, 965
MemberwiseClone(), 348
membres, 396
membres dinstances, 430
membres partags, 430
mmoire tampon, 637
mmoire virtuelle, 134
memory gates, voir portail de mmoire, 128
memory leak, voir fuite de mmoire, 339
MEP, 990
Message Exchange Pattern, 990
message sink, voir intercepteur de messages,
822
Message Transmission Optimization
Mechanism, 1009
messages Windows, 666
metadata, voir mtadonnes, 17
metadata token, voir jeton de mtadonnes,
46
mtadonnes, 17
mtadonnes de lassemblage, 17
MethodDef (table de mtadonnes de type),
19
mthode abstraite, 453
mthode anonyme, 524
mthode finalise, 449
mthode gnrique, 487
mthode virtuelle, 449
mthode virtuelle pure, voir mthode
abstraite, 453
MethodInfo, voir System.Reflection.
MethodInfo, 245

Index
MethodPtr (table de mtadonnes de type),
20
MFC, 666
Microsoft SQL Server Desktop Engine, 740
Microsoft Transaction Server, voir MTS, 295
Microsoft.Win32.RegistryKey, 616
MIME, voir Multipurpose Internet Mail
Exchange, 657
modale (fentre), 674
mode connect, 707, 708
mode dactivation (COM+), 302
mode dappel (WKO), 798
mode dconnect, 707
mode non protg, voir mode non vrifiable,
502
mode non vrifiable, 502
mode noyau, 137
mode utilisateur, 137
modle relationnel, 705
modeless, voir modale, 674
module, 15
module de dploiement, 69
module HTTP, 891
module principal, 15
ModuleDef (table de mtadonnes de type),
19
ModuleRef (table de mtadonnes de type),
19
Mono, 9
monte en charge, 708
Moore (loi de), 137
moteur dexcution (CLR), 87
moteur transactionnel, 742
msbuild.exe, 49
mscorcfg.msc, 196
mscoree.dll, 96
mscorlib, 23
mscorlib.dll, 94
mscorsvr.dll, 94
mscorwks.dll, 94
MSDE, voir Microsoft SQL Server Desktop
Engine, 740
MSIL, voir IL, 41
MSMQ, 298
MTA, 285
MTOM, voir Message Transmission
Optimization Mechanism, 1009
MTS, 295
Multipurpose Internet Mail Exchange, 657

1043
multitche coopratif, 101, 138
multitche premptif, 101, 138
mutant, voir Mutex, 153
Mutex, 153
mutex nomm, 154
Nant, 49
Native Image Generator, voir ngen.exe, 113
navigateur, 859
NDepend, 1034
nested type, voir type encapsul, 416
.NET Framework Configuration, 196
.NET Framework Configuration, 108
new (mot-cl), 423, 451
ngen.exe, 113
NHibernate, 738
niveau de stratgie de scurit, 194
niveau de visibilit, 418
No Touch Deployment, 84
nud, 579
nud SOAP, 1001
nom fort (assemblages nom fort), 28
NT Lan Manager, 660
NTD, voir No Touch Deployment, 84
NTLM, voir NT Lan Manager, 660
null (mot-cl), 342
Nullable<T>, 386
Object Space, 738
ObjectDataSource, 932
ObjectHandle, 242
objet, 395
objet actif (ramasse-miettes), 117
objet crateur, 162
OCX, voir ActiveX, 283
ODBC, 708
OEM, 635
OLE Transaction, 297
OleDB, 708
oleview.exe, 289
one to many (relation), 724
oprateur binaire, 434
oprateur dindirection, 504
oprateur de comparaison, 438
oprateur de drfrencement, 504
oprateur de rsolution de porte, 396, 430
oprateur de transtypage explicite, 436
oprateur de transtypage implicite, 436
oprateur unaire, 433

1044
operator (mot-cl), 433
optimiste (gestion des transactions), 728
overloading, voir surcharge, 406
override (mot-cl), 449
P/Invoke, 265
P/Invoke Marshalling, 267
PAG, voir Pattern & Practices, 1031
page de code, 635
page de contenu, 948
PageCatalogPart, 980
Paint (vnement), 676
PAL, 10
palette, 698
paramtre nomm, 254
parametric polymorphism, voir
polymorphisme paramtr, 470
ParamPtr (table de mtadonnes de type), 20
parcours de la pile dappels, 187
pare feu, 805
partial (mot cl), 392
partial type, voir type dfini sur plusieurs
fichiers sources, 392
Partially trusted assemblies, 198
PascalCase, 326
passage dargument par rfrence, 400
passage dargument par valeur, 401
Passport, 208, 960
PassportIdentity, voir System.Web.Security.
PassportIdentity, 208
PasswordRecovery, 966
Path (classe), voir System.IO.Path, 611
Pattern & Practices, 1031
PE, voir fichier PE, 17
PE/COFF, 17
perfmon.exe, 116, 906
permcalc.exe, 82
permission, 191
permission de scurit, 193
permission elevation, 79
permission sets, voir ensembles de
permissions, 194
permissions didentit, 192
PermissionSet, 199
permview.exe, 204
pessimiste (gestion des transactions), 728
pile, 337
pile, 42
pile, 581

Index
ping, 808
pinvokeimpl, 273
pipe nomm, 830
pipeline (pattern), 554
pipeline HTTP, 891
pitching, 113
Platform Abstraction Layer, voir PAL, 10
Platform Invoke, voir P/Invoke, 265
plugin, 247
point dentre du programme, 335
point protg, 120
point protg, 141
point terminal, 796
pointeur, 344
pointeur gr, 502
pointeur non gr, 502
pointeur non gr de fonction, 272, 502
Policy, voir System.Security.Policy, 188
policy, voir stratgie, 988
policy assemblies, 194
Policy Wizard, 1009
politique de lescalade, 129
politique de principal (scurit), 216
polymorphisme, 238, 449
polymorphisme paramtr, 470
pool de connexions, 718
pool de threads, 167, 639
port (IP), 641
portabilit niveau binaire, 111
portail de mmoire, 128
porte (stockage isol), 205
#pragma managed, 273
#pragma unmanaged, 273
#pragma warning disable, 316
#pragma warning restore, 316
Preprocessing, 313
preuve, 191
primary key, voir cl primaire, 706
primary thread, voir thread principal, 133
principal, 207
PrincipalPermission, voir System.Security.
Permissions.PrincipalPermission, 217
priorit des oprateurs, 360
private, 418
privilge, voir permission, 191
probing, 105
procdure de finalisation, 173, 639, 646
procdure de rappel, 666
procdure stocke, 722

Index
process, voir processus, 133
processus, 94, 133
processus dinactivit, 139
processus fils, 134
processus parent, 134
processus lger, 87
programmation objet (POO), 395
programmation oriente aspect, 466
programmation procdurale, 395
promotable enlistment, 747
Promotable Single Phase Enlistment, 746
PropertyDef, 19
protected, 418, 445
prototype (design pattern), 350
provider, voir fournisseur de donnes, 708
proxy, 280, 300
proxy de dcouverte, 1011
proxy (web service), 994
proxy rel, 819
proxy transparent, 787
proxy transparent, 818
pseudo-attributs, 249
PSPE, voir Promotable Single Phase
Enlistment, 746
public, 418
public key token, voir jeton de cl publique,
30
publication dun objet, 820
Publisher, voir System.Security.Policy.
Publisher, 189
publisher policy, voir assemblage de stratgie
dditeur, 67
publisherPolicy, 106
qualificateurs dalias despace de noms, 321
quantum, 138
quantum, 147
Query Notification, 927
Queue<T>, voir System.Collections.Generic.
Queue<T>, 581
rle, 207
rle SOAP, 1002
race conditions, 144
race conditions, 144
RAD, 688
ramasse-miettes, 96, 116, 339
Rapid Application Development, voir RAD,
688

1045
RCW, 280
readonly (mot-cl), 398
real proxy, voir proxy rel, 819
records (DB), 706
recyclage de domaines dapplication, 126
rentrance, 164
REF _Ref110260763 \h Exemple 22, 25
rfrence (dun objet sur un autre), 398
rfrence dapplication Windows, 83
rfrence faible, 120
rfrence faible courte, 122
rfrence faible longue, 122
reference type, voir type rfrence, 339
Reflection Only, 237
Reflector, 24
rflexion (mcanisme de), 233
reg free COM, voir registration free COM, 287
regasm.exe, 292
regedit.exe, 614
regedt32.exe, 614
regex, 625
regexp, 625
rgion critique, 129
rgion dinterception de messages, 848
rgions dexcution contraintes, 126
registration free COM, 287
registre, voir base des registres, 613
registry, voir base des registres, 613
rgle daccs (ASP.NET), 959
regsvcs.exe, 304
regsvr32.exe, 292
regular expression, 625
relation (DB), 705
relation dquivalence, 346
Remote Procedure Call, 990
remove (accesseur dvnement), 412
rpartiteur, 138
rpartition de charge, 824
Repeater, 940
rpertoire, 608
rpertoire virtuel (IIS), 816
RequestCacheLevel, voir System.Net.Cache.
RequestCacheLevel, 653
resgen.exe, 35
Resource Manager, 742
resxgen.exe, 35
return (mot-cl), 404
reverse engineering, 24
RM, voir Resource Manager, 742

1046
rollback, 741
Rotor, voir Shared Source CLI, 10
row(DB), 705
RPC, 990
RSA, 223
RSA, 223
ruche, 614
Runtime Callable Wrapper, voir RCW, 280
runtime host, voir hte dexcution, 94
S/MIME, voir Secure/Multipurpose Internet
Mail Extensions, 231
SACL, voir SACL, 211
safe point, voir point protg, 120
SAFEARRAY, 285
same-box communication, voir Inter
Processus Communication, 830
sandbox, voir bac sable, 205
saut, 327
SAX, 766
sbyte (mot-cl), 354
scalable, 708
scheduler, voir rpartiteur, 138
schma dune relation, 705
scheme (URI), voir Mode daccs, 651
scope, voir porte, 205
SD, voir descripteur de scurit, 211
SDDL, voir Security Descriptor Definition
Language, 209
sealed (mot-cl), 448
section critique, 147
section de configuration, 60
Secure Socket Layer, 660
Secure/Multipurpose Internet Mail
Extensions, 231
SecureString, voir System.Security.
SecureString, 228
Security Descriptor Definition Language, 209
security policy, voir stratgie de scurit, 193
Security Support Provider Interface, 660
security token, voir jeton de scurit, 207
SecurityAction, voir System.Security.
Permissions.SecurityAction, 203
See Remote Procedure Call, 990
See System.ComponentModel.
ISynchronizeInvoke, 180
See System.Console, 629
See System.ConsoleColor, 630

Index
See System.Data.Common.
DbCommandBuilder, 726
See System.Data.Common.
DbConnectionStringBuilder, 716
See System.Data.Common.
DbProviderSupportedClasses, 716
See System.Data.DataView, 729
See System.Data.DataViewManager, 731
See System.Data.SqlClient.SqlBulkCopy, 739
See System.LocalDataStoreSlot, 177
See System.Security.AllowPartiallyTrustedCallersAttribute,
198
See System.Security.PermissionSet, 199
See System.ThreadStaticAttributes, 176
See Transaction Isolation Level, 750
See Types Models, 1011
seek, 633
SEH, 521
smaphore, 157
squence, 575
srialisation, 790
serveur dapplications, 295
serveur dentreprises, 301
service, 987
service dentreprise (COM+), 295
service de recouvrement, 743
Service Oriented Architecture, 987
service Windows, 815
serviced component, voir composant servi,
299
Services de composants (outil), 305
services web, 988
session logon, 207
session scurise, 222
SGBD, 705
sgen.exe, 782
SHA-1 (algorithme de hachage), 30
shadow copy, 108, 816, 891
shallow copy, voir copie superficielle, 348
Shared Source CLI, 10
SharePoint, 974
ShFusion.dll, 65
shim, voir cale, 96
short (mot-cl), 354
SID, voir identificateurs de scurit, 209
side by side, voir cte cte, 65
signature numrique, 29, 223
signature retarde, 33

Index
simple appel (mode dappel WKO), 798
single call, voir simple appel, 798
singleton, 422
singleton (mode dappel WKO), 798
Site, voir System.Security.Policy.Site, 188
SiteMap provider, voir fournisseur
dorganisation de site, 956
Sites de confiance, 189
Sites sensibles, 189
situation de comptition, voir race conditions,
144
sizeof (mot-cl), 504
SkipVerification, 502
slot de donnes, 177
Smalltalk, 420
sn.exe, 29
snap-in, voir composant logiciel enfichable,
305
SOA, voir Service Oriented Architecture, 987
Soapsuds.exe, 804
socket, 641
SortedDictionary<K,V>, voir System.
Collections.Generic.
SortedDictionary<K,V>, 583
souche, 112
source de traage, 622
souris (gestion de la), 675
SQL, 706
SQL Server, 708
SQL Server 2005 Express Edition, 740
SqlBulkCopy, 739
SqlDataSource, 932
SqlProfileProvider, 966
SQLXML, 778
SSCLI, voir Shared Source CLI, 10
SSL, voir Secure Socket Layer, 660
SSPI, voir Security Support Provider
Interface, 660
STA, 285
stack, voir pile, 42
stack builder sink, voir constructeur de pile,
818
stack frames, voir fentre de pile, 43, voir
fentre de pile, 267
stack overflow, 127
stack walk, voir parcours de la pile dappels,
187
Stack<T>, voir System.Collections.Generic.
Stack<T>, 581

1047
stackalloc, 508
stackalloc (mot-cl), 508
standard deviation, voir cart type, 721
StdCall, 266
STL, 592
stockage isol, 205
stratgie, 988
stratgie de scurit, 193
string, 343
string (mot-cl), 370
strong name, voir nom fort, 28
StrongName, voir System.Security.Policy.
StrongName, 189
StrongNamePublicKeyBlob, voir System.
Security.Permissions.
StrongNamePublicKeyBlob, 189
struct (mot-cl), 364
Structured Exception Handling, voir SEH, 521
structures, 343
stub, 112
stub (dune mthode), 239
stylesheet, 972
subrog (processus), 303
subroutine, 552
substitution post-cache, 923
sucre syntaxique, 392
super classe, 444
surcharge (de mthode), 406
surrogate, voir subrog, 303
Synchronization (attribut), voir System.
Runtime.Remoting.Contexts.
Synchronization, 151, voir System.
Runtime.Remoting.Contexts.
Synchronization, 160
System.Runtime.Remoting.Channels.
IChannel, 830
System.Activator, 241, 242
System.AppDomain, 89, 242
System.AppDomainSetup, 90
System.AsyncCallback, 173
System.Attribute, 251
System.Boolean, 357
System.Byte, 354
System.Char, 357
System.CLSCompliantAttribute, 131
System.Collections.ArrayList, 576
System.Collections.BitArray, 573
System.Collections.Generic.Dictionary<K,V>,
583

1048
System.Collections.Generic.ICollection<T>,
575
System.Collections.Generic.IComparable<T>,
588
System.Collections.Generic.IComparer<K>,
583
System.Collections.Generic.IComparer<T>,
588
System.Collections.Generic.
IDictionary<K,V>, 582
System.Collections.Generic.
IEqualityComparer<T>, 586
System.Collections.Generic.IList<T>, 576
System.Collections.Generic.
KeyValuePair<K,V>, 582, 587
System.Collections.Generic.LinkedList<T>,
579
System.Collections.Generic.
LinkedListNode<T>, 579
System.Collections.Generic.List<T>, 576
System.Collections.Generic.
SortedDictionary<K,V>, 583
System.Collections.Generic.Stack<T>, 581
System.Collections.Generics.Queue<T>, 581
System.Collections.IComparable, 588
System.Collections.IComparer, 588
System.Collections.IEnumerable, 539
System.Collections.IEnumerator, 539
System.Collections.Specialized.BitVector32,
574
System.Collections.Specialized.
StringCollection, 580
System.Comparison<T>, 588
System.ComponentModel.
BackgroundWorker, 676
System.ComponentModel.BindingList<T>,
579
System.ComponentModel.Component, 667
System.ComponentModel.IComponent, 678
System.ComponentModel.IContainer, 678
System.ComponentModel.IListSource, 929
System.ComponentModel.
ISynchronizeInvoke, 180
System.Configuration.
ApplicationSettingsBase, 61
System.Configuration.
LocalFileSettingsProvider, 63
System.Configuration.SettingsProvider, 63
System.Console, 629

Index
System.ConsoleColor, 630
System.ContextBoundObject, 843
System.CrossAppDomainDelegate, 91
System.Data.Common, 712
System.Data.Common.DbCommandBuilder,
726
System.Data.Common.
DbConnectionStringBuilder, 716
System.Data.Common.DbProviderFactories,
713
System.Data.Common.
DbProviderSupportedClasses, 716
System.Data.Constraint, 728
System.Data.DataView, 729
System.Data.DataViewManager, 731
System.Data.ForeignKeyConstraint, 728
System.Data.Rule, 729
System.Data.SqlClient.SqlBulkCopy, 739
System.Data.SqlTypes.SqlXml, 779
System.Data.UniqueConstraint, 728
System.DateTime, 601
System.Decimal, 355
System.Deletage, 382
System.Deployment, 80
System.Diagnostics.Debug, 620
System.Diagnostics.Process, 134
System.Diagnostics.ProcessStartInfo, 135
System.Diagnostics.Trace, 620
System.Diagnostics.TraceListener, 621
System.Diagnostics.TraceSource, 622
System.Double, 355
System.Drawing.Bitmap, 698
System.Drawing.Brush, 696
System.Drawing.Color, 696
System.Drawing.Graphics, 695
System.Drawing.Image, 698
System.Drawing.Imaging.ImageFormat, 698
System.Drawing.Imaging.Metafile, 699
System.Drawing.Imaging.PixelFormat, 698
System.Drawing.Pen, 696
System.EnterpriseServices.ContextUtil, 300
System.EnterpriseServices.
JustInTimeActivation, 296
System.EnterpriseServices.
RegistrationHelper, 304
System.EnterpriseServices.
ServicedComponent, 299
System.Enum, 368
System.Environment, 136, 336

Index
System.EventArgs, 411
System.EventHandler, 670
System.Exception, 512
System.GC, 122
System.Globalization, 601
System.IAsyncResult, 173
System.IDisposable, 425
System.Int16, 354
System.Int32, 354
System.Int64, 354
System.InteropServices.CriticalHandle, 278
System.InteropServices.SafeHandle, 278
System.IO, 607, 633
System.IO.BinaryReader, 634
System.IO.BinaryWriter, 634
System.IO.BueredStream, 658
System.IO.Compression, 659
System.IO.Directory, 608
System.IO.DirectoryInfo, 608
System.IO.DriveInfo, 607
System.IO.File, 610
System.IO.FileInfo, 610
System.IO.FileStream, 637
System.IO.FileSystemInfo, 608
System.IO.FileSystemWatcher, 612
System.IO.IsolatedStorage.
IsolatedStorageFile, 205
System.IO.MemoryStream, 659
System.IO.NotifyFilters, 612
System.IO.Path, 611
System.IO.Ports.SerialPort, 660
System.IO.Stream, 633
System.IO.StreamReader, 635, 637
System.IO.StreamWriter, 635, 637
System.IO.StringReader, 635
System.IO.StringWriter, 635
System.IO.TextReader, 635
System.IO.TextWriter, 635
System.IO.UnmanagedMemoryStream, 659
System.LocalDataStoreSlot, 177
System.Marshal.InteropServices.Marshal, 284
System.MarshalByRefObject, 789
System.Math, 597
System.MTAThread, 286
System.MulticastDelegate, 379
System.Net.Cache.RequestCacheLevel, 653
System.Net.Cache.RequestCachePolicy, 653
System.Net.Dns, 645
System.Net.FileWebRequest, 653

1049
System.Net.FileWebResponse, 653
System.Net.FtpWebRequest, 653
System.Net.FtpWebResponse, 653
System.Net.HttpListener, 654
System.Net.HttpWebRequest, 653
System.Net.Mail.Attachment, 656
System.Net.Mail.MailAddress, 656
System.Net.Mail.MailMessage, 656
System.Net.Mail.SmtpClient, 656
System.Net.Mime, 657
System.Net.NetworkInformation.
NetworkChange, 650
System.Net.NetworkInformation.
NetworkInterface, 649
System.Net.NetworkInformation.Ping, 650
System.Net.Security, 660
System.Net.Security.AuthenticatedStream,
660
System.Net.Security.NegociateSteam, 660
System.Net.Security.SslStream, 660
System.Net.Sockets.Socket, 641
System.Net.Sockets.TcpClient, 642
System.Net.Sockets.TcpListener, 642
System.Net.WebClient, 652
System.Nullable<T>, 386
System.Nullable<T>, 386
System.ObjectDisposedException, 426
System.ParamArrayAttribute, 405
System.Random, 600
System.Reflection, 234
System.Reflection.ConstructorInfo, 243
System.Reflection.Emit, 256
System.Reflection.MethodInfo, 245
System.Resources.ResourceManager, 38
System.Runtime.Remoting.Messaging.
OneWay, 175
System.Runtime.CompilerServices.
InternalsVisibleToAttribute, 27
System.Runtime.CompilerServices.
RuntimeHelpers, 126, 127, 432
System.Runtime.ConstrainedExecution.
CriticalFinalizer, 278
System.Runtime.ConstrainedExecution.
CriticalFinalizerObject, 129
System.Runtime.ConstrainedExecution.
CriticalFinalizerObject, 129
System.Runtime.InteropServices., 265
System.Runtime.InteropServices.
CallingConvention, 266

1050
System.Runtime.InteropServices.
ClassInterface, 291
System.RunTime.InteropServices.
COMVisible, 292
System.Runtime.InteropServices.DllImport,
265
System.Runtime.InteropServices.FieldOset,
270
System.Runtime.InteropServices.GCHandle,
275
System.RunTime.InteropServices.Guid, 291
System.Runtime.InteropServices.
HandleCollector, 277
System.Runtime.InteropServices.In, 271
System.Runtime.InteropServices.MarshalAs,
281
System.Runtime.InteropServices.MarshalAs,
271
System.Runtime.InteropServices.Out, 271
System.RunTime.InteropServices.ProgId, 293
System.Runtime.InteropServices.
StructLayout, 270
System.Runtime.MemoryFailPoint, 127
System.Runtime.Remoting.Channels.http.
HttpChannel, 830
System.Runtime.Remoting.Channels.
IChannelReceiver, 830
System.Runtime.Remoting.Channels.
IChannelSender, 830
System.Runtime.Remoting.Channels.
IClientChannelSink, 833
System.Runtime.Remoting.Channels.
IClientChannelSinkProvider, 834
System.Runtime.Remoting.Channels.
IClientFormatterSink, 833
System.Runtime.Remoting.Channels.Ipc.
IpcChannel, 830
System.Runtime.Remoting.Channels.
IServerChannelSink, 833
System.Runtime.Remoting.Channels.
IServerChannelSinkProvider, 834
System.Runtime.Remoting.Channels.
IServeurFormatterSink, 833
System.Runtime.Remoting.Channels.Tcp.
TcpChannel, 830
System.Runtime.Remoting.Contexts.
IContextProperty, 846
System.Runtime.Remoting.Contexts.Context,
844

Index
System.Runtime.Remoting.Contexts.
IContextAttribute, 845
System.Runtime.Remoting.Contexts.
Synchronization, 151, 160
System.Runtime.Remoting.
IRemotingTypeInfo, 824
System.Runtime.Remoting.Lifetime.
LifetimeServices, 807
System.Runtime.Remoting.Lifetime.ILease,
806
System.Runtime.Remoting.Lifetime.
ISponsor, 806
System.Runtime.Remoting.Messaging.
AsyncResult, 173
System.Runtime.Remoting.Messaging.
ILogicalThreadAnative, 856
System.Runtime.Remoting.Messaging.
IMessageSink, 822
System.Runtime.Remoting.Messaging.
MethodCallMessageWrapper, 829
System.Runtime.Remoting.ObjectHandle,
792
System.Runtime.Remoting.ObjRef, 820
System.Runtime.Remoting.Proxies.
ProxyAttribute, 827
System.Runtime.Remoting.Proxies.
RealProxy, 819
System.Runtime.Remoting.
RemotingException, 802
System.Runtime.Remoting.Services.
RemotingClientProxy, 805
System.Runtime.Serialization.ISerializable,
790
System.Runtime.Serialization.
OptionalFieldAttribute, 791
System.SByte, 354
System.Security.AccessControl, 154, 212
System.Security.AllowPartiallyTrustedCallersAttribute,
198
System.Security.CodeAccessPermission, 191
System.Security.Cryptography.CryptoStream,
664
System.Security.Cryptography.ProtectedData,
226
System.Security.Cryptography.
ProtectedMemory, 227
System.Security.IPermission, 202

Index
System.Security.Permissions.
PrincipalPermission, 217
System.Security.Permissions.
StrongNamePublicKeyBlob, 189
System.Security.Permissions.SecurityAction,
203
System.Security.PermissionSet, 199
System.Security.Policy, 188
System.Security.Policy.ApplicationDirectory,
188
System.Security.Policy.Evidence, 190
System.Security.Policy.Gac, 188
System.Security.Policy.Hash, 190
System.Security.Policy.Publisher, 189
System.Security.Policy.Site, 188
System.Security.Policy.StrongName, 189
System.Security.Policy.Url, 188
System.Security.Policy.Zone, 189
System.Security.Principal.WindowsIdentity,
208
System.Security.SecureString, 228
System.Security.SecurityException, 205
System.Security.SecurityZone, 189
System.Security.
SuppressUnmanagedCodeSecurity,
267
System.Security.SuppressUnmanagedCodeSecurityAttribute,
201
System.Serializable, 790
System.ServiceProcess, 815
System.Single, 355
System.STAThread, 286
System.StringComparer, 585
System.Text.RegularExpressions.RegEx, 627
System.Text.StringBuilder, 376
System.Threading.ApartmentState, 285
System.Threading.AutoResetEvent, 155
System.Threading.EventResetMode, 155
System.Threading.EventWaitHandle, 155
System.Threading.Interlocked, 146
System.Threading.Interlocked, 146
System.Threading.ManualResetEvent, 155
System.Threading.Monitor, 147
System.Threading.ParametrizedThreadStart,
140
System.Threading.ReaderWriterLock, 158
System.Threading.Semaphore, 157
System.Threading.Thread, 140

1051
System.Threading.ThreadPool, 167
System.Threading.ThreadStart, 140
System.Threading.ThreadState, 143
System.Threading.Timer, 171
System.Threading.WaitHandle, 153
System.ThreadStaticAttributes, 176
System.Timers.Timer, 169
System.Transaction.
IPromotableSinglePhaseNotification,
758
System.Transactions.
DistributedTransactionPermission,
754
System.Transactions.IEnlistmentNotification.
IEnlistmentNotification, 755
System.Transactions.
ISinglePhaseNotification, 755
System.Transactions.TransactionScope, 748
System.Type, 241, 243
System.UInt16, 354
System.UInt32, 354
System.UInt64, 354
System.Uri, 652
System.WeakReference, 121
System.Web.Caching.Cache, 925
System.Web.Caching.CacheDependency, 925
System.Web.Hosting.SimpleWorkerProcess,
864
System.Web.HttpApplication, 892
System.Web.HttpApplicationState, 898
System.Web.HttpCachePolicy, 918
System.Web.HttpContext, 893
System.Web.HttpListenerContext, 655
System.Web.IHttpHandler, 895
System.Web.IHttpModule, 893
System.Web.Management, 906
System.Web.Profile.ProfileProvider, 966
System.Web.Security.
ActiveDirectoryMembershipProvider,
963
System.Web.Security.FormsIdentity, 208
System.Web.Security.Membership, 963
System.Web.Security.MembershipProvider,
963
System.Web.Security.PassportIdentity, 208
System.Web.Security.RoleProvider, 965
System.Web.Security.Roles, 965
System.Web.Security.
SqlMembershipProvider, 963

1052
System.Web.Services.WebService, 992
System.Web.Services.SoapExtension, 1009
System.Web.Services.WebMethod, 992
System.Web.SiteMapProvider, 956
System.Web.TraceContext, 906
System.Web.UI System.Web.UI.
WebControls.WebParts.
PersonalizationProvider, 977
System.Web.UI.Control, 873
System.Web.UI.Controls.Style, 971
System.Web.UI.HtmlControls.HtmlControl,
883
System.Web.UI.HtmlControls.HtmlForm,
878
System.Web.UI.MasterPage, 949
System.Web.UI.Page, 861
System.Web.UI.WebControls.WebControl,
883
System.Web.UI.WebControls.WebParts.
WebPart, 977
System.Web.XmlSiteMapProvider.
XmlSiteMapProvider, 956
System.Windows.Forms.ClipBoard, 679
System.Windows.Forms.Control, 667
System.Windows.Forms.Control.DragDrop,
678
System.Windows.Forms.Help, 679
System.Windows.Forms.Menu, 679
System.Windows.Forms.MessageBox, 678
System.Windows.Forms.NotifyIcon, 679
System.Windows.Forms.Timer, 171, 679
System.Windows.Forms.ToolTips, 679
System.Xml.XmlDataDocument, 777
System.Xml.XmlDocument, 769
System.Xml.XmlNode, 769
System.Xml.XmlNodeList, 769
System.Xml.XmlNodeReader, 766
System.Xml.XmlReader, 766
System.Xml.XmlReaderSettings, 767
System.Xml.XmlSerializer, 779
System.Xml.XmlTextReader, 766
System.Xml.XmlTextWriter, 766, 768
System.Xml.XmlWriter, 766
System.Xml.XPath.XPathDocument, 772
System.Xml.XPath.XPathNavigator, 772, 773
System.Xml.Xsl.XslCompiledTransform, 774
systme de persistance, 705
T-SQL, 706

Index
table de hachage, 583
TableAdapter, 733
tableau, 565
tableau dchiquet, voir tableau irrgulier,
567
tableau irrgulier, 567
tas, 338
tas gr, 116
task manager, voir gestionnaire des tches, 134
TCP/IP, 642
template, 939
template (C++/CLI), 275
templates (C++), 471
thme, 971
this (mot-cl), 420
thread, 133
thread
Abort(), 142
CurrentThread, 140
Interrupt(), 142
Join(), 141
Multiple Apartement Thread, 167
Name, 140
Resume(), 141
Single Apartment Thread, 167
Sleep(), 141
Suspend(), 141
thread background, 142
thread foreground, 142
thread gr, 137
thread I/O, 167
thread lger, 101
thread local storage, 177
thread logique, 101
thread ouvrier, 167
thread principal, 133
thread-safe, 150, 160
ThreadStaticAttributes, 176
ThreadStore, 137
throw (mot-cl), 513
tiers de confiance, 231
TIL, 750
time slices, voir quantum, 138
timeout, 145
Timer, 702
timer, 679
TIP, 296
tlbexp.exe, 289
tlbimp.exe, 279

Index
TLS, voir thread local storage, 177, voir
Transport Layer Security, 660
tModel, 1011
trace.axd, 905
TraceWebEventProvider, 907
transaction, 741
transaction dpendante, 752
transaction distribue, 741
Transaction Isolation Level, 750
transaction manager, voir moteur
transactionnel, 742
transparent proxy, voir proxy transparent, 787
Transport Layer Security, 660
transtypage explicite, 505
transtypage implicite, 505
tray icon, 679
try (mot-cl), 511
tuple (DB), 705
Type, voir System.Type, 243
type blittable, 268
type construit, voir type gnrique, 471
type construit ferm, voir type gnrique
ferm, 471
type construit ouvert, voir type gnrique
ouvert, 471
type dfini sur plusieurs fichiers sources, 392
type lmentaire, 343
type encapsul, 416
type gnrique, 469, 471
type gnrique ferm, 471
type gnrique ouvert, 471
type library, voir bibliothque de types, 279
type nullable, 386
type paramtre, 470
type partiel, voir type dfini sur plusieurs
fichiers sources, 392
type rfrence, 339
type valeur, 339
TypeDef (table de mtadonnes de type), 19
typeof (mot-cl), 241
TypeRef (table de mtadonnes de type), 19
Types Models, 1011
types primitifs, 343
UDDI, voir Universal Description Discovery
and Integration, 1011
UDP/IP, 642
UDT, voir User Defined Type, 737
uint (mot-cl), 354

1053
ulong (mot-cl), 354
UnBoxing, 352
UNICODE, 635
union, 270
unit dexcution, voir thread, 133
unit fonctionnelle, voir processus, 133
Universal Description Discovery and
Integration, 1011
unsafe (mot-cl), 502
URI, 651
URI relatif, 652
URL, 104
Url, voir System.Security.Policy.Url, 188
User Defined Type, 737
UserScopedSettingAttribute, 61
ushort (mot-cl), 354
using (mot-cl), 320, 426
UTF, 635
UTF-8, 635, 761
valeur de hachage, 18, 189
value (mot-cl), 408
value type, voir type valeur, 339
variable, 396
variance, 721
VARIANT, 285
vecteur dinitialisation, 220
verbatim, 611
verbatim (string), 372
verbe (WebParts), 975
virtual (mot-cl), 449
Visual Studio .NET, 25
void (mot-cl), 404
voir expression rgulire, 625
voir Message Exchange Pattern, 990
voir type primitif, 343
volatile (mot-cl), 145
volatile (RM), 746
volume, 607
VSHost, 83
W3C, 9, 769
w3wp.exe, 861
WCF, 1013
weak reference, voir Rfrence faible, 120
web form, 861
web garden, 890
web mthode, 991
Web Service Enhancement, 1008

1054

Index

Web Service Interoperability, 989


web.config, 716
WebDev.WebServer.EXE, 862
WebManagementEvent, 906
WebParts, 973
webPartsZone, 977
well-known object, voir WKO, 796
while (mot-cl), 332
Win32, 267
Windows Application, 666
Windows Communication Framework, 1013
Windows Management Instrumentation, 907
Windows Socket API, 654
WindowsIdentity, voir System.Security.
Principal.WindowsIdentity, 208
Winsock, 654
WKO, 796
WM_PAINT, voir Paint, 676
WM_TIMER, 171
WMI, voir Windows Management
Instrumentation, 907
WmiLogWebEventProvider, 907
worker thread, voir thread ouvrier, 167
WS-Addressing, 1009
WS-AtomicTransaction, 743, 1012
WS-BusinessActivity, 745, 1012
WS-Coordination, 745, 1012
WS-Discovery, 1011
WS-Enumeration, 1013
WS-Federation, 1012
WS-I, voir Web Service Interoperability, 989
WS-Management, 1013

WS-MetadataExchange, 1011
WS-Policy, 1009
WS-PolicyAttachment, 1011
WS-ReliableMessage, 1011
WS-SecureConversation, 1009
WS-Security, 1009
WS-SecurityPolicy, 1009
WS-Trust, 1009
WSDL, 1003
wsdl.exe, 994
WSE, voir Web Service Enhancement, 1008
WYSIWYG, 667
X.509, 231
X/Open, 297
X509 Certificate Tool, 1009
XA, 297
XA (transaction), 743
XCopy (dploiement), 63
XML, 759
XP, voir eXtreme Programming, 7
XPath, 763
XPathNodeIterator, 773
XPathNodeIterator, 773
XQuery, 764
XSD, 762
xsd.exe, 731, 782
XSLT, 763
yield break (mot-cl), 546
yield return (mot-cl), 542
zone, 188

Composition et mise en page :


Atelier Fluxus Virus, 11 rue Alsace-Lorraine, F-29200 Brest
http://www.fluxus-virus.com

Vous aimerez peut-être aussi