C Sharp
C Sharp
C Sharp
Le contenu de ce livre pdf de cours d'initiation C#, est inclus dans un ouvrage papier de 1372 pages dit en Novembre 2004 par les ditions Berti Alger.
http://www.berti-editions.com
L'ouvrage est accompagn d'un CD-ROM contenant les assistants du package pdagogique.
SOMMAIRE
Types, oprateurs, instructions
Introduction ... . P.3 P.4 P.12 P.22 P.36 P.41 P.47 P.51 P.55 Les outils lmentaires .. Les lments de base ..... ..
... .. .. . .. . ..
page
IHM avec C#
Les vnements . . .. .... P.213 P.232 P.257 P.293 Proprits et indexeurs
Exceptions compares Delphi et java ... P.317 Donnes simples flux et fichiers ... P.326 ADO donnes relationnelles de .Net ... Exercices ... P.358 P.372
Pour pouvoir s initier C# avec ce cours et peu de frais dans un premier temps, il faut tlcharger gratuitement sur le site de Borland, l environnement Borland studio Delphi 2005 dition personnelle, ou aller sur le site de Microsoft et tlcharger gratuitementVisual C# express 2005 bta2 (qui devrait tre bientt payant)
Remerciements : (anticips)
Aux lecteurs qui trouveront ncessairement encore des erreurs, des oublis, et autres imperfections et qui voudront bien les signaler lauteur afin damliorer le cours, e-mail : discala@univ-tours.fr
page
Introduction
Une stratgie diffrente de rpartition de l'information et de son traitement est propose depuis 2001 par Microsoft, elle porte le nom de .NET (ou en anglais dot net). La conception de cette nouvelle architecture s'appuie sur quelques ides fondatrices que nous nonons ci-dessous :
q
Une disparition progressive des diffrences entre les applications et l'Internet, les serveurs ne fourniront plus seulement des pages HTML, mais des services des applications distantes. Les informations au lieu de rester concentres sur un seul serveur pourront tre rparties sur plusieurs machines qui proposeront chacune un service adapt aux informations qu'elles dtiennent. A la place d'une seule appplication, l'utilisateur aura accs une fdration d'applications distantes ou locales capables de cooprer entre elles pour divers usages de traitement. L'utilisateur n'aurait plus la ncessit d'acheter un logiciel, il louerait plutt les services d'une action spcifique. Le micro-ordinateur reste l'intermdiaire incontournable de cette stratgie, il dispose en plus de la capacit de terminal intelligent pour consulter et traiter les informations de l'utilisateur travers Internet o qu'elles se trouvent. Offrir aux dveloppeurs d'applications .NET un vaste ensemble de composants afin de faire de la programmation par composant unifie au sens des protocoles (comme lutilisation du protocole SOAP) et diversifie quant aux lieux o se trouvent les composants.
Afin de mettre en place cette nouvelle stratgie, microsoft procde par tapes. Les fondations de l'architecture .NET sont poses par l'introduction d'un environnement de dveloppement et d'excution des applications .NET. Cet environnement en version stabilise depuis 2002 porte la dnomination de .NETFramework, il est distribu gratuitement par microsoft sur toutes les versions de Windows (98, Me,..., Xp,...). L'outil Visual Studio .NET contient l'environnement RAD de dveloppement pour l'architecture .NET. Visual Studio .NET permet le dveloppement d'applications classiques Windows ou Internet. Dans ce document nous comparons souvent C# ses deux parents Java et Delphi afin d'en signaler les apports et surtout les diffrences.
page
page
La premire couche CLS est compose des specifications communes communes tous les langages qui veulent produire des applications .NET qui soient excutables dans cet environnement et les langages eux-mme. Le CLS est une sorte de sous-ensemble minimal de spcifications autorisant une interoprabilit complte entre tous les langages de .NET les rgles minimales (il y en a en fait 41 ) sont : Les langages de ..NET doivent savoir utiliser tous les composants du CLS Les langages de .NET peuvent construire de nouvelles classes, de nouveaux composants conformes au CLS
Le C# est le langage de base de .NET, il correspond une synthse entre Delphi et Java (le concepteur principal de .NET. et de C# est l'ancien chef de projet Turbo pascal puis Delphi de Borland). Afin de rendre Visual Basic interoprable sur .NET, il a t entirement reconstruit par microsoft et devient un langage orient objet dnomm VB.NET.
La seconde couche est un ensemble de composants graphiques disponibles dans Visual Studio .NET qui permettent de construire des interfaces homme-machine orientes Web (services Web) ou bien orientes applications classiques avec IHM.
Les donnes sont accdes dans le cas des services Web travers les protocoles qui sont des standards de l'industrie : HTTP, XML et SOAP.
La troisime couche est constitue d'une vaste librairie de plusieurs centaines de classes :
Toutes ces classes sont accessibles telles quelles tous les langages de .NET et cette librairie peut tre tendue par adjonction de nouvelles classes. Cette librairie a la mme fonction que la bibliothque des classes de Java.
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
La librairie de classe de .NET Framework est organise en nom d'espace hierarchiss, exemple ci-dessous de quelques espaces de nom de la hirarchie System :
Un nom complet de classe comporte le "chemin" hirarchique de son espace de nom et se termine par le nom de la classe exemples : La classe DataSet qui se trouve dans l'espace de noms "System.Data.ADO" se dclare comme "System.Data.ADO.Dataset". La classe Console qui se trouve dans l'espace de noms "System" se dclare comme "System.Console".
La quatrime couche forme l'environnement d'excution commun (CLR ou Common Language Runtime) de tous les programmes s'excutant dans l'environnement .NET. Le CLR excute un bytecode crit dans un langage intermdiaire (MSIL ou MicroSoft Intermediate Language) Rappelons qu'un ordinateur ne sait excuter que des programmes crits en instructions machines comprhensibles par son processeur central. C# comme pascal, C etc... fait partie de la famille des langages volus (ou langages de haut niveau) qui ne sont pas comprhensibles immdiatement par le processeur de l'ordinateur. Il est donc ncesaire d'effectuer une "traduction" d'un programme crit en langage volu afin que le processeur puisse l'excuter. Les deux voies utilises pour excuter un programme volu sont la compilation ou l'interprtation :
page
Un compilateur du langage X pour un processeur P, est un logiciel qui traduit un programme source crit en X en un programme cible crit en instructions machines excutables par le processeur P.
Un interprteur du langage X pour le processeur P, est un logiciel qui ne produit pas de programme cible mais qui effectue lui-mme immdiatement les oprations spcifies par le programme source.
Un compromis assurant la portabilit d'un langage : une pseudo-machine Lorsque le processeur P n'est pas une machine qui existe physiquement mais un logiciel simulant (ou interprtant) une machine on appelle cette machine pseudomachine ou p-machine. Le programme source est alors traduit par le compilateur en instructions de la pseudo-machine et se dnomme pseudo-code. La p-machine standard peut ainsi tre implante dans n'importe quel ordinateur physique travers un logiciel qui simule son comportement; un tel logiciel est appel interprteur de la p-machine.
La premire p-machine d'un langage volu a t construite pour le langage pascal assurant ainsi une large diffusion de ce langage et de sa version UCSD dans la mesure o le seul effort d'implementation pour un ordinateur donn tait d'crire l'interprteur de p-machine pascal, le reste de l'environnement de dveloppement (diteurs, compilateurs,...) tant crit en pascal tait fourni et fonctionnait ds que la p-machine tait oprationnelle sur la plate-forme cible. Donc dans le cas d'une p-machine le programme source est compil, mais le programme cible est excut par l'interprteur de la p-machine. Beaucoup de langages possdent pour une plate-forme fixe des interprteurs ou des compilateurs, moins possdent une p-machine, Java de Sun est l'un de ces langages. Tous les langages de la plateforme .NET fonctionnent selon ce principe, C# conu par microsoft en est le dernier, un programme C# compil en p-code, s'excute sur la p-machine virtuelle incluse dans le CLR. Nous dcrivons ci-dessous le mode opratoire en C#.
Compilation native
La compilation native consiste en la traduction du source C# (ventuellement pralablement traduit instantanment en code intermdiare) en langage binaire excutable sur la plate-forme concerne. Ce genre de compilation est quivalent n'importe quelle compilation d'un langage dpendant de la plate-forme, l'avantage est la rapidit d'excution des instructions machines par le processeur central. La stratgie de dveloppement multi-plateforme de .Net, fait que Microsoft ne fournit pas pour linstant, de compilateur C# natif, il faut aller voir sur le net les entreprises vendant ce type de produit.
page
Programe source C# : xxx.cs Programe excutable sous Windows : xxx.exe (code natif processeur)
ATTENTION
Bien que se terminant par le suffixe exe, un programme issu d'une compilation sous .NET n'est pas un excutable en code natif, mais un bytecode en MSIL; ce qui veut dire que vous ne pourrez pas faire excuter directement sur un ordinateur qui n'aurait pas la machine virtuelle .NET, un programme PE "xxx.exe" ainsi construit .
Ci-dessous le schma d'un programme source Exemple.cs traduit par le compilateur C# sous .NET en un programme cible crit en bytecode nomm Exemple.exe
page
Programe source C# : Exemple.cs Programe excutable sous .NET : Exemple.exe (code portable IL )
Une fois le programme source C# traduit en bytecode MSIL, la machine virtuelle du CLR se charge de l'excuter sur la machine physique travers son systme d'exploitation (Windows, Unix,...)
page
On peut mentalement considrer qu'avec cette technique vous obtenez un programme C# cible compil en deux passages : le premier passage est d l'utilisation du compilateur C# produisant excutable portable ( PE ) en bytecode MSIL, le second passage tant le compilateur JIT lui-mme qui optimise et traduit localement la vole et chaque appel de mthode, le bytecode MSIL en instructions du processeur de la plate-forme. Ce qui donne au bout d'un temps trs bref, un code totalement traduit en instruction du processeur de la plateforme, selon le schma ci-aprs :
page
10
page
11
Tout est objet dans C#, en outre C# est un langage fortement typ. Comme en Delphi et en Java vous devez dclarer un objet C# ou une variable C# avec son type avant de l'utiliser. C# dispose de types valeurs intrinsques qui sont dfinis partir des types de base du CLS (Common Language Specification).
Struct
Les classes encapsulant les types lmentaires dans .NET Framework sont des classes de type valeur du genre structures. Dans le CLS une classe de type valeur est telle que les allocations d'objets de cette classe se font directement dans la pile et non dans le tas, il n'y a donc pas de rfrence pour un objet de type valeur et lorsqu'un objet de type valeur est pass comme paramtre il est pass par valeur. Dans .NET Framework les classes-structures de type valeur sont dclares comme structures et ne sont pas drivables, les classes de type rfrence sont dclares comme des classes classiques et sont drivables. Afin d'clairer le lecteur prenons par exemple un objet x instanci partir d'une classe de type rfrence et un objet y instanci partir d'un classe de type valeur contenant les mmes membres que la classe par rfrence. Ci-dessous le schma d'allocation de chacun des deux objets :
page
12
En C# on aurait le genre de syntaxe suivant : Dclaration de classe-structure : struct StructAmoi { int b; void meth(int a){ b = 1000+a; } } Dclaration de classe : class ClassAmoi { int b; void meth(int a) { b = 1000+a; } } instanciation : ClassAmoi x = new ClassAmoi ( ) ; instanciation : StructAmoi y = new StructAmoi ( ) ;
Les classes-structures de type valeur peuvent comme les autres classes possder un constructeur explicite, qui comme pour tout classe C# doit porter le mme nom que celui de la classe-structure. Exemple ci-desssous d'une classe-structure dnomme Menulang: public struct Menulang { public String MenuTexte; public String Filtre; public Menulang(String M, String s) { MenuTexte = M; Filtre = s; } } On instancie alors un objet de type valeur comme un objet de type rfrence. En reprenant l'exemple de la classe prcdente on instancie et on utilise un objet Rec : Menulang Rec = new Menulang ( Nomlang , FiltreLang ); Rec.MenuTexte = "Entrez" ; Rec.Filtre = "*.ent" ;
13
Byte Char Double Single Int16 Int32 Int64 UInt16 UInt32 UInt64 Decimal
octet non sign 0 ... 255 caractres unicode (valeurs de 0 65535) Virgule flottante double prcision ~ 15 dcimales Virgule flottante simple prcision ~ 7 dcimales entier sign court [ -215... +215-1 ] entier sign [ -231... +231-1 ] entier sign long [ -263... +263-1 ] entier non sign court 0216-1 entier non sign 0232 -1 entier non sign long 0264-1 reel = entier* 10n (au maximum 28 dcimales exactes)
8 bits 16 bits 64 bits 32 bits 16 bits 32 bits 64 bits 16 bits 32 bits 64 bits 128 bits
Compatibilit des types de .NET Framework Le type System.Int32 qui le type valeur entier sign sur 32 bits dans le CLS. Voici selon 4 langages de .NET Framework ( VB, C#, C++, J# ) la dclaration syntaxique du type Int32 : [Visual Basic] Public Structure Int32 Implements IComparable, IFormattable, IConvertible [C#] public struct Int32 : IComparable, IFormattable, IConvertible [C++] public __value struct Int32 : public IComparable, IFormattable, IConvertible [J#] public class Int32 extends System.ValueType implements System.IComparable, System.IFormattable, System.IConvertible
Les trois premires dclarations comportent syntaxiquement le mot clef struct ou Structure indiquant le mode de gestion par valeur donc sur la pile des objets de ce type. La dernire dclaration en J# compatible syntaxiquement avec Java, utilise une classe qui par contre gre ses
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
14
objets par rfrence dans le tas. C'est le CLR qui va se charger de maintenir une cohrence interne entre ces diffrentes variantes; ici on peut raisonnablement supposer que grce au mcanisme d'embotage (Boxing) le CLR allouera un objet par rfrence encapsulant l'objet par valeur, mais cet objet encapsul sera marqu comme objet-valeur.
enum
Un type enum est un type valeur qui permet de dclarer un ensemble de constantes de base comme en pascal. En C#, chaque numration de type enum, possde un type sous-jacent, qui peut tre de n'importe quel type entier : byte, sbyte, short, ushort, int, uint, long ou ulong. Le type int est le type sous-jacent par dfaut des lments de l'numration. Par dfaut, le premier numrateur a la valeur 0, et l'numrateur de rang n a la valeur n-1. Soit par exemple un type numr jour :
enum jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} par dfaut : rang de lundi=0, rang de mardi=1, ... , rang de dimanche=6
1) Il est possible de dclarer classiquement une variable du type jour comme un objet de type jour, de l'instancier et de l'affecter :
jour unJour = new jour ( ); unJour = jour.lundi ; int rang = (int)unJour; // rang de la constante dans le type numr System.Console.WriteLine("unJour = "+unJour.ToString()+" , place = '+rang); Rsultat de ces 3 lignes de code affich sur la console : unJour = lundi , place = 0
2) Il est possible de dclarer d'une manire plus courte la mme variable du type jour et de l'affecter : jour unJour ; unJour = jour.lundi ; int rang = (int)unJour; System.Console.WriteLine("unJour = "+unJour.ToString()+" , place = '+rang); Rsultat de ces 3 lignes de code affich sur la console : unJour = lundi , place = 0
page
15
Remarque C# accepte que des numrations aient des noms de constantes d'numrations identiques : enum jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} enum weekEnd { vendredi, samedi, dimanche}
Dans cette ventualit faire attention, la comparaison de deux variables de deux types diffrents, affectes chacune une valeur de constante identique dans les deux types, ne conduit pas l'galit de ces variables (c'est en fait le rang dans le type numr qui est test). L'exemple ci-dessous illustre cette remarque : enum jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} enum weekEnd { vendredi, samedi, dimanche} jour unJour ; weekEnd repos ; unJour = jour.samedi ; repos = weekEnd.samedi; if ( (jour)repos == unJour ) // il faut transtyper l'un des deux si l'on veut les comparer System.Console.WriteLine("Le mme jour"); else System.Console.WriteLine("Jours diffrents"); Rsultat de ces lignes de code affich sur la console : Jours diffrents
16
Rappelons qu'en C# toute variable qui sert de conteneur une valeur d'un type lmentaire prcis doit pralablement avoir t dclare sous ce type. Remarque importante
Une variable de type lmentaire en C# est (pour des raisons de compatibilit CLS) automatiquement un objet de type valeur (Par exemple une variable de type float peut tre considre comme un objet de classe Single).
Il est possible d'indiquer au compilateur le type d'une valeur numrique en utilisant un suffixe :
l ou L pour dsigner un entier du type long f ou F pour dsigner un rel du type float d ou D pour dsigner un rel du type double m ou M pour dsigner un rel du type decimal
Exemples : 45l ou 45L reprsente la valeur 45 en entier sign sur 64 bits. 45f ou 45F reprsente la valeur 45 en virgule flottante simple prcision sur 32 bits. 45d ou 45D reprsente la valeur 45 en virgule flottante double prcision sur 64 bits. 5.27e-2f ou 5.27e-2F reprsente la valeur 0.0527 en virgule flottante simple prcision sur 32 bits.
page
17
Transtypage oprateur ( )
Les conversions de type en C# sont identiques pour les types numriques aux conversions utilises dans un langage fortement typ comme Delphi par exemple. Toutefois C# pratique la conversion implicite lorsque celle-ci est possible. Si vous voulez malgr tout, convertir explicitement une valeur immdiate ou une valeur contenue dans une variable il faut utiliser l'oprateur de transtypage not ( ). Nous nous sommes dj servi de la fonctionnalit de transtypage explicite au paragraphe prcdent dans l'instruction : int rang = (int)unJour; et dans l'instruction if ( (jour)repos == unJour )... Transtypage implicite en C# : int n = 1234; float x1 = n ; double x2 = n ; double x3 = x1 ; long p = n ; .....
Transtypage explicite en C# : int x; x = (int) y ; signifie que vous demandez de transtyper la valeur contenue dans la variable y en un entier sign 32 bits avant de la mettre dans la variable x. Tous les types lmentaires peuvent tre transtyps l'exception du type bool qui ne peut pas tre converti en un autre type (diffrence avec le C). Les conversions peuvent tre restrictives quant au rsultat; par exemple le transtypage du rel 5.27e-2 en entier ( x = (int)5.27e-2) mettra l'entier zro dans x.
Identificateur C# :
Premier pas dans .Net avec C# - ( rv. 15.08.2005 )
page
18
Attention C# fait une diffrence entre majuscules et minuscules, c'est dire que la variable BonJour n'est pas la mme que la variable bonjour ou encore la variable Bonjour. En plus des lettres, les caractres suivants sont autoriss pour construire une identificateur C# : "$" , "_" , "" et les lettres accentues.
Exemples de dclaration de variables : int Bonjour ; int Enumration_fin$; float Valeur ; char UnCar ; bool Test ; etc ... Exemples d'affectation de valeurs ces variables : Affectation Bonjour = 2587 ; Valeur = -123.5687 UnCar = 'K' ; Test = false ; Dclaration avec initialisation int Bonjour = 2587 ; float Valeur = -123.5687 char UnCar = 'K' ; bool Test = false ;
Exemple avec transtypage : int Valeur ; char car = '8' ; Valeur = (int)car - (int)'0'; fonctionnement de l'exemple : Lorsque la variable car est l'un des caractres '0', '1', ... ,'9', la variable Valeur est gale la valeur numrique associe (il s'agit d'une conversion car = '0' ---> Valeur = 0, car = '1' ---> Valeur = 1, ... , car = '9' ---> Valeur = 9).
page
19
Les constantes en C#
C# dispose de deux mots clefs pour qualifier des variables dont le contenu ne peut pas tre modifi : const et readonly sont des qualificateurs de dclaration qui se rajoutent devant les autres qualificateurs de dclaration.. - Les constantes qualifies par const doivent tre initialises lors de leur dclaration. Une variable membre de classe ou une variable locale une mthode peut tre qualifie en constante const. Lorsque de telles variables sont dclares comme variables membre de classe, elles sont considres comme des variables de classe statiques : const int x ; // erreur , le compilateur n'accepte pas une constante non initialise. const int x = 1000 ; // x est dclare comme constante entire initialise 1000. x = 8 ; <------ provoquera une erreur de compilation interdisant la modification de la valeur de x.
- Les constantes qualifies par readonly sont uniquement des variables membre de classes, elles peuvent tre initialises dans le constructeur de la classe (et uniquement dans le constructeur) : readonly int x ; // correct. readonly int x = 100 ; // correct.
-Rappelons enfin pour mmoire les constantes de base d'un type numr ( cf. enum )
page
20
Les oprateurs
1. Priorit d'oprateurs en C#
Les oprateurs du C# sont trs semblables ceux de Java et donc de C++, ils sont dtaills par famille, plus loin . Ils sont utiliss comme dans tous les langages impratifs pour manipuler, sparer, comparer ou stocker des valeurs. Les oprateurs ont soit un seul oprande, soit deux oprandes, il n'existe en C# qu'un seul oprateur trois oprandes (comme en Java) l'oprateur conditionnel " ? : ". Dans le tableau ci-dessous les oprateurs de C# sont classs par ordre de priorit croissante (0 est le plus haut niveau, 13 le plus bas niveau). Ceci sert lorsqu'une expression contient plusieurs oprateurs indiquer l'ordre dans lequel s'effectueront les oprations. Par exemple sur les entiers l'expression 2+3*4 vaut 14 car l'oprateur * est plus prioritaire que l'oprateur +, donc l'oprateur * est effectu en premier. Lorsqu'une expression contient des oprateurs de mme priorit alors C# effectue les valuations de gauche droite. Par exemple l'expression 12/3*2 vaut 8 car C# effectue le parenthsage automatique de gauche droite ((12/3)*2).
page
21
7 8 9 10 11 12 13
oprateurs travaillant avec des oprandes valeur immdiate ou variable Oprateur + * / % + priorit 1 1 2 2 2 3 3 action signe positif signe ngatif multiplication division reste addition soustraction exemples +a; +(a-b); +7 (unaire) -a; -(a-b); -7 (unaire) 5*4; 12.7*(-8.31); 5*2.6 5 / 2; 5 % 2; 5.0 / 2; 5.0 / 2.0 5.0 %2; 5.0 % 2.0
Ces oprateurs sont binaires ( deux oprandes) excepts les oprateurs de signe positif ou ngatif. Ils travaillent tous avec des oprandes de types entiers ou rels. Le rsultat de l'opration est converti automatiquement en valeur du type des oprandes. L'oprateur " % " de reste n'est intressant que pour des calculs sur les entiers longs, courts, signs ou non signs : il renvoie le reste de la division euclidienne de 2 entiers.
page
22
Exemples d'utilisation de l'oprateur de division selon les types des oprandes et du rsultat : programme C# int x = 5 , y ; float a , b = 5 ; y=x/2; y=b/2; y = b / 2.0 ; a=b/2; rsultat obtenu x = 5 , y =??? b = 5 , a =??? y = 2 // type int erreur de conversion : interdit erreur de conversion: interdit a = 2.5 // type float dclaration dclaration int x et int 2 rsultat : int conversion implicite impossible (float b --> int y) conversion implicite impossible (float b --> int y) float b et int 2 rsultat : float int x et int 2 rsultat : int conversion automatique int 2 --> float 2.0 int x et float 2f rsultat : float commentaire
a=x/2;
a = x / 2f ;
Pour l'instruction prcdente " y = b / 2 " engendrant une erreur de conversion voici deux corrections possibles utilisant le transtypage explicite : y = (int)b / 2 ; // b est converti en int avant la division qui s'effectue sur deux int. y = (int)(b / 2) ; // c'est le rsultat de la division qui est converti en int. oprateurs travaillant avec une unique variable comme oprande Oprateur priorit ++ 1 action exemples
post ou pr incrmentation : incrmente de 1 son oprande numrique : short, ++a; a++; (unaire) int, long, char, float, double. post ou pr dcrmentation : dcrmente de 1 son oprande numrique : short, --a; a--; (unaire) int, long, char, float, double.
--
L'objectif de ces oprateurs est l'optimisation de la vitesse d'excution du bytecode MSIL dans le CLR (cette optimisation n'est pas effective dans le version actuelle du MSIL) mais surtout la reprise syntaxique aise de code source Java et C++.
page
23
post-incrmentation : k++
la valeur de k est d'abord utilise telle quelle dans l'instruction, puis elle est augmente de un la fin. Etudiez bien les exemples ci-aprs qui vont vous permettre de bien comprendre le fonctionnement de cet oprateur. Nous avons mis ct de l'instruction C# les rsultats des contenus des variables aprs excution de l'instruction de dclaration et de la post incrmentation. Exemple 1 : int k = 5 , n ; n = k++ ; n=5 k=6
Dans l'instruction k++ - k nous avons le calcul suivant : la valeur de k (k=5) est utilise comme premier oprande de la soustraction, puis elle est incrmente (k=6), la nouvelle valeur de k est maintenant utilise comme second oprande de la soustraction ce qui revient calculer n = 5-6 et donne n = -1 et k = 6. Exemple 3 : int k = 5 , n ; n = k - k++ ; n=0 k=6
Dans l'instruction k - k++ nous avons le calcul suivant : la valeur de k (k=5) est utilise comme premier oprande de la soustraction, le second oprande de la soustraction est k++ c'est la valeur actuelle de k qui est utilise (k=5) avant incrmentation de k, ce qui revient calculer n = 5-5 et donne n = 0 et k = 6. Exemple 4 :Utilisation de l'oprateur de post-incrmentation en combinaison avec un autre oprateur unaire. int nbr1, z , t , u , v ; nbr1 = 10 ; v = nbr1++ nbr1 = 10 ; z = ~ nbr1 ; nbr1 = 10 ; t = ~ nbr1 ++ ; nbr1 = 10 ; u = ~ (nbr1 ++) ; v = 10 z = -11 t = -11 u = -11 nbr1 = 11 nbr1 = 10 nbr1 = 11 nbr1 = 11
page
24
La notation " (~ nbr1) ++ " est refuse par C# comme pour Java. remarquons que les expressions "~nbr1 ++ " et "~ (nbr1 ++)" produisent les mmes effets, ce qui est logique puisque lorsque deux oprateurs (ici ~ et ++ )ont la mme priorit, l'valuation a lieu de gauche droite.
pr-incrmentation : ++k
la valeur de k est d'abord augmente de un ensuite utilise dans l'instruction. Exemple1 : int k = 5 , n ; n = ++k ; n=6 k=6
Dans l'instruction ++k - k nous avons le calcul suivant : le premier oprande de la soustraction tant ++k c'est donc la valeur incrmente de k (k=6) qui est utilise, cette mme valeur sert de second oprande la soustraction ce qui revient calculer n = 6-6 et donne n = 0 et k = 6.
Dans l'instruction k - ++k nous avons le calcul suivant : le premier oprande de la soustraction est k (k=5), le second oprande de la soustraction est ++k, k est immdiatement incrment (k=6) et c'est sa nouvelle valeur incrmente qui est utilise, ce qui revient calculer n = 5-6 et donne n = 1 et k = 6.
post-dcrmentation : k-la valeur de k est d'abord utilise telle quelle dans l'instruction, puis elle est diminue de un la fin. Exemple1 : int k = 5 , n ; n = k-- ; n=5 k=4
page
25
pr-dcrmentation : --k
la valeur de k est d'abord diminue de un, puis utilise avec sa nouvelle valeur. Exemple1 : int k = 5 , n ; n = --k ; n=4 k=4
Reprenez avec l'oprateur - - des exemples semblables ceux fournis pour l'oprateur ++ afin d'tudier le fonctionnement de cet oprateur (tudiez (- -k - k) et (k - - -k)).
3. Oprateurs de comparaison
Ces oprateurs employs dans une expression renvoient un rsultat de type boolen (false ou true). Nous en donnons la liste sans autre commentaire car ils sont strictement identiques tous les oprateurs classiques de comparaison de n'importe quel langage algorithmique (C, pascal, etc...). Ce sont des oprateurs deux oprandes. Oprateur < <= > >= == != is priorit 5 5 5 5 6 6 5 action strictement infrieur infrieur ou gal strictement suprieur suprieur ou gal gal diffrent Teste le type de l'objet exemples 5 < 2 ; x+1 < 3 ; y-2 < x*4 -5 <= 2 ; x+1 <= 3 ; etc... 5 > 2 ; x+1 > 3 ; etc... 5 >= 2 ; etc...
4. Oprateurs boolens
Ce sont les oprateurs classiques de l'algbre de boole { { V, F }, ! , & , | } o { V, F } reprsente l'ensemble {Vrai,Faux}. Les connecteurs logiques ont pour syntaxe en C# : ! , & , |, ^:
& : { V, F } x { V, F } { V, F } (oprateur binaire qui se lit " et ") | : { V, F } x { V, F } { V, F } (oprateur binaire qui se lit " ou ") ^ : { V, F } x { V, F } { V, F } (oprateur binaire qui se lit " ou exclusif ") ! : { V, F } { V, F } (oprateur unaire qui se lit " non ")
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
26
Remarque : p { V, F } , q { V, F } , p &q est toujours valu en entier ( p et q sont toujours valus). p { V, F } , q { V, F } , p |q est toujours valu en entier ( p et q sont toujours valus). C# dispose de 2 clones des oprateurs binaires & et | . Ce sont les oprateurs && et || qui se diffrentient de leurs originaux & et | par leur mode d'excution optimis (application de thormes de l'algbre de boole) : L'oprateur et optimis : && Thorme q { V, F } , F &q = F Donc si p est faux (p = F) , il est inutile d'valuer q car l'expression p &q est fausse (p &q = F), comme l'oprateur & value toujours l'expression q, C# des fins d'optimisation de la vitesse d'excution du bytecode MSIL dans le CLR , propose un oprateur ou not && qui a la mme table de vrit que l'oprateur & mais qui applique ce thorme. p { V, F } , q { V, F } , p &&q = p &q Mais dans p&&q , q n'est valu que si p = V.
L'oprateur ou optimis : | | Thorme q { V, F } , V |q = V Donc si p est vrai (p = V) , il est inutile d'valuer q car l'expression p |q est vraie (p |q = V), comme l'oprateur | value toujours l'expression q, C# des fins d'optimisation de la vitesse d'excution du bytecode dans la machine virtuelle C#, propose un oprateur ou not || qui
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
27
applique ce thorme et qui a la mme table de vrit que l'oprateur | . p { V, F } , q { V, F } , p ||q = p |q Mais dans p||q , q n'est valu que si p = F.
En rsum: Oprateur priorit action non boolen et boolen complet ou boolen complet et boolen optimis ou boolen optimis exemples ! (5 < 2) ; !(x+1 < 3) ; etc... (5 = = 2) & (x+1 < 3) ; etc... (5 != 2) | (x+1 >= 3) ; etc... (5 = = 2) && (x+1 < 3) ; etc... (5 != 2) || (x+1 >= 3) ; etc...
! & | && ||
1 7 9 10 11
Nous allons voir ci-aprs une autre utilisation des oprateurs &et | sur des variables ou des valeurs immdiates en tant qu'oprateur bit-level.
Les tables de vrits des oprateurs "&", " | " et celle du ou exclusif " ^ " au niveau du bit sont identiques aux tables de verit boolennes ( seule la valeur des constantes V et F change, V est remplac par le bit 1 et F par le bit 0) .
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
28
0 1 1 0
Afin de bien comprendre ces oprateurs, le lecteur doit bien connatre les diffrents codages des entiers en machine (binaire pur, binaire sign, complment deux) car les entiers C# sont cods en complment deux et la manipulation bit bit ncessite une bonne comprhension de ce codage. Afin que le lecteur se familiarise bien avec ces oprateurs de bas niveau nous dtaillons un exemple pour chacun d'entre eux.
Les exemples en 3 instructions C# sur la mme mmoire : Rappel : int i = -14 ; soit reprsenter le nombre -14 dans la variable i de type int (entier sign sur 32 bits)
codage de |-14|= 14
complment 1
addition de 1
page
29
Soient la dclaration C# suivante : int i = -14 , j ; Etudions les effets de chaque oprateur bit level sur cette mmoire i.
Etude de l'instruction : j = ~ i
~i
Tous les bits 1 sont transforms en 0 et les bits 0 en 1, puis le rsultat est stock dans j qui contient la valeur 13 (car 000...01101 reprsente +13 en complment deux). Etude de l'instruction : j = i >> 2
~ i >> 2
Tous les bits sont dcals de 2 positions vers la droite (vers le bit de poids faible), le bit de signe (ici 1) est recopi partir de la gauche ( partir du bit de poids fort) dans les emplacements librs (ici le bit 31 et le bit 30), puis le rsultat est stock dans j qui contient la valeur -4 (car 1111...11100 reprsente -4 en complment deux). Etude de l'instruction : j = i << 2
~ i << 2
Tous les bits sont dcals de 2 positions vers la gauche (vers le bit de poids fort), des 0 sont introduits partir de la droite ( partir du bit de poids faible) dans les emplacements librs (ici le bit 0 et le bit 1), puis le rsultat est stock dans j contient la valeur -56 (car 11...1001000 reprsente -56 en complment deux).
page
30
page
31
Rsultats d'excution de ce progamme : x&y =0 x&z =0 x&t =4 y&z =3 x | y = -1 x|z =7 x|t =7 y | z = -5 z^t =4 ~x = -5, ~y = 4, ~z = -4, ~t = -8
page
32
page
33
Rsultats d'excution de ce progamme : x < y = true (x < y) & (z = = t) = false (x < y) | (z = = t) = true (x < y) && (z = = t) = false (x < y) || (z = = t) = true (x < y) || ((calcul=z) == t) = true ** calcul = 0 (x < y) | ((calcul=z) == t) = true ** calcul = 3
page
34
Les instructions
Les instructions de base de C# sont identiques syntaxiquement et smantiquement celles de Java, le lecteur qui connat dj le fonctionnement des instructions en Java peut ignorer ces chapitres.
Ici, nous expliquons les instructions C# en les comparant pascal-delphi. Voici la syntaxe d'une instruction en C#:
instruction :
instruction complte :
page
35
est aussi dnomm bloc ou instruction compose au sens de la visibilit des variables C#.
visibilit dans un bloc - instruction : Exemple de dclarations licites et de visibilit dans 3 blocs instruction imbriqus : int a, b = 12; { int x , y = 8 ; { int z =12; x=z; a=x+1; { int u = 1 ; y=u-b; } } }
2 - l'affectation
C# est un langage de la famille des langages hybrides, il possde la notion d'instruction d'affectation.
x=y;
// x doit obligatoirement tre un identificateur de variable.
Affectation simple
L'affectation peut tre utilise dans une expression : soient les instruction suivantes : int a , b = 56 ; a = (b = 12)+8 ; // b prend une nouvelle valeur dans l'expression a = b = c = d =8 ; // affectation multiple
page
36
x op= y ;
signifie en fait : x = x op y
Il s'agit plus d'un raccourci syntaxique que d'un oprateur nouveau (sa traduction en MSIL est exactement la mme : la traduction de a op= b devrait tre plus courte en instructions pcode que a = a op b). Ci-dessous le code MSIL engendr par i = i+5; et i +=5; est effectivement identique : Code MSIL engendr
IL_0077: IL_0078: IL_0079: IL_007a: ldloc.1 ldc.i4.5 add stloc.1
Instruction C#
i=i+5;
i += 5 ;
37
Remarques : Cas d'une optimisation intressante dans l'instruction suivante : table[ f(a) ] = table[ f(a) ] + x ; // o f(a) est un appel la fonction f qui serait longue calculer. Si l'on rcrit l'instruction prcdente avec l'oprateur += : table[ f(a) ] += x ; // l'appel f(a) n'est effectu qu'une seule fois
Ci-dessous le code MSIL engendr par "table[ f(i) ] = table[ f(i) ] +9 ;" et "table[ f(i) ] += 9 ;" n'est pas le mme : Code MSIL engendr Instruction C#
table[ f(i) ]
Au total, 12 instructions MSIL dont deux appels : call instance int32 exemple.WinForm::f(int32)
page
38
Instruction C#
table[ f(i) ] += 9 ;
IL_009b: IL_009c: IL_009d: IL_009f: IL_00a0: IL_00a1: IL_00a6: IL_00a7: IL_00a9: IL_00ab: IL_00ad: IL_00ae: IL_00b0: IL_00b1: ldloc.3 dup stloc.s CS$00000002$00000000 ldarg.0 ldloc.1 call instance int32 exemple.WinForm::f(int32) dup stloc.s CS$00000002$00000001 ldloc.s CS$00000002$00000000 ldloc.s CS$00000002$00000001 ldelem.i4 ldc.i4.s 9 add stelem.i4 table[ f(i) ]
+=
table[ f(i) ] += 9 ; Au total, 14 instructions MSIL dont un seul appel : call instance int32 exemple.WinForm::f(int32)
Dans l'exemple qui prcde, il y a rellement gain sur le temps d'excution de l'instruction table[ f(i) ] += 9, si le temps d'excution de l'appel f(i) travers l'instruction MSIL < call instance int32 exemple.WinForm::f(int32) > , est significativement long devant les temps d'excution des oprations ldloc et stloc. En fait d'une manire gnrale en C# comme dans les autres langages, il est prfrable d'adopter l'attitude prise en Delphi qui consiste encourager la lisibilit du code en ne cherchant pas crire du code le plus court possible. Dans notre exemple prcdent, la simplicit consisterait utiliser une variable locale x et stocker la valeur de f(i) dans cette variable :
Ces deux critures tant quivalentes seulement si f(i) ne contient aucun effet de bord ! Info MSIL : ldloc : Charge la variable locale un index spcifique dans la pile d'valuation. stloc : Dpile la pile d'valuation et la stocke dans la liste de variables locales un index spcifi.
page
39
1 - l'instruction conditionnelle
Syntaxe :
Schmatiquement les conditions sont de deux sortes : if ( Expr ) Instr ; if ( Expr ) Instr ; else Instr ;
La dfinition de l'instruction conditionnelle de C# est classiquement celle des langages algorithmiques; comme en pascal l'expression doit tre de type boolen (diffrent du C), la notion d'instruction a t dfinie plus haut.
Exemple d'utilisation du if..else (comparaison avec Delphi) Pascal-Delphi var a , b , c : integer ; .... if b=0 then c := 1 else begin c := a / b; writeln("c = ",c); end; c := a*b ; if c <>0 then c:= c+b else c := a C# int a , b , c ; .... if ( b = = 0 ) c =1 ; else { c = a / b; System.Console.WriteLine ("c = " + c); } if ((c = a*b) != 0) c += b; else c = a;
page
40
Remarques : L'instruction " if ((c = a*b) != 0) c +=b; else c = a; " contient une affectation intgre dans le test afin de vous montrer les possibilits de C# : la valeur de a*b est range dans c avant d'effectuer le test sur c. Comme Delphi, C# contient le manque de fermeture des instructions conditionnelles ce qui engendre le classique problme du dandling else d'algol, c'est le compilateur qui rsout l'ambigut par rattachement du else au dernier if rencontr (valuation par la gauche. L'instruction suivante est ambigu : if ( Expr1 ) if ( Expr2 ) InstrA ; else InstrB ; Le compilateur rsoud l'ambigit de cette instruction ainsi (rattachement du else au dernier if):
2 - l'oprateur conditionnel
Il s'agit ici comme dans le cas des oprateurs d'affectation d'une sorte de raccourci entre l'oprateur conditionnel if...else et l'affectation. Le but tant encore d'optimiser le MSIL engendr.
Syntaxe :
page
41
O expression renvoie une valeur boolenne (le test), les deux termes valeur sont des expressions gnrales (variable, expression numrique, boolnne etc...) renvoyant une valeur de type quelconque. Smantique : Exemple : int a,b,c ; c = a = = 0 ? b : a+1 ; Si l'expression est true l'oprateur renvoie la premire valeur, (dans l'exemple c vaut la valeur de b) Si l'expression est false l'oprateur renvoie la seconde valeur (dans l'exemple c vaut la valeur de a+1). Smantique de l'exemple avec un if..else : if (a = = 0) c = b; else c = a+1;
Instruction C#
Oprateur conditionnel :
c = a == 0 ? b : a+1 ;
une seule opration de stockage pour c : IL_0010: stloc.2
ldloc.0 brtrue.s IL_0018 ldloc.1 stloc.2 br.s IL_001c ldloc.0 ldc.i4.1 add stloc.2
Instruction conditionnelle :
if (a = = 0) c = b; else c = a+1;
deux oprations de stockage pour c : IL_0015: stloc.2 IL_001b: stloc.2
Le code MSIL engendr a la mme structure classique de code de test pour les deux instructions, la traduction de l'oprateur sera lgrement plus rapide que celle de l'instructions car, il n'y a pas besoin de stocker deux fois le rsultat du test dans la variable c (qui ici, est reprsente par l'instruction MSIL stloc.2)
page
42
Question : utiliser l'oprateur conditionnel pour calculer le plus grand de deux entiers. rponse : int a , b , c ; ... c = a>b ? a : b ;
Question : que fait ce morceau le programme ci-aprs ? int a , b , c ; .... c = a>b ? (b=a) : (a=b) ; rponse : a,b,c contiennent aprs excution le plus grand des deux entiers contenus au dpart dans a et b.
bloc switch :
Smantique : La partie expression d'une instruction switch doit tre une expression ou une variable du type byte, char, int, short, string ou bien enum. La partie expression d'un bloc switch doit tre une constante ou une valeur immdiate du type byte, char, int, short, string ou bien enum. switch <Epr1> s'appelle la partie slection de l'instruction : il y a valuation de <Epr1> puis selon la valeur obtenue le programme s'excute en squence partir du case contenant la valeur immdiate gale. Il s'agit donc d'un droutement du programme, ds que <Epr1> est value, vers l'instruction tiquete par le case <Epr1> associ.
page
43
Cette instruction en C#, contrairement Java, est structure , elle doit obligatoirement tre utilise avec l'instruction break afin de simuler le comportement de l'instruction structure case..of du pascal. Exemple de switch..case..break Pascal-Delphi var x : char ; .... case x of 'a' : InstrA; 'b' : InstrB; else InstrElse end; C# char x ; .... switch (x) { case 'a' : InstrA ; break; case 'b' : InstrB ; break; default : InstrElse; break; }
Dans ce cas le droulement de l'instruction switch aprs droutement vers le bon case, est interrompu par le break qui renvoie la suite de l'excution aprs la fin du bloc switch. Une telle utilisation correspond une utilisation de if...else imbriqus (donc une utilisation structure) mais devient plus lisible que les if ..else imbriqus, elle est donc fortement conseille dans ce cas. Exemples : C# - source
int x = 10; switch (x+1) { case 11 : System.Console.WriteLine (">> case 11"); break; case 12 : System.Console.WriteLine (">> case 12"); break; default : System.Console.WriteLine (">> default")"); break; } int x = 11; switch (x+1) { case 11 : System.Console.WriteLine (">> case 11"); break; case 12 : System.Console.WriteLine (">> case 12"); break; default : System.Console.WriteLine (">> default")"); break; }
Rsultats de l'excution
>> case 11
>> case 12
page
44
Il est toujours possible d'utiliser des instructions if else imbriques pour reprsenter un switch avec break :
C# - if...else
Bien que la syntaxe du switch break soit plus contraignante que celle du caseof de Delphi, le fait que cette instruction apporte commme le caseof une structuration du code, conduit une amlioration du code et augmente sa lisibilit. Lorsque cela est possible, il est donc conseill de l'utiliser d'une manire gnrale comme alternative des if...thenelse imbriqus.
page
45
page
46
Sa smantique peut aussi tre explique l'aide d'une autre instruction C#, le while( ) : do Instr while ( Expr ) <=> Instr ; while ( Expr ) Instr
Exemple de boucle do...while Pascal-Delphi repeat InstrA ; InstrB ; ... until not Expr do { InstrA ; InstrB ; ... } while ( Expr ) C#
Smantique : Une boucle for contient 3 expressions for (Expr1 ; Expr2 ; Expr3 ) Instr, d'une manire gnrale chacune de ces expressions joue un rle diffrent dans l'instruction for. Une instruction for en C# (comme en C) est plus puissante et plus riche qu'une boucle for dans d'autres langages algorithmiques. Nous donnons ci-aprs une smantique minimale : Expr1 sert initialiser une ou plusieurs variables (dont ventuellement la variable de contrle de la boucle) sous forme d'une liste d'instructions d'initialisation spares par des virgules. Expr2 sert donner la condition de rebouclage sous la fome d'une expression renvoyant une valeur boolenne (le test de l'itration). Expr3 sert ractualiser les variables (dont ventuellement la variable de contrle de la boucle)sous forme d'une liste d'instructions spares par des virgules.
page
47
L'instruction "for (Expr1 ; Expr2 ; Expr3 ) Instr" fonctionne au minimum comme l'instruction algorithmique pour... fpour, elle est toutefois plus puissante que cette dernire. Sa smantique peut aussi tre approximativement(*) explique l'aide d'une autre instruction C# while : Expr1 ; while ( Expr2 ) { Instr ; Expr3 }
(*)Nous verrons au paragraphe consacr l'instruction continue que si l'instruction for contient un continue cette dfinition smantique n'est pas valide. Exemples montrant la puissance du for Pascal-Delphi for i:=1 to 10 do begin InstrA ; InstrB ; ... end i := 10; k := i; while (i>-450) do begin InstrA ; InstrB ; ... k := k+i; i := i-15; end i := n; while i<>1 do if i mod 2 = 0 then i := i div 2 else i := i+1 C# for ( i = 1; i<=10; i++ ) { InstrA ; InstrB ; ... }
Le premier exemple montre une boucle for classique avec la variable de contrle "i" (indice de boucle), sa borne initiale "i=1" et sa borne finale "10", le pas d'incrmentation squentiel tant de 1. Le second exemple montre une boucle toujours contrle par une variable "i", mais dont le pas de dcrmentation squentiel est de -15. Le troisme exemple montre une boucle aussi contrle par une variable "i", mais dont la variation n'est pas squentielle puisque la valeur de i est modifie selon sa parit ( i % 2 == 0 ? i /=2 : i++).
page
48
Voici un exemple de boucle for dite boucle infinie : for ( ; ; ); est quivalente while (true);
Voici une boucle ne possdant pas de variable de contrle(f(x) est une fonction dj dclare) : for (int n=0 ; Math.abs(x-y) < eps ; x = f(x) );
Terminons par une boucle for possdant deux variables de contrle : //inverse d'une suite de caractre dans un tableau par permutation des deux extrmes char [ ] Tablecar ={'a','b','c','d','e','f'} ; for ( i = 0 , j = 5 ; i<j ; i++ , j-- ) { char car ; car = Tablecar[i]; Tablecar[i ]= Tablecar[j]; Tablecar[j] = car; } dans cette dernire boucle ce sont les variations de i et de j qui contrlent la boucle.
Remarques rcapitulatives sur la boucle for en C# : rien n'oblige incrmenter ou dcrmenter la variable de contrle, rien n'oblige avoir une instruction excuter (corps de boucle), rien n'oblige avoir une variable de contrle, rien n'oblige n'avoir qu'une seule variable de contrle.
page
49
Smantique : Une instruction break ne peut se situer qu' l'intrieur du corps d'instruction d'un bloc switch ou de l'une des trois itrations while, do..while, for.
Lorsque break est prsente dans l'une des trois itrations while, do..while, for : break interrompt l'excution de la boucle dans laquelle elle se trouve, l'excution se poursuit aprs le corps d'instruction. Exemple d'utilisation du break dans un for : (recherche squentielle dans un tableau) int [ ] table = {12,-5,7,8,-6,6,4,78}; int elt = 4; for ( i = 0 ; i<8 ; i++ ) if (elt= =table[i]) break ; if (i = = 8)System.out.println("valeur : "+elt+" pas trouve."); else System.out.println("valeur : "+elt+" trouve au rang :"+i);
Explications Si la valeur de la variable elt est prsente dans le tableau table, lexpression (elt= =table[i]) est true et break est excute (arrt de la boucle et excution de if (i = = 8)... ).
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
50
Aprs l'excution de la boucle for, lorsque l'instruction if (i = = 8)... est excute, soit la boucle s'est excute compltement (recherche infructueuse), soit le break l'a arrte prmaturment (elt est trouv dans le tableau).
Smantique : Une instruction continue ne peut se situer qu' l'intrieur du corps d'instruction de l'une des trois itrations while, do..while, for. Lorsque continue est prsente dans l'une des trois itrations while, do..while, for : Si continue n'est pas suivi d'une tiquette elle interrompt l'excution de la squence des instructions situes aprs elle, l'excution se poursuit par rebouclage de la boucle. Elle agit comme si l'on venait d'excuter la dernire instruction du corps de la boucle. Si continue est suivi d'une tiquette elle fonctionne comme un goto (utilisation dconseille en programmation moderne, c'est pourquoi nous n'en dirons pas plus !)
Exemple d'utilisation du continue dans un for : int [ ] ta = {12,-5,7,8,-6,6,4,78}, tb = new int[8]; for ( i = 0, n = 0 ; i<8 ; i++ , k = 2*n ) { if ( ta[i] = = 0 ) continue ; tb[n] = ta[i]; n++; }
Explications Rappelons qu'un for s'crit gnralement : for (Expr1 ; Expr2 ; Expr3 ) Instr L'instruction continue prsente dans une telle boucle for s'effectue ainsi : excution immdiate de Expr3 ensuite, excution de Expr2
page
51
Si l'expression ( ta[i] = = 0 ) est true, la suite du corps des instructions de la boucle (tb[n] = ta[i]; n++;) n'est pas excute et il y a rebouclage du for . Le droulement est alors le suivant : i++ , k = 2*n en premier , puis la condition de rebouclage : i<8
et la boucle se poursuit en fonction de la valeur de la condition de rebouclage. Cette boucle recopie dans le tableau d'entiers tb les valeurs non nulles du tableau d'entiers ta.
Attention
Nous avons dj signal plus haut que l'quivalence suivante entre un for et un while Expr1 ; while ( Expr2 ) { Instr ; Expr3 }
valide dans le cas gnral, tait mise en dfaut si le corps d'instruction contenait un continue.
Voyons ce qu'il en est en reprenant l'exemple prcdent. Essayons d'crire la boucle while qui lui serait quivalente selon la dfinition gnrale. Voici ce que l'on obtiendrait : i = 0; n = 0 ; while ( i<8 ) { if ( ta[i] = = 0 ) continue ; tb[n] = ta[i]; n++; i++ ; k = 2*n; }
page
52
Dans le while le continue rexcute la condition de rebouclage i<8 sans excuter l'expression i++ ; k = 2*n; (nous avons d'ailleurs ici une boucle infinie).
Une boucle while strictement quivalente au for prcdent pourrait tre la suivante : i = 0; n = 0 ; while ( i<8 ) { if ( ta[i] = = 0 ) { i++ ; k = 2*n; continue ; } tb[n] = ta[i]; n++; i++ ; k = 2*n; }
page
53
Une classe suffit Les mthodes sont des fonctions Transmission des paramtres en C# (plus riche qu'en java) Visibilit des variables
Avant d'utiliser les possibilits offertes par les classes et les objets en C#, apprenons utiliser et excuter des applications simples C# ne ncessitant pas la construction de nouveaux objets, ni de navigateur pour s'excuter. Lorsqu'il existe des diffrences avec le langage Java nous les mentionnerons explicitement. Comme C# est un langage entirement orient objet, un programme C# est compos de plusieurs classes, nous nous limiterons une seule classe.
page
54
Exemple2 de squelette d'une classe minimale excutable : class Exemple2 { static void Main(string[ ] args) { // c'est ici que vous crivez votre programme principal } }
Exemple3 trivial d'une classe minimale excutable : class Exemple3 { static void Main(string[ ] args) { System.Console.WriteLine("Bonjour !"); } }
page
55
Contrairement java il n'est pas ncessaire en C#, de sauvegarder la classe dans un fichier qui porte le mme nom, tout nom de fichier est accept condition que le suffixe soit cs, ici "AppliExo1.cs". Lorsque l'on demande la compilation (production du bytecode) de ce fichier source "AppliExo1.cs" le fichier cible produit en bytecode MSIL se dnomme "AppliExo1.exe", il est alors prt tre excut par la machine virtuelle du CLR.
Le rsultat de l'excution de ce programme est le suivant : tableau avant : abcdef tableau aprs : fedcba
Exemple2 class Application2 { static void Main(string[ ] args) { // recherche squentielle dans un tableau int [ ] table= {12,-5,7,8,-6,6,4,78}; int elt = 4, i ; for ( i = 0 ; i<8 ; i++ ) if (elt= =table[i]) break ; if (i = = 8) System.out.println("valeur : "+elt+" pas trouve."); else System.out.println("valeur : "+elt+" trouve au rang :"+i); } }
Aprs avoir sauvegard la classe dans un fichier xxx.cs, ici dans notre exemple "AppliExo2.cs", la compilation de ce fichier "AppliExo2.cs" produit le fichier "AppliExo2.exe" prt tre excut par la machine virtuelle du CLR.
Conseil de travail : Reprenez tous les exemples simples du chapitre sur les instructions de boucle et le switch en les intgrant dans une seule classe (comme nous venons de le faire avec les deux exemples prcdents) et excutez votre programme.
page
56
static.
Donc par la suite dans ce document lorsque nous emploierons le mot mthode sans autre adjectif, il s'agira d'une mthode de classe, comme nos applications ne possdent qu'une seule classe, nous pouvons assimiler ces mthodes aux fonctions de l'application et ainsi retrouver une utilisation classique de C# en mode appplication. Attention, il est impossible en C# de dclarer une mthode l'intrieur d'une autre mthode comme en pascal; toutes les mthodes sont au mme niveau de dclaration : ce sont les mthodes de la classe !
Syntaxe :
corps de fonction :
page
57
Nous dnommons en-tte de fonction la partie suivante : <qualificateurs><type du rsultat><nom de fonction> (<liste paramtres formels>) Smantique : Les qualificateurs sont des mots clefs permettant de modifier la visibilit ou le fonctionnement d'une mthode, nous n'en utiliserons pour l'instant qu'un seul : le mot clef static permettant de dsigner la mthode qu'il qualifie comme une mthode de classe dans la classe o elle est dclare. Une mthode n'est pas ncessairement qualifie donc ce mot clef peut tre omis. Une mthode peut renvoyer un rsultat d'un type C# quelconque en particulier d'un des types lmentaires (int, byte, short, long, bool, double, float, char...) et nous verrons plus loin qu'elle peut renvoyer un rsultat de type objet comme en Delphi. Ce mot clef ne doit pas tre omis. Il existe en C# comme en C une criture fonctionnelle correspondant aux procdures des langages procduraux : on utilise une fonction qui ne renvoie aucun rsultat. L'approche est inverse celle du pascal o la procdure est le bloc fonctionnel de base et la fonction n'en est qu'un cas particulier. En C# la fonction (ou mthode) est le seul bloc fonctionnel de base et la procdure n'est qu'un cas particulier de fonction dont le retour est de type void. La liste des paramtres formels est semblable la partie dclaration de variables en C# (sans initialisation automatique). La liste peut tre vide. Le corps de fonction est identique au bloc instruction C# dj dfini auparavant. Le corps de fonction peut tre vide (la mthode ne reprsente alors aucun intrt).
Exemples d'en-tte de mthodes sans paramtres en C# int calculer( ){.....} bool tester( ){.....} void uncalcul( ){.....} renvoie un entier de type int renvoie un entier de type bool procdure ne renvoyant rien
Exemples d'en-tte de mthodes avec paramtres en C# int calculer(byte a, byte b, int x ) {.....} bool tester( int k) {.....} void uncalcul(int x, int y, int z ) {.....} fonction 3 paramtres fonction 1 paramtre procdure 3 paramtres
58
Appel de la mthode afficher Afficher (i , elt ); Les deux paramtres effectifs "i" et "elt" sont du mme type que le paramtre formel associ. - Le paramtre effectif "i" est associ au paramtre formel rang. - Le paramtre effectif "elt" est associ au paramtre formel val.
page
59
La dmarche en informatique est semblable celle qui, en mathmatiques, consiste crire la fonction f(x) = 3*x - 7, dans laquelle x est une variable muette indiquant comment f est calcule : en informatique elle joue le rle du paramtre formel. Lorsque l'on veut obtenir une valeur effective de la fonction mathmatique f, par exemple pour x=2, on crit f(2) et l'on calcule f(2)=3*2 - 7 = -1. En informatique on "passera" un paramtre effectif dont la valeur vaut 2 la fonction. D'une manire gnrale, en informatique, il y a un sous-programme appelant et un sous-programme appel par le sous-programme appelant.
page
60
Appel de la mthode afficher afficher(i,elt); Les deux paramtres effectifs "i" et "elt" sont d'un type compatible avec celui du paramtre formel associ. - Le paramtre effectif "i" est associ au paramtre formel rang.(short = entier sign sur 16 bits et int = entier sign sur 32 bits) - Le paramtre effectif "elt" est associ au paramtre formel val.(sbyte = entier sign sur 8 bits et long = entier sign sur 64 bits)
page
61
La question technique qui se pose en C# comme dans tout langage de programmation est de connatre le fonctionnement du passage des paramtres :
En C#, ces trois modes de transmission (ou de passage) des paramtres (trs semblables Delphi) sont implants.
En C# tous les paramtres sont passs par dfaut par valeur (lorsque le paramtre est un objet, c'est en fait la rfrence de l'objet qui est passe par valeur). Pour ce qui est de la vision algorithmique de C#, le passage par valeur permet une variable d'tre passe comme paramtre d'entre.
static int methode1(int a , char b) { //....... return a+b; }
page
62
Cette mthode possde 2 paramtres a et b en entre passs par valeur et renvoie un rsultat de type int.
En C# pour indiquer un passage par rfrence on prcde la dclaration du paramtre formel du mot clef ref :
static int methode1(int a , ref char b) { //....... return a+b; }
Lors de l'appel d'un paramtre pass par rfrence, le mot clef ref doit obligatoirement prcder le paramtre effectif qui doit obligatoirement avoir t initialis auparavant : int x = 10, y = '$', z = 30; z = methode1(x, ref y) ;
En C# pour indiquer un passage par rsultat on prcde la dclaration du paramtre formel du mot
Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
63
out :
static int methode1(int a , out char b) { //....... return a+b; }
Lors de l'appel d'un paramtre pass par rsultat, le mot clef out doit obligatoirement prcder le paramtre effectif qui n'a pas besoin d'avoir t initialis : int x = 10, y , z = 30; z = methode1(x, out y) ;
Remarque : Le choix de passage selon les types limine les inconvnients ds l'encombrement mmoire et la lenteur de recopie de la valeur du paramtre par exemple dans un passage par valeur, car nous verrons plus loin que les tableaux en C# sont des objets et que leur structure est passe par rfrence.
Syntaxe :
L'expression lorsqu'elle est prsente est quelconque mais doit tre obligatoirement du mme type que le type du rsultat dclar dans l'en-tte de fonction (ou d'un type compatible). Lorsque le return est rencontr il y a arrt de l'excution du code de la mthode et retour du rsultat dans le bloc appelant.
page
64
Visibilit de bloc
C# est un langage structure de blocs ( comme pascal et C ) dont le principe gnral de visibilit est : Toute variable dclare dans un bloc est visible dans ce bloc et dans tous les blocs imbriqus dans ce bloc.
En C# les blocs sont constitus par : les classes, les mthodes, les instructions composes, les corps de boucles, les try...catch
Le masquage des variables n'existe que pour les variables dclares dans des mthodes : Il est interdit de redfinir une variable dj dclare dans une mthode soit : comme paramtre de la mthode, comme variable locale la mthode, dans un bloc inclus dans la mthode. Il est possible de redfinir une variable dj dclare dans une classe.
page
65
Contrairement ce que nous avions signal plus haut nous n'avons pas prsent un exemple fonctionnant sur des mthodes de classes (qui doivent obligatoirement tre prcdes du mot clef static), mais sur des mthodes d'instances dont nous verrons le sens plus loin en POO.
Remarquons avant de prsenter le mme exemple cette fois-ci sur des mthodes de classes, que quelque soit le genre de mthode la visibilit des variables est identique.
Les variables dfinies dans une mthode (de classe ou d'instance) suivent les rgles classiques de la visibilit du bloc dans lequel elles sont dfinies : Elles sont visibles dans toute la mthode et dans tous les blocs imbriqus dans cette mthode et seulement ce niveau (les autres mthodes de la classe ne les voient pas), c'est pourquoi on emploie aussi le terme de variables locales la mthode.
page
66
Reprenons l'exemple prcdent en adjoignant des variables locales aux deux mthodes f et g. Exemple de variables locales
class ExempleVisible3 { static int a = 10; static int g (int x ) { char car = 't'; long a = 123456; .... return 3*x-a; } static int f (int x, int a ) { char car ='u'; .... return 3*x-a; } } La variable de classe "a" dfinie dans static int a = 10; est masque dans les deux mthodes f et g. Dans la mthode g, c'est la variable locale longa = 123456 qui masque la variable de classe static int a. char car ='t'; est une variable locale la mthode g. - Dans la mthode f, char car ='u'; est une variable locale la mthode f, le paramtre inta masque la variable de classe static int a. Les variables locales char car n'existent que dans la mthode o elles sont dfinies, les variables "car" de f et celle de g n'ont aucun rapport entre elles, bien que portant le mme nom.
67
} return 3*x-a+b; } }
10; est masque dans la mthode f dans le bloc imbriqu for. La variable de classe "b" dfinie dans static int b = 2; est masque dans la mthode f dans le bloc imbriqu if. Dans l'instruction { int b = 8; b = 5-a+i*b; } , c'est la variable b interne ce bloc qui est utilise car elle masque la variable b de la classe. Dans l'instruction else b = 5-a+i*b; , c'est la variable b de la classe qui est utilise (car la variable int b = 8 n'est plus visible ici) .
class ExempleVisible5 { static int a = 10, b = 2; static int f (int x ) { char car = 't'; for (int i = 0; i < 5 ; i++) {int a=7; if (a < 7) {int b = 8, a = b = 5-a+i*b; } else b = 5-a+i*b; }
9;
Toutes les remarques prcdentes restent valides puisque l'exemple ci-contre est quasiment identique au prcdent. Nous avons seulement rajout dans le bloc if la dfinition d'une nouvelle variable interne a ce bloc. C# produit une erreur de compilation int b = 8, a = 9; sur la variable a, en indiquant que c'est une redfinition de
page
68
return 3*x-a+b; } }
variable l'intrieur de la mthode f, car nous avions dj dfini une variable a ({ int a=7;...) dans le bloc englobant for {...}.
Remarquons que le principe de visiblit des variables adopt en C# est identique au principe inclus dans tous les langages structures de bloc y compris pour le masquage, s'y rajoute en C# comme en Java, uniquement l'interdiction de la redfinition l'intrieur d'une mme mthode (semblable en fait, l'interdiction de redclaration sous le mme nom, de variables locales un bloc).
page
69
Spcificits du compilateur C#
Le compilateur C# n'accepte pas que l'on utilise une variable de classe qui a t redfinie dans un bloc interne une mthode, sans la qualifier par la notation uniforme aussi appele opration de rsolution de porte. Les 4 exemples ci-dessous situent le discours : class ClA1 {
Variables de classe Variables de classe
static int a = 10, b = 2; static int f (int x ) { char car = 't'; for (int i = 0; i < 5 ; i++) {int a=7; 'a' redfinie if (a < 7) { b = 5-a+i*b; Refus 'a' : conflit } else b = 10-a+i*b; } return 3*x-a+b; erreur return 3*x- ClA1.a+b; } }
Variables de classe accept
class ClA2 { static int a = 10, b = 2; ' b' redfinie static int f (int x ) { char car = 't'; int b = 8; for (int i = 0; i < 5 ; i++) {int a=7; ' a' redfinie if (a < 7) { b = 5-a+i*b; Refus 'a' : conflit } else b = 10-a+i*b; } return 3*x-a+b; erreur return 3*x- ClA2.a+b; } accept }
Variables de classe
class ClA3 { static int a = 10, b = 2; static int f (int x ) { char car = 't'; for (int i = 0; i < 5 ; i++) ' a' redfinie {int a=7; ' b' redfinie if (a < 7) { int b = 8; b = 5-a+i*b; Refus 'b' : conflit } else b = 10-a+i*b; else ClA3.b = 10-a+i* ClA3.b; } accept
Refus 'a' et 'b' : conflit
class ClA4 { static int a = 10, b = 2; static int f (int x ) { char car = 't'; for (int i = 0; i < 5 ; i++) {int a=7; int b = 8; if (a < 7) ' a' et ' b' redfinies { b = 5-a+i*b; } else b = 10-a+i*b; } return 3*x-a+b; Refus 'a' et 'b' : conflit return 3*x- ClA4.a+ ClA4.b; } }
accept
page
70
Observez les utilisations et les redfinitions correctes des variables "static int a = 10, int b = 2;" dclares comme variables de classe et redfinies dans diffrents blocs de la classe (lorsqu'il y a un conflit signal par le compilateur sur une instruction nous avons mis tout de suite aprs le code correct accept par le compilateur) L o le compilateur C# dtecte un conflit potentiel, il suffit alors de qualifier la variable grce l'oprateur de rsolution de porte, comme par exemple ClasseA.b pour indiquer au compilateur la variable que nous voulons utiliser.
Comparaison C# , java : la mme classe gauche passe en Java mais fournit 6 conflits en C#
En Java
class ClA5 { static int a = 10, b = 2; static int f (int x ) { char car = 't'; for (int i = 0; i < 5 ; i++) { if (a < 7) { int b = 8 , a = 7; b = 5-a+i*b; } else b = 10-a+i* b; } return 3*x- a+ b; } }
Conflits levs en C#
class ClA5 { static int a = 10, b = 2; static int f (int x ) Evite le conflit { char car = 't'; for (int i = 0; i < 5 ; i++) { if (ClA5.a < 7) { int b = 8 , a = 7; b = 5-a+i*b; } else ClA5.b = 10-ClA5.a+i*ClA5.b; } return 3*x- ClA5.a+ ClA5.b; } }
page
71
La classe string
Le type de donnes String (chane de caractre) est une classe de type rfrence dans l'espace de noms System de .NET Framework. Donc une chane de type string est un objet qui n'est utilisable qu' travers les mthodes de la classe string. Le type string est un alias du type System.String dans .NET
Un littral de chane est une suite de caractres entre guillemets : " abcdef " est un exemple de littral de String. Toutefois un objet string de C# est immuable (son contenu ne change pas) Etant donn que cette classe est trs utilise les variables de type string bnficient d'un statut d'utilisation aussi souple que celui des autres types lmentaires par valeurs. On peut les considrer comme des listes de caractres Unicode numrots de 0 n-1 (si n figure le nombre de caractres de la chane). Il est possible d'accder chaque caractre de la chane en la considrant comme un tableau de caractres en lecture seule.
page
72
On accde un caractre de rang fix d'une chane par l'oprateur [ ] : (la chane est lue comme un tableau de char) Il est possible d'accder en lecture seulement chaque caractres d'une chane, mais qu'il est impossible de modifier un caractre directement dans une chane.
Remarque En fait l'oprateur [ ] est un indexeur de la classe string (cf. chapitre indexeurs en C#), et il est en lecture seule : public char this [ int index ] { get ; } Ce qui signifie au stade actuel de comprhension de C#, qu'il est possible d'accder en lecture seulement chaque caractres d'une chane, mais qu'il est impossible de modifier un caractre grce l'indexeur. char car = ch1[7] ; // l'indexeur renvoie le caractre 'h' dans la variable car. ch1[5] = car ; // Erreur de compilation : l'criture dans l'indexeur est interdite !! ch1[5] = 'x' ; // Erreur de compilation : l'criture dans l'indexeur est interdite !!
Position d'une sous-chane l'intrieur d'une chane donne : Mthode surcharge 6 fois : int indexOf ( ) Ci-contre une utilisation de la surcharge : int IndexOf ( string ssch) qui renvoie l'indice de la premire occurrence du string ssch contenue dans la chane scanne.
Recherche de la position de ssch dans ch1 : String ch1 = " abcdef " , ssch="cde";
page
73
Concatnation de deux chanes Un oprateur ou une mthode Oprateur : + sur les chanes ou Mthode static surcharge 8 fois : String Concat() Les deux critures ci-dessous sont donc quivalentes en C# : str3 = str1+str2 str3 = str.Concat(str2)
Soit :
Appel de la mthode Insert de la chane ch1 afin de construire une nouvelle chane ch2 qui est une copie de ch1 dans laquelle on a insr une sous-chane, ch1 n'a pas chang (immuabilit).
ch2 est une copie de ch1 dans laquelle on a insrer la sous-chane "..x..". Attention : les mthodes d'insertion, suppression, etcne modifient pas la chane objet qui invoque la mthode mais renvoient un autre objet de chane diffrent, obtenu aprs action de la mthode sur l'objet initial.
page
74
tCarac = "abcdefghijk".ToCharArray( );
page
75
System.Console.WriteLine("s1="+s1); System.Console.WriteLine("s2="+s2); System.Console.WriteLine("s3="+s3); System.Console.WriteLine("ch="+ch); if( s2 == ch )System.Console.WriteLine("s2=ch"); else System.Console.WriteLine("s2<>ch"); if( s2 == s3 )System.Console.WriteLine("s2=s3"); else System.Console.WriteLine("s2<>s3"); if( s3 == ch )System.Console.WriteLine("s3=ch"); else System.Console.WriteLine("s3<>ch"); if( s3.Equals(ch) )System.Console.WriteLine("s3 gal ch"); else System.Console.WriteLine("s3 diffrent de ch");
page
76
Aprs excution on obtient : s1=abcdef s2=abcdef s3=abcdef ch=abcdef s2=ch s2=s3 s3=ch s3 gal ch
Programme Java
String ch; ch = "abcdef" ; String s2,s1="abc" ; s2 = s1+"def"; //-- tests d'galit avec l'oprateur = = if( s2 == "abcdef" ) System.out.println ("s2==abcdef"); else System.out.println ("s2<>abcdef"); if( s2 == ch ) System.out.println ("s2==ch"); else System.out.println ("s2<>ch");
Programme C#
string ch; ch = "abcdef" ; string s2,s1="abc" ; s2 = s1+"def"; //-- tests d'galit avec l'oprateur = = if( s2 == "abcdef" ) System.Console.WriteLine ("s2==abcdef"); else System.Console.WriteLine ("s2<>abcdef"); if( s2 == ch ) System.Console.WriteLine ("s2==ch"); else System.Console.WriteLine ("s2<>ch");
page
77
1) On ne peut pas considrer un char comme un cas particulier de string, le transtypage suivant est refus comme en Java : char car = 'r'; string s; s = (string)car; Il faut utiliser l'une des surcharges de la mthode de conversion ToString de la classe Convert : System.Object |__System.Convert mthode de classe static :
Remarque : La classe Convert contient un grand nombre de mthodes de conversion de types. Microsoft indique que cette classe : "constitue une faon, indpendante du langage, d'effectuer les conversions et est disponible pour tous les langages qui ciblent le Common Language Runtime. Alors que divers langages peuvent recourir diffrentes techniques pour la conversion des types de donnes, la classe Convert assure que toutes les conversions communes sont disponibles dans un format gnrique."
2) On peut concatner avec l'oprateur +, des char une chane string dj existante et affecter le rsultat une String : string s1 , s2 ="abc" ; char c = 'e' ; s1 = s2 + 'd' ; s1 = s2 + c ;
page
78
Toutes les critures prcdentes sont licites et acceptes par le compilateur C#, il n'en est pas de mme pour les critures ci-aprs :
Les critures suivantes seront refuses : String s1 , s2 ="abc" ; char c = 'e' ; s1 = 'd' + c ; // types incompatibles
Le compilateur enverra le message d'erreur suivant pour l'instruction l'instruction s1 = 'd' + 'e'; :
s1 = 'd' + c ; et pour
Car il faut qu'au moins un des deux oprandes de l'oprateur + soit du type string : v Le littral 'e' est de type char, v Le littral "e" est de type string (chane ne contenant qu'un seul caractre)
Pour plus d'information sur toutes les mthodes de la classe string consulter la documentation de .Net framework.
page
79
Tableaux et matrices
Tableaux une dimension. Tableaux plusieurs dimensions (matrices,) comme en Delphi. Tableaux dchiquets comme en Java.
Chaque dimension d'un tableau en C# est dfinie par une valeur ou longueur, qui est un nombre ou une variable N entier (char, int, long,) dont la valeur est suprieur ou gal zro. Lorsqu'une dimension a une longueur N, l'indice associ varie dans l'intervalle [ 0 , N 1 ].
Tableau uni-dimensionnel
Ci-dessous un tableau 'tab' une dimension, de n+1 cellules numrotes de 0 n :
Les tableaux C# contiennent comme en Delphi, des tableaux de types quelconques de C# (type rfrence ou type valeur). Il n'y a pas de mot clef spcifique pour la classe tableaux, mais l'oprateur symbolique [ ] indique qu'une variable de type fix est un tableau. La taille d'un tableau doit obligatoirement avoir t dfinie avant que C# accepte que vous l'utilisiez !
page
80
Remarque : Les tableaux de C# sont des objets d'une classe dnomme Array qui est la classe de base d'implmentation des tableaux dans le CLR de .NET framework (localisation : System.Array) Cette classe n'est pas drivable pour l'utilisateur : "public abstract class Array : ICloneable, IList, ICollection, IEnumerable". C'est en fait le compilateur qui est autoris implmenter une classe physique de tableau. Il faut utiliser les tableaux selon la dmarche ci-dessous en sachant que l'on dispose en plus des proprits et des mthodes de la classe Array si ncessaire (longueur, tri, etc...)
Le mot clef new correspond la cration d'un nouvel objet (un nouveau tableau) dont la taille est fixe par la valeur indique entre les crochets. Ici 4 tableaux sont cres et prts tre utiliss : table1 contiendra 5 entiers 32 bits, table2 contiendra 12 caractres, table3 contiendra 8 rels en simple prcision et tableStr contiendra 9 chanes de type string. On peut aussi dclarer un tableau sous la forme de deux instructions : une instruction de dclaration et une instruction de dfinition de taille avec le mot clef new, la seconde pouvant tre mise n'importe o dans le corps d'instruction, mais elle doit tre utilise avant toute manipulation du tableau. Cette dernire instruction de dfinition peut tre rpte plusieurs fois dans le programme, il s'agira alors chaque fois de la cration d'un nouvel objet (donc un nouveau tableau), l'ancien tant dtruit et dsallou automatiquement par le ramasse-miettes (garbage collector) de C#.
page
81
int [ ] table1; char [ ] table2; float [ ] table3; string [ ] tableStr; .... table1 = new int [5]; table2 = new char [12]; table3 = new float [8]; tableStr = new string [9];
Il existe en C# un attribut de la classe abstraite mre Array, qui contient la taille d'un tableau uni-dimensionnel, quelque soit son type, c'est la proprit Length en lecture seule. Exemple :
int [ ] table1 = {17,-9,4,3,57}; int taille; taille = table1.Length; // taille = 5
Attention
Il est possible de dclarer une rfrence de tableau, puis de l'initialiser aprs uniquement ainsi :
int [ ] table1 ; // cre une rfrence table1 de type tableau de type int table1 = new int {17,-9,4,3,57}; // instancie un tableau de taille 5 lments refrenc par table1 table1 = new int {14,-7,9}; // instancie un autre tableau de taille 3 lments refrenc par table1
page
82
Utiliser un tableau
Un tableau en C# comme dans les autres langages algorithmiques s'utilise travers une cellule de ce tableau repre par un indice obligatoirement de type entier ou un char considr comme un entier (byte, short, int, long ou char). Le premier lment d'un tableau est numrot 0, le dernier Length-1. On peut ranger des valeurs ou des expressions du type gnral du tableau dans une cellule du tableau. Exemple avec un tableau de type int :
int [ ] table1 = new int [5]; // dans une instruction d'affectation: table1[0] = -458; table1[4] = 5891; table1[5] = 72; <--- est une erreur de dpassement de la taille ! (valeur entre 0 et 4) // dans une instruction de boucle: for (int i = 0 ; i<= table1.Length-1; i++) table1[i] = 3*i-1; // aprs la boucle: table1 = {-1,2,5,8,11}
Remarque : Dans une classe excutable la mthode Main reoit en paramtre un tableau de string nomm args qui correspond en fait aux ventuels paramtres de l'application elle-mme: static void Main(string [ ] args)
page
83
page
84
L' attribut Length en lecture seule, de la classe abstraite mre Array, contient en fait la taille d'un tableau en nombre total de cellules qui constituent le tableau (nous avons vu dans le cas uni-dimensionnel que cette valeur correspond la taille de la dimension du tableau). Dans le cas d'un tableau multi-dimensionnel Length correspond au produit des tailles de chaque dimension d'indice : int [ , ] table1 = new int [5, 2]; char [ , , ] table2 = new char [9,4,5]; float [ , , , ] table3 = new float [2,8,3,4]; table1.Length = 5 x 2 = 10 table2.Length = 9 x 4 x 5 = 180 table3.Length = 2 x 8 x 3 x 4= 192
Ce schma montre bien qu'un tel tableau T est constitu de tableaux unidimensionnels, les tableaux composs de cases blanches contiennent des pointeurs (rfrences). Chaque case blanche est une rfrence vers un autre tableau unidimensionnel, seules les cases grises contiennent les informations utiles de la structure de donnes : les lments de mme type du tableau T.
page
85
Pour fixer les ides figurons la syntaxe des dclarations en C# d'un tableau d'lments de type int nomm myArray bi-dimensionnel en escalier :
int n = 10; int [ ][ ] myArray = new int [n][ ]; myArray[0] = new int[7]; myArray[1] = new int[9]; myArray[2] = new int[3]; myArray[3] = new int[4]; myArray[n-2] = new int[2]; myArray[n-1] = new int[8];
Ce tableau comporte 7+9+3+4+.+2+8 cellules utiles au stockage de donnes de type int, on peut le considrer comme une succession de tableaux d'int unidimensionnels (le premier ayant 7 cellules, le second ayant 9 cellules, etc) . Les dclarations suivantes :
int n = 10; int [ ][ ] myArray = new int [n][ ];
dfinissent un sous-tableau de 10 pointeurs qui vont chacun pointer vers un tableau unidimensionnel qu'il faut instancier :
instancie un tableau d'int unidimensionnel 7 cases et renvoie sa rfrence qui est range dans la cellule de rang 0 du sous-tableau.
instancie un tableau d'int unidimensionnel 9 cases et renvoie sa rfrence qui est range dans la cellule de rang 1 du sous-tableau.
Etc.
Attention
Dans le cas d'un tableau dchiquet, le champ Length de la classe Array, contient la taille du sous-tableau unidimensionnel associ la rfrence.
page
86
Soit la dclaration : int [ ][ ] myArray = new int [n][ ]; myArray est une rfrence vers un sous-tableau de pointeurs. myArray.Length vaut 10 (taille du sous-tableau point)
Soit la dclaration : myArray[0] = new int [7]; MyArray [0] est une rfrence vers un sous-tableau de cellules d'lments de type int. myArray[0].Length vaut 7 (taille du sous-tableau point) Soit la dclaration : myArray[1] = new int [9]; MyArray [1] est une rfrence vers un sous-tableau de cellules d'lments de type int. myArray[1].Length vaut 9 (taille du sous-tableau point)
C# initialise les tableaux par dfaut 0 pour les int, byte, ... et null pour les objets. On peut simuler une matrice avec un tableau dchiquet dont tous les sous-tableaux ont exactement la mme dimension. Voici une figuration d'une matrice n+1 lignes et p+1 colonnes avec un tableau en escalier :
- Contrairement Java qui l'accepte, le code cidessous ne sera pas compil par C# : int [ ][ ] table = new int [n+1][p+1]; - Il est ncessaire de crer manuellement tous les sous-tableaux : int [ ][ ] table = new int [n+1][ ]; for (int i=0; i<n+1; i++) table[i] = new int [p+1];
Conseil
L'exemple prcdent montre l'vidence que si l'on souhaite rellement utiliser des matrices en C#, il est plus simple d'utiliser la notion de tableau multi-dimensionnel [ , ] que celle de tableau en escalier [ ] [ ].
page
87
if(t1==t2)System.Console.WriteLine("t1=t2"); else System.Console.WriteLine("t1<>t2"); if(t1.Equals(t2))System.Console.WriteLine("t1 gal t2"); else System.Console.WriteLine("t1 diffrent de t2"); Aprs excution on obtient : t1<>t2 t1 diffrent de t2 Ces deux objets (les tableaux) sont diffrents (leurs rfrences pointent vers des blocs diffrents)bien que le contenu de chaque objet soit le mme.
page
88
Si l'on souhaite que t1 soit une copie identique de t2, tout en conservant le tableau t2 et sa rfrence distincte il faut utiliser l'une des deux mthodes suivante de la classe abstraite mre Array : public virtual object Clone( ) : mthode qui renvoie une rfrence sur une nouvelle instance de tableau contenant les mmes lments que l'objet de tableau qui l'invoque. (il ne reste plus qu' transtyper la rfrence retourne puisque clone renvoie un type object)
public static void Copy ( Array t1 , Array t2, int long) : mthode de classe qui copie dans un tableau t2 dj existant et dj instanci, long lments du tableau t1 depuis son premier lment (si l'on veut une copie complte du tableau t1 dans t2, il suffit que long reprsente le nombre total d'lments soit long = t1.Length).
Attention
Dans le cas o le tableau t1 contient des rfrences qui pointent vers des objets :
la recopie dans un autre tableau travers les mthode Clone ou Copy ne recopie que les rfrences, mais pas les objets points, voici un "clone" du tableau t1 de la figure prcdente dans le tableau t2 :
page
89
Si l'on veut que le clonage (la recopie) soit plus complte et comprenne aussi les objets points, il suffit de construire une telle mthode car malheureusement la classe abstraite Array n'est pas implantable par l'utilisateur mais seulement par le compilateur et nous ne pouvons pas redfinir la mthode virtuelle Clone).
Code source d'utilisation de ces deux mthodes sur un tableau unidimensionnel et sur une matrice : //-- tableau une dimension :
Dclaration avec dfinition explicite de taille. Dclaration de rfrence, puis clonage et transtypage.
int[ ] t = new int[10]; for (int i=0; i<10; i++) t[i]=10*i; int[ ] tab; tab = ( int[ ] )t.Clone( );
Dclaration avec dfinition explicite ncessaire de la taille, afin de pouvoir lancer la copie.
Dclaration de rfrence, puis clonage et transtypage. Dclaration avec dfinition explicite ncessaire de la taille, afin de pouvoir lancer la copie.
page
90
La classe Array est en fait un type de collection car elle implmente l'interface ICollection : public abstract class Array : ICloneable, IList, ICollection, IEnumerable Donc tout objet de cette classe (un tableau) est susceptible d'tre parcouru par un instruction foreachin. Mais les lments ainsi parcourus ne peuvent tre utiliss qu'en lecture, ils ne peuvent pas tre modifis, ce qui limite d'une faon importante la porte de l'utilisation d'un foreachin.
Lorsque T est un tableau multi-dimensionnel microsoft indique : les lments sont parcourus de manire que les indices de la dimension la plus droite soient augments en premier, suivis de ceux de la dimension immdiatement gauche, et ainsi de suite en continuant vers la gauche. Dans l'exemple ci-aprs o une matrice table est instancie et remplie il y a quivalence de parcours de la matrice table, entre l'instruction for de gauche et l'instruction foreach de droite (fonctionnement identique pour les autres types de tableaux multi-dimensionnels et en escalier) :
int [ , ] table = new int [ 3 , 2 ]; . Remplissage de la matrice for (int i=0; i<3; i++) for (int j=0; j<2; i++) System.Console.WriteLine ( table[ i , j ] ); foreach ( int val in table) System.Console.WriteLine ( val );
Avantage : la simplicit d'criture, toujours la mme quelle que soit le type du tableau. Inconvnient : on ne peut qu'numrer en lecture les lments d'un tableau.
page
91
interface IEnumerable
interface IEnumerator
interface ICollection
interface IList
IEnumerable :
contient une seule mthode qui renvoie un numrateur (objet de type IEnumerator) qui peut itrer sur les lment d'une collection (c'est une sorte de pointeur qui avance dans la collection, comme un pointeur de fichier se dplace sur les enregistrements du fichier) : public IEnumerator GetEnumerator( );
IEnumerator :
Proprits public object Current {get;} Obtient l'lment en cours point actuellement par l'numrateur dans la
collection.
Mthodes public bool MoveNext( ); Dplace l'numrateur d'un lment il pointe maintenant vers l'lment
suivant dans la collection (renvoie false si l'numrateur est aprs le dernier lment de la collection sinon renvoie true).
public void Reset( ); Dplace l'numrateur au dbut de la collection, avant le premier lment (donc si
l'on effectue un Current on obtiendra la valeur null, car aprs un Reset( ), l'numrateur ne pointe pas devant le premier lment de la collection mais avant ce premier lment !).
page
92
ICollection :
Proprits public int Count {get;} public bool IsSynchronized {get;}
Fournit le nombre d'lments contenus dans ICollection. Fournit un boolen indiquant si l'accs ICollection est synchronis (les lments de ICollection sont protgs de l'accs simultans de plusieurs threads diffrents). Fournit un objet qui peut tre utilis pour synchroniser (verrouiller ou dverrouiller) l'accs ICollection.
Copie les lments de ICollection dans un objet de type Array (table), commenant un index fix.
IList :
Proprits public bool IsFixedSize {get;} : indique si IList est de taille fixe. public bool IsReadOnly {get;} : indique si IList est en lecture seule.
Les classes implmentant l'interface IList sont indexables par l'indexeur [ ].
Mthodes (classique de gestion de liste) public int Add( object elt ); public void Clear( ); public bool Contains( object elt ); public int IndexOf( object elt );
Ajoute l'lment elt IList. Supprime tous les lments de IList. Indique si IList contient l'lment elt en son sein. Indique le rang de l'lment elt dans IList. Insre l'lment elt dans IList la position Supprime la premire occurrence de l'objet elt de IList. Supprime l'lment de IList dont le rang est spcifi.
public void Insert( int rang , object elt ); spcifie par rang. public void Remove( object elt ); public void RemoveAt (int rang);
Ces quatre interfaces C# servent de contrat d'implmentation de nombreuses classes de structures de donnes, nous en tudions quelques unes sur le plan pratique dans la suite du document.
page
93
Si l'on rajoute l'instruction suivante aux prcdentes < TableCar = new char[10]; > il y a cration d'un nouveau tableau de mme nom et de taille 10, l'ancien tableau 8 cellules est alors dtruit. Nous ne redimensionnons pas le tableau, mais en fait nous crons un nouvel objet utilisant la mme variable de rfrence TableCar que le prcdent, toutefois la rfrence TableCar pointe vers le nouveau bloc mmoire :
Ce qui nous donne aprs excution de la liste des instructions ci-dessous, un tableau TabCar ne contenant plus rien :
char [ ] TableCar ; TableCar = new char[8]; TableCar[0] = 'a'; TableCar[1] = '#'; ... TableCar[7] = '?'; TableCar = new char[10];
Il faut dclarer un nouveau tableau t2 plus grand, puis recopier l'ancien dans le nouveau, par exemple en utilisant la mthode public static void Copy ( Array t1 , Array t2, int long)
page
94
Il est possible d'viter cette faon de faire en utilisant une classe de vecteur (tableau unidimensionnel dynamique) qui est en fait une liste dynamique gre comme un tableau. La classe concerne se dnomme System.Collections.ArrayList, elle hrite de la classe object et implmente les interfaces IList, ICollection, IEnumerable, ICloneable ( public class ArrayList : IList, ICollection, IEnumerable, ICloneable;) Un objet de classe ArrayList peut "grandir" automatiquement d'un certain nombre de cellules pendant l'excution, c'est le programmeur qui peut fixer la valeur d'augmentation du nombre de cellules supplmentaires ds que la capacit maximale en cours est dpasse. Dans le cas o la valeur d'augmentation n'est pas fixe, c'est la machine virtuelle du CLR qui procde une augmentation par dfaut. Vous pouvez utiliser le type ArrayList avec n'importe quel type d'objet puisqu'un ArrayList contient des lments de type drivs d'object (ils peuvent tre tous de types diffrents et le vecteur est de type htrogne). Les principales mthodes permettant de manipuler les lments d'un ArrayList sont :
public virtual int Add( object value ); public virtual void Insert(int index, object value); public virtual void Clear(); public virtual void Remove(object obj); Ajoute un l'objet value la fin de ArrayList. Insre un lment dans ArrayList l'index spcifi. Supprime tous les lments de ArrayList. Supprime la premire occurrence d'un objet spcifique de ArrayList. Trie les lments dans l'intgralit de ArrayList l'aide de l'implmentation IComparable de chaque lment (algorithme QuickSort). Accs en lecture et en criture un lment quelconque de rang i du tableau par Table[i] PROPRIETE public virtual int Count { get ;} Vaut le nombre d'lments contenus dans ArrayList, proprit en lecture seulement.. Proprit indexeur de la classe, on l'utilise comme un oprateur tab[ i ] accde l'lment de rang i.
[ ]
Voici un exemple simple de vecteur de chanes utilisant quelques unes des mthodes prcdentes :
static void afficheVector (ArrayList vect) //affiche un vecteur de string { System.Console.WriteLine( "Vecteur taille = " + vect.Count ); for ( int i = 0; i<= vect.Count-1; i++ ) System.Console.WriteLine( "Vecteur[" + i + "]=" + (string)vect[ i ] ); } static void VectorInitialiser ( ) // initialisation du vecteur de string { ArrayList table = new ArrayList( ); string str = "val:"; for ( int i = 0; i<=5; i++ ) table.Add(str + i.ToString( ) ); afficheVector(table); } Premier pas dans .Net avec C# - ( rv. 15.08.2005 ) page
95
Voici le rsultat de l'excution de la mthodeVectorInitialiser : Vector taille = 6 Vector[0] = val:0 Vector[1] = val:1 Vector[2] = val:2 Vector[3] = val:3 Vector[4] = val:4 Vector[5] = val:5
ou bien bi-directionnelles dans lesquelles chaque lment possde deux liens de chanage, l'un sur l'lment qui le suit, l'autre sur l'lment qui le prcde, le parcours s'effectuant en suivant l'un ou l'autre sens de chanage :
La classe ArrayList peut servir une implmentation de la liste chane uni ou bi-directionnelle; un ArrayList contient des lments de type drivs d'Object, la liste peut donc tre htrogne, cf exercice sur les listes chanes.
96
d'une liste d'identifiants et de valeur associe cet identifiant, par exemple une liste de personne dont l'identifiant (la clef) est un entier et la valeur associe des informations sur cette personne sont stockes dans une structure (le nom, l'ge, le genre, ...). Cette classe n'est pas utile pour la gestion d'une liste chane classique non range cause de son tri automatique selon les clefs. En revanche, si l'on stocke comme clef la valeur de Hashcode de l'lment, la recherche est amliore. Les principales mthodes permettant de manipuler les lments d'un SortedList sont :
public virtual int Add( object key,object value ); Ajoute un lment avec la cl key et la valeur value spcifies dans le SortedList. Copie les lments du SortedList dans une instance Array array unidimensionnelle l'index arrayIndex spcifi (valeur de l'index dans array o la copie commence). Supprime tous les lments de SortedList. Obtient la valeur l'index spcifi de la liste SortedList. Obtient la cl l'index spcifi de SortedList. Retourne l'index de base zro de la premire occurrence de la valeur value spcifie dans SortedList. Retourne l'index de base zro de la cl key spcifie dans SortedList. Supprime de SortedList l'lment ayant la cl key spcifie. Supprime l'lment au niveau de l'index spcifi de SortedList. Obtient un objet de liste IList en lecture seule contenant toutes les valeurs tries dans le mme ordre que dans le SortedList. PROPRIETE public virtual int Count { get ; } Vaut le nombre d'lments contenus dans SortedList, proprit en lecture seulement. Proprit indexeur de la classe, on l'utilise comme un oprateur tab[ i ] accde l'lment dont la clef vaut i. Obtient dans un objet de ICollection les valeurs dans SortedList. (les lments de ICollection sont tous tris dans le mme ordre que les valeurs du SortedList) Obtient dans un objet de ICollection les cls dans SortedList. (les lments de ICollection sont tous tris dans le mme ordre que les clefs du SortedList)
public virtual void CopyTo( Array array, int arrayIndex ); public virtual void Clear( ); public virtual object GetByIndex( int index ); public virtual object GetKey( int index ); public virtual int IndexOfValue( object value );
public virtual int IndexOfKey( object key ); public virtual void Remove( object key ); public virtual void RemoveAt( int index );
[ ]
97
Liste.Add(201,"Claudie"); Liste.Add(35,"Jos"); Liste.Add(28,"Luc"); //----> Balayage complet de la Liste par index : for (int i=0; i<Liste.Count; i++) System.Console.WriteLine( (string)Liste.GetByIndex(i) ); //----> Balayage complet de la collection des valeurs : foreach(string s in Liste.Values) System.Console.WriteLine( s ); //----> Balayage complet de la collection des clefs : foreach(object k in Liste.Keys) System.Console.WriteLine( Liste[k] ); //----> Balayage complet de l'objet IList retourn : for (int i = 0; i < Liste.GetValueList( ).Count; i++) System.Console.WriteLine( Liste.GetValueList( ) [ i ] );
Les trois boucles affichent dans l'ordre : Luc Jos Murielle Jean Claudie
page
98
La classe "public class Queue : ICollection, IEnumerable, ICloneable" reprsente une file Fifo :
public virtual object Peek ( ); public virtual object Dequeue( ); public virtual void Enqueue ( object elt ); public virtual object [ ] ToArray( ); Renvoie la rfrence de l'objet situ au sommet de la file. L'objet au dbut de la file est enlev et renvoy. Ajoute un objet la fin de la file. Recopie toute la file dans un tableau d'objet depuis le dbut de la fifo jusqu' la fin de la file.
Exemple d'utilisation d'une Lifo de type Stack Construisons une pile de string possdant une mthode getArray permettant d'empiler immdiatement dans la pile tout un tableau de string. Le programme ci-dessous rempli avec les chanes du tableau t1 grce la mthode getArray , la pile Lifo construite. On tente ensuite de rcuprer le contenu de la pile sous forme d'un tableau de chane t2 (opration inverse) en utilisant la mthode ToArray. Le compilateur signale une erreur :
class Lifo : Stack { public virtual void getArray(string[ ] t) { foreach(string s in t) this.Push (s); } } class Class { static void Main ( string[ ] args ) { Lifo piLifo = new Lifo ( ); string [ ] t1 = {"aaa","bbb","ccc","ddd","eee","fff","fin"}; string [ ] t2 ; piLifo.getArray(t1) ; t2 = piLifo.ToArray( ) ; foreach ( string s in t2 ) System.Console.WriteLine(s) ; }
En effet la mthode ToArray renvoie un tableau d'object et non un tableau de string. On pourrait penser transtyper explicitement : t2 = ( string [ ] ) piLifo.ToArray( ) ; en ce cas C# ragit comme Java, en acceptant la compilation, mais en gnrant une exception de cast invalide, car il est en effet dangereux d'accepter le transtypage d'un tableau d'object en un tableau de quoique ce soit, car chaque object du tableau peut tre d'un type quelconque et tous les types peuvent tre diffrents !
page
99
Il nous faut donc construire une mthode qui ToArray effectue le transtypage de chaque cellule du tableau d'object et renvoie un tableau de string, or nous savons que la mthode de classe Array nomme Copy un tableau t1 vers un autre tableau t2 en effectuant ventuellement le transtypage des cellules : Array.Copy(t1 , t2 , t1.Length) Voici le code de la nouvelle mthode ToArray :
class Lifo : Stack { public virtual void getArray ( string[ ] t ) { foreach(string s in t) this.Push (s); } public new virtual string [ ] ToArray ( ){ string[ ] t = new string [this.Count]; Array.Copy( base.ToArray( ), t , this.Count ); return t ; } } Appel la mthode ToArray mre qui renvoie un object[ ] class Class { static void Main ( string[ ] args ) { Lifo piLifo = new Lifo ( ); string [ ] t1 = {"aaa","bbb","ccc","ddd","eee","fff","fin"}; string [ ] t2 ; piLifo.getArray(t1) ; t2 = piLifo.ToArray( ) ; foreach ( string s in t2 ) System.Console.WriteLine(s) ; }
Nous avons mis le qualificateur new car cette mthode masque la mthode mre de la classe Stack, nous avons maintenant une pile Lifo de string, construisons de la mme manire la classe Fifo de file de string drivant de la classe Queue avec une mthode getArray et la mthode ToArray redfinie :
class Lifo : Stack { public virtual void getArray ( string[ ] t ) { foreach(string s in t) this.Push (s); } public new virtual string [ ] ToArray ( ){ string[ ] t = new string [this.Count]; Array.Copy( base.ToArray( ), t , this.Count ); return t ; } } class Fifo : Queue { public virtual void getArray ( string[ ] t ) { foreach(string s in t) this. Enqueue (s); } public new virtual string [ ] ToArray ( ){ string[ ] t = new string [this.Count]; Array.Copy( base.ToArray( ), t , this.Count ); return t ; } }
class Class { static void Main ( string[ ] args ) { Lifo piLifo = new Lifo ( ); string [ ] t1 = {"aaa","bbb","ccc","ddd","eee","fff","fin"}; string [ ] t2 ; fin piLifo.getArray(t1) ; fff t2 = piLifo.ToArray( ) ; eee foreach ( string s in t2 ) ddd System.Console.WriteLine(s) ; ccc bbb System.Console.WriteLine("------------"); aaa Fifo filFifo = new Fifo ( ); filFifo.getArray(t1); t2 = filFifo.ToArray( ); foreach (string s in t2) aaa System.Console.WriteLine(s); bbb System.Console.ReadLine(); ccc } ddd eee fff fin
page
100
Classes, objets et mthodes Polymorphisme d'objets Polymorphisme de mthodes Polymorphisme d'interfaces Classe de dlgation
page
101
Plan gnral:
3. Variables et mthodes
3.1 Variables dans une classe en gnral 3.2 Variables et mthodes d'instance 3.3 Variables et mthodes de classe - static 3.4 Bilan et exemple d'utilisation
page
102
Introduction
Nous proposons des comparaisons entre les syntaxes de C# et Delphi et/ou Java, lorsque les dfinitions sont semblables. Tableau des limitations des niveaux de visibilit fourni par microsoft :
Modification de visibilit Rappelons les classiques modificateurs de visibilit des variables et des mthodes dans les langages orients objets, dont C# dispose : Les mots clef (modularit public-priv)
par dfaut (aucun mot clef) public Les variables et les mthodes d'une classe non prcdes d'un mot clef sont private et ne sont visibles que dans la classe seulement. Les variables et les mthodes d'une classe prcdes du mot clef public sont visibles par toutes les classes de tous les modules. Les variables et les mthodes d'une classe prcdes du mot clef private ne sont visibles que dans la classe seulement. Les variables et les mthodes d'une classe prcdes du mot clef protected sont visibles par toutes les classes incluses dans le module, et par les classes drives de cette classe. Les variables et les mthodes d'une classe prcdes du
)
private
protected
internal
page
103
mot clef internal sont visibles par toutes les classes inclues dans le mme assembly.
Les attributs d'accessibilit public, private, protected sont identiques ceux de Delphi et Java, pour les classes nous donnons ci-dessous des informations sur leur utilisation. L'attribut internal joue peu prs le rle (au niveau de l'assembly) des classes Java dclares sans mot clef dans le mme package , ou des classes Delphi dclares dans la mme unit (classes amies). Toutefois pour des raisons de scurit C# ne possde pas la notion de classe amie.
Java
package Biblio;
C#
namespace Biblio { // les dclarations et implmentation des classes
1.1 Dclaration d'une classe En C#, nous n'avons pas comme en Delphi, une partie dclaration de la classe et une partie implmentation spares l'une de l'autre. La classe avec ses attributs et ses mthodes sont dclars et implments un seul endroit comme en Java. Delphi
Premier pas dans .Net avec C# - ( rv 15.08.2005
Java
)
C#
page
104
interface uses biblio; type Exemple = class x : real; y : integer; function F1(a,b:integer): real; procedure P2; end; implementation function F1(a,b:integer): real; begin ...........code de F1 end; procedure P2; begin ...........code de P2 end; end.
import biblio; class Exemple { float x; int y; float F1(int a, int b) { ...........code de F1 } void P2( ) { ...........code de P2 } }
using biblio; namespace Machin { class Exemple { float x; int y; float F1(int a, int b) { ...........code de F1 } void P2( ) { ...........code de P2 } } }
1.2 Une classe est un type en C# Comme en Delphi et Java, une classe C# peut tre considre comme un nouveau type dans le programme et donc des variables d'objets peuvent tre dclares selon ce nouveau "type". Une dclaration de programme comprenant 3 classes : Delphi
interface type Un = class ... end; Deux = class ... end; Appli3Classes = class x : Un; y : Deux; public procedure main; end; implementation procedure Appli3Classes.main; var x : Un; y : Deux; begin ... end; end.
Java
class Appli3Classes { Un x; Deux y; public static void main(String [ ] arg) { Un x; Deux y; ... } } class Un { ... } class Deux { ... }
C#
class Appli3Classes { Un x; Deux y; static void Main(String [ ] arg) { Un x; Deux y; ... } } class Un { ... } class Deux { ... }
page
105
1.3 Toutes les classes ont le mme anctre - hritage Comme en Delphi et en Java, toutes les classes C# drivent automatiquement d'une seule et mme classe anctre : la classe Object. En C# le mot-clef pour indiquer la drivation (hritage) partir d'une autre classe est le symbole deux points ':', lorsqu'il est omis c'est donc que la classe hrite automatiquement de la classe Object : Les deux dclarations de classe ci-dessous sont quivalentes : Delphi
type Exemple = class ( TObject ) ...... end; type Exemple = class ...... end;
Java
class Exemple extends Object { ....... } class Exemple { ....... }
C#
class Exemple : Object { ....... } class Exemple { ....... }
L'hritage en C# est tout fait classiquement de l'hritage simple comme en Delphi et en Java. Une classe fille qui drive d'une seule classe mre, hrite de sa classe mre toutes ses mthodes et tous ses champs. En C# la syntaxe de l'hritage fait intervenir le symbole clef ':', comme dans "class Exemple : Object". Une dclaration du type : class ClasseFille : ClasseMere { } signifie que la classe ClasseFille dispose de tous les attributs et de toutes les mthodes de la classe ClasseMere. Comparaison hritage : Delphi
type ClasseMere = class // champs de ClasseMere // mthodes de ClasseMere end; ClasseFille = class ( ClasseMere )
// hrite des champs de ClasseMere // hrite des mthodes de ClasseMere
Java
class ClasseMere { // champs de ClasseMere // mthodes de ClasseMere } class ClasseFille extends ClasseMere {
// hrite des champs de ClasseMere // hrite des mthodes de ClasseMere
C#
class ClasseMere { // champs de ClasseMere // mthodes de ClasseMere } class ClasseFille : ClasseMere {
// hrite des champs de ClasseMere // hrite des mthodes de ClasseMere
end;
Bien entendu une classe fille peut dfinir de nouveaux champs et de nouvelles mthodees qui lui sont propres.
page
106
La visibilit et la protection des classes en Delphi est apporte par le module Unit o toutes les classes sont visibles dans le module en entier et ds que la unit est utilise les classes sont visibles partout. Il n'y a pas de possibilit d'imbriquer une classe dans une autre. En C#, nous avons la possibilit d'imbriquer des classes dans d'autres classes (classes internes), par consquent la visibilit de bloc s'applique aussi aux classes. Remarque La notion de classe interne de C# (qui n'existe pas en Delphi) est moins riche ce jour qu'en Java (pas de classe membre statique, pas de classe locale et pas de classe anonyme), elle corespond la notion de classe membre de Java.
Mots clefs pour la protection des classes et leur visibilit : Une classe C# peut se voir attribuer un modificateur de comportement sous la forme d'un mot clef devant la dclaration de classe. Par dfaut si aucun mot clef n'est indiqu la classe est visible dans tout le namespace dans lequel elle est dfinie. Il y a 4 qualificateurs possibles pour modifier le comportement de visibilit d'une classe selon sa position (imbrique ou non) : public, private, protected, internal (dnomms modificateurs d'accs) et abstract (qualificateur d'abstraction pouvant tre associ l'un des 3 autres modificateurs d'accs). On rappelle que sans qualificateur public, private, internal ou protected, une classe C# est automatiquement public. Le nom du fichier source dans lequel plusieurs classes C# sont stockes n'a aucun rapport avec le nom d'une des classes dclares dans le texte source, il est laiss au libre choix du dveloppeur et peut ventuellement tre celui d'une classe du namespace etc...
Attention Par dfaut dans une classe tous les membres sans qualificateur de visibilit (classes internes inclues) sont private.
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
107
C# mot clef abstract : abstract class ApplicationClasse1 { ... } mot clef public : public class ApplicationClasse2 { ... } mot clef protected : protected class ApplicationClasse3 { ... } mot clef internal : internal class ApplicationClasse4 { ... } mot clef private : private class ApplicationClasse5 { ... } pas de mot clef : class ApplicationClasse6 { ... } - sauf si c'est une classe interne
Explication classe abstraite non instanciable. Aucun objet ne peut tre cr. classe visible par n'importe quel programme d'un autre namespace classe visible seulement par toutes les autres classes hritant de la classe conteneur de cette classe.
classe visible seulement par toutes les autres classes du mme assembly. classe visible seulement par toutes les autres classes du mme namespace o elle est dfinie.
qualifie public -si c'est une classe interne elle est alors qualifie private.
Nous remarquons donc qu'une classe ds qu'elle est dclare dans lespace de noms est toujours visible et par dfaut public, que le mot clef public soit prsent ou non. Les mots clefs abstract et protected n'ont de l'influence que pour l'hritage. Remarque La notion de classe sealed en C# correspond strictement la notion de classe final de Java : ce sont des classes non hritables.
Nous tudions ci-aprs la visibilit des classes prcdentes dans deux contextes diffrents.
1.5 Exemple de classes imbriques dans une autre classe Dans le premier contexte, ces six classes sont utilises en tant intgres (imbriques) une classe publique. La classe ApplicationClasses : C# Explication
page
108
namespace Exemple { public class ApplicationClasses { abstract class ApplicationClasse1 { ... } public class ApplicationClasse2 { ... } protected class ApplicationClasse3 { ... } internal class ApplicationClasse4 { ... } private class ApplicationClasse5 { ... } class ApplicationClasse6 { ... } } }
Ces 6 "sous-classes" sont visibles ou non, partir de l'accs la classe englobante "ApplicationClasses", elles peuvent donc tre utilises dans tout programme qui utilise la classe "ApplicationClasses".
Le programme de gauche "class AppliTestClasses" utilise la classe prcdente ApplicationClasses et ses sous-classes. La notation uniforme de chemin de classe est standard.
1.6 Mme exemple de classes non imbriques situes dans le mme espace de noms Dans ce second exemple, ces mmes 6 classes sont utilises en tant incluses dans le mme namespace.
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
109
C#
Explication
Une classe dans un espace de nom ne peut pas tre qualifie protected ou private (si elle est imbrique comme ci-haut cela est possible): Les classes ApplicationClasse3 et ApplicationClasse5 ne peuvent donc pas faire partie du mme namespace Exemple. La classe ApplicationClasse1 est ici abstraite et public, donc visible. La classe ApplicationClasse2 est public, donc visible. La classe ApplicationClasse4 n'est visible que dans le mme assembly. Par dfaut la classe ApplicationClasse6 est ici public, donc visible.
namespace Exemple { abstract class ApplicationClasse1 { ... } public class ApplicationClasse2 { ... } protected class ApplicationClasse3 { ... } internal class ApplicationClasse4 { ... } private class ApplicationClasse5 { ... } class ApplicationClasse6 { ... } }
Un programme AppliTestClasses utilisant ces 4 classes : C# dans deux namespace diffrents Explication
Le programme de gauche "class AppliTestClasses" utilise les classes qui composent le namespace Exemple.
Cette classe AppliTestClasses est dfinie dans un autre namespace dnomm Essai qui est suppos ne pas faire partie du mme assembly que le namespace Exemple. Elle ne voit donc pas la classe ApplicationClasse4 (visible dans le mme assembly seulement).
using Exemple; namespace Essai { class AppliTestClasses{ ApplicationClasse2 a2; ApplicationClasse6 a6; } } Premier pas dans .Net avec C# - ( rv 15.08.2005
Si l'on veut instancier des objets, seules les classes ApplicationClasse2 et ApplicationClasse6 sont de bonnes candidates, car la classe ApplicationClasse1 bien qu'elle soit visible est abstraite.
page
110
Explication
Le programme de gauche "class AppliTestClasses" utilise les classes qui composent le namespace Exemple.
La classe AppliTestClasses est dfinie dans le mme namespace Exemple que les 4 autres classes. Toutes les classes du mme namespace sont visibles entre elles. namespace Exemple { class AppliTestClasses{ ApplicationClasse1 a1; ApplicationClasse2 a2; ApplicationClasse4 a4; ApplicationClasse6 a6; } } Ici les toutes les 4 classes sont visibles pour la classe AppliTestClasses.
Remarque pratique : Selon sa situation imbrique ou non imbrique, une classe peut ou ne peut pas tre qualifie par les divers modificateurs de visibilit. En cas de doute le compilateur fournit un diagnostique clair, comme ci-dessous : [C# Erreur] Class.cs(nn): Les lments namespace ne peuvent pas tre dclars explicitement comme private, protected ou protected internal.
1.7 Mthodes abstraites Le mot clef abstract est utilis pour reprsenter une classe ou une mthode abstraite. Quel est l'intrt de cette notion ? Avoir des modles gnriques permettant de dfinir ultrieurement des actions spcifiques.
Une mthode dclare en abstract dans une classe mre : N'a pas de corps de mthode. N'est pas excutable.
)
page
111
Une mthode abstraite n'est qu'une signature de mthode sans implmentation dans la classe. Exemple de mthode abstraite : class Etre_Vivant { } La classe Etre_Vivant est une classe mre gnrale pour les tres vivants sur la plante, chaque catgorie d'tre vivant peut tre reprsente par une classe drive (classe fille de cette classe) : class Serpent : Etre_Vivant { } class Oiseau : Etre_Vivant { } class Homme : Etre_Vivant { } Tous ces tres se dplacent d'une manire gnrale, donc une mthode SeDeplacer est commune toutes les classes drives, toutefois chaque espce excute cette action d'une manire diffrente et donc on ne peut pas dire que se dplacer est une notion concrte mais une notion abstraite que chaque sous-classe prcisera concrtement. En C#, les mthodes abstraites sont automatiquement virtuelles, elles ne peuvent tre dclares que public ou protected, enfin elles doivent tre redfinies avec le qualificateur override. Cidessous deux dclarations possibles pour le dplacement des tres vivants :
abstract class Etre_Vivant { public abstract void SeDeplacer( ); } class Serpent : Etre_Vivant { public override void SeDeplacer( ) { //.....en rampant } } class Oiseau : Etre_Vivant { public override void SeDeplacer( ) { //.....en volant } } class Homme : Etre_Vivant { public override void SeDeplacer( ) { //.....en marchant } }
abstract class Etre_Vivant { protected abstract void SeDeplacer( ); } class Serpent : Etre_Vivant { protected override void SeDeplacer( ) { //.....en rampant } } class Oiseau : Etre_Vivant { protected override void SeDeplacer( ) { //.....en volant } } class Homme : Etre_Vivant { protected override void SeDeplacer( ) { //.....en marchant } }
C#
abstract class Etre_Vivant { public abstract void SeDeplacer( );
)
page
112
procedure SeDeplacer;virtual; abstract ; end; Serpent = class ( Etre_Vivant ) procedure SeDeplacer; override; end; Oiseau = class ( Etre_Vivant ) procedure SeDeplacer; override; end; Homme = class ( Etre_Vivant ) procedure SeDeplacer; override; end;
} class Serpent : Etre_Vivant { public override void SeDeplacer( ) { //.....en rampant } } class Oiseau : Etre_Vivant { public override void SeDeplacer( ) { //.....en volant } } class Homme : Etre_Vivant { public override void SeDeplacer( ) { //.....en marchant } }
En C# une mthode abstraite est une mthode virtuelle nayant pas dimplmentation dans la classe o elle est dclare. Son implmentation est dlgue une classe drive. Les mthodes abstraites doivent tre dclares en spcifiant la directive abstract .
1.8 Classe abstraite, Interface Classe abstraite Comme nous venons de le voir dans l'exemple prcdent, une classe C# peut tre prcde du mot clef abstract, ce qui signifie alors que cette classe est abstraite, nous avons les contraintes de dfinition suivantes pour une classe abstraite en C# : Si une classe contient au moins une mthode abstract, elle doit imprativement tre dclare en classe abstract elle-mme. C'est ce que nous avons crit au paragraphe prcdent pour la classe Etre_Vivant que nous avons dclare abstract parce qu'elle contenait la mthode abstraite SeDeplacer. Une classe abstract ne peut pas tre instancie directement, seule une classe drive (sous-classe) qui redfinit obligatoirement toutes les mthodes abstract de la classe mre peut tre instancie. Consquence du paragraphe prcdent, une classe drive qui redfinit toutes les mthodes abstract de la classe mre sauf une (ou plus d'une) ne peut pas tre instancie et subit la mme rgle que la classe mre : elle contient au moins une mthode abstraite donc elle est aussi une classe abstraite et doit donc tre dclare en abstract. Une classe abstract peut contenir des mthodes non abstraites et donc implantes dans la classe. Une classe abstract peut mme ne pas contenir du tout de mthodes abstraites, dans ce cas une classe fille n'a pas la ncessit de redfinir les mthodes de la classe mre pour tre instancie. Delphi contrairement C# et Java, ne possde pas ce jour le modle de la classe abstraite, seule la version Delphi2005 pour le Net Framework possde les mmes caractristiques que C#. Interface Lorsqu'une classe est dclare en abstract et que toutes ses mthodes sont dclares en abstract, on appelle en C# une telle classe une Interface.
page
113
Les interfaces ressemblent aux classes abstraites sur un seul point : elles contiennent des membres expliquant certains comportements sans les implmenter. Les classes abstraites et les interfaces se diffrencient principalement par le fait qu'une classe peut implmenter un nombre quelconque d'interfaces, alors qu'une classe abstraite ne peut hriter que d'une seule classe abstraite ou non.
Vocabulaire et concepts :
Une interface est un contrat, elle peut contenir des proprits, des mthodes et des vnements mais ne doit contenir aucun champ ou attribut. Une interface ne peut pas contenir des mthodes dj implmentes. Une interface doit contenir des mthodes non implmentes. Une interface est hritable. On peut contsruire une hirarchie d'interfaces. Pour pouvoir construire un objet partir d'une interface, il faut dfinir une classe non abstraite implmentant toutes les mthodes de l'interface.
Une classe peut implmenter plusieurs interfaces. Dans ce cas nous avons une excellente alternative l'hritage multiple. Lorsque l'on cre une interface, on fournit un ensemble de dfinitions et de comportements qui ne devraient plus tre modifis. Cette attitude de constance dans les dfinitions, protge les applications crites pour utiliser cette interface. Les variables de types interface respectent les mmes rgles de transtypage que les variables de types classe. Les objets de type classe clA peuvent tre transtyps et refrencs par des variables d'interface IntfA dans la mesure o la classe clA implmente linterface IntfA. (cf. polymorphisme d'objet)
Si vous voulez utiliser la notion d'interface pour fournir un polymorphisme une famille de classes, elles doivent toutes implmenter cette interface, comme dans l'exemple ci-dessous. Exemple : l'interface Vhicule dfinissant 3 mthodes (abstraites) Dmarrer, RpartirPassagers de
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
114
rpartition des passagers bord du vhicule (fonction de la forme, du nombre de places, du personnel charg de s'occuper de faire fonctionner le vhicule...), et PriodicitMaintenance renvoyant la priodicit de la maintenance obligatoire du vhicule (fonction du nombre de km ou miles parcourus, du nombre d'heures d'activits,...) Soit l'interface Vhicule dfinissant ces 3 mthodes : interface IVehicule{ void Demarrer( ); void RpartirPassager( ); void PriodicitMaintenance( ); } Soient les deux classes Vhicule terrestre et Vhicule marin, qui implmentent partiellemnt chacune l'interface Vhicule , ainsi que trois classes voiture, voilier et croiseur hritant de ces deux classes :
Les trois mthodes de l'interface Vhicule sont abstraites et publiques par dfinition. Les classes Vhicule terrestre et Vhicule marin sont abstraites, car la mthode abstraite Dmarrer de l'interface Vhicule n'est pas implmente elles reste comme "modle" aux futurs classes. C'est dans les classes voiture, voilier et croiseur que l'on implmente le comportement prcis du genre de dmarrage.
Dans cette vision de la hirarchie on a suppos que les classes abstraites Vhicule terrestre et Vhicule marin savent comment rpartir leur ventuels passagers et quand effectuer une maintenance du vhicule. Les classes voiture, voilier et croiseur , n'ont plus qu' implmenter chacune son propre comportement de dmarrage.
Une interface C# peut tre qualifie par un des 4 modificateur public, protected, internal,
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
115
private. Contrairement Java, une classe abstraite C# qui implmente une interface doit obligatoirement dclarer toutes les mthodes de l'interface, celles qui ne sont pas implmentes dans la classe abstraite doivent tre dclares abstract. C'est le cas dans l'exemple ci-dessous pour la mthode abstraite Dmarrer , nous proposons deux critures possibles pour cette hirarchie de classe : C# mthode abstraite sans corps
interface IVehicule{ void Demarrer( ); void RpartirPassager( ); void PriodicitMaintenance( ); } abstract class Terrestre : IVehicule { public abstract void Demarrer( ); public void virtual RpartirPassager( ){...} public void virtual PriodicitMaintenance( ){...} } class Voiture : Terrestre { override public void Demarrer( ){...} } abstract class Marin : IVehicule { public abstract void Demarrer( ); public void virtual RpartirPassager( ){...} public void virtual PriodicitMaintenance( ){...} } class Voilier : Marin { override public void Demarrer( ){...} } class Croiseur : Marin { override public void Demarrer( ){...} }
Les mthodes RpartirPassagers, PriodicitMaintenance et Demarrer sont implantes en virtual , soit comme des mthodes liaison dynamique, afin de laisser la possibilit pour des classes enfants de redfinir ces mthodes. Remarque : Attention le qualificateur new peut masquer que des membres non abstract. Un membre abstract doit imprativement redfini (implment) par le qualificateur override, car ce genre de membre est implicitement virtual en C#.
page
116
Soit titre de comparaison, les deux mmes critures en Java de ces classes :
Java , mthode abstraite sans corps
page
117
C'est dans le segment de mmoire du CLR de .NetFramework que s'effectue l'allocation et la dsallocation d'objets. Objets type valeur Les classes encapsulant les types lmentaires dans .NET Framework sont des classes de type valeur du genre structures. Dans le CLS une classe de type valeur est telle que les allocations d'objets de cette classe se font directement dans la pile et non dans le tas, il n'y a donc pas de rfrence pour un objet de type valeur et lorsqu'un objet de type valeur est pass comme paramtre il est pass par valeur. Dans .NET Framework les classes-structures de type valeur sont dclares comme structures et ne sont pas drivables Objets type rfrence Le principe d'allocation et de reprsentation des objets type rfrence en C# est identique celui de Delphi il s'agit de la rfrence, qui est une encapsulation de la notion de pointeur. Dans .NET Framework les classes de type rfrence sont dclares comme des classes classiques et sont drivables. Afin d'clairer le lecteur prenons par exemple un objet x instanci partir d'une classe de type rfrence et un objet y instanci partir d'un classe de type valeur contenant les mmes membres que la classe par rfrence. Ci-dessous le schma d'allocation de chacun des deux catgories d'objets :
Pour les types valeurs, la gestion mmoire des objets est classiquement celle de la pile dynamique, un tel objet se comporte comme une variable locale de la mthode dans laquelle il est instanci et ne ncessite pas de gestion supplmentaire. Seuls les objets type rfrence instancis sur le tas, ncessitent une gestion mmoire spciale que nous dtaillons ci-aprs (dans un
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
118
programme C# les types rfrences du dveloppeur reprsentent prs de 99% des objets du programme). 2.1 Modle de la rfrence en C# Rappelons que dans le modle de la rfrence chaque objet (reprsent par un identificateur de variable) est caractris par un couple (rfrence, bloc de donnes). Comme en Delphi, C# dcompose l'instanciation (allocation) d'un objet en deux tapes : La dclaration d'identificateur de variable type qui contiendra la rfrence, la cration de la structure de donnes elle-mme (bloc objet de donnes) avec new.
Delphi type Un = class ...... end; // la dclaration : var x , y : Un; .... // la cration : x := Un.create ; y := Un.create ; class Un { ... } // la dclaration : Un x , y ; .... // la cration : x = new Un( ); y = new Un( );
C#
Aprs excution du pseudo-programme prcdent, les variables x et y contiennent chacune une rfrence (adresse mmoire) vers un bloc objet diffrent:
Un programme C# est fait pour tre excut par l'environnement CLR de .NetFramework. Deux objets C# seront instancis dans le CLR de la manire suivante :
page
119
Attitude rapprocher pour comparaison, celle dont Delphi gre les objets dans une pile d'excution de type LIFO et un tas :
Attention l'utilisation de l'affectation entre variables d'objets dans le modle de reprsentation par rfrence. L'affectation x = y ne recopie pas le bloc objet de donnes de y dans celui de x, mais seulement la rfrence (l'adresse) de y dans la rfrence de x. Visualisons cette remarque importante : Situation au dpart, avant affectation
En C#, la dsallocation tant automatique, le bloc de donnes objet qui tait rfrenc par y avant
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
120
l'affectation, n'est pas perdu, car le garbage collector se charge de restituer la mmoire libre au segment de mmoire du CLR
2.2 Les constructeurs d'objets rfrences ou valeurs Un constructeur est une mthode spciale d'une classe dont la seule fonction est d'instancier un objet (crer le bloc de donnes). Comme en Delphi une classe C# peut possder plusieurs constructeurs, il est possible de pratiquer des initialisations d'attributs dans un constructeur. Comme toutes les mthodes, un constructeur peut avoir ou ne pas avoir de paramtres formels. Si vous ne dclarez pas de constructeur spcifique pour une classe, par dfaut C# attribue automatiquement un constructeur sans paramtres formels, portant le mme nom que la classe. A la diffrence de Delphi o le nom du constructeur est quelconque, en C# le( ou les) constructeur doit obligatoirement porter le mme nom que la classe (majuscules et minuscules comprises). Un constructeur d'objet d'une classe n'a d'intrt que s'il est visible par tous les programmes qui veulent instancier des objets de cette classe, c'est pourquoi l'on mettra toujours le mot clef public devant la dclaration du constructeur. Un constructeur est une mthode spciale dont la fonction est de crer des objets, dans son en-tte il n'a pas de type de retour et le mot clef void n'est pas non plus utilis !
Soit une classe dnomme Un dans laquelle, comme nous l'avons fait jusqu' prsent nous n'indiquons aucun constructeur spcifique : class Un { int a; } Automatiquement C# attribue un constructeur public cette classe public Un ( ). C'est comme si C# avait introduit dans votre classe votre insu , une nouvelle mthode dnomme Un. Cette mthode "cache" n'a aucun paramtre et aucune instruction dans son corps. Ci-dessous un exemple de programme C# correct illustrant ce qui se passe : class Un { public Un ( ) { } int a; }
Vous pouvez programmer et personnaliser vos propres constructeurs. Une classe C# peut contenir plusieurs constructeurs dont les en-ttes diffrent uniquement par la liste des paramtres formels.
page
121
Le constructeur public Un sert ici initialiser 100 la valeur de l'attribut "int a" de chaque objet qui sera instanci.
Exemple de constructeur avec paramtre : C# class Un { public Un (int b ) { a = b; } int a; } Explication Le constructeur public Un sert ici initialiser la valeur de l'attribut "int a" de chaque objet qui sera instanci. Le paramtre int b contient cette valeur.
Exemple avec plusieurs constructeurs : C# class Un { public Un (int b ) { a = b; } public Un ( ) { a = 100; } public Un (float b ) { a = (int)b; } int a; } Explication
La classe Un possde 3 constructeurs servant initialiser chacun d'une manire diffrente le seul attribut int a.
Il est possible de rappeller un constructeur de la classe dans un autre constructeur, pour cela C# utilise comme Java le mot clef this, avec une syntaxe diffrente : Exemple avec un appel un constructeur de la mme classe: C# class Un { int a; public Un (int b ) { a = b; } public Un ( )
Premier pas dans .Net avec C# - ( rv 15.08.2005
Explication La classe Un possde 3 constructeurs servant initialiser chacun d'une manire diffrente le seul attribut int a.
page
122
public Un (int x , float y ) : this(y) { a += 100; } Ce constructeur appelle tout d'abord le constructeur Un (float y ) par l'intermdiaire de this(y), puis il excute le corps de mthode soit : a += 100; Ce qui revient calculer : a = (int)y + 100 ;
Comparaison Delphi - C# pour la dclaration de constructeurs Delphi class Un { int a; public Un ( ) { a = 100; } public Un (int b ) { a = b; } public Un (float b ) { a = (int)b; } public Un (int x , float y ) : this(y) { a += 100; } } C#
Un = class a : integer; public constructor creer; overload; constructor creer (b:integer); overload; constructor creer (b:real); overload; end; implementation constructor Un.creer; begin a := 100 end; constructor Un.creer(b:integer); begin a := b end; constructor Un.creer(b:real); begin a := trunc(b) end; constructor Un.creer(x:integer; y:real); begin self.creer(y); a := a+100; end;
En Delphi un constructeur a un nom quelconque, tous les constructeurs peuvent avoir des noms diffrents ou le mme nom comme en C#.
2.3 Utilisation du constructeur d'objet automatique (par dfaut) Le constructeur d'objet par dfaut de toute classe C# qu'elle soit de type valeur ou de type rfrence, comme nous l'avons signal plus haut est une mthode spciale sans paramtre, l'appel
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
123
cette mthode spciale afin de construire un nouvel objet rpond une syntaxe spcifique par utilisation du mot clef new. Syntaxe Pour un constructeur sans paramtres formels, l'instruction d'instanciation d'un nouvel objet partir d'un identificateur de variable dclare selon un type de classe, s'crit syntaxiquement ainsi :
Exemple : (deux faons quivalentes de crer un objet x de classe Un) Un x ; x = new Un( );
Un x = new Un( )
Cette instruction cre dans le segment de mmoire, un nouvel objet de classe Un dont la rfrence (l'adresse) est mise dans la variable x, si x est de type rfrence, ou bien l'objet est directement cr dans la pile et mis dans la variable x, si x est de type valeur.
Soit Un une classe de type rfrence et Deux une autre classe de type valeur, ci-dessous une image des rsulats de l'instanciation d'un objet de chacune de ces deux classes : Un x = new Un( ) ; Deux y = new Deux ( ) ;
Dans l'exemple ci-dessous, nous utilisons le constructeur par dfaut de la classe Un , pour crer deux objets dans une autre classe : class Un { ... }
Premier pas dans .Net avec C# - ( rv 15.08.2005
page
124
class UnAutre { // la dclaration : Un x , y ; .... // la cration : x = new Un( ); y = new Un( ); } Un programme de 2 classes, illustrant l'affectation de rfrences : C#
class AppliClassesReferences { public static void Main(String [ ] arg) { Un x,y ; x = new Un( ); y = new Un( ); System.Console.WriteLine("x.a="+x.a); System.Console.WriteLine"y.a="+y.a); y = x; x.a =12; System.Console.WriteLine("x.a="+x.a); System.Console.WriteLine("y.a="+y.a); } } class Un { int a=10; }
Explication
Ce programme C# contient deux classes : class AppliClassesReferences et class Un La classe AppliClassesReferences est une classe excutable car elle contient la mthode main. C'est donc cette mthode qui agira ds l'excution du programme.
y = x;
2.4 Utilisation d'un constructeur d'objet personnalis L'utilisation d'un constructeur personnalis d'une classe est semblable celle du constructeur par
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
125
dfaut de la classe. La seule diffrence se trouve lors de l'instanciation : il faut fournir des paramtres effectifs lors de l'appel au constructeur. Syntaxe
Exemple avec plusieurs constructeurs : une classe C# class Un { int a ; public Un (int b ) { a=b; } public Un ( ) { a = 100 ; } public Un (float b ) { a = (int)b ; } public Un (int x , float y ) : this(y) { a += 100; } } Des objets crs Un obj1 = newUn( ); Un obj2 = new Un( 15 ); int k = 14; Un obj3 = new Un( k ); Un obj4 = new Un( 3.25f ); float r = -5.6; Un obj5 = new Un( r ); int x = 20; float y = -0.02; Un obj6 = new Un( x , y );
2.5 Le mot clef this - cas de la rfrence seulement Il est possible de dnommer dans les instructions d'une mthode de classe, un futur objet qui sera instanci plus tard. Le paramtre ou (mot clef) this est implicitement prsent dans chaque objet instanci et il contient la rfrence l'objet actuel. Il joue exactement le mme rle que le mot clef self en Delphi. Nous avons dj vu une de ses utilisations dans le cas des constructeurs. C# class Un { public Un ( ) { a = 100; } int a; } C# quivalent class Un { public Un ( ) { this.a = 100; } int a; }
Dans le programme de droite le mot clef this fait rfrence l'objet lui-mme, ce qui dans ce cas est superflu puisque la variable int a est un champ de l'objet.
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
126
Montrons deux exemples d'utilisation pratique de this. Cas o l'objet est pass comme un paramtre dans une de ses mthodes : C#
class Un { public Un ( ) { a = 100; } public void methode1(Un x) { System.Console.WriteLine("champ a = " + x.a); } public void methode2( int b ) { a += b; methode1(this); } int a; }
Explications
La methode1(Un x) reoit un objet de type Exemple en paramtre et imprime son champ int a. La methode2( int b ) reoit un entier int b qu'elle additionne au champ int a de l'objet, puis elle appelle la mthode1 avec comme paramtre l'objet lui-mme.
C#
class Un { public Un ( ) { a = 100; } public void methode1(Un x) { System.Console.WriteLine("champ a ="+x.a); } public void methode2( int b ) { a += b; methode1(this); } int a; }
Explications
La methode1(float a) possde un paramtre float a dont le nom masque le nom du champ int a. Si nous voulons malgr tout accder au champ de l'objet, l'objet tant rfrenc par this, "this.a" est donc le champ int a de l'objet lui-mme.
page
127
Comparaison Delphi - C# sur ce second exemple (similitude complte aussi) Delphi Un = class a : integer; public procedure methode( a:real ); end; implementation procedure Un.methode( a:real );begin a = self.a + 7 ; end; C# class Un { int a; public void methode(float a) { a = this.a + 7 ; } }
3. Variables et mthodes
Nous examinons dans ce paragraphe comment C# utilise les variables et les mthodes l'intrieur d'une classe. Il est possible de modifier des variables et des mthodes d'une classe ceci sera examine plus loin. En C#, les champs et les mthodes sont classs en deux catgories : Variables et mthodes de classe Variables et mthodes d'instance
3.1 Variables dans une classe en gnral Rappelons qu'en C#, nous pouvons dclarer dans un bloc (for, try,...) de nouvelles variables la condition qu'elles n'existent pas dj dans le corps de la mthode o elles sont dclares. Nous les dnommerons : variables locales de mthode. Exemple de variables locales de mthode : class Exemple { void calcul ( int x, int y ) {int a = 100; for ( int i = 1; i<10; i++ ) {char carlu;
System.Console.Write("Entrez un caractre : "); carlu = (char)System.Console.Read( );
La dfinition int a = 100; est locale la mthode en gnral La dfinition int i = 1; est locale la boucle for.
Les dfinitions char carlu et int b sont locales au corps de la boucle for.
page
128
} } }
C# ne connat pas la notion de variable globale au sens habituel donn cette dnomination, dans la mesure o toute variable ne peut tre dfinie qu' l'intrieur d'une classe, ou d'une mthode inclue dans une classe. Donc part les variables locales de mthode dfinies dans une mthode, C# reconnat une autre catgorie de variables, les variables dfinies dans une classe mais pas l'intrieur d'une mthode spcifique. Nous les dnommerons : attributs de classes parce que ces variables peuvent tre de deux catgories. Exemple de attributs de classe : class AppliVariableClasse { float r ; void calcul ( int x, int y ) { .... } int x =100; int valeur ( char x ) { ..... } long y; } Les attributs de classe peuvent tre soit de la catgorie des variables de classe, soit de la catgorie des variables d'instance. 3.2 Variables et mthodes d'instance C# se comporte comme un langage orient objet classique vis vis de ses variables et de ses mthodes. A chaque instanciation d'un nouvel objet d'une classe donne, la machine CLR enregistre le p-code des mthodes de la classe dans la zone de stockage des mthodes, elle alloue dans le segment de mmoire autant d'emplacements mmoire pour les variables que d'objet crs. C# dnomme cette catgorie les variables et les mthodes d'instance. une classe C# class AppliInstance { int x ; int y ; } Instanciation de 3 objets AppliInstance obj1 = new AppliInstance( ); AppliInstance obj2 = new AppliInstance( ); AppliInstance obj3 = new AppliInstance( ); Les variables float r , long y et int x sont des attributs de classe (ici en fait plus prcisment, des variables d'instance). La position de la dclaration de ces variables n'a aucune importance. Elles sont visibles dans tout le bloc classe (c'est dire visibles par toutes les mthodes de la classe).
Conseil : regroupez les variables de classe au dbut de la classe afin de mieux les grer.
page
129
Segment de mmoire associ ces 3 objets si la classe AppliInstance est de type rfrence :
Segment de mmoire associ ces 3 objets si la classe AppliInstance tait de type valeur (pour mmoire):
Un programme C# 2 classes illustrant l'exemple prcdent (classe rfrence): Programme C# excutable class AppliInstance { public int x = -58 ; public int y = 20 ; } class Utilise
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
130
{ public static void Main( ) { AppliInstance obj1 = new AppliInstance( ); AppliInstance obj2 = new AppliInstance( ); AppliInstance obj3 = new AppliInstance( ); System.Console.WriteLine( "obj1.x = " + obj1.x ); } }
3.3 Variables et mthodes de classe - static Variable de classe On identifie une variable ou une mthode de classe en prcdant sa dclaration du mot clef static. Nous avons dj pris la majorit de nos exemples simples avec de tels composants. Voici deux dclaration sde variables de classe : static int x ; static int a = 5; Une variable de classe est accessible comme une variable d'instance(selon sa visibilit), mais aussi sans avoir instancier un objet de la classe, uniquement en rfrenant la variable par le nom de la classe dans la notation de chemin uniforme d'objet. une classe C# class AppliInstance { static int x ; int y ; } Instanciation de 3 objets AppliInstance obj1 = new AppliInstance( ); AppliInstance obj2 = newAppliInstance( ); AppliInstance obj3 = newAppliInstance( );
131
class ApplistaticVar { public static int x =15 ; } class UtiliseApplistaticVar { int a ; void f( ) { a = ApplistaticVar.x ; ..... } }
La dfinition "static int x =15 ;" cre une variable de la classe ApplistaticVar, nomme x. L'instruction "a = ApplistaticVar.x ;" utilise la variable x comme variable de classe ApplistaticVar sans avoir instanci un objet de cette classe.
Nous pouvons utiliser la classe Math ( public sealed class Math ) qui contient des constantes et des fonctions mathmatiques courantes : public static const double E; // la constante e reprsente la base du logarithme nprien. public static const double PI; // la constante pi reprsente le rapport de la circonfrence d'un cercle son diamtre.
Mthode de classe Une mthode de classe est une mthode dont l'implmentation est la mme pour tous les objets de la classe, en fait la diffrence avec une mthode d'instance a lieu sur la catgorie des variables sur lesquelles ces mthodes agissent. De par leur dfinition les mthodes de classe ne peuvent travailler qu'avec des variables de classe, alors que les mthodes d'instances peuvent utiliser les deux catgories de variables. Un programme correct illustrant le discours : C#
class Exemple { public static int x ; int y ; public void f1(int a) { x = a; y = a; } public static void g1(int a) { x = a; } } class Utilise { public static void Main( ) { Exemple obj = new Exemple( ); obj.f1(10); System.Console.WriteLine("<f1(10)>obj.x="+obj.x); obj.g1(50); System.Console.WriteLine("<g1(50)>obj.x="+obj.x); } }
Explications
public void f1(int a) { x = a; //accs la variable de classe y = a ; //accs la variable d'instance } public static void g1(int a) { x = a; //accs la variable de classe y = a ; //engendrerait un erreur de compilation : accs une variable non static interdit ! } La mthode f1 accde toutes les variables de la classe Exemple, la mthode g1 n'accde qu'aux variables de classe (static et public). Aprs excution on obtient : <f1(10)>obj.x = 10 <g1(50)>obj.x = 50
page
132
1) - Les mthodes et les variables de classe sont prcdes obligatoirement du mot clef static. Elles jouent un rle semblable celui qui est attribu aux variables et aux sousroutines globales dans un langage impratif classique.
C# class Exemple1 { int a = 5; static int b = 19; void m1( ){...} static void m2( ) {...} }
Explications La variable a dans int a = 5; est une variable d'instance. La variable b dans static int b = 19; est une variable de classe. La mthode m2 dans static void m2( ) {...} est une mthode de classe.
2) - Pour utiliser une variable x1 ou une mthode meth1 de la classe Classe1, il suffit de d'crire Classe1.x1 ou bien Classe1.meth1.
C# class Exemple2 { public static int b = 19; public static void m2( ) {...} } class UtiliseExemple { Exemple2.b = 53; Exemple2.m2( ); ... }
Explications Dans la classe Exemple2, b est une variable de classe, m2 une mthode de classe. La classe UtiliseExemple fait appel la mthode m2 directement avec le nom de la classe, il en est de mme avec le champ b de la classe Exemple2
3) - Une variable de classe (prcde du mot clef static) est partage par tous les objets de la mme classe.
C#
class AppliStatic { public static int x = -58 ; public int y = 20 ; ... } class Utilise { public static void main(String [ ] arg) { AppliStatic obj1 = new AppliStatic( ); AppliStatic obj2 = new AppliStatic( ); AppliStatic obj3 = new AppliStatic( ); Premier pas dans .Net avec C# - ( rv 15.08.2005
Explications
Dans la classe AppliStatic x est une variable de classe, et y une variable d'instance. La classe Utilise cre 3 objets (obj1,obj2,obj3) de classe AppliStatic. L'instruction obj1.y = 100; est un accs au champ y de l'instance obj1. Ce n'est que le champ x de cet objet qui est modifi,les champs x des objets obj2 et obj3 restent inchangs
)
page
133
obj1.y = 100; obj1.x = 101; System.Console.WriteLine("obj1.x="+obj1.x); System.Console.WriteLine("obj1.y="+obj1.y); System.Console.WriteLine("obj2.x="+obj2.x); System.Console.WriteLine("obj2.y="+obj2.y); System.Console.WriteLine("obj3.x="+obj3.x); System.Console.WriteLine("obj3.y="+obj3.y); AppliStatic.x = 99; System.Console.WriteLine(AppliStatic.x=" +obj1.x); } }
Il y a deux manires d'accder la variable static x, : soit comme un champ de l'objet (accs semblable celui de y) : obj1.x = 101; soit comme une variable de classe proprement dite : AppliStatic.x = 99; Dans les deux cas cette variable x est modifie globalement et donc tous les champs x des 2 autres objets, obj2 et obj3 prennent la nouvelle valeur.
Au dbut lors de la cration des 3 objets, chacun des champs x vaut -58 et chacun des champs y vaut 20, l'affichage par System.out.println(...) donne les rsultats suivants qui dmontrent le partage de la variable x par tous les objets. Aprs excution : obj1.x = 101 obj1.y = 100 obj2.x = 101 obj2.y = 20 obj3.x = 101 obj3.y = 20 <AppliStatic>obj1.x = 99
4) - Une mthode de classe (prcde du mot clef static) ne peut utiliser que des variables de classe (prcdes du mot clef static) et jamais des variables d'instance.Une mthode d'instance peut accder aux deux catgories de variables
5) - Une mthode de classe (prcde du mot clef static) ne peut appeler (invoquer) que des mthodes de classe (prcdes du mot clef static).
C#
class AppliStatic { static int x = -58 ; int y = 20 ; void f1(int a) { AppliStatic.x = a; y=6; } } class Utilise { static void f2(int a) { AppliStatic.x = a; } Premier pas dans .Net avec C# - ( rv 15.08.2005
Explications
Nous reprenons l'exemple prcdent en ajoutant la classe AppliStatic une mthode interne f1 : void f1(int a) { AppliStatic.x = a; y=6; } Cette mthode accde la variable de classe comme un champ d'objet. Nous rajoutons la classe Utilise, un mthode static (mthode de classe) note f2: static void f2(int a) { AppliStatic.x = a; } Cette mthode accde elle aussi la variable de classe parce qu c'est une mthode static.
)
page
134
public static void Main( ) { AppliStatic obj1 = new AppliStatic( ); AppliStatic obj2 = new AppliStatic( ); AppliStatic obj3 = new AppliStatic( ); obj1.y = 100; obj1.x = 101; AppliStatic.x = 99; f2(101); obj1.f1(102); } }
Nous avons donc quatre manires d'accder la variable static x, : soit comme un champ de l'objet (accs semblable celui de y) : obj1.x = 101; soit comme une variable de classe proprement dite : AppliStatic.x = 99; soit par une mthode d'instance sur son champ : obj1.f1(102); soit par une mthode static (de classe) : f2(101);
Comme la mthode Main est static, elle peut invoquer la mthode f2 qui est aussi static.
Au paragraphe prcdent, nous avons indiqu que C# ne connaissait pas la notion de variable globale stricto sensu, mais en fait une variable static peut jouer le rle d'un variable globale pour un ensemble d'objets instancis partir de la mme classe.
page
135
Polymorphisme d'objet en
Plan gnral:
page
136
Le polymorphisme en C#
Rappel utile sur les notions de bases Il existe un concept essentiel en POO dsignant la capacit d'une hirarchie de classes fournir diffrentes implmentations de mthodes portant le mme nom et par corollaire la capacit qu'ont des objets enfants de modifier les comportements hrits de leur parents. Ce concept d'adaptation diffrentes "situations" se dnomme le polymorphisme qui peut tre implment de diffrentes manires.
Polymorphisme d'objet C'est une interchangeabilit entre variables d'objets de classes de la mme hirarchie sous certaines conditions, que dnommons le polymorphisme d'objet.
Polymorphisme par hritage de mthode Lorsqu'une classe enfant hrite d'une classe mre, des mthodes supplmentaires nouvelles peuvent tre implmentes dans la classe enfant mais aussi des mthodes des parents peuvent tre substitues pour obtenir des implmentations diffrentes.
Polymorphisme par hritage de classes abstraites Une classe abstraite est une classe qui ne peut pas s'instancier elle-mme ; elle doit tre hrite. Certains membres de la classe peuvent ne pas tre implments, et c'est la classe qui hrite de fournir cette implmentation.
Polymorphisme par implmentation d'interfaces Une interface dcrit la signature complte des membres qu'une classe doit implmenter, mais elle laisse l'implmentation de tous ces membres la charge de la classe d'implmentation de l'interface.
Polymorphisme d'objet en C# Soit une classe Mere et une classe Fille hritant de la classe Mere :
page
137
Les objets peuvent avoir des comportements polymorphes (s'adapter et se comporter diffrement selon leur utilisation) licites et des comportements polymorphes dangereux selon les langages. Dans un langage dont le modle objet est la rfrence (un objet est un couple : rfrence, bloc mmoire) comme C#, il y a dcouplage entre les actions statiques du compilateur et les actions dynamiques du systme d'excution, le compilateur protge statiquement des actions dynamiques sur les objets une fois crs. C'est la dclaration et l'utilisation des variables de rfrences qui autorise ou non les actions licites grce la compilation. Supposons que nous ayons dclar deux variables de rfrence, l'une de classe Mere, l'autre de classe Fille, une question qui se pose est la suivante : au cours du programme quelle genre d'affectation et d'instanciation est-on autoris effectuer sur chacune de ces variables dans un programme C#.
En C# : public class Mere { ..... } public class Fille : Mere { ..... }
L'hritage permet une variabilit entre variables d'objets de classes de la mme hirarchie, c'est cette variabilit que dnommons le polymorphisme d'objet.
Nous envisageons toutes les situations possibles et les valuons, les exemples explicatifs sont crits en C# (lorsqu'il y a discordance avec java ou Delphi autres langages, celle-ci est mentionne explicitement), il existe 3 possibilits diffrentes illustres par le schma cidessous.
page
138
L'instanciation et l'utilisation de rfrences dans le mme type L'affectation de rfrences : polymorphisme implicite L'affectation de rfrences : polymorphisme par transtypage d'objet La dernire de ces possibilits pose un problme d'excution lorsqu'elle mal employe !
En C# : Mere x , u ; Fille y , w ; ..... x = new Mere( ) ; // instanciation dans le type initial u = x ; // affectation de rfrences du mme type y = new Fille( ) ; // instanciation dans le type initial v = y ; // affectation de rfrences du mme type
page
139
En C# : Mere x ; Fille ObjF = new Fille( ) ; x = ObjF; // affectation de rfrences du type descendant implicite
Nous pouvons en effet dire que x peut se rfrer implicitement tout objet de classe Mere ou de toute classe hritant de la classe Mere.
fig - 1
fig - 2
Dans la figure fig-1 ci-dessus, une hirarchie de classes decendant toutes de la classe Mere, dans fig-2 ci-contre le schma montre une rfrence de type Mere qui peut 'pointer' vers n'importe quel objet de classe descendante (polymorphisme d'objet).
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
140
D'une faon gnrale vous pourrez toujours crire des affectations entre deux rfrences d'objets :
En C# : Classe1 x ; Classe2 y ; .......... x=y; si et seulement si Classe2 est une classe descendante de Classe1.
Exemple pratique tir du schma prcdent 1) Le polymorphisme d'objet est typiquement fait pour reprsenter des situations pratiques figures ci-dessous :
Une hirarchie de classe de vhicules descendant toutes de la classe mre Vehicule, on peut noncer le fait suivant : Un vhicule peut tre de plusieurs sortes : soit un croiseur, soit une voiture, soit un vhicule terrestre etc... Traduit en termes informatiques, si l'on dclare une rfrence de type vhicule (vehicule x) elle pourra pointer vers n'importe quel objet d'une des classe filles de la classe vehicule.
En C# : public class Vehicule { .......... } public class terrestre : Vehicule{ .......... } public class voiture : terrestre { .......... } public class marin : Vehicule { .......... } public class voilier : marin { .......... } public class croiseur : marin { .......... }
page
141
Polymorphisme implicite = cration d'objet de classe descendante rfrenc par une variable parent Ajoutons 2 classes la hirarchie des vhicules :
Partons de la situation pratique suivante : on cre un vhicule du type voiture , on cre une voiture de type berline , enfin on cre un break de type break
Traduit en termes informatiques : nous dclarons 3 rfrences x, y et z de type vehicule, voiture et break et nous crons 3 objets de classe voiture, berline et break. Comme il est possible de crer directement un objet de classe descendante partir d'une rfrence de classe mre, nous proposons les instanciations suivantes : on cre une voiture rfrence par la variable de classe vehicule, on cre une berline rfrence par la variable de classe voiture, enfin on cre un break rfrenc par la variable de classe break.
page
142
Il est alors possible de faire "pointer" la variable y (de type Fille) vers l'objet (de type Fille) auquel se rfre x en effectuant une affectation de rfrences : y = x ne sera pas accepte directement car statiquement les variables x et y ne sont pas du mme type, il faut indiquer au compilateur que l'on souhaite temporairement changer le type de la variable x afin de pouvoir effectuer l'affectation. Cette opration de changement temporaire, se dnomme le transtypage ( note en C# : y = (Fille)x ) :
page
143
Fille ObjF = new Fille( ) ; x = ObjF ; // x pointe vers un objet de type Fille y = (Fille) x ; // transtypage et affectation de rfrences du type ascendant explicite compatible dynamiquement.
Attention
La validit du transtypage n'est pas vrifie statiquement par le compilateur, donc si votre variable de rfrence pointe vers un objet qui n'a pas la mme nature que l'oprateur de transtypage, c'est lors de l'excution qu'il y aura production d'un message d'erreur indiquant le transtypage impossible. Il est donc impratif de tester l'appartenance la bonne classe de l'objet transtyper avant de le transtyper, les langages C#, Delphi et Java disposent d'un oprateur permettant de tester cette appartenance ou plutt l'appartenance une hirarchie de classes (oprateur is en C#). L'oprateur as est un oprateur de transtypage de rfrence d'objet semblable l'oprateur ( ). L'oprateur as fournit la valeur null en cas d'chec de conversion alors que l'oprateur ( ) lve une exception.
En C# : Mere x ; Fille y ; x = new Mere( ) ; // instanciation dans le type initial { affectation de rfrences du type ascendant explicite mais dangereuse si x est uniquement Mere : } Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
144
y = (Fille)x ; <--- erreur lors de l'excution ici {affectation accepte statiquement mais refuse dynamiquement, car x pointe vers un objet de type Mere }
Puisque x pointe vers un objet de type voiture toute variable de rfrence acceptera de pointer vers cet objet, en particulier la variable voiture aprs transtypage de la rfrence de x. En C# l'affectation s'crirait par application de l'oprateur de transtypage :
y = ( voiture ) x;
Pour pallier cet inconvnient de programmation pouvant lever des exceptions lors de l'excution, C# offre au programmeur la possibilit de tester l'appartenance d'un objet rfrenc par une variable quelconque une classe ou plutt une hirarchie de classe ; en C# cet oprateur se dnote is : L'oprateur "is" de C# est identique celui de Delphi : L'oprateur is, qui effectue une vrification de type dynamique, est utilis pour vrifier quelle est effectivement la classe d'un objet l'excution. L'expression : objet is classeT renvoie True si objet est une instance de la classe dsigne par classeT ou de l'un de ses descendants, et False sinon. Si objet a la valeur nil, le rsultat est False.
En C# : Mere x ; Fille y ; x = new Mere( ) ; // instanciation dans le type initial if ( x is Fille) // test d'appartenance de l'objet rfrenc par x la bonne classe y = (Fille)x ; Premier pas dans .Net avec C# - ( rv 15.08.2005
page
145
Lorsque vous dclarez une mthode meth avec un paramtre formel x de type ClasseT : void meth ( ClasseT x ); { ........ } Vous pouvez utiliser lors de l'appel de la mthode meth n'importe quel paramtre effectif de ClasseT ou bien d'une quelconque classe descendant de ClasseT et ensuite l'intrieur de la procdure vous transtypez le paramtre. Cet aspect est utilis en particulier en C# lors de la cration de gestionnaires d'vnements communs plusieurs composants :
private void meth1(object sender, System.EventArgs e) { if (sender is System.Windows.Forms.TextBox) (sender as TextBox).Text="Fin"; else if (sender is System.Windows.Forms.Label) (sender as Label).Text="ok"; // ou encore : if (sender is System.Windows.Forms.TextBox) ( (TextBox)sender ).Text="Fin"; else if (sender is System.Windows.Forms.Label) ( (Label)sender ).Text="ok";
} Autre exemple avec une mthode meth2 personnelle sur la hirarchie des vhicules :
private void meth2 ( vehicule Sender ); { if (Sender is voiture) ((voiture)Sender). ....... ; else if (Sender is voilier) ((voilier)Sender). ....... ; ............ }
page
146
Polymorphisme de mthode en
Plan gnral:
1. Le polymophisme de mthodes en C#
Rappel des notions de base 1.1 Surcharge et redfinition en C# 1.2 Liaison statique et masquage en C# 1.3 Liaison dynamique en C# 1.4 Comment opre le compilateur Rsum pratique
page
147
1. Le polymorphisme de mthode en C#
Nous avons vu au chapitre prcdent le polymorphisme d'objet, les mthodes peuvent tre elles aussi polymorphes. Nous avons vu comment Delphi mettait en oeuvre le polymorphisme d'objet et de mthode, nous voyons ici comment C# hrite une bonne part de Delphi pour ses comportements et de la souplesse de Java pour l'criture. Rappel de base
1.1 Surcharge La surcharge de mthode (polymorphisme statique de mthode) est une fonctionnalit classique des langages trs volus et en particulier des langages orients objet dont C# fait partie; elle consiste dans le fait qu'une classe peut disposer de plusieurs mthodes ayant le mme nom, mais avec des paramtres formels diffrents ou ventuellement un type de retour diffrent. On dit alors que ces mthodes n'ont pas la mme signature
On rappelle que la signature d'une mthode est forme par l'en-tte de la mthode avec ses paramtres formels et leur type. Nous avons dj utilis cette fonctionnalit prcdement dans le paragraphe sur les constructeurs, o la classe Un disposait de quatre constructeurs surchargs (quatre signatures diffrentes du constructeur) :
class Un { int a; public Un ( ) { a = 100; } public Un (int b ) Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
148
{ a = b;
Mais cette surcharge est possible aussi pour n'importe quelle mthode de la classe autre que le constructeur.
Le compilateur n'prouve aucune difficult lorsqu'il rencontre un appel l'une des versions surcharge d'une mthode, il cherche dans la dclaration de toutes les surcharges celle dont la signature (la dclaration des paramtres formels) concide avec les paramtres effectifs de l'appel. Remarque : Le polymorphisme statique (ou surcharge) de C# est syntaxiquement semblable celui de Java.
Programme C# excutable
class Un { int a; public Un (int b ) { a = b; } void f ( ) { a *=10; } void f ( int x ) { a +=10*x; } int f ( int x, char y ) { a = x+(int)y; return a; } } class AppliSurcharge { public static void main(String [ ] arg) { Un obj = new Un(15); System.Console.WriteLine("<cration> a ="+obj.a); obj.f( ); System.Console.WriteLine("<obj.f()> a ="+obj.a); obj.f(2); System.Console.WriteLine("<obj.f()> a ="+obj.a); obj.f(50,'a'); System.Console.WriteLine("<obj.f()> a ="+obj.a); } }
Explications
La mthode f de la classe Un est surcharge trois fois : void f ( ) { a *=10; } void f ( int x ) { a +=10*x; } int f ( int x, char y ) { a = x+(int)y; return a; } La mthode f de la classe Un peut donc tre appele par un objet instanci de cette classe sous l'une quelconque des trois formes : obj.f( ); pas de paramtre => choix : void f ( ) obj.f(2); paramtre int => choix : void f ( int x ) obj.f(50,'a'); deux paramtres, un int un char => choix : int f ( int x, char y )
page
149
Delphi
Un = class a : integer; public constructor methode( b : integer ); procedure f;overload; procedure f(x:integer);overload; function f(x:integer;y:char):integer;overload; end; implementation constructor Un.methode( b : integer ); begin a:=b end; procedure Un.f; begin a:=a*10; end; procedure Un.f(x:integer); begin a:=a+10*x end; function Un.f(x:integer;y:char):integer; begin a:=x+ord(y); result:= a end; procedure Main; var obj:Un; begin obj:=Un.methode(15); obj.f; Memo1.Lines.Add('obj.f='+inttostr(obj.a)); obj.f(2); Memo1.Lines.Add('obj.f(2)='+inttostr(obj.a)); obj.f(50,'a'); Memo1.Lines.Add('obj.f(50,''a'')='+inttostr(obj.a)); end; class Un { int a; public Un (int b ) { a = b; }
C#
public void f ( ) { a *=10; } public void f ( int x ) { a +=10*x; } public int f ( int x, char y ) { a = x+(int)y; return a; } }
class AppliSurcharge { public static void Main(String [ ] arg) { Un obj = new Un(15); System.Console.WriteLine("<cration> a ="+obj.a); obj.f( ); System.Console.WriteLine("<obj.f()> a ="+obj.a); obj.f(2); System.Console.WriteLine("<obj.f()> a ="+obj.a); obj.f(50,'a'); System.Console.WriteLine("<obj.f()> a ="+obj.a); } }
Redfinition
La redfinition de mthode (ou polymorphisme dynamique) est spcifique aux langages orients objet. Elle est mise en oeuvre lors de l'hritage d'une classe mre vers une classe fille dans le cas d'une mthode ayant la mme signature dans les deux classes. Dans ce cas les actions des l'appel de la mthode, dpendent du code inhrent chaque version de la mthode (celle de la classe mre, ou bien celle de la classe fille). Dans l'exemple ci-dessous, nous supposons que dans la classe PortesEtFenetres la mthode ouvrir(fenetre) explique le mode opratoire gnral d'ouverture d'une fentre, il est clair que dans les deux classes descendantes l'on doit "redfinir" le mode opratoire selon que l'on est en prsence d'une fentre la franaise, ou une fentre l'anglaise :
page
150
prcoce
et/ou
tardive
Ces deux actions sont diffrentes selon que le compilateur du langage met en place la laison du code de la mthode immdiatement lors de la compilation (liaison statique ou prcoce) ou bien lorsque le code est li lors de l'excution (laison dynamique ou tardive). Ce phnomne se dnomme la rpartition des mthodes. Le terme de rpartition fait rfrence la faon dont un programme dtermine o il doit rechercher une mthode lorsqu'il rencontre un appel cette mthode. Le code qui appelle une mthode ressemble un appel classique de mthode. Mais les classes ont des faons diffrentes de rpartir les mthodes.
Le langage C# supporte d'une manire identique Delphi, ces deux modes de laison du code, la liaison statique tant comme en Delphi le mode par dfaut. Le dveloppeur Java sera plus dcontenanc sur ce sujet, car la laison statique en Java n'existe que pour les methodes de classe static, de plus la laison du code par dfaut est dynamique en Java.
Donc en C# comme en Delphi , des mots clefs comme virtual et override sont ncessaires pour la redfinition de mthode, ils sont utiliss strictement de la mme manire qu'en Delphi.
page
151
1.2 Liaison statique et masquage en C# Toute mthode C# qui n'est prcde d'aucun des deux qualificateurs virtual ou override est liaison statique. Le compilateur dtermine l'adresse exacte de la mthode et lie la mthode au moment de la compilation. L'avantage principal des mthodes statiques est que leur rpartition est trs rapide. Comme le compilateur peut dterminer l'adresse exacte de la mthode, il la lie directement (les mthodes virtuelles, au contraire, utilisent un moyen indirect pour rcuprer l'adresse des mthodes l'excution, moyen qui ncessite plus de temps). Une mthode statique ne change pas lorsqu'elle est transmise en hritage une autre classe. Si vous dclarez une classe qui inclut une mthode statique, puis en drivez une nouvelle classe, la classe drive partage exactement la mme mthode situe la mme adresse. Cela signifie qu'il est impossible de redfinir les mthodes statiques; une mthode statique fait toujours exactement la mme chose, quelque soit la classe dans laquelle elle est appele. Si vous dclarez dans une classe drive une mthode ayant le mme nom qu'une mthode statique de la classe anctre, la nouvelle mthode remplace simplement (on dit aussi masque) la mthode hrite dans la classe drive. Comparaison masquage en Delphi et C# : Delphi
type ClasseMere = class x : integer; procedure f (a:integer); end; ClasseFille = class ( ClasseMere ) y : integer; procedure f (a:integer);//masquage end; implementation procedure ClasseMere.f (a:integer); begin... end; procedure ClasseFille.f (a:integer); begin... end;
C#
public class ClasseMere { int x = 10; public void f ( int a) { x +=a; } } public class ClasseFille : ClasseMere { int y = 20; public void f ( int a) //masquage { x +=a*10+y; } }
Remarque importante : L'exprience montre que les tudiants comprennent immdiatement le masquage lorsque le polymorphisme d'objet n'est pas prsent. Ci-dessous un exemple de classe UtiliseMereFille qui instancie et utilise dans le mme type un objet de classe ClasseMere et un objet de classe ClasseFille :
page
152
Lors de la compilation de l'instruction M.meth(10), c'est le code de la mthode meth de la classe ClasseMere qui est li avec comme paramtre par valeur 10; ce qui donnera la valeur 11 au champ x de l'objet M. Lors de la compilation de l'instruction F.meth(10), c'est le code de la mthode meth de la classe ClasseFille qui masque celui de la classe parent et qui est donc li avec comme paramtre par valeur 10; ce qui donnera la valeur 101 au champ x de l'objet F.
Pour bien comprendre toute la porte du masquage statique et les risques de mauvaises interprtations, il faut tudier le mme exemple lgrement modifi en incluant le cas du polymorphisme d'objet, plus prcisment le polymorphisme d'objet implicite. Dans l'exemple prcdent nous instancions la variable ClasseMere M en un objet de classe ClasseFille (polymorphisme implicite d'objet) soient les instructions ClasseMere M ; M = new ClasseFille ( ) ; Une erreur courante est de croire que dans ces conditions, dans l'instruction M.meth(10) c'est la mthode meth(int a) de la classe ClasseFille (en particulier si l'on ne connat que Java qui ne pratique pas le masquage) :
page
153
Que fait alors le compilateur C# dans ce cas ? : il ralise une liaison statique : Lors de la compilation de l'instruction M.meth(10), c'est le code de la mthode meth(int a) de la classe ClasseMere qui est li, car la rfrence M a t dclare de type ClasseMere et peu importe dans quelle classe elle a t instancie ( avec comme paramtre par valeur 10; ce qui donnera la valeur 11 au champ x de l'objet M). Lors de la compilation de l'instruction F.meth(10), c'est le code de la mthode meth de la classe ClasseFille comme dans l'exemple prcdent (avec comme paramtre par valeur 10; ce qui donnera la valeur 101 au champ x de l'objet F).
page
154
Afin que le programmeur soit bien conscient d'un effet de masquage d'une mthode hrite par une mthode locale, le compilateur C# envoie, comme le compilateur Delphi, un message d'avertissement indiquant une possibilit de manque de cohrence smantique ou un masquage. S'il s'agit d'un masquage voulu, le petit plus apport par le langage C# est la proposition que vous fait le compilateur de l'utilisation optionnelle du mot clef new qualifiant la nouvelle mthode masquant la mthode parent. Cette criture amliore la lisibilit du programme et permet de se rendre compte que l'on travaille avec une liaison statique. Ci-dessous deux critures quivalentes du masquage de la mthode meth de la classe ClasseMere : masquage C#
public class ClasseMere { int x = 10; public void meth ( int a) //liaison statique { x +=a; } } public class ClasseFille : ClasseMere { public void meth ( int a) //masquage { x +=a*10+y; } }
page
155
L'exemple ci-dessous rcapitule les notions de masquage et de surcharge en C# : public class ClasseMere { int x = 1; public void meth1 ( int a) { x += a ; } public void meth1 ( int a , int b) { x += a*b ; } } public class ClasseFille : ClasseMere { public new void meth1 ( int a) { x += a*100 ; } public void meth1 ( int a , int b , int c) { x += a*b*c ; } } public class UtiliseMereFille { public static void Main (string [ ] args) { ClasseMere M ; ClasseFille F ; M = new ClasseFille ( ) ; F = new ClasseFille ( ) ; M.meth1 (10) ; <--- meth1(int a) de ClasseMere M.meth1 (10,5) ; <--- meth1(int a, int b) de ClasseMere M.meth1 (10,5,2) ; <--- erreur! n'existe pas dans ClasseMere . F.meth1 (10) ; <--- meth1(int a) de ClasseFille F.meth1 (10,5) ; <--- meth1(int a, int b) de ClasseFille F.meth1 (10,5,2) ; <--- meth1(int a, int b, int c) de ClasseFille } 1.3 Liaison dynamique (ou redfinition) en C# Dans l'exemple ci-dessous la classe ClasseFille qui hrite de la classe ClasseMere, redfini la mthode f de sa classe mre : Comparaison redfinition Delphi et C# : Delphi
type ClasseMere = class x : integer; procedure f (a:integer);virtual;//autorisation procedure g(a,b:integer); end; ClasseFille = class ( ClasseMere ) y : integer; procedure f (a:integer);override;//redfinition procedure g1(a,b:integer); end; implementation procedure ClasseMere.f (a:integer); begin... end; procedure ClasseMere.g(a,b:integer); begin... end; Premier pas dans .Net avec C# - ( rv 15.08.2005
)
C#
class ClasseMere { int x = 10; public virtual void f ( int a) { x +=a; } void g ( int a, int b) { x +=a*b; } } class ClasseFille extends ClasseMere { int y = 20; public override void f ( int a) //redfinition { x +=a; } void g1 (int a, int b) //nouvelle mthode { ...... } } page
156
Comme delphi, C# peut combiner la surcharge et la redfinition sur une mme mthode, c'est pourquoi nous pouvons parler de surcharge hrite : C#
class ClasseMere { public int x = 10; public virtual void f ( int a) { x +=a; } public virtual void g ( int a, int b) { x +=a*b; } } class ClasseFille : ClasseMere { int y = 20; public override void f ( int a) //redfinition { x +=a; } public virtual void g (char b) //surcharge de g { x +=b*y; } }
1.4 Comment opre le compilateur C# C'est le compilateur C# qui fait tout le travail de recherche de la bonne mthode. Prenons un objet obj de classe Classe1, lorsque le compilateur C# trouve une instruction du genre "obj.method1(paramtres effectifs);", sa dmarche d'analyse est semblable celle du compilateur Delphi, il cherche dans l'ordre suivant : Y-a-t-il dans Classe1, une mthode qui se nomme method1 ayant une signature identique aux paramtres effectifs ? si oui c'est la mthode ayant cette signature qui est appele, si non le compilateur remonte dans la hierarchie des classes mres de Classe1 en posant la mme question rcursivement jusqu' ce qu'il termine sur la classe Object. Si aucune mthode ayant cette signature n'est trouve il signale une erreur.
Soit partir de l'exemple l'exemple prcdent les instructions suivantes : ClasseFille obj = new ClasseFille( ); obj.g(-3,8);
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
157
obj.g('h'); Le compilateur C# applique la dmarche d'analyse dcrite, l'instruction "obj.g(-3,8);". Ne trouvant pas dans ClasseFille de mthode ayant la bonne signature (signature = deux entiers) , le compilateur remonte dans la classe mre ClasseMere et trouve une mthode " void g ( int a, int b) " de la classe ClasseMere ayant la bonne signature (signature = deux entiers), il procde alors l'appel de cette mthode sur les paramtres effectifs (-3,8). Dans le cas de l'instruction obj.g('h'); , le compilateur trouve immdiatement dans ClasseFille la mthode " void g (char b) " ayant la bonne signature, c'est donc elle qui est appele sur le paramtre effectif 'h'. Le compilateur consulte les mta-donnes (informations de description) de l'assemblage en cours ( applicationXXX.exe ), plus particulirement les mtadonnes de type qui sont stockes au fur et mesure dans de nombreuses tables. Nous figurons ci-dessous deux tables de dfinition importantes relativement au polymorphisme de mthode MethodDef et TypeDef utilises par le compilateur.
page
158
Le masquage ne se produit que dans l'hritage d'une classe, par redfinition (liaison statique) de la mthode mre par une mthode fille (ayant la mme signature). Toute mthode est considre liaison statique sauf si vous la dclarez autrement.
classe fille class ClasseB : ClasseA { public new void meth01 ( ) { attrA = 1000 ; } public void meth02 ( ) { meth01 ( ); } }
ClasseA
{ public int attrA ; private int attrXA ; public void meth01 ( ) { attrA = 57 ; } }
La mthode meth02 ( ) invoque la mthode meth01 ( ) de la classe ClasseB. Il est impossible directement de faire appel la mthode meth01 ( ) de la classe mre ClasseA car celle-ci est masque dans la classe fille.
page
159
Il existe en C# un mcanisme dclench par un mot clef qui permet d'accder la classe mre (classe immdiatement au dessus): ce mot est base. Le mot clef base est utilis pour accder tous les membres visibles de la classe mre partir d'une classe fille drive directement de cette classe mre ( la super-classe en Java).
Ce mot clef base est trs semblable au mot clef inherited de Delphi qui joue le mme rle sur les mthodes et les proprits (il est en fait plus proche du mot clef super de Java car il ne remonte qu' la classe mre), il permet l'appel d'une mthode de la classe de base qui a t substitue (masque ou redfinie) par une autre mthode dans la classe fille. Exemple :
Remarques : Le fait d'utiliser le mot clef base partir d'une mthode statique constitue une erreur. base est utile pour spcifier un constructeur de classe mre lors de la cration d'instances de la classe fille.
Nous dveloppons ci-dessous l'utilisation du mot clef base afin d'initialiser un constructeur.
2.2 Initialiseur de constructeur this et base Semblablement Delphi et Java, tous les constructeurs d'instance C# autorisent l'appel d'un autre constructeur d'instance immdiatement avant le corps du constructeur, cet appel est dnomm l'initialiseur du constructeur, en Delphi cet appel doit tre explicite, en C# et en Java cet appel peut tre implicite. Rappelons que comme en Java o dans toute classe ne contenant aucun constructeur, en C# un constructeur sans paramtres par dfaut est implicitement dfini :
160
Remarque : Lors de l'hritage d'une classe fille, diffremment Delphi et Java, si un constructeur d'instance C# de la classe fille ne fait pas figurer explicitement d'initialiseur de constructeur, c'est qu'en fait un initialiseur de constructeur ayant la forme base( ) lui a t fourni implicitement.
Soit par suite une classe fille ClasseB drivant de ClasseA possdant elle aussi 2 constructeurs, les deux dclarations ci-dessous sont quivalentes : Initialiseur implicite class ClasseB : ClasseA { /* premier constructeur */ public ClasseB ( ) { attrStrA = "..." ; } /* second constructeur */ public ClasseB ( string s ) { attrStrA = s ; } } Initialiseur explicite quivalent class ClasseB : ClasseA { /* premier constructeur */ public ClasseB ( ) : base( ) { attrStrA = "..." ; } /* second constructeur */ public ClasseB ( string s ) : base( ) { attrStrA = s ; } }
Dans les deux cas le corps du constructeur de la classe fille est initialis par un premier appel au constructeur de la classe mre ( ), en l'occurrence << public ClasseA ( ) ... /* premier constructeur */ >>
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
161
Remarque : De mme pour une classe fille, C# comme Java, tout constructeur de la classe fille appelle implicitement et automatiquement le constructeur par dfaut (celui sans paramtres) de la classe mre.
Exemple : vous crivez votre code comme ceci : class ClasseA { public int attrA ; public string attrStrA ; } class ClasseB : ClasseA { } } Le constructeur de ClasseA sans paramtres est implicitement dclar par le compilateur. class ClasseB : ClasseA { public ClasseB ( ): base( ) { } } il est complt implicitement ainsi : class ClasseA { public int attrA ; public string attrStrA ; public ClasseA ( ) { }
Si la classe mre ne possde pas de constructeur par dfaut, le compilateur engendre un message d'erreur : vous crivez votre code comme ceci : class ClasseA { public int attrA ; public string attrStrA ; public ClasseA ( int a ) { } class ClasseB : ClasseA { public ClasseB ( ) { //..... } }
La classe de base ClasseA ne comporte qu'un seul constructeur explicite un paramtre. Le constructeur sans paramtres n'existe que si vous le dclarez explicitement, ou bien si la classe ne possde pas de constructeur explicitement dclar.
il est complt implicitement ainsi : class ClasseA { public int attrA ; public string attrStrA ; public ClasseA ( int a ) { } } class ClasseB : ClasseA { public ClasseB ( ): base( ) { // .... } }
L'initialiseur implicite base( ) renvoie le compilateur chercher dans la classe de base un constructeur sans paramtres. Or il n'existe pas dans la classe de base (ClasseA) de constructeur par dfaut sans paramtres. Donc la tentative choue !
Le message d'erreur sur la ligne " public ClasseB ( ) { ", est le suivant : [C# Erreur] Class.cs(54): Aucune surcharge pour la mthode 'ClasseA' ne prend d'arguments '0'
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
162
Remarques : Donc sans initialiseur explicite, tout objet de classe fille ClasseB est minima et par dfaut, instanci comme un objet de classe de base ClasseA. Lorsque l'on veut invoquer dans un constructeur d'une classe donne un autre constructeur de cette mme classe tant donn que tous les constructeurs ont le mme nom, il faut utiliser le mot clef this comme nom d'appel.
Exemple : Reprenons la mme classe ClasseA possdant 2 constructeurs et la classe ClasseB drivant de ClasseA, nous marquons les actions des constructeurs par une chane indiquant le numro du constructeur invoqu ainsi que sa classe : class ClasseA { public int attrA ; public string attrStrA ; public ClasseA ( ) { /* premier constructeur */ attrA = 57 ; } public ClasseA ( string s ) { /* second constructeur */ attrStrA = s +"...classeA1..." ; } } Ci-dessous la ClasseB crite de deux faons quivalentes : avec initialiseurs implicites-explicites
class ClasseB : ClasseA { /* premier constructeur */ public ClasseB ( ) { attrA = 100+attrA ; } /* second constructeur */ public ClasseB ( string s ) { attrStrA = attrStrA +s+"...classeB2..." ; } /* troisime constructeur */ public ClasseB ( int x , string ch ) : this( ch ) { attrStrA = attrStrA+"...classeB3..." ; } /* quatrime constructeur */ public ClasseB ( char x , string ch ) : base( ch ) { attrStrA = attrStrA+"...classeB4..." ; Premier pas dans .Net avec C# - ( rv 15.08.2005
)
163
} } } }
attrStrA = attrStrA+"...classeB4..." ;
Crons quatre objets de ClasseB, chacun avec l'un des 4 constructeurs de la ClasseB : class MaClass { static void Main(string[] args) { int x=68; ClasseB ObjetB= new ClasseB( ); System.Console.WriteLine(ObjetB.attrA); ObjetB= new ClasseB(x,"aaa"); System.Console.WriteLine(ObjetB.attrStrA); ObjetB= new ClasseB((char)x,"bbb"); System.Console.WriteLine(ObjetB.attrStrA); ObjetB= new ClasseB("ccc"); System.Console.WriteLine(ObjetB.attrStrA); System.Console.ReadLine(); } } Voici le rsultat console de l'excution de ce programme :
Explications :
public ClasseB ( ) { attrA = 100+attrA ; } ClasseB ObjetB= new ClasseB( ); System.Console.WriteLine(ObjetB.attrA); C# slectionne la signature du premier constructeur de la ClasseB (le constructeur sans paramtres). C# appelle d'abord implicitement le constructeur sans paramtre de la classe mre ( : base( ) ) public ClasseA ( ) { attrA = 57 ; } Le champ attrA vaut 57, puis C# excute le corps du constructeur : attrA = 100+attrA ; attrA vaut 100+57 = 157 public ClasseB ( string s ) { attrStrA = attrStrA +s+"...classeB2..." ; } public ClasseB ( int x , string ch ) : this( ch ) { Premier pas dans .Net avec C# - ( rv 15.08.2005 C# slectionne la signature du troisime constructeur de la ClasseB (le constructeur avec paramtres : int x , string ch). C# appelle d'abord explicitement le constructeur local de la classeB avec un paramtre de type string ( le second
)
page
164
constructeur de la ClasseB ) s = "aaa" ; public ClasseB ( string s ) { attrStrA = attrStrA +s+"...classeB2..." ; } Le champ attrStrA vaut "aaa...classeB2...", puis C# excute le corps du constructeur : attrStrA = attrStrA+"...classeB3..." ; attrStrA vaut "aaa...classeB2......classeB3..."
public ClasseB ( char x , string ch ) : base( ch ) { attrStrA = attrStrA+"...classeB4..." ; } ObjetB= new ClasseB((char)x,"bbb"); System.Console.WriteLine(ObjetB.attrStrA);
C# slectionne la signature du quatrime constructeur de la ClasseB (le constructeur avec paramtres : char x , string ch). C# appelle d'abord explicitement le constructeur de la classe mre (de base) classeA avec un paramtre de type string ( ici le second constructeur de la ClasseA ) s = "bbb" ; public ClasseA ( string s ) { attrStrA = s +"...classeA1..." ; } Le champ attrStrA vaut "bbb...classeA1..." puis C# excute le corps du constructeur : attrStrA = attrStrA+"...classeB4..." ; attrStrA vaut "bbb...classeA1......classeB4..."
La dernire instanciation : ObjetB= new ClasseB("ccc"); est strictement identique la premire mais avec appel au second constructeur.
Java
class ClasseA { public int attrA ; public String attrStrA = "" ; public ClasseA ( ) { attrA = 57 ; } public ClasseA ( String s ) { attrStrA = s +"...classeA1..." ; }
C#
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
Delphi
page
165
class ClasseA { public int attrA ; public string attrStrA ; public ClasseA ( ) { attrA = 57 ; } public ClasseA ( string s ) { attrStrA = s +"...classeA1..." ; } }
ClasseA = class public attrA : integer ; attrStrA: string ; constructor Creer;overload; constructor Creer(s:string); overload; end; constructor ClasseA.Creer begin attrA := 57 ; end; constructor ClasseA.Creer(s:string); begin attrStrA := s +'...classeA1...' ; end;
Exemple classe fille : C# class ClasseB : ClasseA { /* premier constructeur */ public ClasseB ( ) { attrA = 100+attrA ; } /* second constructeur */ public ClasseB ( string s ) { attrStrA = attrStrA +s+"...classeB2..." ; } Java class ClasseB extends ClasseA { /* premier constructeur */ public ClasseB ( ) { super( ) ; attrA = 100+attrA ; } /* second constructeur */ public ClasseB ( String s ) { super( ) ; attrStrA = attrStrA +s+"...classeB2..." ; } /* troisime constructeur */ public ClasseB ( int x , String ch ) { this( ch ) ; attrStrA = attrStrA+"...classeB3..." ; } /* quatrime constructeur */ public ClasseB ( char x , String ch ) { super( ch ) ; attrStrA = attrStrA+"...classeB4..." ; } }
C#
class ClasseB : ClasseA Premier pas dans .Net avec C# - ( rv 15.08.2005
)
Delphi
ClasseB = class( ClasseA ) page
166
public constructor Creer;overload; constructor Creer(s:string); overload; constructor Creer(x:integer;ch:string); overload; constructor Creer(x:char;ch:string); overload; end; /* premier constructeur */ constructor ClasseB.Creer; begin inherited ; attrA := 100+attrA ; end; /* second constructeur */ constructor ClasseB.Creer(s:string); begin inherited Creer ; attrStrA := attrStrA +s+'...classeB2...' ; end; /* troisime constructeur */ constructor ClasseB.Creer(x:integer;ch:string); begin Creer( ch ) ; attrStrA := attrStrA+'...classeB3...' ; end; /* quatrime constructeur */ constructor ClasseB.Creer(x:integer;ch:string); begin inherited Creer( ch ) ; attrStrA := attrStrA+'...classeB4...' ; end;
2.4 Traitement d'un exercice complet soit une hirarchie de classe de vhicules :
167
Supposons que la classe Vhicule contienne 3 mthodes, qu'elle n'implmente pas la mthode Dmarrer qui est alors abstraite, qu'elle fournit et implante vide la mthode "RpartirPassagers" de rpartition des passagers bord du vhicule, qu'elle fournit aussi et implante vide une mthode "PriodicitMaintenance" renvoyant la priodicit de la maintenance obligatoire du vhicule.
La classe Vhicule est abstraite : car la mthode Dmarrer est abstraite et sert de "modle" aux futures classes drivant de Vhicule. Supposons que l'on implmente le comportement prcis du genre de dmarrage dans les classes Voiture , Voilier et Croiseur .
Dans cette hirarchie, les classes Terrestre et Marin hritent de la classe Vehicule, mais n'implmentent pas la mthode abstraite Dmarrer, ce sont donc par construction des classes abstraites elles aussi. Elles implantent chacune la mthode "RpartirPassagers" (fonction de la forme, du nombre de places, du personnel charg de s'occuper de faire fonctionner le vhicule...) et la mthode "PriodicitMaintenance" (fonction du nombre de km ou miles parcourus, du nombre d'heures d'activits,...) Les classes Voiture , Voilier et Croiseur savent par hritage direct comment rpartir leur ventuels passagers et quand effectuer une maintenance, chacune d'elle implmente son propre comportement de dmarrage.
Quelques implantations en C#
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
168
Une implmentation de la classe Voiture avec des mthodes non virtuelles (Version-1) :
abstract class Vehicule { public abstract void Demarrer( ); public void RpartirPassagers( ){} public void PriodicitMaintenance( ){} } La mthode Dmarrer de la classe Vehicule est abstraite et donc automatiquement virtuelle ( liaison dynamique). Les mthodes RpartirPassagers et PriodicitMaintenance sont concrtes mais avec un corps vide. Ces deux mthodes sont non virtuelles ( liaison statique) abstract class Terrestre : Vehicule { public new void RpartirPassagers( ){ //...} public new void PriodicitMaintenance( ){ //...} } La classe Terrestre est abstraite car elle n'implmente pas la mthode abstraite Dmarrer. Les deux mthodes dclares dans la classe Terrestre masquent chacune la mthode du mme nom de la classe Vehicule (d'o l'utilisation du mot clef new)
La classe Voiture est la seule tre instanciable car toutes ses mthodes sont concrtes : Elle hrite des 2 mthodes implmentes de la classe Terrestre et elle implante (redfinition avec override) la mthode abstraite de l'anctre.
La classe Voiture est la seule tre instanciable car toutes ses mthodes sont concrtes : Elle hrite des 2 mthodes implmentes de la classe Terrestre et elle implante (redfinition avec override) la mthode abstraite de l'anctre.
Supposons que les mthodes non virtuelles RpartirPassagers et PriodicitMaintenance sont implantes compltement dans la classe Vehicule, puis reprenons la classe Terrestre en masquant ces deux mthodes : abstract class Vehicule { public abstract void Demarrer( ); public void RpartirPassagers( ){
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
169
//....} public void PriodicitMaintenance( ){ //....} } abstract class Terrestre : Vehicule { public new void RpartirPassagers( ){ //...} public new void PriodicitMaintenance( ){ //...} } Question Nous voulons qu'un vhicule Terrestre rpartisse ses passagers ainsi : 1) d'abord comme tous les objets de classe Vehicule, 2) ensuite qu'il rajoute un comportement qui lui est propre Rponse La mthode RpartirPassagers est non virtuelle, elle masque la mthode mre du mme nom, si nous voulons accder au comportement de base d'un vhicule, il nous faut utiliser le mot clef base permettant d'accder aux membres de la classe mre : abstract class Terrestre : Vehicule { public new void RpartirPassagers( ){ base.RpartirPassagers( ); //... 1; comportement du parent //... 2 comportement propre } public new void PriodicitMaintenance( ){ //...} }
Il est conseill au lecteur de reprendre le mme schma et d'implanter l'identique les autres classe de la hirarchie pour la branche des vhicules Marin.
page
170
Polymorphisme et interfaces en
Plan gnral:
Rappels sur la notion d'interface 1.Concepts et vocabulaire d'interface en C# les interfaces peuvent constituer des hirarchies et hriter entre elles la construction d'un objet necessite une classe implmentant l'interface les implmentations d'un membre d'interface sont en gnral public les implmentations explicites d'un membre d'interface sont spciales 1.1 Spcification d'un exemple complet 1.1.A Une classe abstraite 1.1.B Une interface 1.1.C Une simulation d'hritage multiple 1.1.D Encore une classe abstraite, mais plus concrte 1.1.E Une classe concrte 1.2 Implantation en C# de l'exemple 1.2.A La classe abstraite 1.2.B L'interface 1.2.C La simulation d'hritage multiple 1.2.D La nouvelle classe abstraite 1.2.E La classe concrte 2. Analyse du code de liaison de la solution prcdente 2.1 Le code de la classe Vehicule 2.2 Le code de l'interface IVehicule 2.3 Le code de la classe UnVehicule 2.4 Le code de la classe Terrestre 2.5 Le code de la classe Voiture 3. Cohrence de C# entre les notions de classe et d'interface une classe peut implmenter plusieures interfaces les interfaces et les classes respectent les mmes rgles de polymorphisme les conflits de noms dans les interfaces
page
171
Quelques conseils gnraux prodigus par des dveloppeurs professionnels (microsoft, Borland, Sun) :
Les interfaces bien conues sont plutt petites et indpendantes les unes des autres. Un trop grand nombre de fonctions rend l'interface peu maniable. Si une modification s'avre ncessaire, une nouvelle interface doit tre cre. Si la fonctionnalit que vous crez peut tre utile de nombreux objets diffrents, faites appel une interface. Si vous crez des fonctionnalits sous la forme de petits morceaux concis, faites appel aux interfaces. L'utilisation d'interfaces permet d'envisager une conception qui spare la manire d'utiliser une classe de la manire dont elle est implmente. Deux classes peuvent partager la mme interface sans descendre ncessairement de la mme classe de base.
1. Vocabulaire et concepts en C#
Une interface C# est un contrat, elle peut contenir des proprits, des mthodes , des vnements ou des indexeurs, mais ne doit contenir aucun champ ou attribut. Une interface ne peut pas contenir des mthodes dj implmentes. Une interface ne contient que des signatures (proprits, mthodes ). Tous les membres d'une interface sont automatiquement public. Une interface est hritable. On peut construire une hirarchie d'interfaces. Pour pouvoir construire un objet partir d'une interface, il faut dfinir une classe non abstraite implmentant tous les membres de l'interface.
Les interfaces peuvent constituer des hirarchies et hriter entre elles soient l'interface IinterfA et l'interface IinterfB hritant de IinterfA . On pourra employer aussi le vocable d'tendre au sens o l'interface drive IinterfB "tend" le contrat (augmente le nombre de membres contractuels) de l'interface IinterfA . Dans tous les cas il faut une classe pour implmenter ces contrats :
page
172
code C# : interface IinterfA { event TypEvent1 OnTruc ; // vnement char Prop1 // proprit { get ; set ; } void meth1 ( ); // mthode int meth2 ( ); // mthode char meth3 ( int x ); // mthode } interface IinterfB : IinterfA { event TypEvent2 OnChose ; // vnement string Prop2 // proprit { get ; } void meth4 ( ); // mthode }
La construction d'un objet necessite une classe implmentant l'interface La classe ClasseY doit implmenter tous les 8 membres provenant de l'hritage des interfaces : les 2 vnements OnTruc et Onchose, les 2 proprits Prop1 et Prop2, et enfin les 4 mthodes meth1, ... , meth4 . La classe ClasseY est une classe concrte (instanciable), un objet de cette classe possde en particulier tous les membres de l'interface IinterfB (et donc IinterfA car IinterfB hrite de IinterfA)
class ClasseY : ClasseX , IinterfB { // ... implmente tous les membres de InterfB } // construction et utilisation d'un objet : ClasseY Obj = new ClasseY( ) ; Obj.Prop1 = 'a' ; // proprit hrite de InterfA string s = Obj.prop2 ; // proprit hrite de InterfB Obj.meth1( ) ; // mthode hrite de InterfA etc ...
Si, ClasseY n'implmente par exemple que 7 membres sur les 8 alors C# considre que c'est une classe abstraite et vous devez la dclarer abstract :
abstract class ClasseY : ClasseX , IinterfB { // ...n' implmente que certains membres de InterfB } class ClasseZ : ClasseY { // ... implmente le reste des membres de InterfB } // ... construction d'un objet : ClasseZ Obj = new ClasseZ( ) ; Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
173
Obj.Prop1 = 'a' ; // proprit hrite de InterfA string s = Obj.prop2 ; // proprit hrite de InterfB Obj.meth1( ) ; // mthode hrite de InterfA etc ...
Les implmentations des membres d'une interface sont en gnral public Par dfaut sans dclaration explicite, les membres (indexeurs, proprits, vnements, mthodes) d'une interface ne ncessitent pas de qualificateurs de visibilit car ils sont automatiquement dclars par C# comme tant de visibilit public, contrairement une classe ou par dfaut les membres sont du niveau assembly. Ce qui signifie que toute classe qui implmente un membre de l'interface doit obligatoirement le qualifier de public sous peine d'avoir un message d'erreur du compilateur, dans cette ventualit le membre devient un membre d'instance. Comme la signature de la mthode n'est qu'un contrat, le mode de liaison du membre n'est pas fix; la classe qui implmente le membre peut alors choisir de l'implmenter soit en liaison statique, soit en liaison dynamique. Soient une interface IinterfA et une classe ClasseX hritant directement de la classe Object et implmentant cette interface, ci-dessous les deux seules implmentations possibles d'une mthode avec un rappel sur les redfinitions possibles dans des classes descendantes :
interface IinterfA { void meth1 ( ); // mthode de l'interface }
Redfinitions possibles
class ClasseY : ClasseX { public new void meth1 ( ){ ... } masque statiquement celle de ClasseX } class ClasseZ : ClasseX { public new virtual void meth1 ( ){ ... } masque dynamiquement celle de ClasseX }
Redfinitions possibles
class ClasseY : ClasseX { public new void meth1 ( ){ ... } masque statiquement celle de ClasseX } class ClasseZ : ClasseX { public new virtual void meth1 ( ){ ... } masque dynamiquement celle de ClasseX } class ClasseT : ClasseX { public override void meth1 ( ){ ... } redfinit dynamiquement celle de ClasseX }
Les implmentations explicites des membres d'une interface sont spciales Une classe qui implmente une interface peut aussi implmenter de faon explicite un membre de cette interface. Lorsqu'un membre est implment de faon explicite (le nom du membre
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
174
est prfix par le nom de l'interface : InterfaceXxx.NomDuMembre ), il n'est pas accessible via une rfrence de classe, il est alors invisible tout objet instanci partir de la classe o il est dfini. Un membre implment de faon explicite n'est donc pas un membre d'instance. Pour utiliser un membre d'interface implment de manire explicite, il faut utiliser une rfrence sur cette interface et non une rfrence de classe; il devient visible uniquement travers une rfrence sur l'interface. Nous reprenons le mme tableau de diffrentes implmentations de la mthode void meth1 ( ) en ajoutant une nouvelle mthode void meth2 ( int x ) que nous implmentons explicitement dans les classes drives : interface IinterfA { void meth2 ( int x ); // mthode de l'interface void meth1 ( ); // mthode de l'interface } Nous implmentons l'interface IinterfA dans la classe ClasseX : 1) nous implmentons explicitement void meth2 ( int x ), 2) nous implmentons void meth1 ( ) en mthode virtuelle.
interface IinterfA { void meth2 ( int x ); // mthode de l'interface void meth1 ( ); // mthode de l'interface } class ClasseX : IinterfA { void IinterfA.meth2 ( int x ){ ... } public virtual void meth1 ( ){ ... } } Comprenons bien que la classe ClasseX ne possde pas cet instant une mthode d'instance qui se nommerait meth2, par exemple si dans la mthode virtuelle meth1 nous utilisons le paramtre implicite this qui est une rfrence la future instance, l'audit de code de C#Builder nous renvoie 7 mthodes comme visibles (6 provenant de la classe mre Object et une seule provenant de ClasseX), la mthode IinterfA.meth2 n'est pas visible :
page
175
Lorsque l'on instancie effectivement un objet de classe ClasseX, cet objet ne voit comme mthode provenant de ClasseX que la mthode meth1 :
La mthode meth2 implmente explicitement en IinterfA.meth2 devient visible uniquement si l'on utilise une rfrence sur l'interface IinterfA, l'exemple ci-dessous montre qu'alors les deux mthodes meth1 et meth2 sont visibles :
page
176
Nous voyons bien que la mthode est qualifie avec sa signature dans IinterfA, voyons dans l'exemple ci-dessous que nous pouvons dclarer une mthode d'instance ayant la mme signature que la mthode explicite, voir mme de surcharger cette mthode d'instance sans que le compilateur C# n'y voit de conflit car la mthode explicite n'est pas rang dans la table des mthodes d'instances de la classe : class ClasseX : IinterfA { void IinterfA.meth2 ( int x ){ ... } //mthode de IinterfA implmente explicitement public virtual void meth2 ( int x ){ ... } //mthode de la ClasseX surcharge public virtual void meth2 ( char c ){ ... } //mthode de la ClasseX surcharge public virtual void meth1 ( ){ ... } //mthode de IinterfA implmente virtuellement }
page
177
La rfrence Obj1 peut appeller les deux surcharges de la mthode d'instance meth2 de la classe ClasseX :
La rfrence Obj2 sur IinterfA fonctionne comme nous l'avons montr plus haut, elle ne peut voir de la mthode meth2 que son implmentation explicite :
Cette fonctionnalit d'implmentation explicite spcifique C# peut tre utilise dans au moins deux cas utiles au dveloppeur : Lorsque vous voulez qu'un membre (une mthode par exemple) implment d'une interface soit priv dans une classe pour toutes les instances de classes qui en driveront, l'implmentation explicite vous permet de rendre ce membre (cette mthode) inaccessible tout objet. Lors d'un conflit de noms si deux interfaces possdent un membre ayant la mme signature et que votre classe implmente les deux interfaces.
1.1 Spcification d'un exemple complet Utilisons la notion d'interface pour fournir un polymorphisme une hirarchie de classe de vhicules fonde sur une interface. : Soit au dpart une classe abstraite Vehicule et une interface IVehicule.
1.1.A) Une classe abstraite La classe abstraite Vehicule contient trois mthodes :
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
178
La mthode Dmarrer qui est abstraite. La mthode RpartirPassagers de rpartition des passagers bord du vhicule, implante avec un corps vide. La mthode PriodicitMaintenance renvoyant la priodicit de la maintenance obligatoire du vhicule, implante avec un corps vide.
1.1.B) Une interface Afin d'utiliser les possibilits de C#, l'interface IVehicule propose un contrat d'implmentation pour un vnement, un indexeur, une proprit et une mthode :
L'vnement OnStart est de type dlgu (on construit un type dlgu Starting( ) spcifique pour lui) et se dclenchera au dmarrage du futur vhicule, L'indexeur this [int ] est de type string et permettra d'accder une liste indice d'informations sur le futur vhicule, La proprit TypeEngin est en lecture et criture et concerne le type du futur vhicule dans la marque, La mthode Stopper( ) indique comment le futur vhicule s'immobilisera.
1.1.C) Une simulation d'hritage multiple Nous souhaitons construire une classe abstraite UnVehicule qui "hrite" la fois des fonctionnalits de la classe Vehicule et de celles de l'interface IVehicule. Il nous suffit en C# de faire hriter la classe UnVehicule de la classe Vehicule , puis que la classe UnVehicule implmente les propositions de contrat de l'interface IVehicule :
page
179
1.1.D) Encore une classe abstraite, mais plus "concrte" Nous voulons maintenant proposer une spcialisation du vhicule en crant une classe abstraite Terrestre , base des futurs vhicules terrestres. Cette calsse implantera de faon explicite la mthode RpartirPassagers de rpartition des passagers et la mthode PriodicitMaintenance renvoyant la priodicit de la maintenance. Cette classe Terrestre reste abstraite car elle ne fournit pas l'implmentation de la mthode Demarrer :
1.1.E) Une classe concrte Nous finissons notre hirarchie par une classe Voiture qui descend de la classe Terrestre , qui implante la mthode Demarrer( ) et qui redfinie la mthode Stopper( ) :
page
180
1.2 Implantation en C# de l'exemple Nous proposons ci-dessous pour chaque classe ou interface une implmenation en C#. 1.2.A) La classe abstraite Vehicule
page
181
abstract class Vehicule // classe abstraite mre { public abstract void Demarrer ( ); // mthode abstraite public void RpartirPassagers ( ) { } // implantation de mthode avec corps vide public void PriodicitMaintenance ( ) { } // implantation de mthode avec corps vide }
interface IVehicule { event Starting OnStart ; // dclaration d'vnement du type dlgu : Starting string this [ int index] // dclaration d'indexeur { get ; set ; } string TypeEngin // dclaration de proprit { get ; set ; } void Stopper ( ); // dclaration de mthode }
page
182
abstract class UnVehicule : Vehicule , IVehicule // hrite de la classe mre et implmente l'interface { private string nom = ""; private string [ ] ArrayInfos = new string [10] ; public event Starting OnStart ; protected void LancerEvent () { if( OnStart != null) OnStart (); } public string this [ int index] // implantation Indexeur { get { return ArrayInfos[index] ; } set { ArrayInfos[index] = value ; } } public string TypeEngin // implantation proprit { get { return nom ; } set { nom = value ; } } public virtual void Stopper ( ) { } // implantation de mthode avec corps vide }
page
183
abstract class Terrestre : UnVehicule { public new void RpartirPassagers ( ) { //...implantation de mthode } public new void PriodicitMaintenance ( ) { //...implantation de mthode } }
class Voiture : Terrestre { public override void Demarrer ( ) { LancerEvent ( ); } public override void Stopper ( ) { //... } }
page
184
Nous nous interessons au mode de liaison des membres du genre : mthodes, proprits, indexeurs et vnements. Rappelons au lecteur que la liaison statique indique que le compilateur lie le code lors de la compilation, alors que dans le cas d'une liaison dynamique le code n'est choisi et li que lors de l'excution. 2.1 Le code de la classe Vehicule
abstract class Vehicule { public abstract void Demarrer ( ); // mthode abstraite public void RpartirPassagers ( ) { } // implantation de mthode avec corps vide public void PriodicitMaintenance ( ) { } // implantation de mthode avec corps vide }
Analyse : Sans qualification particulire une mthode est liaison statique : La mthode public void RpartirPassagers ( ) est donc laison statique. La mthode public void PriodicitMaintenance ( ) est donc laison statique. Une mthode qualifie abstract est implicitement virtuelle : La mthode public abstract void Demarrer ( ) est donc laison dynamique.
interface IVehicule { event Starting OnStart ; // dclaration d'vnement du type dlgu : Starting string this [ int index] // dclaration d'indexeur { get ; set ; } string TypeEngin // dclaration de proprit { get ; set ; } void Stopper ( ); // dclaration de mthode }
Analyse :
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
185
Une interface n'est qu'un contrat, les membres dclars comme signatures dans l'interface n'tant pas implmentes, la question de leur liaison ne se pose pas au niveau de l'interface, mais lors de l'implmentation dans une classe ultrieure : La mthode void Stopper ( ); pourra donc tre plus tard soit statique, soit dynamique. L'vnement event Starting OnStart ; pourra donc tre plus tard soit statique, soit dynamique. La proprit string TypeEngin , pourra donc tre plus tard soit statique, soit dynamique. L'indexeur string this [ int index] , pourra donc tre plus tard soit statique, soit dynamique.
abstract class UnVehicule : Vehicule , IVehicule { private string nom = ""; private string [ ] ArrayInfos = new string [10] ; public event Starting OnStart ; protected void LancerEvent ( ) { if( OnStart != null) OnStart (); } public string this [ int index] // implantation Indexeur { get { return ArrayInfos[index] ; } set { ArrayInfos[index] = value ; } } public string TypeEngin // implantation proprit { get { return nom ; } set { nom = value ; } } public virtual void Stopper ( ) { } // implantation de mthode avec corps vide }
Analyse : Le qualificateur virtual indique que l'lment qualifi est virtuel, donc liaison dynamique; sans autre qualification un lment est par dfaut liaison statique : La mthode public virtual void Stopper ( ) est liaison dynamique. L'vnement public event Starting OnStart est liaison statique. La proprit public string TypeEngin est liaison statique. L'indexeur public string this [ int index] est liaison statique.
)
page
186
abstract class Terrestre : UnVehicule { public new void RpartirPassagers ( ) { //...implantation de mthode } public new void PriodicitMaintenance ( ) { //...implantation de mthode } }
Analyse : Dans la classe mre Vehicule les deux mthodes RpartirPassagers et PriodicitMaintenance sont liaison statique, dans la classe Terrestre : La mthode public new void RpartirPassagers ( ) est laison statique et masque la mthode mre. La mthode public new void PriodicitMaintenance ( ) est laison statique et masque la mthode mre.
class Voiture : Terrestre { public override void Demarrer ( ) { LancerEvent ( ); } public override void Stopper ( ) { //... } }
Analyse :
page
187
est hrite de la classe UnVehicule : La mthode public override void Demarrer ( ) est laison dynamique et redfinit la mthode abstraite mre. La mthode public override void Stopper ( ) est laison dynamique et redfinit la mthode virtuelle corps vide de la classe UnVehicule.
Une classe peut implmenter plusieurs interfaces. Dans ce cas nous avons une excellente alternative l'hritage multiple. Lorsque l'on cre une interface, on fournit un ensemble de dfinitions et de comportements qui ne devraient plus tre modifis. Cette attitude de constance dans les dfinitions, protge les applications crites pour utiliser cette interface. Les variables de types interface respectent les mmes rgles de transtypage que les variables de types classe. Les objets de type classe clA peuvent tre transtyps et refrencs par des variables d'interface IntfA dans la mesure o la classe clA implmente linterface IntfA. (cf. polymorphisme d'objet)
Une classe peut implmenter plusieures interfaces Soit la dfinition suivante o la classe UnVehiculeMoteur hrite de la classe abstraite Vehicule et implmente l'interface IVehicule de l'exemple prcdent, et supposons qu'en plus cette classe implmente l'interface IMoteur. L'interface IMoteur explique que lorsqu'un vhicule est moteur, il faut se proccuper de son type de carburant et de sa consommation :
page
188
Ci-dessous le code C# correspondant cette dfinition, plus une classe concrte Voiture instanciable drivant de la classe abstraite UnVehiculeMoteur :
abstract class Vehicule { public abstract void Demarrer ( ); // mthode abstraite public void RpartirPassagers ( ) { } // implantation de mthode avec corps vide public void PriodicitMaintenance ( ) { } // implantation de mthode avec corps vide } interface IVehicule { event Starting OnStart ; // dclaration d'vnement du type dlgu : Starting string this [ int index] // dclaration d'indexeur { get ; set ; } string TypeEngin // dclaration de proprit { get ; set ; } void Stopper ( ); // dclaration de mthode } enum Energie { gaz , fuel , ess } // type numr pour le carburant interface IMoteur { Energie carburant // dclaration de proprit { get ; } int consommation ( ); // dclaration de mthode } abstract class UnVehiculeMoteur : Vehicule , IVehicule , IMoteur { private string nom = ""; private Energie typeEnerg = Energie.fuel ; private string [ ] ArrayInfos = new string [10] ; public event Starting OnStart ; Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
189
protected void LancerEvent ( ) { if( OnStart != null) OnStart ( ); } public string this [ int index] // implantation Indexeur de IVehicule { get { return ArrayInfos[index] ; } set { ArrayInfos[index] = value ; } } public string TypeEngin // implantation proprit de IVehicule { get { return nom ; } set { nom = value ; } } public Energie carburant // implantation proprit de IMoteur { get { return typeEnerg ; } } public virtual void Stopper ( ) { } // implantation vide de mthode de IVehicule public virtual int consommation ( ) { return .... } // implantation de mthode de IMoteur } class Voiture : UnVehiculeMoteur { public override void Demarrer ( ) { LancerEvent ( ); } public override void Stopper ( ) { //...implantation de mthode } public new void RpartirPassagers ( ) { //...implantation de mthode } public new void PriodicitMaintenance ( ) { //...implantation de mthode } }
Les interfaces et classes respectent les mmes rgles de polymorphisme Il est tout fait possible d'utiliser des variables de rfrence sur des interfaces et de les transtyper d'une manire identique des variables de rfrence de classe. En particulier le polymorphisme de rfrence s'applique aux rfrences d'interfaces.
Le polymorphisme de rfrence sur les classes de l'exemple prcdent abstract class Vehicule { ... } interface IVehicule { ... } enum Energie { gaz , fuel , ess } interface IMoteur { ... } abstract class UnVehiculeMoteur : Vehicule , IVehicule , IMoteur { ... } class Voiture : UnVehiculeMoteur { ... } class UseVoiture1 { public string use ( IVehicule x ){ return x.TypeEngin ; } Premier pas dans .Net avec C# - ( rv 15.08.2005 class MaClass {
static void Main(string [ ] args) { const string ch; s = "le client utilise une "; string ch ; IVehicule a1; UnVehiculeMoteur b1; Voiture c1; a1 = new Voiture( ); a1.TypeEngin = "Renault"; b1= new Voiture( ); b1.TypeEngin = "Citron";
)
page
190
} class UseVoiture2 { public string use ( UnVehiculeMoteur x ){ return x.TypeEngin ; } } class UseVoiture3 { public string use ( Voiture x ){ return x.TypeEngin ; } }
c1 = new Voiture( ); c1.TypeEngin = "Peugeot"; UseVoiture1 client = new UseVoiture1( ); ch = s+client.use(a1); System.Console.WriteLine(ch); ch = s+client.use(b1); System.Console.WriteLine(ch); ch = s+client.use(c1); System.Console.WriteLine(ch); } }
code d'excution avec 3 objets diffrents static void Main(string [ ] args) { .... idem UseVoiture1 client = new UseVoiture1( ); ch = s+client.use(a1); System.Console.WriteLine(ch); ch = s+client.use(b1); System.Console.WriteLine(ch); ch = s+client.use(c1); System.Console.WriteLine(ch); } IVehicule |__UnVehiculeMoteur |__Voiture Aprs excution :
a1 est une rfrence sur IVehicule, b1 est une rfrence sur une interface fille de IVehicule, c1 est de classe Voiture implmentant IVehicule. Donc chacun de ces trois genres de paramtre peut tre pass par polymorphisme de rfrence d'objet la mthode public string use ( IVehicule x )
static void Main(string [ ] args) { .... idem UseVoiture2 client = new UseVoiture2( ); ch = s+client.use((UnVehiculeMoteur)a1); System.Console.WriteLine(ch); ch = s+client.use(b1); System.Console.WriteLine(ch); ch = s+client.use(c1); System.Console.WriteLine(ch); } IVehicule |__UnVehiculeMoteur |__Voiture
Aprs excution :
Le polymorphisme de rfrence d'objet appliqu la mthode public string use ( UnVehiculeMoteur x ) indique que les paramtres passs doivent tre de type UnVehiculeMoteur ou de type descendant. La variable a1 est une rfrence sur IVehicule qui ne descend pas de UnVehiculeMoteur, il faut donc transtyper la rfrence a1 soit : (UnVehiculeMoteur)a1. Aprs excution :
static void Main(string [ ] args) { .... idem UseVoiture3 client = new UseVoiture3( ); ch = s+client.use( (Voiture)a1 ); System.Console.WriteLine(ch); ch = s+client.use( (Voiture)b1 ); System.Console.WriteLine(ch); ch = s+client.use(c1); System.Console.WriteLine(ch); } IVehicule |__UnVehiculeMoteur
Le polymorphisme de rfrence d'objet appliqu la mthode public string use ( Voiture x ) indique que les paramtres pass doivent tre de type Voiture ou de type descendant. La variable a1 est une rfrence sur IVehicule qui ne descend pas de Voiture, il faut donc transtyper la rfrence a1 soit :
)
page
191
|__Voiture
(Voiture)a1 La variable b1 est une rfrence sur UnVehiculeMoteur qui ne descend pas de Voiture, il faut donc transtyper la rfrence b1 soit : (Voiture)b1
Les oprateurs is et as sont utilisables avec des rfrences d'interfaces en C#. Reprenons l'exemple prcdent :
abstract class Vehicule { ... } interface IVehicule { ... } enum Energie { gaz , fuel , ess } interface IMoteur { ... } abstract class UnVehiculeMoteur : Vehicule , IVehicule , IMoteur { ... } class Voiture : UnVehiculeMoteur { ... } class UseVoiture1 { public string use ( IVehicule x ){ if (x is UnVehiculeMoteur ) { int consom = (x as UnVehiculeMoteur).consommation( ); return " consommation="+consom.ToString( ) ; } else return x.TypeEngin ; } }
Les conflits de noms dans les interfaces Il est possible que deux interfaces diffrentes possdent des membres ayant la mme signature. Une classe qui implmente ces deux interfaces se trouvera confronte un conflit de nom (ambigut). Le compilateur C# exige ds lors que l'ambigut soit leve avec le prfixage du nom du membre par celui de l'interface correspondante (implmentation explicite). L'exemple ci-dessous est figur avec deux interfaces IntA et IntB contenant chacune deux mthodes portant les mmes noms et plus particulirement la mthode meth1 possde la mme signature dans chaque interface. Soit ClasseUn une classe implmentant ces deux interfaces. Voici comment fait C# pour choisir les appels de mthodes implantes. Le code source de ClasseUn implmentant les deux interfaces IntA et IntB :
interface IntA{ void meth1( int x ); void meth2( ); } interface IntB{ void meth1( int x ); void meth2( int y ); } class ClasseUn : IntA , IntB { public virtual void meth1( int x ){ } void IntA.meth1( int x ){ } void IntB.meth1( int x ){ } public virtual void meth2( ){ } public void meth2( int y ){ } }
page
192
Lorsque l'on instancie effectivement 3 objets de classe ClasseUn prcdente, et que l'on dclare chaque objet avec un type de rfrence diffrent : classe ou interface, le schma cidessous indique quels sont les diffrents appels de mthodes corrects possibles :
Il est aussi possible de transtyper une rfrence d'objet de classe ClasseUn en une rfrence d'interface dont elle hrite (les appels sont identiques ceux du schma prcdent) : class Tests { void methTest( ) { ClasseUn Obj1 = new ClasseUn( ); IntA Obj2 = ( IntA )Obj1; IntB Obj3 = ( IntB )Obj1; Obj1.meth2( );
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
193
Obj1.meth2(74); Obj2.meth2( ); Obj3.meth2(100); Obj1.meth1(50); Obj2.meth1(40); Obj3.meth1(40); } } Nous remarquons qu'aucun conflit et aucune ambigut de mthode ne sont possibles et que grce l'implmentation explicite, toutes les mthodes de mme nom sont accessibles. Enfin, nous avons prfr utiliser le transtypage dtaill dans : IntA Obj2 = ( IntA )Obj1 ; Obj2.meth1(40) ;
Car l'oubli du parenthsage externe dans l'instruction " (( IntA )Obj1).meth1(40) " peut provoquer des incomprhensions dans la mesure o aucune erreur n'est signal par le compilateur car ce n'est plus la mme mthode qui est appele. ( IntA )Obj1.meth1(40) ; //...appel de public virtual ClasseUn.meth1(int x)
page
194
Classe de dlgation
Plan gnral:
page
195
Il existe en Delphi la notion de pointeur de mthode utilise pour implanter la notion de gestionnaire d"vnements. C# a repris cette ide en l'encapsulant dans un concept objet plus abstrait : la notion de classes de dlgations. Dans la machine virtuelle CLR, la gestion des vnements est fonde sur les classes de dlgations, il est donc essentiel de comprendre le modle du dlgu pour comprendre le fonctionnement des vnements en C#.
1.1 Dfinition classe de dlgation - dlgu Le langage C# contient un mot cl delegate , permettant au compilateur de construire une classe drive de la classe MulticastDelegate drive elle-mme de la classe Delegate. Nous ne pouvons pas instancier une objet de classe Delegate, car le constructeur est spcifi protected et n'est donc pas accessible :
C'est en fait via ce mot cl delegate que nous allons construire des classes qui ont pour nom : classes de dlgations. Ces classes sont des classes du genre rfrence, elles sont instanciables et un objet de classe dlgation est appel un dlgu.
Un objet de classe dlgation permet de rfrencer (pointer vers) une ou plusieurs mthodes. Il s'agit donc de l'extension de la notion de pointeur de mthode de Delphi. Selon les auteurs une classe de dlgation peut tre ausi nomme classe dlgue, type dlgu voir mme tout simplement dlgu. Il est essentiel dans le texte lu de bien distinguer la classe et l'objet instanci.
page
196
Lorsque nous utiliserons le vocable classe dlgu ou type dlgu nous parlerons parlerons de la classe de dlgation dun objet dlgu. Il donc possible de crer un nouveau type de dlgu (une nouvelle classe) dans un programme, ceci d'une seule manire : en utilisant le qualificateur delegate. Ci-dessous nous dclarons un type dlgu (une nouvelle classe particulire) nomm NomTypeDelegue : delegate string NomTypeDelegue ( int parametre) ; Un objet dlgu peut donc tre instanc partir de cette "classe" comme n'importe quel autre objet : NomTypeDelegue Obj = new NomTypeDelegue ( <paramtre> ) Il ne doit y avoir qu'un seul paramtre <paramtre> et c'est obligatoirement un nom de mthode. Soit MethodeXYZ le nom de la mthode pass en paramtre, nous dirons alors que le dlgu (l'objet dlgu) rfrence la mthode MethodeXYZ .
Les mthodes rfrences par un dlgu peuvent tre : des mthodes de classe ( static ) ou des mthodes d'instance Toutes les mthodes rfrences par un mme dlgu ont la mme signature partielle : mme type de retour du rsultat, mme nombre de paramtres, mme ordre et type des paramtres, seul leur nom diffre.
page
197
Un type commenant par le mot clef delegate est une classe dlgation. ci-dessous la syntaxe de 2 exemples de dclaration de classe dlgation : delegate string Deleguer1( int x ) ; delegate void Deleguer2( string s ) ; Un objet instanci partir de la classe Deleguer1 est appel un dlgu de classe Deleguer1 : Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ; o Fonc1 est une mthode : static string Fonc1 ( int x ) { ... } Un objet instanci partir de la classe Deleguer2 est appel un dlgu de classe Deleguer2 : Deleguer2 FoncDeleg2 = new Deleguer2 ( Fonc2 ) ; o Fonc2 est une autre mthode : static void Fonc2 ( string x ) { ... }
Nous avons cr deux types dlgations nomms Deleguer1 et Deleguer2 : Le type Deleguer1 permet de rfrencer des mthodes ayant un paramtre de type int et renvoyant un string. Le type Deleguer2 permet de rfrencer des mthodes ayant un paramtre de type string et ne renvoyant rien. Les fonctions de classe Fonc1 et Fonc11 rpondent la signature partielle du type Deleguer1 static string Fonc1 ( int x ) { ... } static string Fonc11 ( int x ) { ... } On peut crer un objet (un dlgu) qui va rfrencer l'une ou l'autre de ces deux fonctions : Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ; ou bien Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc11 ) ; On peut maintenant appeler le dlgu FoncDeleg1 dans une instruction avec un paramtre d'entre de type int, selon que le dlgu rfrence Fonc1 ou bien Fonc11 c'est l'une ou l'autre des ces fonctions qui est en fait appele.
page
198
s = FoncDeleg1 ( 32 ); // appel au dlgu qui appelle la fonction System .Console.WriteLine ("FoncDeleg1(32) = " + s ); System .Console.WriteLine ("\nLe dlgu rfrence maintenant Fonc11 :"); FoncDeleg1 = new Deleguer1 ( Fonc11 ) ; // on change d'objet rfrenc (de fonction) s = FoncDeleg1 ( 32 ); // appel au dlgu qui appelle la fonction System .Console.WriteLine ("FoncDeleg1(32) = " + s ); System .Console.ReadLine ( ); } }
1.3 Dlgu et mthodes de classe - informations pendant l'excution Comme une rfrence de dlgu peut pointer (rfrencer) vers des mthodes de classes diffrentes au cours de l'excution, il est intressant d'obtenir des informations sur la mthode de classe actuellement rfrence par le dlgu. Nous donnons ci-dessous les deux proprits publiques qui sont utiles lors de cette recherche d'informations, elles proviennent de la classe mre Delegate non hritable par programme :
page
199
La proprit Target sert plus particulirement lorsque le dlgu rfrence une mthode d'instance, la proprit Method est la seule utilisable lorsque la mthode rfrence par le dlgu est une mthode de classe (mthode marque static). Lorsque la mthode rfrence par le dlgu est une mthode de classe le champ Target a la valeur null. Ci-dessous nous avons extrait quelques informations concernant la proprit Method qui est elle-mme de classe MethodInfo :
Ces proprits sont des membres de la proprit Method qui est applicable uniquement lorsque le dlgu en cours rfrence une mthode de classe (qualifie static).
Nous illustrons dans la figure ci-aprs, dans le cas d'une mthode de classe, l'utilisation des proprits Name, DeclaringType et ReturnType membres de la proprit Method :
page
200
Nous obtenons ainsi des informations sur le nom, le type du rsultat et la classe de la mthode static pointe par le dlgu.
Source complet excutable d'un exemple d'information sur la mthode de classe rfrence :
namespace PrDelegate { delegate string Deleguer1 ( int x ); class ClasseA { static string Fonc1 ( int x ) { return ( x * 10 ) .ToString ( ); } static void Main ( string [ ] args ) { System .Console.WriteLine ("\nLe dlgu rfrence Fonc1 :"); Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ; System .Console.WriteLine ( "nom : "+FoncDeleg1.Method.Name ); System .Console.WriteLine ("classe : "+ FoncDeleg1.Method.DeclaringType.ToString( ) ); System .Console.WriteLine ( "retour : "+FoncDeleg1.Method.ReturnType.ToString( ) ); System .Console.ReadLine ( ); } } }
page
201
Nous illustrons dans la figure ci-aprs, dans le cas d'une mthode d'instance, l'utilisation de membres de la proprit Method et de la proprit Target :
page
202
Source complet excutable d'un exemple d'information sur la mthode d'instance rfrence :
namespace PrDelegate { delegate int Deleguer ( char x ); class ClasseA { public int meth1 ( char x ) { return x ; } static void Main ( string [ ] args ) { ClasseA ObjX , ObjA = new ClasseA( ); System .Console.WriteLine ("Un dlgu rfrence ObjA.meth1 :"); Deleguer FoncDeleg = new Deleguer ( ObjA.meth1 ) ; ObjX = (ClasseA)FoncDeleg.Target; if (ObjX.Equals(ObjA)) System.Console.WriteLine ("Target rfrence bien ObjA"); else System.Console.WriteLine ("Target ne rfrence pas ObjA"); System.Console.WriteLine ( "\nnom : "+FoncDeleg.Method.Name ); System.Console.WriteLine ("classe : "+ FoncDeleg.Method.DeclaringType.ToString( ) ); System.Console.WriteLine ( "retour : "+FoncDeleg.Method.ReturnType.ToString( ) ); System.Console.ReadLine ( ); } } }
page
203
Dans le programme prcdent, les lignes de code suivantes : ObjX = (ClasseA)FoncDeleg.Target ; if (ObjX.Equals(ObjA)) System.Console.WriteLine ("Target rfrence bien ObjA") ; else System.Console.WriteLine ("Target ne rfrence pas ObjA") ; servent faire "pointer" la rfrence ObjX vers l'objet vers lequel pointe FoncDeleg.Target. La rfrence de cet objet est transtype car ObjX est de type ClasseA, FoncDeleg.Target est de type Object et le compilateur n'accepterait pas l'affectation ObjX = FoncDeleg.Target. Le test if (ObjX.Equals(ObjA))... permet de nous assurer que les deux rfrences ObjX et ObjA pointent bien vers le mme objet.
1.5 Plusieurs mthodes pour le mme dlgu C# autorise le rfrencement de plusieurs mthodes par le mme dlgu, nous utiliserons le vocabulaire de dlgu multicast pour bien prciser qu'il rfrence plusieurs mthodes. Le dlgu multicast conserve les rfrencements dans une liste d'objet. Les mthodes ainsi rfrences peuvent chacune tre du genre mthode de classe ou mthode d'instance, elles doivent avoir la mme signature. Rappelons qu'un type dlgu multicast est une classe qui hrite intrinsquement de la classe MulticasteDelegate :
La documentation de .Net Framework indique que la classe MulticasteDelegate contient en particulier trois champs privs : Object _target ; Int32 _methodPtr ;
Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
204
MulticasteDelegate _prev ; Le champ _prev est utilis pour maintenir une liste de MulticasteDelegate Lorsque nous dclarons un programme comme celui-ci :
delegate int Deleguer ( char x ); class ClasseA { public int meth100 ( char x ) { System.Console.WriteLine ("Excution de meth100('"+x+"')"); return x+100 ; } static void Main ( string [] args ) { ClasseA ObjA = new ClasseA( ); Deleguer FoncDeleg = new Deleguer ( ObjA.meth100 ) ; } }
Lors de l'excution, nous avons vu qu'il y a cration d'un ObjA de ClasseA et cration d'un objet dlgu FoncDeleg, les proprits Method et Target sont automatiquement initialises par le compilateur :
En fait, ce sont les champs privs qui sont initialiss et les proprits Method et Target qui sont en lecture seulement, lisent les contenus respectifs de _methodPtr et de _target; le champ _prev est pour l'instant mis null , enfin la mthode meth100(...) est actuellement en tte de liste.
Premier pas dans .Net avec C# - ( rv 15.08.2005 page
205
Il est possible d'ajouter une nouvelle mthode meth101(...) au dlgu qui va la mettre en tte de liste la place de la mthode meth100(...) qui devient le deuxime lment de la liste. C# utilise l'oprateur d'addition pour implmenter l'ajout d'une nouvelle mthode au dlgu. Nous tendons le programme prcdent :
delegate int Deleguer ( char x ); class ClasseA { public int meth100 ( char x ) { System.Console.WriteLine ("Excution de meth100('"+x+"')"); return x+100 ; } public int meth101 ( char x ) { System.Console.WriteLine ("Excution de meth101('"+x+"')"); return x+101 ; } static void Main ( string [] args ) { ClasseA ObjA = new ClasseA( ); //-- meth100 est en tte de liste : Deleguer FoncDeleg = new Deleguer ( ObjA.meth100 ) ; // meth101 est ajoute en tte de liste devant meth100 : FoncDeleg = FoncDeleg + new Deleguer ( ObjA.meth101 ) ; } }
page
206
C# permet de consulter et d'utiliser si nous le souhaitons toutes les rfrencement de mthodes en nous renvoyant la liste dans une tableau de rfrence de type Delegate grce la mthode GetInvocationList. Le source ci-dessous retourne dans le tableau Liste, la liste d'appel dans l'ordre d'appel, du dlgu FoncDeleg : Delegate[ ] Liste = FoncDeleg.GetInvocationList( ) ;
1.6 Excution des mthodes d'un dlgu multicast - Exemple de code Lorsque l'on invoque le dlgu sur un paramtre effectif, C# appelle et excute squentiellement les mthodes contenues dans la liste jusqu' puisement. L'ordre d'appel est celui du stockage : la premire stocke est excute en premier, la suivante aprs, la dernire mthode ajoute est excute en dernier, s'il y a un rsultat de retour, c'est celui de la dernire mthode ajoute qui est renvoy , les autres rsultats de retour sont ignors. L'exemple ci-dessous reprend les notions que nous venons d'exposer. Source complet excutable d'un exemple de dlgu multicast :
namespace PrDelegate { delegate int Deleguer ( char x ); class ClasseA { public int champ; public int meth100 ( char x ) { System.Console.WriteLine ("Excution de meth100('"+x+"')"); return x+100 ; } public int meth101 ( char x ) { System.Console.WriteLine ("Excution de meth101('"+x+"')"); return x+101 ; } public int meth102 ( char x ) { Premier pas dans .Net avec C# - ( rv 15.08.2005
)
page
207
System.Console.WriteLine ("Excution de meth102('"+x+"')"); return x+102 ; } public static int meth103 ( char x ) { System.Console.WriteLine ("Excution de meth103('"+x+"')"); return x+103 ; } static void Main ( string [] args ) { System.Console.WriteLine ("Un dlgu rfrence ObjA.meth1 :" ) ; ClasseA ObjX , ObjA = new ClasseA( ); //-- instanciation du dlgu avec ajout de 4 mthodes : Deleguer FoncDeleg = new Deleguer ( ObjA.meth100 ) ; FoncDeleg += new Deleguer ( ObjA.meth101 ) ; FoncDeleg += new Deleguer ( ObjA.meth102 ) ; FoncDeleg += new Deleguer ( meth103 ) ; //--la mthode meth103 est en tte de liste : ObjX = (ClasseA)FoncDeleg.Target ; if (ObjX == null) System.Console.WriteLine ("Mthode static, Target = null") ; else if (ObjX.Equals(ObjA))System .Console.WriteLine ("Target rfrence bien ObjA") ; else System.Console.WriteLine ("Target ne rfrence pas ObjA") ; System.Console.WriteLine ( "\nnom : "+FoncDeleg.Method.Name ); System.Console.WriteLine ( "classe : "+FoncDeleg.Method.DeclaringType.ToString( ) ) ; System.Console.WriteLine ( "retour : "+FoncDeleg.Method.ReturnType.ToString( ) ); //--Appel du dlgu sur le paramtre effectif 'a' : ObjA.champ = FoncDeleg('a') ; //code ascii 'a' = 97 System.Console.WriteLine ( "\nvaleur du champ : "+ObjA.champ) ; System.Console.WriteLine ( "----------------------------------") ; //-- Parcours manuel de la liste des mthodes rfrences : Delegate[ ] Liste = FoncDeleg.GetInvocationList( ) ; foreach ( Delegate Elt in Liste ) { ObjX = (ClasseA)Elt.Target ; if (ObjX == null) System.Console.WriteLine ("Mthode static, Target = null") ; else if (ObjX.Equals(ObjA))System .Console.WriteLine ("Target rfrence bien ObjA") ; else System.Console.WriteLine ("Target ne rfrence pas ObjA") ; System.Console.WriteLine ( "\nnom : "+Elt.Method.Name ) ; System.Console.WriteLine ( "classe : "+Elt.Method.DeclaringType.ToString( ) ) ; System.Console.WriteLine ( "retour : "+Elt.Method.ReturnType.ToString( ) ) ; System.Console.WriteLine ( "----------------------------------") ; } System.Console.ReadLine ( ) ; } } }
page
208
Nous voyons bien que le dlgu FoncDeleg contient la liste des rfrencement des mthodes meth100, meth101,meth102 et meth103 ordonn comme figur ci-dessous :
page
209
Remarquons que le premier objet de la liste est une rfrence sur une mthode de classe, la proprit Target renvoie la valeur null (le champ _target est null). La mthode de classe meth103 ajoute en dernier est bien en tte de liste :
Le parcours manuel de la liste montre bien que ce sont des objets de type Delegate qui sont stocks et que l'on peut accder entre autre possibilits, leurs proprits :
page
210
IHM et Winforms
Les vnements Proprits et indexeurs Fentres et ressources Contrles dans les formulaires Exceptions Flux et fichiers : donnes simples
page
211
Plan gnral:
page
212
Rappel Programmation oriente vnements Un programme objet orient vnements est construit avec des objets possdant des proprits telles que les interventions de lutilisateur sur les objets du programme et la liaison dynamique du logiciel avec le systme dclenchent lexcution de routines associes. Le traitement en programmation vnementielle, consiste mettre en place un mcanisme d'inteception puis de gestion permettant d'informer un ou plusieurs objets de la survenue d'un vnement particulier.
page
213
C# propose des mcanismes supportant les vnements, mais avec une implmentation totalement diffrente de celle de Java. En observant le fonctionnement du langage nous pouvons dire que C# combine efficacement les fonctionnalits de Java et de Delphi. Abonn un vnement C# utilise les dlgus pour fournir un mcanisme explicite permettant de grer l'abonnement/notification. En C# la dlgation de l'coute (gestion) d'un vnement est confie un objet de type dlgu : l'abonn est alors une mthode appele gestionnaire de l'vnement (contrairement Java o l'abonn est une classe) acceptant les mmes arguments et paramtres de retour que le dlgu.
Code C# :
using System; using System.Collections; namespace ExempleEvent { //--> dclaration du type dlgation :(par exemple procdure avec 1 paramtre string ) public delegate void DelegueTruc (string s); public class ClassA { //--> dclaration d'une rfrence event de type dlgu : public event DelegueTruc Truc; } } Premier pas dans .Net avec C# - (maj. 15.08.2005
page
214
Invocation d'un vnement Une fois qu'une classe a dclar un vnement Truc, elle peut traiter cet vnement exactement comme un dlgu ordinaire. La dmarche est trs semblable celle de Delphi, le champ Truc vaudra null si le client ObjA de ClassA n'a pas raccord un dlgu l'vnement Truc. En effet tre sensible plusieurs vnements n'oblige pas chaque objet grer tous les vnement, dans le cas o un objet ne veut pas grer un vnement Truc on n'abonne aucune mthode au dlgu Truc qui prend alors la valeur null. Dans l'ventualit o un objet doit grer un vnement auquel il est sensible, il doit invoquer l'vnement Truc (qui rfrence une ou plusieurs mthodes). Invoquer un vnement Truc consiste gnralement vrifier d'abord si le champ Truc est null, puis appeler l'vnement (le dlgu Truc). Remarque importante L'appel d'un vnement ne peut tre effectu qu' partir de la classe qui a dclar cet vnement.
Exemple construit pas pas Considrons ci-dessous la classe ClassA qui est sensible un vnement que nous nommons Truc (on dclare la rfrence Truc), dans le corps de la mthode void DeclencheTruc( ) on appelle l'vnement Truc. Nous dclarons cette mthode void DeclencheTruc( ) comme virtuelle et protge, de telle manire qu'elle puisse tre redfinie dans la suite de la hirarchie; ce qui constitue un gage d'volutivit des futures classes quant leur comportement relativement l'vnement Truc :
Il nous faut aussi prvoir une mthode publique qui permettra d'invoquer l'vnement depuis une autre classe, nous la nommons LancerTruc.
page
215
Code C# , construisons progressivement notre exemple, voici les premires lignes du code :
using System; using System.Collections; namespace ExempleEvent { //--> dclaration du type dlgation :(par exemple procdure avec 1 paramtre string) public delegate void DelegueTruc (string s); public class ClassA { //--> dclaration d'une rfrence event de type dlgu : public event DelegueTruc Truc; protected virtual void DeclencheTruc( ) { .... if ( Truc != null ) Truc("vnement dclench"); .... } public void LancerTruc( ) { .... DeclencheTruc( ) ; .... } } }
Comment s'abonner (se raccorder, s'inscrire, ...) un vnement Un vnement ressemble un champ public de la classe qui l'a dclar. Toutefois l'utilisation de ce champ est trs restrictive, c'est pour cela qu'il est dclar avec le spcificateur event. Seulement deux oprations sont possibles sur un champ d'vnement qui rapellons-le est un dlgu : Ajouter une nouvelle mthode ( la liste des mthodes abonnes l'vnement). Supprimer une mthode de la liste (dsabonner une mthode de l'vnement).
page
216
} } public class ClasseUse { static private void methodUse( ) { ClassA ObjA = new ClassA( ); ClasseUse ObjUse = new ClasseUse ( ); //.... } static public void Main(string[] x) { methodUse( ) ; //.... } } }
Il faut maintenant dfinir des gestionnaires de l'vnement Truc (des mthodes ayant la mme signature que le type dlgation " public delegate void DelegueTruc (string s); ". Ensuite nous ajouterons ces mthodes au dlgu Truc (nous les abonnerons l'vnement Truc), ces mthodes peuvent tre de classe ou d'instance. Supposons que nous ayons une mthode de classe et trois mthodes d'instances qui vont s'inscrire sur la liste des abonns Truc, ce sont quatre gestionnaires de l'vnement Truc :
page
217
} public void LancerTruc( ) { .... DeclencheTruc( ) ; .... } } public class ClasseUse { public void method100(string s); { //...gestionnaire d'vnement Truc: mthode d'instance. } public void method101(string s); { //...gestionnaire d'vnement Truc: mthode d'instance. } public void method102(string s); { //...gestionnaire d'vnement Truc: mthode d'instance. } static public void method103(string s); { //...gestionnaire d'vnement Truc: mthode de classe. } static private void methodUse( ) { ClassA ObjA = new ClassA( ); ClasseUse ObjUse = new ClasseUse ( ); //... il reste abonner les gestionnaires de l'vnement Truc } static public void Main(string[ ] x) { methodUse( ) ; } } }
Lorsque nous ajoutons en C# les nouvelles mthodes method100, ... , method103 au dlgu Truc, par surcharge de l'oprateur +, nous dirons que les gestionnaires method100,...,method103, s'abonnent l'vnement Truc.
Prvenir (informer) un abonn correspond ici l'action d'appeler l'abonn (appeler la mthode) :
page
218
page
219
if ( Truc != null ) Truc("vnement Truc dclench"); //.... } public void LancerTruc( ) { //.... DeclencheTruc( ) ; //.... } } public class ClasseUse { public void method100(string s) { //...gestionnaire d'vnement Truc: mthode d'instance. System.Console.WriteLine("information utilisateur : "+s); } public void method101(string s) { //...gestionnaire d'vnement Truc: mthode d'instance. System.Console.WriteLine("information utilisateur : "+s); } public void method102(string s) { //...gestionnaire d'vnement Truc: mthode d'instance. System.Console.WriteLine("information utilisateur : "+s); } static public void method103(string s) { //...gestionnaire d'vnement Truc: mthode de classe. System.Console.WriteLine("information utilisateur : "+s); } static private void methodUse( ) { ClassA ObjA = new ClassA( ); ClasseUse ObjUse = new ClasseUse ( ); //-- abonnement des gestionnaires: ObjA.Truc += new DelegueTruc ( ObjUse.method100 ); ObjA.Truc += new DelegueTruc ( ObjUse.method101 ); ObjA.Truc += new DelegueTruc ( ObjUse.method102 ); ObjA.Truc += new DelegueTruc ( method103 ); //-- invocation de l'vnement: ObjA.DeclencheTruc( ) ; //...l'appel cette mthode permet d'invoquer l'vnement Truc } static public void Main(string[] x) { methodUse( ) ; } } }
Restrictions et normalisation .NET Framework Bien que le langage C# autorise les vnements utiliser n'importe quel type dlgu, le .NET Framework applique ce jour des fins de normalisation, certaines indications plus restrictives quant aux types dlgus utiliser pour les vnements. Les indications .NET Framework spcifient que le type dlgu utilis pour un vnement doit disposer de deux paramtres et d'un retour dfinis comme suit : un paramtre de type Object qui dsigne la source de l'vnement, un autre paramtre soit de classe EventArgs , soit d'une classe qui drive de EventArgs, il encapsule toutes les informations personnelles relatives l'vnement, enfin le type du retour du dlgu doit tre void.
)
page
220
Evnement normalis sans information : Si vous n'utilisez pas d'informations personnelles pour l'vnement, la signature du dlgu sera : public delegate void DelegueTruc ( Object sender , EventArgs e ) ;
Evnement normalis avec informations : Si vous utilisez des informations personnelles pour l'vnement, vous dfinirez une classe MonEventArgs qui hrite de la classe EventArgs et qui contiendra ces informations personnelles, dans cette ventualit la signature du dlgu sera : public delegate void DelegueTruc ( Object sender , MonEventArgs e ) ;
Il est conseill d'utiliser la reprsentation normalise d'un vnement comme les deux exemples ci-dessous le montre, afin d'augmenter la lisibilit et le portage source des programmes vnementiels.
page
221
page
222
Remarque, en utilisant la dclaration public delegate void EventHandler ( object sender, EventArgs e ) contenue dans System.EventHandler, l'vnement n'ayant aucune donne personnalise, le deuxime paramtre n'tant pas utilis, il est possible de fournir le champ static Empty de la classe EventArgs .
page
223
page
224
//...gestionnaire d'vnement Truc: mthode d'instance. System.Console.WriteLine("information utilisateur 100 : vnement dclench"); } public void method101( object sender, EventArgs e ) { //...gestionnaire d'vnement Truc: mthode d'instance. System.Console.WriteLine("information utilisateur 101 : vnement dclench"); } public void method102( object sender, EventArgs e ) { //...gestionnaire d'vnement Truc: mthode d'instance. System.Console.WriteLine("information utilisateur 102 : vnement dclench"); } static public void method103( object sender, EventArgs e ) { //...gestionnaire d'vnement Truc: mthode de classe. System.Console.WriteLine("information utilisateur 103 : vnement dclench"); } static private void methodUse( ) { ClassA ObjA = new ClassA( ); ClasseUse ObjUse = new ClasseUse ( );
page
225
noms System.Windows.Forms qui contient des classes permettant de crer des applications contenant des IHM (interface humain machine) et en particulier d'utiliser les fonctionnalits affrentes aux IHM de Windows. Plus spcifiquement, la classe System.Windows.Forms.Control est la classe mre de tous les composants visuels. Par exemple, les classes Form, Button, TextBox, etc... sont des descendants de la classe Control qui met disposition du dveloppeur C# 58 vnements auxquels un contrle est sensible. Ces 58 vnements sont tous normaliss, certains sont des vnements sans information spcifique, d'autres possdent des informations spcifiques, ci-dessous un extrait de la liste des vnements de la classe Control, plus particulirement les vnements traitant des actions de souris :
Nous avons mis en vidence deux vnements Click et Paint dont l'un est sans information (Click), l'autre est avec information (Paint). Afin de voir comment nous en servir nous traitons un exemple : Soit un fiche (classe Form1 hritant de la classe Form) sur laquelle est dpos un bouton poussoir (classe Button) de nom button1 :
page
226
Montrons comment nous programmons la raction du bouton un click de souris et son redessinement. Nous devons faire ragir button1 qui est sensible au moins 58 vnements, aux deux vnements Click et Paint. Rappelons la liste mthodologique ayant trait au cycle vnementiel, on doit utiliser :
1) une classe d'informations personnalises sur l'vnement 2) une dclaration du type dlgation normalise (nom termin par EventHandler) 3) une dclaration d'une rfrence Truc du type dlgation normalise spcifie event 4.1) une mthode protge qui dclenche l'vnement Truc (nom commenant par On: OnTruc) 4.2) une mthode publique qui lance l'vnement par appel de la mthode OnTruc 5) un ou plusieurs gestionnaires de l'vnement Truc 6) abonner ces gestionnaires au dlgu Truc 7) consommer l'vnement Truc
Les tapes 1 4 ont t conues et developpes par les quipes de .Net et ne sont plus notre charge. Il nous reste les tapes suivantes : 5) construire un gestionnaire de raction de button1 l'vnement Click et un gestionnaire de raction de button1 6) abonner chaque gestionnaire au dlgu correspondant (Click ou Paint) L'tape 7 est assure par le systme d'exploitation qui se charge d'envoyer des messages et de lancer les vnements.
Dans le cas de l'vnement Paint, le dlgu est du type PaintEventArgs situ dans System.WinForms :
page
227
signature du dlgu Paint dans System.Windows.Forms : public delegate void PaintEventHandler ( object sender, PaintEventArgs e );
La classe PaintEventArgs :
page
228
signature du dlgu Click dans System : public delegate void EventHandler ( object sender, EventArgs e );
En fait l'inspecteur d'objet de C#Builder permet de raliser en mode visuel la machinerie des tapes qui sont notre charge : le dlgu de l'vnement, [ public event EventHandler Click; ] le squelette du gestionnaire de l'vnement, [ private void button1_Click(object sender, System.EventArgs e){ } ] l'abonnement de ce gestionnaire au dlgu. [ this.button1.Click += new System.EventHandler ( this.button1_Click ); ]
Code C# gnr
Voici le code gnr par C#Builder utilis en conception visuelle pour faire ragir button1 aux deux vnements Click et Paint :
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
229
public class WinForm : System.Windows.Forms.Form { private System.ComponentModel.Container components = null ; private System.Windows.Forms.Button button1; public WinForm( ) { InitializeComponent( ); } protected override void Dispose (bool disposing) { if (disposing) { if (components != null ) { components.Dispose( ) ; } } base.Dispose(disposing) ; } private void InitializeComponent( ) { this.button1 = new System.Windows.Forms.Button( ); ...... this.button1.Click += new System.EventHandler( this.button1_Click ); this.button1.Paint += new System.Windows.Forms.PaintEventHandler( this.button1_Paint ); ..... } static void Main( ) { Application.Run( new WinForm( ) ); } private void button1_Click(object sender, System.EventArgs e) //..... gestionnaire de l'vnement OnClick } {
La machinerie vnementielle est automatiquement gnre par C#Builder comme dans Delphi, ce qui pargne de nombreuses lignes de code au dveloppeur et le laisse libre de penser au code spcifique de raction l'vnement.
page
230
Proprits et indexeurs en
Plan gnral:
1. Les proprits
1.1 Dfinition et dclaration de proprit 1.2 Accesseurs de proprit 1.3 Dtail et exemple de fonctionnement d'une proprit Exemple du fonctionnement Explication des actions 1.4 Les proprits sont de classes ou d'instances 1.5 Les proprits peuvent tre masques comme les mthodes 1.6 Les proprits peuvent tre virtuelles et redfinies comme les mthodes 1.7 Les proprits peuvent tre abstraites comme les mthodes 1.8 Les proprits peuvent tre dclares dans une interface 1.9 Exemple complet excutable 1.9.1 Dtail du fonctionnement en criture 1.9.2 Dtail du fonctionnement en lecture
2. Les indexeurs
2.1 Dfinitions et comparaisons avec les proprits 2.1.1 Dclaration 2.1.2 Utilisation 2.1.3 Paramtres 2.1.4 Liaison dynamique abstraction et interface 2.2 Code C# complet compilable
page
231
1. Les proprits
Les proprits du langage C# sont trs proches de celle du langage Delphi, mais elles sont plus compltes et restent cohrentes avec la notion de membre en C#.
1.1 Dfinition et dclaration de proprit Dfinition d'une proprit Une proprit dfinie dans une classe permet d'accder certaines informations contenues dans les objets instancis partir de cette classe. Une proprit a la mme syntaxe de dfinition et d'utilisation que celle d'un champ d'objet (elle possde un type de dclaration), mais en fait elle invoque une ou deux mthodes internes pour fonctionner. Les mthodes internes sont dclares l'intrieur d'un bloc de dfinition de la proprit.
Dclaration d'une proprit propr1 de type int : public int propr1 { //...... bloc de dfinition } Un champ n'est qu'un emplacement de stockage dont le contenu peut tre consult (lecture du contenu du champ) et modifi (criture dans le champ), tandis qu'une proprit associe des actions spcifiques la lecture ou l'criture ainsi que la modification des donnes que la proprit reprsente.
page
232
1.2 Accesseurs de proprit En C#, une proprit fait systmatiquement appel une ou deux mthodes internes dont les noms sont les mmes pour toutes les proprits afin de fonctionner soit en lecture, soit en criture. On appelle ces mthodes internes des accesseurs; leur noms sont get et set , cidessous un exemple de lecture et d'criture d'une proprit au moyen d'affectations :
cet accesseur indique que la proprit est en lecture et doit renvoyer un rsultat dont le type doit tre le mme que celui de la proprit. La proprit propr1 ci-dessous est dclare en lecture seule et renvoie le contenu d'un champ de mme type qu'elle : private int champ; public int propr1{ get { return champ ; } }
cet accesseur indique que la proprit est en criture et sert initialiser ou modifier la proprit. La proprit propr1 ci-dessous est dclare en criture seule et stocke une donne de mme type qu'elle dans la variable champ : private int champ; public int propr1{ set { champ = value ; } } Le mot clef value est une sorte de paramtre implicite interne l'accesseur set, il contient la valeur effective qui est transmise la proprit lors de l'accs en criture.
page
233
D'un manire gnrale lorsqu'une proprit fonctionne travers un attribut (du mme type que la proprit), l'attribut contient la donne brute laquelle la proprit permet d'accder. Ci-dessous une dclaration d'une proprit en lecture et criture avec attribut de stockage : private int champ; public int propr1{ get { return champ ; } set { champ = value ; } } Le mcanisme de fonctionnement est figur ci-aprs :
Dans l'exemple prcdent, la proprit accde directement sans modification la donne brute stocke dans le champ, mais il est tout fait possible une proprit d'accder cette donne en en modifiant sa valeur avant stockage ou aprs rcupration de sa valeur.
1.3 Dtail et exemple de fonctionnement d'une proprit L'exemple ci-dessous reprend la proprit propr1 en lecture et criture du paragraphe prcdent et montre comment elle peut modifier la valeur brute de la donne stocke dans l'attribut " int champ " : private int champ; public int propr1{ get {return champ*10;} set {champ = value + 5 ;} } Utilisons cette proprit en mode criture travers une affectation : prop1 = 14 ;
page
234
Le mcanisme d'criture est simul ci-dessous : La valeur 14 est passe comme paramtre dans la mthode set la variable implicite value, le calcul value+5 est effectu et le rsultat 19 est stock dans l'attribut champ.
Utilisons maintenant notre proprit en mode lecture travers une affectation : int x = propr1 ; Le mcanisme de lecture est simul ci-dessous : La valeur brute 19 stocke dans l'attribut champ est rcupre par la proprit qui l'utilise dans la mthode accesseur get en la multipliant par 10, c'est cette valeur modifie de 190 qui renvoye par la proprit.
page
235
Exemple pratique d'utilisation d'une proprit Une proprit servant fournir automatiquement le prix d'un article en y intgrant la TVA au taux de 19.6% et arrondi l'unit d'euro suprieur :
private Double prixTotal ; private Double tauxTVA = 1.196 ; public Double prix { get { return Math.Round(prixTotal); } set { prixTotal = value * tauxTVA ; } }
Rsultats d'excution :
page
236
Explications des actions excutes : On rentre 100 dans la variable prix : Double val = 100 ; prix = val ; Action effectue : On crit 100 dans la proprit prix et celle-ci stocke 100*1.196=119.6 dans le champ prixTotal. On excute l'instruction : val = prix ; Action effectue : On lit la proprit qui arrondi le champ prixTotal l'euro suprieur soit : 120
1.4 Les proprits sont de classes ou d'instances Les proprits, comme les champs peuvent tre des proprits de classes et donc qualifies par les mots clefs comme static, abstract etc ...Dans l'exemple prcdent nous avons qualifi tous les champs et la proprit prix en static afin qu'ils puissent tre accessibles la mthode Main qui est elle-mme obligatoirement static.
Voici le mme exemple utilisant une version avec des proprits et des champs d'instances et non de classe (non static) :
using System ; namespace ProjPropIndex { class clA { private Double prixTotal ; private Double tauxTVA = 1.196 ; public Double prix { get { return Math.Round ( prixTotal ) ; } set { prixTotal = value * tauxTVA ; } } } class Class { [STAThread] static void Main ( string [] args ) { clA Obj = new clA ( ); Double val = 100 ; Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
237
System .Console.WriteLine ("Valeur entre :" + val ); Obj.prix = val ; // le champ prixTotal n'est pas accessible car il est priv val = Obj.prix ; System .Console.WriteLine ("valeur arrondie (lue) : " + val ) ; System .Console.ReadLine ( ); } } }
Rsultats d'excution :
1.5 Les proprits peuvent tre masques comme les mthodes Une proprit sans spcificateur particulier de type de liaison est considre comme une entit liaison statique par dfaut. Dans l'exemple ci-aprs nous drivons une nouvelle classe de la classe clA nomme clB, nous redclarons dans la classe fille une nouvelle proprit ayant le mme nom, l'instar d'un champ ou d'une mthode C# considre que nous masquons la proprit mre et nous suggre le conseil suivant : [C# Avertissement] Class...... : Le mot cl new est requis sur '...........', car il masque le membre hrit...... ' Nous mettons donc le mot clef new devant la nouvelle dclaration de la proprit dans la classe fille. En reprenant l'exemple prcdent supposons que dans la classe fille clB, la TVA soit 5%, nous redclarons dans clB une proprit prix qui va masquer celle de la mre :
using System ; namespace ProjPropIndex { class clA { private Double prixTotal ; private Double tauxTVA = 1.196 ; public Double prix { // proprit de la classe mre get { return Math.Round ( prixTotal ) ; } set { prixTotal = value * tauxTVA ; } } } class clB : clA { private Double prixLocal ; public new Double prix { // masquage de la proprit de la classe mre get { return Math.Round ( prixLocal ) ; } set { prixLocal = value * 1.05 ; } } } class Class { Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
238
[STAThread] static void Main ( string [] args ) { clA Obj = new clA ( ); Double val = 100 ; System .Console.WriteLine ("Valeur entre clA Obj :" + val ); Obj.prix = val ; val = Obj.prix ; System .Console.WriteLine ("valeur arrondie (lue)clA Obj : " + val ) ; System .Console.WriteLine ("--------------------------------------"); clB Obj2 = new clB ( ); val = 100 ; System .Console.WriteLine ("Valeur entre clB Obj2 :" + val ); Obj2.prix = val ; val = Obj2.prix ; System .Console.WriteLine ("valeur arrondie (lue)clB Obj2: " + val ) ; System .Console.ReadLine ( ); } } }
Rsultats d'excution :
1.6 Les proprits peuvent tre virtuelles et redfinies comme les mthodes Les proprit en C# ont l'avantage important d'tre utilisables dans le contexte de liaison dynamique d'une manire strictement identique celle des mthodes en C# , ce qui confre au langage une "orthogonalit" solide relativement la notion de polymorphisme. Une proprit peut donc tre dclare virtuelle dans une classe de base et tre surcharge dynamiquement dans les classes descendantes de cette classe de base. Dans l'exemple ci-aprs semblable au prcdent, nous dclarons dans la classe mre clA la proprit prix comme virtual, puis : Nous drivons clB, une classe fille de la classe clA possdant une proprit prix masquant statiquement la proprit virtuelle de la classe clA, dans cette classe clB la TVA applique la variable prix est 5% (nous mettons donc le mot clef new devant la nouvelle dclaration de la proprit prix dans la classe fille clB). La proprit prix est dans cette classe clB liaison statique. Nous drivons une nouvelle classe de la classe clA nomme clB2 dans laquelle nous redfinissons en override la proprit prix ayant le mme nom, dans cette classe clB2 la TVA applique la variable prix est aussi 5%. La proprit prix est dans cette classe clB2 liaison dynamique.
Notre objectif est de comparer les rsultats d'excution obtenus lorsque l'on utilise une
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
239
rfrence d'objet de classe mre instancie soit en objet de classe clB ou clB2. C'est le comportement de la proprit prix dans chacun de deux cas (statique ou dynamique) qui nous intresse :
using System ; namespace ProjPropIndex { class clA { private Double prixTotal ; private Double tauxTVA = 1.196 ; public virtual Double prix { // proprit virtuelle de la classe mre get { return Math.Round ( prixTotal ) ; } set { prixTotal = value * tauxTVA ; } } } class clB : clA { private Double prixLocal ; public new Double prix { // masquage de la proprit de la classe mre get { return Math.Round ( prixLocal ) ; } set { prixLocal = value * 1.05 ; } } } class clB2 : clA { private Double prixLocal ; public override Double prix {// redfinition de la proprit de la classe mre get { return Math.Round ( prixLocal ) ; } set { prixLocal = value * 1.05 ; } } } class Class { static private Double prixTotal ; static private Double tauxTVA = 1.196 ; static public Double prix { get { return Math.Round ( prixTotal ) ; } set { prixTotal = value * tauxTVA ; } } [STAThread] static void Main ( string [] args ) { clA Obj = new clA ( ); Double val = 100 ; System.Console.WriteLine ("Valeur entre Obj=new clA :" + val ); Obj.prix = val ; val = Obj.prix ; System.Console.WriteLine ("valeur arrondie (lue)Obj=new clA : " + val ) ; System.Console.WriteLine ("----------------------------------------"); Obj = new clB ( ); val = 100 ; System.Console.WriteLine ("Valeur entre Obj=new clB :" + val ); Obj.prix = val ; val = Obj.prix ; System.Console.WriteLine ("valeur arrondie (lue)Obj=new clB : " + val ) ; System.Console.WriteLine ("----------------------------------------"); Obj = new clB2 ( ); val = 100 ; System.Console.WriteLine ("Valeur entre Obj=new clB2 :" + val ); Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
240
Obj.prix = val ; val = Obj.prix ; System.Console.WriteLine ("valeur arrondie (lue)Obj=new clB2 : " + val ) ; System.Console.ReadLine ( ); } } }
Rsultats d'excution :
Nous voyons bien que le mme objet Obj instanci en classe clB ou en classe clB2 ne fournit pas les mmes rsultats pour la proprit prix, ces rsulats sont conformes la notion de polymorphisme en particulier pour l'instanciation en clB2. Rappelons que le masquage statique doit tre utilis comme pour les mthodes bon escient, plus spcifiquement lorsque nous ne souhaitons pas utiliser le polymorphisme, dans le cas contraire c'est la liaison dynamique qui doit tre utilise pour dfinir et redfinir des proprits.
1.7 Les proprits peuvent tre abstraites comme les mthodes Les proprits en C# peuvent tre dclares abstract, dans ce cas comme les mthodes elles sont automatiquement virtuelles sans necssiter l'utilisation du mot clef virtual. Comme une mthode abstraite, une proprit abstraite n'a pas de corps de dfinition pour le ou les accesseurs qui la composent, ces accesseurs sont implments dans une classe fille. Toute classe dclarant une proprit abstract doit elle-mme tre dclare abstract, l'implmentation de la proprit a lieu dans une classe fille, soit en masquant la proprit de la classe mre (grce une dclaration liaison statique avec le mot clef new), soit en la redfinissant (grce une dclaration liaison dynamique avec le mot clef override) :
abstract class clA { public abstract Double prix { // proprit abstraite virtuelle de la classe mre get ; // proprit abstraite en lecture set ; // proprit abstraite en criture } } class clB1 : clA { private Double prixTotal ; private Double tauxTVA = 1.196 ;
page
241
public new Double prix { //--redfinition par new refuse (car membre abstract) get { return Math.Round (prixTotal ) ; } set { prixTotal = value * tauxTVA ; } } } class clB2 : clA { private Double prixTotal ; private Double tauxTVA = 1.05 ; public override Double prix { // redfinition de la proprit par override correcte get { return Math.Round (prixTotal ) ; } set { prixTotal = value * tauxTVA ; } } }
1.8 Les proprits peuvent tre dclares dans une interface Les proprits en C# peuvent tre dclares dans une interface comme les vnements et les mthodes sans le mot clef abstract, dans ce cas comme dans le cas de proprit abstraites la dclaration ne contient pas de corps de dfinition pour le ou les accesseurs qui la composent, ces accesseurs sont implments dans une classe fille qui implmente elle-mme l'interface. Les proprits dclares dans une interface lorsqu'elles sont implmentes dans une classe peuvent tre dfinies soit liaison statique, soit liaison dynamique. Ci dessous une exemple de hirarchie abstraite de vhicules, avec une interface IVehicule contenant un vnement ( cet exemple est spcifi au chapitre sur les interfaces ) :
abstract class Vehicule { // classe abstraite mre Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
242
.... }
interface IVehicule { .... string TypeEngin { // dclaration de proprit abstraite par dfaut get ; set ; } .... }
abstract class UnVehicule : Vehicule , IVehicule { private string nom = ""; .... public virtual string TypeEngin { // implantation virtuelle de la proprit get { return nom ; } set { nom = "["+value+"]" ; } } .... }
abstract class Terrestre : UnVehicule { .... public override string TypeEngin { // redfinition de proprit get { return base .TypeEngin ; } set { string nomTerre = value + "-Terrestre"; base .TypeEngin = nomTerre ; } } }
1.9 Exemple complet excutable Code C# complet compilable avec l'vnement et une classe concrte
public delegate void Starting ( ); // delegate declaration de type pour l'vnement abstract class Vehicule { // classe abstraite mre public abstract void Demarrer ( ); // mthode abstraite public void RpartirPassagers ( ) { } // implantation de mthode avec corps vide public void PriodicitMaintenance ( ) { } // implantation de mthode avec corps vide } interface IVehicule { event Starting OnStart ; // dclaration d'vnement du type dlgu : Starting string TypeEngin { // dclaration de proprit abstraite par dfaut get ; set ; } void Stopper ( ); // dclaration de mthode abstraite par dfaut } Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
243
//-- classe abstraite hritant de la classe mre et implmentant l'interface : abstract class UnVehicule : Vehicule , IVehicule { private string nom = ""; private string [ ] ArrayInfos = new string [10] ; public event Starting OnStart ; protected void LancerEvent ( ) { if( OnStart != null) OnStart ( ); } public virtual string TypeEngin { // implantation virtuelle de la proprit get { return nom ; } set { nom = "["+value+"]" ; } } public virtual void Stopper ( ) { } // implantation virtuelle de mthode avec corps vide } abstract class Terrestre : UnVehicule { private string nomTerre = ""; public new void RpartirPassagers ( ) { } public new void PriodicitMaintenance ( ) { } public override string TypeEngin { // redfinition de proprit get { return base.TypeEngin ; } set { string nomTerre = value + "-Terrestre"; base.TypeEngin = nomTerre ; } } } class Voiture : Terrestre { public override string TypeEngin { // redfinition de proprit get { return base.TypeEngin + "-voiture"; } set { base.TypeEngin = "(" + value + ")"; } } public override void Demarrer ( ) { LancerEvent( ); } public override void Stopper ( ) { //... } }
class UseVoiture { // instanciation d'une voiture particulire static void Main ( string [] args ) { UnVehicule x = new Voiture ( ) ; x.TypeEngin = "Picasso" ; // proprit en criture System.Console.WriteLine ( "x est une " + x.TypeEngin ) ; // proprit en lecture System.Console.ReadLine ( ) ; } }
page
244
Rsultats d'excution :
page
245
page
246
nom est le champ priv dans lequel est stock la valeur effective de la proprit.
page
247
L'accesseur get va chercher le rsultat dans base.TypeEngin et lui concatne le mot "-voiture". base.TypeEngin rfrence ici la proprit dans la classe Terrestre.
L'accesseur get va chercher le rsultat dans base.TypeEngin. base.TypeEngin rfrence ici la proprit dans la classe UnVehicule.
page
248
L'accesseur get va chercher le rsultat dans le champ nom. nom est le champ priv dans lequel est stock la valeur effective de la proprit.
2. Les indexeurs
Nous savons en Delphi qu'il existe une notion de proprit par dfaut qui nous permet par exemple dans un objet Obj de type TStringList se nomme strings, d'crire Obj[5] au lieu de Obj.strings[5]. La notion d'indexeur de C# est voisine de cette notion de proprit par dfaut en Delphi.
2.1 Dfinitions et comparaisons avec les proprits Un indexeur est un membre de classe qui permet un objet d'tre index de la mme manire qu'un tableau. La signature d'un indexeur doit tre diffrente des signatures de tous les autres indexeurs dclars dans la mme classe. Les indexeurs et les proprits sont trs similaires de par leur concept, c'est pourquoi nous allons dfinir les indexeurs partir des proprits. Tous les indexeurs sont reprsents par l' oprateur [ ] . Les liens sur les proprits ou les indexeurs du tableau ci-dessous renvoient directement au paragraphe associ. Proprit Dclare par son nom. Identifie et utilise par son nom. Indexeur Dclar par le mot clef this. Identifi par sa signature, utilis par l'oprateur [ ] . L'oprateur [ ] doit tre situ immdiatement aprs le nom de l'objet. Ne peut pas tre un membre static, est toujours un membre d'instance.
page
249
L'accesseur get correspond une mthode pourvue de la mme liste de paramtres formels que l'indexeur. L'accesseur set correspond une mthode pourvue de la mme liste de paramtres formels que l'indexeur plus le paramtre implicite value. Un indexeur Prop hrit est accessible par la syntaxe base.[ ]
L'accesseur set correspond une mthode avec un seul paramtre implicite value.
Les proprits peuvent tre liaison statique, Les indexeurs peuvent tre liaison statique, liaison dynamique, masques ou redfinies. liaison dynamique, masqus ou redfinis. Les proprits peuvent tre abstraites. Les proprits peuvent tre dclares dans une interface. Les indexeurs peuvent tre abstraits. Les indexeurs peuvent tre dclars dans une interface.
2.1.1 Dclaration
Proprit Dclare par son nom, avec champ de stockage : private int champ; public int propr1{ get { return champ ; } set { champ = value ; } } Indexeur Dclar par le mot clef this, avec champ de stockage : private int [ ] champ = new int [10]; public int this [int index]{ get { return champ[ index ] ; } set { champ[ index ] = value ; } }
page
250
2.1.2 Utilisation
Proprit Dclaration : class clA { private int champ; public int propr1{ get { return champ ; } set { champ = value ; } } } Utilisation : clA Obj = new clA( ); Obj.propr1 = 100 ; Indexeur Dclaration : class clA { private int [ ] champ = new int [10]; public int this [int index]{ get { return champ[ index ] ; } set { champ[ index ] = value ; } } } Utilisation : clA Obj = new clA( ); for ( int i =0; i<5; i++ ) Obj[ i ] = i ;
2.1.3 Paramtres
Proprit Dclaration : class clA { private int champ; public int propr1{ get { return champ*10 ; } set { champ = value + 1 ; } } } value est un paramtre implicite.
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
Indexeur Dclaration : class clA { private int [ ] champ = new int [10]; public int this [int k]{ get { return champ[ k ]*10 ; } set { champ[ k ] = value + 1 ; } } } k est un paramtre formel de l'indexeur.
page
251
Utilisation : clA Obj = new clA( ); Obj.propr1 = 100 ; int x = Obj.prop1 ; // x = 1010
Utilisation : clA Obj = new clA( ); for ( int i =0; i<5; i++ ) Obj[ i ] = i ; int x = Obj[ 2 ] ; // x = 30 int y = Obj[ 3 ] ; // x = 40 ...
2.1.4 Indexeur liaison dynamique, abstraction et interface Reprenons l'exemple de hirarchie de vhicules, trait avec la proprit TypeEngin de type string, en y ajoutant un indexeur de type string en proposant des dfinitions parallles la proprit et l'indexeur :
interface IVehicule { .... string TypeEngin { // dclaration de proprit abstraite par dfaut get ; set ; } .... string this [ int k ] { // dclaration d'indexeur abstrait par dfaut Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
252
abstract class UnVehicule : Vehicule , IVehicule { private string [ ] ArrayInfos = new string [10] ; private string nom = ""; .... public virtual string TypeEngin { // implantation virtuelle de la proprit get { return nom ; } set { nom = "["+value+"]" ; } } .... public virtual string this [ int k ] { // implantation virtuelle de l'indexeur get { return ArrayInfos[ k ] ; } set { ArrayInfos[ k ] = value ; } } .... }
abstract class Terrestre : UnVehicule { private string nomTerre = ""; .... public override string TypeEngin { // redfinition de proprit get { return base .TypeEngin ; } set { string nomTerre = value + "-Terrestre"; base .TypeEngin = nomTerre ; } } .... public override string this [ int k ] { // redfinition de l'indexeur get { return base[ k ] ; } set { string nomTerre = value + "-Terrestre" ; base[ k ] = nomTerre + "/set =" + k.ToString( ) + "/" ; } } }
class Voiture : Terrestre { public override string TypeEngin { // redfinition de proprit get { return base.TypeEngin + "-voiture"; } set { base.TypeEngin = "(" + value + ")"; } } public override string this [ int n ] { // redfinition de l'indexeur get { return base[ n ] + "-voiture{get =" + n.ToString( ) + "}" ; } set { base[ n ] = "(" + value + ")"; } } ... }
page
253
abstract class Vehicule { public abstract void Demarrer ( ); public void RpartirPassagers ( ) { } public void PriodicitMaintenance ( ) { } } interface IVehicule { event Starting OnStart ; // dclaration vnement string this [ int index] // dclaration Indexeur { get ; set ; } string TypeEngin // dclaration proprit { get ; set ; } void Stopper ( ); } abstract class UnVehicule : Vehicule, IVehicule { private string nom = ""; private string [] ArrayInfos = new string [10] ; public event Starting OnStart ; // implantation vnement protected void LancerEvent ( ) { if( OnStart != null) OnStart ( ); } public virtual string this [ int index] { // implantation indexeur virtuel get { return ArrayInfos[index] ; } set { ArrayInfos[index] = value ; } } public virtual string TypeEngin { // implantation proprit virtuelle get { return nom ; } set { nom = "[" + value + "]"; } } public virtual void Stopper ( ) { } } abstract class Terrestre : UnVehicule public new void RpartirPassagers ( ) { } public new void PriodicitMaintenance ( ) { } public override string this [ int k] { // redfinition indexeur get { return base [k] ; } set { string nomTerre = value + "-Terrestre"; base [k] = nomTerre + "/set = " + k.ToString () + "/"; } } public override string TypeEngin { // redfinition proprit get { return base .TypeEngin ; } set { string nomTerre = value + "-Terrestre"; base .TypeEngin = nomTerre ; } Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
254
} } class Voiture : Terrestre { public override string this [ int n] { // redfinition indexeur get { return base [n] + "-voiture {get = " + n.ToString ( )+ " }"; } set { string nomTerre = value + "-Terrestre"; base [n] = "(" + value + ")"; } } public override string TypeEngin { // redfinition proprit get { return base .TypeEngin + "-voiture"; } set { base .TypeEngin = "(" + value + ")"; } } public override void Demarrer ( ) { LancerEvent ( ); } public override void Stopper ( ) { //... } }
class UseVoiture { static void Main ( string [] args ) { // instanciation d'une voiture particulire : UnVehicule automobile = new Voiture ( ); // utilisation de la proprit TypeEngin : automobile .TypeEngin = "Picasso"; System .Console.WriteLine ("x est une " + automobile.TypeEngin ); // utilisation de l'indexeur : automobile [0] = "Citroen"; automobile [1] = "Renault"; automobile [2] = "Peugeot"; automobile [3] = "Fiat"; for( int i = 0 ; i < 4 ; i ++ ) System .Console.WriteLine ("Marque possible : " + automobile [i] ); System .Console.ReadLine ( ); } }
Rsultats d'excution :
page
255
page
256
etc ... L'espace des noms System.Windows.Forms est le domaine privilgi du NetFrameWork dans lequel l'on trouve des classes permettant de travailler sur des applications fentres. La classe Form de l'espace des noms System.Windows.Forms permet de crer une fentre classique avec barre de titre, zone client, boutons de fermeture, de zoom... En C#, Il suffit d'instancier un objet de cette classe pour obtenir une fentre classique qui s'affiche sur l'cran.
page
257
Le code C# peut tout comme le code Java tre crit avec un diteur de texte rudimentaire du style bloc-note, puis tre compil directement la console par appel au compilateur csc.exe. Soient par exemple dans un dossier temp du disque C: , deux fichiers :
Le fichier "_exoconsole.bat" contient la commande systme permettant d'appeller le compilateur C#. Le fichier "_exoconsole.cs" le programme source en C#. Construction de la commande de compilation "_exoconsole.bat" : On indique d'abord le chemin (variable path) du rpertoire o se trouve le compilateur csc.exe, puis on lance l'appel au compilateur avec ici , un nombre minimal de paramtres : Attributs et paramtres de la commande
set path = C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 /t:exe
fonction associe
Le chemin absolu permettant d'accser au dossier contenant le compilateur C# (csc.exe) Indique que nous voulons engendrer une excutable console (du code MSIL) Indique le nom que doit porter le fichier excutable MSIL aprs compilation Le chemin complet du fichier source C# compiler (ici il est dans le mme rpertoire que la commande, seul le nom du fichier suffit)
/out: _exo.exe
_exoconsole.cs
page
258
Le programme prcdent affiche le mot Bonjour suivit de l'excution de la boucle sur 5 itrations. On lance l'invite de commande de Windows ici dans le rpertoire c:\temp :
Elle appelle le compilateur csc.exe qui effectue la compilation du programme _exoconsole.cs sans signaler d'erreur particulire et qui a donc engendr une fichier MSIL nomm _exo.exe :
page
259
Lanons par un double click l'excution du programme _exo.exe qui vient d'tre engendr :
Nous constatons que l'excution par le CLR du fichier _exo.exe a produit le rsultat escompt c'est dire l'affichage du mot Bonjour suivit de l'excution de la boucle sur 5 itrations. Afin que le lecteur soit bien convaincu que nous sommes sous NetFramework et que les fichiers excutables ne sont pas du binaire excutable comme jusqu' prsent sous Windows, mais des fichiers de code MSIL excutable par le CLR, nous passons le fichier _exo.exe au dsassembleur ildasm par la commande "ildasm.bat". Le dsassembleur MSIL Disassembler (Ildasm.exe) est un utilitaire inclus dans le kit de dveloppement .NET Framework SDK, il est de ce fait utilisable avec tout langage de .Net dont C#. ILDasm.exe analyse toutes sortes d'assemblys .NET Framework .exe ou .dll et prsente les informations dans un format explicite. Cet outil affiche bien plus que du code MSIL (Microsoft Intermediate Language) ; il prsente galement les espaces de noms et les types, interfaces comprises.
page
260
Demandons ildasm l'inspection du code MSIL engendr pour la mthode Main( ) : Nous avons mis en gras et en italique les commentaires d'instructions sources Exemple::methodeMain void( )
.method private hidebysig static void Main( ) cil managed { .entrypoint // Code size 51 (0x33) .maxstack 2 .locals init ([0] int32 i) .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' // Source File 'c:\temp\_exoconsole.cs' //000007: System.Console.WriteLine("Bonjour"); IL_0000: ldstr "Bonjour" IL_0005: call void [mscorlib]System.Console::WriteLine(string) //000008: for ( int i=1; i<6; i++ ) IL_000a: ldc.i4.1 IL_000b: stloc.0 IL_000c: br.s IL_0028 //000009: System.Console.WriteLine( "i = "+i.ToString( ) ); IL_000e: ldstr "i = " IL_0013: ldloca.s i IL_0015: call instance string [mscorlib]System.Int32::ToString() IL_001a: call string [mscorlib]System.String::Concat(string,string) IL_001f: call void [mscorlib]System.Console::WriteLine(string) //000008: for ( int i=1; i<6; i++ ) IL_0024: ldloc.0 IL_0025: ldc.i4.1 IL_0026: add IL_0027: stloc.0 IL_0028: ldloc.0 IL_0029: ldc.i4.6 IL_002a: blt.s IL_000e //000009: System.Console.WriteLine( "i = "+i.ToString( ) ); //000010: System.Console.ReadLine( ); IL_002c: call string [mscorlib]System.Console::ReadLine() IL_0031: pop Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
261
1.2 Des fentres la console On peut donc de la mme faon compiler et excuter partir de la console, des programmes C# contenant des fentres, comme en java il est possible d'excuter partir de la console des applications contenant des Awt ou des Swing, idem en Delphi. Nous proposons au lecteur de savoir utiliser des programmes qui allient la console une fentre classique, ou des programmes qui ne sont forms que d'une fentre classique ( minima).
Ce programme "_exowin.cs" utilise la classe Form et affiche une fentre de type Form : La premire instruction instancie un objet nomm fiche de la classe Form Form fiche = new Form( ) ; La fentre fiche est ensuite "initialise" et "lance" par l'instruction Application.Run(fiche) ;
On construit une commande nomme _exowin.bat, identique celle du paragraphe prcdent, afin de lancer la compilation du programme _exowin.cs, nous dcidons de nommer _exow.exe l'excutable MSIL obtenu aprs compilation.
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
262
contenu fichier de commande _exowin.bat : set path=C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 csc /t:exe /out:_exow.exe _exowin.cs
Cette commande a gnr comme prcdemment l'excutable MSIL nomm ici _exow.exe dans le dossier c:\temp, nous excutons le programme _exow.exe et nous obtenons l'affichage d'une fentre de console et de la fentre fiche :
page
263
Remarque: L'attribut /debug+ rajout ici, permet d'engendrer un fichier _exo.pdb qui contient des informations de dboguage utiles ildasm.
Nous compilons en lanant la nouvelle commande _exowin.bat puis nous excutons le nouveau programme _exow.exe. Nous obtenons cette fois-ci l'affichage d'une seule fentre (la fentre de console a disparu) :
Cette fentre est un peu trop terne, nous pouvons travailler en mode console sur la fiche Form, afin par exemple de mettre un texte dans la barre de titre et de modifier sa couleur de fond.
page
264
Nous compilons en lanant la commande _exowin.bat puis nous excutons le nouveau programme _exow.exe et nous obtenons l'affichage de la fentre fiche avec le texte "Exemple de Form" dans la barre de titre et sa couleur de fond marron-ros (Color.RosyBrown) :
Consultons titre informatif avec ildasm le code MSIL engendr pour la mthode Main( ) : Nous avons mis en gras et en italique les commentaires d'instructions sources Exemple::methodeMain void( )
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 35 (0x23) .maxstack 2 .locals init ([0] class [System.Windows.Forms]System.Windows.Forms.Form fiche) .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' // Source File 'c:\temp\_exowin.cs' //000008: Form fiche = new Form( ); IL_0000: newobj instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor() IL_0005: stloc.0 //000009: fiche.Text="Exemple de Form"; IL_0006: ldloc.0 IL_0007: ldstr "Exemple de Form" IL_000c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string) //000010: fiche.BackColor=System.Drawing.Color.RosyBrown; IL_0011: ldloc.0 IL_0012: call valuetype [System.Drawing]System.Drawing.Color [System.Drawing]System.Drawing.Color::get_RosyBrown() IL_0017: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_BackColor(valuetype [System.Drawing]System.Drawing.Color) //000011: Application.Run(fiche); IL_001c: ldloc.0 IL_001d: call void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form) //000012: } IL_0022: ret } // end of method Exemple::Main
page
265
Lorsque nous compilons puis excutons ce programme la fiche apparat correctement (titre et couleur) d'une manire fugace car elle disparat aussi vite qu'elle est apparue. En effet le programme que nous avons crit est correct : Ligne d'instruction du programme
{ Form fiche = new Form( ); fiche.Text="Exemple de Form"; fiche.BackColor=System.Drawing.Color.RosyBrown; Premier pas dans .Net avec C# - (maj. 15.08.2005
)
266
fiche.Show( ); }
il rend la fiche visible fin de la mthode Main et donc tout est dtruit et libr et le processus est termin.
La fugacit de l'affichage de notre fentre fiche est donc normale, puisqu' peine cre la fiche a t dtruite. Si nous voulons que notre objet de fiche persiste sur l'cran, il faut simuler le comportement de la mthode classe Run, c'est dire qu'il nous faut crire une boucle de messages.
Nous creons artificiellement une boucle en apparence infinie qui laisse le traitement des messages s'effectuer et qui attend qu'on lui demande de s'arrter par lintermdiaire dun boolen stop dont la valeur change par effet de bord grce DoEvents : static bool stop = false; while (!stop) Application.DoEvents( );
page
267
Il suffit que lorsque DoEvents( ) s'excute l'une des actions de traitement de messages provoque la mise du boolen stop true pour que la boucle s'arrte et que le processus se termine. Choisissons une telle action par exemple lorsque l'utilisateur clique sur le bouton de fermeture de la fiche, la fiche se ferme et l'vnement closed est dclench, DoEvents( ) revient dans la boucle d'attente while (!stop) Application.DoEvents( ); au tour de boucle suivant. Si lorsque l'vnement close de la fiche a lieu nous en profitons pour mettre le boolen stop true, ds le retour de DoEvents( ) le prochain tour de boucle arrtera l'excution de la boucle et le corps d'instruction de main continuera s'excuter squentiellement jusqu' la fin (ici on arrtera le processus). Ci-dessous le programme C# mettre dans le fichier "_exowin.cs" :
using System; using System.Windows.Forms; namespace ApplicationTest { public class Exemple { static bool stop = false; // le drapeau d'arrt de la boucle d'attente static void fiche_Closed (object sender, System.EventArgs e) { // le gestionnaire de l'vnement closed de la fiche stop = true; } static void Main( ) { System.Windows.Forms.Button button1 = new System.Windows.Forms.Button( ); Form fiche = new Form( ); fiche.Text="Exemple de Form"; fiche.BackColor=System.Drawing.Color.RosyBrown; fiche.Closed += new System.EventHandler(fiche_Closed); // abonnement du gestionnaire fiche.Show( ); while (!stop) Application.DoEvents( ); // boucle d'attente //... suite ventuelle du code avant arrt } } } Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
268
Lorsque nous compilons puis excutons ce programme la fiche apparat correctement et reste prsente sur l'cran :
Elle se ferme et disparat lorsque nous cliquons sur le bouton de fermeture. On peut aussi vouloir toujours en utilisant la boucle infinie qui laisse le traitement des messages s'effectuer ne pas se servir d'un boolen et continuer aprs la boucle, mais plutt essayer d'interrompre et de terminer l'application directement dans la boucle infinie sans excuter la suite du code. La classe Application ne permet pas de terminer le processus. Attention l'utilisation de la mthode Exit de la classe Application qui semblerait tre utilisable dans ce cas, en effet cette mthode arrte toutes les boucles de messages en cours sur tous les threads et ferme toutes les fentres de l'application; mais cette mthode ne force pas la fermeture de l'application. Pour nous en convaincre compilons et excutons le programme ci-aprs dans lequel l'vnement fiche_Closed appelle Application.Exit( ) Ci-dessous le programme C# mettre dans le fichier "_exowin.cs" :
using System; using System.Windows.Forms; namespace ApplicationTest { public class Exemple { static void fiche_Closed (object sender, System.EventArgs e) { // le gestionnaire de l'vnement closed de la fiche Application.Exit( ); // on ferme la fentre, mais on ne termine pas le processus } static void Main( ) { System.Windows.Forms.Button button1 = new System.Windows.Forms.Button( ); Form fiche = new Form( ); fiche.Text="Exemple de Form"; fiche.BackColor=System.Drawing.Color.RosyBrown; fiche.Closed += new System.EventHandler(fiche_Closed); // abonnement du gestionnaire fiche.Show( ); while (true) Application.DoEvents( ); // boucle infinie } } }
page
269
Lorsque nous compilons puis excutons ce programme la fiche apparat correctement et reste prsente sur l'cran, puis lorsque nous fermons la fentre comme prcdemment, elle disparat, toutefois le processus _exow.exe est toujours actif (la boucle tourne toujours, mais la fentre a t ferme) en faisant apparatre le gestionnaire des tches de Windows l'onglet processus nous voyons qu'il est toujours prsent dans la liste des processus actifs. Si nous voulons l'arrter il faut le faire manuellement comme indiqu ci-dessous :
page
270
static void Main( ) { System.Windows.Forms.Button button1 = new System.Windows.Forms.Button( ); Form fiche = new Form( ); fiche.Text="Exemple de Form"; fiche.BackColor=System.Drawing.Color.RosyBrown; fiche.Closed += new System.EventHandler(fiche_Closed); // abonnement du gestionnaire fiche.Show( ); while (true) Application.DoEvents( ); // boucle infinie } } }
Aprs compilation, excution et fermeture en faisant apparatre le gestionnaire des tches de Windows l'onglet processus nous voyons que le processus a disparu de la liste des processus actifs du systme. Nous avons donc bien interrompu la boucle infinie. Toutefois la console n'est pas l'outil prfrentiel de C# dans le sens o C# est l'outil de dveloppement de base de .Net et que cette architecture a vocation travailler essentiellement avec des fentres. Dans ce cas nous avons tout intrt utiliser un RAD visuel C# pour dvelopper ce genre d'applications (comme l'on utilise Delphi pour le dveloppement d'IHM en pascal objet). Une telle utilisation nous procure le confort du dveloppement visuel, la gnration automatique d'une bonne partie du code rptitif sur une IHM, l'utilisation et la rutilisation de composants logiciels distribus sur le net.
RAD utilisables
Visual Studio de microsoft contient deux RAD de dveloppement pour .Net, VBNet (fond sur Visual Basic rellement objet et entirement rnov) et Visual C# (fond sur le langage C#), parfaitement adapt .Net. (prix trs rduit pour l'ducation) C# Builder de Borland reprenant les fonctionnalits de Visual C# dans Visual Studio, avec un intrt supplmentaire pour un tudiant ou un apprenant : une version personnelle gratuite est tlchargeable. Le monde de l'open source construit un produit nomm sharpDevelop qui devrait terme fournir tous gratuitement aussi les mmes fonctions.
1.3 un formulaire en C# est une fiche Les fiches ou formulaires C# reprsentent l'interface utilisateur (IHM) d'une application sous l'apparence d'une fentre visuelle. Comme les deux environnements RAD, Visual studio C# de Microsoft et C# Builder de Borland permettent de concevoir visuellement des applications avec IHM, nous dnommerons l'un ou l'autre par le terme gnral RAD C#.
Premier pas dans .Net avec C# - (maj. 15.08.2005
page
271
Etant donn la disponibilit gratuite du RAD C# Builder en version personnelle (trs suffisante pour dj crire de bonnes applications) nous illustrerons tous nos exemples avec ce RAD. Voici l'apparence d'un formulaire (ou fiche) dans le RAD C# Builder en mode conception :
La fiche elle-mme est figure par l'image ci-dessous retaillable volont partir de cliqu gliss sur l'une des huits petites "poignes carres" situes aux points cardinaux de la fiche :
Ces formulaires sont en faits des objets d'une classe nomme Form de l'espace des noms System.Windows.Forms. Ci-dessous la hirarchie d'hritage de Object Form : System.Object System.MarshalByRefObject System.ComponentModel.Component
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
272
System.Windows.Forms.Control System.Windows.Forms.ScrollableControl System.Windows.Forms.ContainerControl System.Windows.Forms.Form La classe Form est la classe de base de tout style de fiche (ou formulaire) utiliser dans votre application (statut identique TForm dans Delphi) : fiche de dialogue, sans bordure etc.. Les diffrents styles de fiches sont spcifis par l'numration FormBorderStyle : public Enum FormBorderStyle {Fixed3D, FixedDialog, FixedSingle, FixedToolWindow, None, Sizable, SizableToolWindow } Dans un formulaire, le style est spcifi par la proprit FormBorderStyle de la classe Form : public FormBorderStyle FormBorderStyle {get; set;} Toutes les proprits en lecture et criture d'une fiche sont accessibles travers l'inspecteur d'objet qui rpercute immdiatement en mode conception toute modification. Certaines provoquent des changements visuels d'autres non : 1) changeons le nom d'identificateur de notre fiche dans le programme en modifiant la proprit Name qui vaut par dfaut WinForm :
WinForm
Le RAD construit automatiquement notre fiche principale comme une classe hrite de la classe Form et l'appelle WinForm : public class WinForm : System.Windows.Forms.Form { ... } Aprs modification de la proprit Name par le texte Form1, nous obtenons :
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
273
Form1
La classe de notre formulaire s'appelle dsormais Form1, mais son aspect visuel est rest le mme :
2) Par exemple slectionnons dans l'inspecteur d'objet de C# Builder, la proprit FormBorderStyle ( le style par dfaut est FormBorderStyle.Sizable ) modifions la la valeur None et regardons dans l'onglet conception la nouvelle forme de la fiche :
page
274
1.4 code C# engendr par le RAD pour un formulaire Aprs avoir remis grce l'inspecteur d'objet, la proprit FormBorderStyle sa valeur Sizable et remis le Name sa valeur initiale WinForm, voyons maintenant en supposant avoir appel notre application ProjApplication0 ce que C# Builder a engendr comme code source que nous trouvons dans l'onglet code pour notre formulaire :
L'intgralit du code propos par C# Builder est sauvegard dans un fichier nomm WinForm.cs
page
275
page
276
correspond
au constructeur d'objet de la classe WinForm et que la mthode
au point d'entre d'excution de l'application, son corps contient un appel de la mthode statique Run de la classe Application, elle instancie un objet "new WinForm ( )"de classe WinForm pass en paramtre la mthode Run : c'est la fiche principale de l'application.
Application.Run (new WinForm ( )); La classe Application (semblable TApplication de Delphi) fournit des membres statiques (proprits et mthodes de classes) pour grer une application (dmarrer, arrter une application, traiter des messages Windows), ou d'obtenir des informations sur une application. Cette classe est sealed et ne peut donc pas tre hrite. La mthode Run de la classe Application dont voici la signature : public static void Run( ApplicationContext context ); Excute une boucle de messages d'application standard sur le thread en cours, par dfaut le paramtre context coute l'vnement Closed sur la fiche principale de l'application et ds lors arrte l'application.
Pour les connaisseurs de Delphi , le dmarrage de l'excution s'effectue dans le programme principal :
program Project1; uses Forms, Unit1 in 'Unit1.pas' {Form1}; {$R *.res} begin Application.Initialize; Application.CreateForm (WinForm , Form1); Application.Run; end.
Pour les connaisseurs des Awt et des Swing de Java, cette action C# correspond aux lignes suivantes :
Java2 avec Awt class WinForm extends Frame { public WinForm ( ) { enableEvents(AWTEvent.WINDOW_EVENT_MASK); } protected void processWindowEvent(WindowEvent e) { Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
277
super.processWindowEvent(e); if(e.getID( ) == WindowEvent.WINDOW_CLOSING) { System.exit(0); } } public static void main(String[] x ) new WinForm ( ); } } Java2 avec Swing class WinForm extends JFrame { public WinForm ( ) { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] x ) { new WinForm ( ); } } {
Lorsque l'on regarde de plus prs le code de la classe WinForm situ dans l'onglet code on se rend compte qu'il existe une ligne en gris entre la mthode Dispose et la mthode main :
Il s'agit en fait de code repli (masqu). Voici le contenu exact de l'onglet code avec sa zone de code repli :
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace ProjApplication0 { /// <summary> /// Description Rsum de Form1. /// </summary> public class WinForm : System.Windows.Forms.Form { /// <summary> /// Variable requise par le concepteur. /// </summary> private System.ComponentModel.Container components = null; public WinForm ( ) { // // Requis pour la gestion du concepteur Windows Form // InitializeComponent( ); // // TODO: Ajouter tout le code du constructeur aprs l'appel de InitializeComponent // Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
278
} /// <summary> /// Nettoyage des ressources utilises. /// </summary> protected override void Dispose (bool disposing) { if (disposing) { if (components != null) { components.Dispose( ); } } base.Dispose(disposing); } /// <summary> /// Le point d'entre principal de l'application. /// </summary> [STAThread] static void Main( ) { Application.Run(new WinForm ( )); } } }
Si nous le dplions et nous voyons apparatre la mthode prive InitializeComponent( ) contenant du code qui a manifestement t gnr directement. En outre cette mthode est appele dans le constructeur d'objet WinForm :
page
279
#region Code gnr par le concepteur Windows Form /// <summary> /// Mthode requise pour la gestion du concepteur - ne pas modifier /// le contenu de cette mthode avec l'diteur de code. /// </summary> private void InitializeComponent( ) { // // WinForm // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(232, 157); this.Name = "WinForm"; this.Text = "WinForm"; } #endregion
Essayons de voir comment une manipulation visuelle engendre des lignes de code, pour cela modifions dans l'inspecteur d'objet deux proprits FormBorderStyle et BackColor, la premire est mise None la seconde qui indique la couleur du fond de la fiche est mise LightSalmon :
Consultons aprs cette opration le contenu du nouveau code gnr, nous trouvons deux nouvelles lignes de code correspondant aux nouvelles actions visuelles effectues (les nouvelles lignes sont figures en rouge ) : #region Code gnr par le concepteur Windows Form /// <summary> /// Mthode requise pour la gestion du concepteur - ne pas modifier /// le contenu de cette mthode avec l'diteur de code. /// </summary> private void InitializeComponent( ) { // // WinForm //
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
280
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.BackColor = System.Drawing.Color.LightSalmon; this.ClientSize = new System.Drawing.Size(232, 157); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.Name = "WinForm"; this.Text = "WinForm"; } #endregion
1.5 Libration de ressources non manages Dans le code engendr par Visual studio ou C# Builder, nous avons laiss de ct la mthode Dispose : protected override void Dispose (bool disposing) if (disposing) { if (components != null) { components.Dispose( ); } } base.Dispose( disposing ); } {
Pour comprendre son utilit, il nous faut avoir quelques lumires sur la faon que NetFrameWork a de grer les ressources, rappelons que le CLR excute et gre le code administr c'est dire qu'il vrifie la validit de chaque action avant de l'excuter. Le code non administr ou ressource non manage en C# est essentiellement du code sur les pointeurs qui doivent tre dclars unsafe pour pouvoir tre utiliss, ou bien du code sur des fichiers, des flux , des handles . La mthode Dispose existe dj dans la classe mre System.ComponentModel.Component sous forme de deux surcharges avec deux signatures diffrentes. Elle peut tre utile si vous avez mobilis des ressources personnelles ( on dit aussi ressources non manages ) et que vous souhaitiez que celles-ci soient libres lors de la fermeture de la fiche :
classe : System.ComponentModel.Component mthode : public virtual void Dispose( ); Libre toutes les ressources utilises par Component. mthode : protected virtual void Dispose( bool disposing ); Libre uniquement les ressources non manages utilises par Component.. ou Libre toutes les ressources utilises par Component. Selon la valeur de disposing disposing = true pour librer toutes les ressources (manages et non manages) ; disposing = false pour librer uniquement les ressources non manages.
page
281
Remarque-1 Notons que pour le dbutant cette mthode ne sera jamais utilise et peut tre omise puisqu'il s'agit d'une surcharge dynamique de la mthode de la classe mre.
Remarque-2 Il est recommand par Microsoft, qu'un objet Component libre des ressources explicitement en appelant sa mthode Dispose sans attendre une gestion automatique de la mmoire lors d'un appel implicite au Garbage Collector. Si nous voulons comprendre comment fonctionne le code engendr pour la mthode Dispose, il nous faut revenir des lments de base de la gestion mmoire en particulier relativement la libration des ressources par le ramasse-miettes (garbage collector).
1.6 Comment la libration a-t-elle lieu dans le NetFrameWork ? La classe mre de la hirarchie dans le NetFrameWork est la classe System.Object, elle possde une mthode virtuelle protected Finalize, qui permet de librer des ressources et d'excuter d'autres oprations de nettoyage avant que Object soit rcupr par le garbage collecteur GC.
Lorsqu'un objet devient inaccessible il est automatiquement plac dans la file d'attente de finalisation de type FIFO, le garbage collecteur GC, lorsque la mmoire devient trop basse, effectue son travail en parcourant cette file d'attente de finalisation et en librant la mmoire occupe par les objets de la file par appel la mthode Finalize de chaque objet. Donc si l'on souhaite librer des ressources personnalises, il suffit de redfinir dans une classe fille la mthode Finalize( ) et de programmer dans le corps de la mthode la libration de ces ressources. En C# on pourrait crire pour une classe MaClasse :
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
282
protected override void Finalize( ) { try { // libration des ressources personnelles } finally { base.Finalize( ); // libration des ressources du parent } } Mais syntaxiquement en C# la mthode Finalize n'existe pas et le code prcdent, s'il reprsente bien ce qu'il faut faire, ne sera pas accept par le compilateur. En C# la mthode Finalize s'crit comme un destructeur de la classe MaClasse : ~MaClasse( ) { // libration des ressources personnelles }
1.7 Peut-on influer sur cette la libration dans le NetFrameWork ? Le processus de gestion de la libration mmoire et de sa rcupration est entirement automatis dans le CLR, mais selon les ncessits on peut avoir le besoin de grer cette dsallocation : il existe pour cela, une classe System.GC qui autorise le dveloppeur une certaine dose de contrle du garbage collector. Par exemple, vous pouvez empcher explicitement la mthode Finalize d'un objet figurant dans la file d'attente de finalisation d'tre appele, (utilisation de la mthode : public static void SuppressFinalize( object obj );)
Vous pouvez aussi obliger explicitement la mthode Finalize d'un objet figurant dans la file d'attente de finalisation mais contenant GC.SuppressFinalize(...) d'tre appele, ( utilisation de la mthode : public static void ReRegisterForFinalize( object obj ); ). Microsoft propose deux recommandations pour la libration des ressources :
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
283
Il est recommand d'empcher les utilisateurs de votre application d'appeler directement la mthode Finalize d'un objet en limitant sa porte protected. Il est vivement dconseill d'appeler une mthode Finalize pour une autre classe que votre classe de base directement partir du code de votre application. Pour supprimer correctement des ressources non manages, il est recommand d'implmenter une mthode Dispose ou Close publique qui excute le code de nettoyage ncessaire pour l'objet.
1.8 Design Pattern de libration des ressources non manages Le NetFrameWork propose une interface IDisposable ne contenant qu'une seule mthode : Dispose
page
284
Rappel Il est recommand par Microsoft, qu'un objet Component libre des ressources explicitement en appelant sa mthode Dispose sans attendre une gestion automatique de la mmoire lors d'un appel implicite au Garbage Collector. C'est ainsi que fonctionnent tous les contrles et les composants de NetFrameWork. Il est bon de suivre ce conseil car dans le modle de conception fourni ci-aprs, la libration d'un composant fait librr en cascade tous les lments de la hirarchie sans les mettre en liste de finalisation ce qui serait une perte de mmoire et de temps pour le GC.
Ce modle n'est prsent que pour mmoire afin de bien comprendre le modle pour une classe fille qui suit et qui correspond au code gnr par le RAD C# .
page
285
Information NetFrameWork sur la classe Container : System.ComponentModel.Container La classe Container est l'implmentation par dfaut pour l'interface IContainer, une instance s'appelle un conteneur. Les conteneurs sont des objets qui encapsulent et effectuent le suivi de zro ou plusieurs composants qui sont des objets visuels ou non de la classe System.ComponentModel.Component. Les rfrences des composants d'un conteneur sont ranges dans une file FIFO, qui dfinit galement leur ordre dans le conteneur. La classe Container suit le modle de conception mentionn plus haut quant la libration des ressources manages ou non. Elle possde deux surcharges de Dispose implmentes selon le Design Pattern : la mthode protected virtual void Dispose( bool disposing); et la mthode public void Dispose( ). Cette mthode libre toutes les ressources dtenues par les objets manags stocks dans la FIFO du Container. Cette mthode appelle la mthode Dispose( ) de chaque objet rfrenc dans la FIFO.
page
286
Donc tous les composants de C# sont construits selon le Design Pattern de libration : La classe Component implmente le Design Pattern de libration de la classe de base et toutes les classe descendantes dont la classe Form implmente le Design Pattern de libration de la classe fille. Nous savons maintenant quoi sert la mthode Dispose dans le code engendr par le RAD, elle nous propose une libration automatique des ressources de la liste des composants que nous aurions ventuellement crs : // Nettoyage des ressources utilises : protected override void Dispose (bool disposing) { if (disposing) { // Nettoyage des ressources manages if (components != null) { components.Dispose( ); } } // Nettoyage des ressources non manages base.Dispose(disposing); }
1.9 Un exemple utilisant la mthode Dispose d'un formulaire Supposons que nous avons construit un composant personnel dans une classe UnComposant qui hrite de la classe Component selon le Design Pattern prcdent, et que nous avons dfini cette classe dans le namespace ProjApplication0 : System.ComponentModel.Component |__ProjApplication0.UnComposant Nous voulons construire une application qui est un formulaire et nous voulons crer lors de l'initialisation de la fiche un objet de classe UnComposant que notre formulaire utilisera. A un moment donn notre application ne va plus se servir du tout de notre composant, si nous souhaitons grer la libration de la mmoire alloue ce composant, nous pouvons : Soit attendre qu'il soit ligible au GC, en ce cas la mmoire sera libre lorsque le GC le dcidera, Soit le recenser auprs du conteneur de composants components (l'ajouter dans la FIFO de components si le conteneur a dj t cr ). Sinon nous crons ce conteneur et nous utilisons la mthode d'ajout (Add) de la classe System.ComponentModel.Container pour ajouter notre objet de classe UnComposant dans la FIFO de l'objet conteneur components.
page
287
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace ProjApplication0 { /// <summary> /// Description Rsum de Form1. /// </summary> public class WinForm : System.Windows.Forms.Form { /// <summary> /// Variable requise par le concepteur. /// </summary> private System.ComponentModel.Container components = null; private UnComposant MonComposant = new UnComposant( ); public WinForm ( ) { InitializeComponent( ); if ( components == null ) components = new Container( ); components.Add ( MonComposant ); Dclaration-instanciation d'un composant personnel.
} /// <summary> /// Nettoyage des ressources utilises. /// </summary> protected override void Dispose (bool disposing) { Notre composant personnel est if (disposing) { libr avec les autres if (components != null) { components.Dispose( ); } //-- libration ici d'autres ressources manages... } //-- libration ici de vos ressources non manages... base.Dispose(disposing); } /// <summary> /// Le point d'entre principal de l'application. /// </summary> [STAThread] static void Main( ) { Application.Run(new WinForm ( )); } } }
page
288
1.10 L'instruction using appelle Dispose( ) La documentation technique signale que deux utilisations principales du mot cl using sont possibles : Directive using : Cre un alias pour un espace de noms ou importe des types dfinis dans d'autres espaces de noms. Ex: using System.IO ; using System.Windows.Forms ; ... Instruction using : Dfinit une porte au bout de laquelle un objet est supprim. C'est cette deuxime utilisation qui nous intresse : l'instruction using <instruction using> ::= using ( <identif. Objet> | <liste de Dclar & instanciation> ) <bloc instruction> <bloc instruction> ::= { < suite d'instructions > } <identif. Objet> ::= un identificateur d'un objet existant et instanci <liste de Dclar & instanciation> ::= une liste spare par des virgules de dclaration et initialisation d'objets semblable la partie initialisation d'une boucle for. Ce qui nous donne deux cas d'criture de l'instruction using : 1 - sur un objet dj instanci :
classeA Obj = new classeA( ); .... using ( Obj ) { // code quelconque.... }
Le using lance la mthode Dispose : Dans les deux cas, on utilise une instance (Obj de classeA) ou l'on cre des instances (Obj1, Obj2 et Obj3 de classeB) dans l'instruction using pour garantir que la mthode Dispose est appele sur l'objet lorsque l'instruction using est quitte. Les objets que l'on utilise ou que l'on cre doivent implmenter l'interface System.IDisposable. Dans les exemples prcdents classeA et classeB doivent implmenter elles-mme ou par hritage l'interface System.IDisposable.
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
289
Exemple Soit un objet visuel button1de classe System.Windows.Forms.Button, c'est la classe mre Control de Button qui implmente l'interface System.IDisposable : public class Control : IComponent, IDisposable, IParserAccessor, IDataBindingsAccessor Soient les lignes de code suivantes o this est une fiche : // .... this.button1 = new System.Windows.Forms.Button ( ); using( button1) { // code quelconque.... } // suite du code .... A la sortie de l'instruction using juste avant la poursuite de l'excution de la suite du code, button1.Dispose( ) a t automatiquement appele par le CLR (le contrle a t dtruit et les ressources utilises ont t libres immdiatement).
1.11 L'attribut [STAThread] Nous terminons notre examen du code gnr automatiquement par le RAD pour une application fentre de base, en indiquant la signification de l'attribut (mot entre crochets [STAThread]) situ avant la mthode main : /// <summary> /// Le point d'entre principal de l'application. /// </summary> [STAThread] static void Main( ) { Application.Run(new WinForm ( )); }
Cet attribut plac ici devant la mthode main qualifie la manire dont le CLR excutera l'application, il signifie : Single Thread Apartments. Il s'agit d'un modle de gestion mmoire o l'application et tous ses composants est gre dans un seul thread ce qui vite des conflits de ressources avec d'autres threads. Le dveloppeur n'a pas s'assurer de la bonne gestion des ventuels conflits de ressources entre l'application et ses composants.
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
290
Si on omet cet attribut devant la mthode main, le CLR choisi automatiquement [MTAThread] Multi Thread Apartments, modle de mmoire dans lequel l'application et ses composants sont grs par le CLR en plusieurs thread, le dveloppeur doit alors s'assurer de la bonne gestion des ventuels conflits de ressources entre l'application et ses composants. Sauf ncessit d'augmentation de la fluidit du code, il faut laisser (ou mettre en mode console) l'attribut [STAThread] : ... [STAThread] static void Main( ) { Application.Run(new WinForm ( )); }
page
291
Plan gnral:
page
292
1.1 les composants en gnral System.Windows.Forms.Control dfinit la classe de base des contrles qui sont des composants avec reprsentation visuelle, les fiches en sont un exemple.
System.Object System.MarshalByRefObject System.ComponentModel.Component System.Windows.Forms.Control System.Windows.Forms.ScrollableControl System.Windows.Forms.ContainerControl System.Windows.Forms.Form
Dans les deux RAD Visual C# et C#Builder la programmation visuelle des contrles a lieu d'une faon trs classique, par glisser dposer de composants situs dans une palette ou bote d'outils. Il est possible de dposer visuellement des composants, certains sont visuels ils s'appellent contrles, d'autres sont non visuels, ils s'appellent seulement composants. C# Builder : Visual C# :
page
293
Cette distinction entre visuel et non visuel n'est pas trs prcise et dpend de la prsentation dans le RAD. Cette variabilit de la dnomination n'a aucune importance pour l'utilisateur car tous les composants ont un fonctionnement du code identique dans les deux RAD. En effet si nous prenons la classe System.Windows.Forms.MainMenu : System.ComponentModel.Component System.Windows.Forms.Menu System.Windows.Forms.MainMenu Nous voyons que Visual C# range System.Windows.Forms.MainMenu dans les contrles alors que C# Builder le range dans les composants (donc non visuels). Ci-dessous les outils de composants proposs par les deux RAD : C# Builder Visual C# :
Pour pouvoir construire une IHM, il nous faut pouvoir utiliser minima les composants visuels habituels que nous retrouvons dans les logiciels windows-like. Ici aussi la documentation technique fournie avec le RAD dtaillera les diffrentes entits mises disposition de l'utilisateur.
page
294
Il existe des contrles qui sont des conteneurs visuels, les quatre classes ci-aprs sont les principales classe de conteneurs visuels de C# : System.Windows.Forms.Form System.Windows.Forms.Panel System.Windows.Forms.GroupBox System.Windows.Forms.TabPage Sur la fiche prcdente nous relevons outre le formulaire lui-mme, quatre conteneurs visuels rpartis en trois catgories de conteneurs visuels :
System.Windows.Forms.Panel
System.Windows.Forms.TabPage
System.Windows.Forms.GroupBox
page
295
Un conteneur visuel permet d'autres contrles de s'afficher sur lui et lui communique par lien de parent des valeurs de proprits par dfaut (police de caractres, couleur du fond,...). Un objet de chacune de ces classes de conteneurs visuels peut tre le "parent" de n'importe quel contrle grce sa proprit Parent qui est en lecture et criture : public Control Parent {get; set;} C'est un objet de classe Control qui reprsente le conteneur visuel du contrle.
Sur chaque conteneur visuel a t dpos un contrle de classe System.Windows.Forms.Button qui a "hrit" par dfaut des caractristiques de police et de couleur de fond de son parent. Cidessous les classes de tous les contrles dposs :
System.Windows.Forms.Label
System.Windows.Forms.PictureBox
System.Windows.Forms.Button
System.Windows.Forms.TextBox
page
296
System .Windows.Forms.TabPage tabPage2 ; System .Windows.Forms.Button button3 ; System .Windows.Forms.TextBox textBox2 ; System .Windows.Forms.ListBox listBox1 ; System .Windows.Forms.Button button4 ; System .Windows.Forms.Button button5 ; System .Windows.Forms.TextBox textBox3 ; System .Windows.Forms.MainMenu mainMenu1 ;
public WinForm ( ) { // // Requis pour la gestion du concepteur Windows Form // InitializeComponent ( ); // // TODO: Ajouter tout le code du constructeur aprs l'appel de InitializeComponent // } /// <summary> /// Nettoyage des ressources utilises. /// </summary> protected override void Dispose ( bool disposing ) { if ( disposing ) { if ( components != null) { components.Dispose ( ); } } base .Dispose ( disposing ); } #region Code gnr par le concepteur Windows Form private void InitializeComponent ( ) { System .Resources.ResourceManager resources = new System .Resources.ResourceManager ( typeof ( WinForm )); this.panel1 = new System .Windows.Forms.Panel ( ); this.button2 = new System .Windows.Forms.Button ( ); this.textBox1 = new System .Windows.Forms.TextBox ( ); this.label2 = new System .Windows.Forms.Label ( ); this.label1 = new System .Windows.Forms.Label ( ); this.panel2 = new System .Windows.Forms.Panel ( ); this.button1 = new System .Windows.Forms.Button ( ); this.pictureBox1 = new System .Windows.Forms.PictureBox ( ); this.groupBox1 = new System .Windows.Forms.GroupBox ( ); this.textBox3 = new System .Windows.Forms.TextBox ( ); this.button5 = new System .Windows.Forms.Button ( ); this.tabControl1 = new System .Windows.Forms.TabControl ( ); this.tabPage1 = new System .Windows.Forms.TabPage ( ); this.textBox2 = new System .Windows.Forms.TextBox ( ); this.button3 = new System .Windows.Forms.Button ( ); this.tabPage2 = new System .Windows.Forms.TabPage ( ); this.button4 = new System .Windows.Forms.Button ( ); this.listBox1 = new System .Windows.Forms.ListBox ( ); this.mainMenu1 = new System .Windows.Forms.MainMenu ( ); this.panel1.SuspendLayout ( ); this.panel2.SuspendLayout ( ); this.groupBox1.SuspendLayout ( ); this.tabControl1.SuspendLayout ( ); this.tabPage1.SuspendLayout ( ); Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
297
this.tabPage2.SuspendLayout ( ); this.SuspendLayout ( ); // // panel1 // this.panel1.BackColor = System .Drawing.Color.NavajoWhite ; this.panel1.Controls.Add (this .button2 ); this.panel1.Controls.Add (this .textBox1 ); this.panel1.Controls.Add (this .label2 ); this.panel1.Location = new System .Drawing.Point ( 32, 40 ); this.panel1.Name = "panel1"; this.panel1.Size = new System .Drawing.Size ( 224, 104 ); this.panel1.TabIndex = 0 ; // // button2 // this.button2.Location = new System .Drawing.Point ( 88, 24 ); this.button2.Name = "button2"; this.button2.Size = new System .Drawing.Size ( 72, 24 ); this.button2.TabIndex = 4 ; this.button2.Text = "button2"; // // textBox1 // this.textBox1.Location = new System .Drawing.Point ( 16, 76 ); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System .Drawing.Size ( 192, 20 ); this.textBox1.TabIndex = 3 ; this.textBox1.Text = "textBox1"; // // label2 // this.label2.BackColor = System .Drawing.SystemColors.Info ; this.label2.Location = new System .Drawing.Point ( 16, 32 ); this.label2.Name = "label2"; this.label2.Size = new System .Drawing.Size ( 88, 40 ); this.label2.TabIndex = 2 ; this.label2.Text = "label2"; // toutes ces lignes correspondent ceci :
// // panel2 // this.panel2.BackColor = System .Drawing.Color.PaleTurquoise ; this.panel2.Controls.Add (this .button1 ); this.panel2.Controls.Add (this .pictureBox1 ); this.panel2.Location = new System .Drawing.Point ( 200, 16 ); this.panel2.Name = "panel2"; this.panel2.Size = new System .Drawing.Size ( 208, 112 ); this.panel2.TabIndex = 2 ; Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
298
// // button1 // this.button1.Location = new System .Drawing.Point ( 24, 48 ); this.button1.Name = "button1"; this.button1.Size = new System .Drawing.Size ( 72, 24 ); this.button1.TabIndex = 4 ; this.button1.Text = "button1"; // // pictureBox1 // this.pictureBox1.BackColor = System .Drawing.Color.DarkKhaki ; this.pictureBox1.Image = (( System .Drawing.Image )( resources.GetObject ("pictureBox1.Image"))); // la ligne prcdente charge l'image :
this.pictureBox1.Location = new System .Drawing.Point ( 120, 32 ); this.pictureBox1.Name = "pictureBox1"; this.pictureBox1.Size = new System .Drawing.Size ( 70, 63 ); this.pictureBox1.SizeMode = System .Windows.Forms.PictureBoxSizeMode.AutoSize ; this.pictureBox1.TabIndex = 0 ; this.pictureBox1.TabStop = false ; // toutes ces lignes correspondent ceci :
// // groupBox1 // this.groupBox1.BackColor = System .Drawing.Color.SkyBlue ; this.groupBox1.Controls.Add (this .textBox3 ); this.groupBox1.Controls.Add (this .button5 ); this.groupBox1.Location = new System .Drawing.Point ( 8, 160 ); this.groupBox1.Name = "groupBox1"; this.groupBox1.Size = new System .Drawing.Size ( 184, 104 ); this.groupBox1.TabIndex = 4 ; this.groupBox1.TabStop = false ; this.groupBox1.Text = "groupBox1"; // // textBox3 // this.textBox3.Location = new System .Drawing.Point ( 8, 72 ); this.textBox3.Name = "textBox3"; this.textBox3.Size = new System .Drawing.Size ( 136, 20 ); this.textBox3.TabIndex = 1 ; this.textBox3.Text = "textBox3"; // // button5 // this.button5.Location = new System .Drawing.Point ( 16, 32 ); Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
299
this.button5.Name = "button5"; this.button5.Size = new System .Drawing.Size ( 72, 24 ); this.button5.TabIndex = 0 ; this.button5.Text = "button5"; // toutes ces lignes correspondent ceci :
// // tabControl1 // this.tabControl1.Controls.Add (this .tabPage1 ); this.tabControl1.Controls.Add (this .tabPage2 ); this.tabControl1.Location = new System .Drawing.Point ( 224, 144 ); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0 ; this.tabControl1.Size = new System .Drawing.Size ( 200, 120 ); this.tabControl1.TabIndex = 5 ; // // tabPage1 // this.tabPage1.BackColor = System .Drawing.Color.DarkTurquoise ; this.tabPage1.Controls.Add (this .textBox2 ); this.tabPage1.Controls.Add (this .button3 ); this.tabPage1.Location = new System .Drawing.Point ( 4, 22 ); this.tabPage1.Name = "tabPage1"; this.tabPage1.Size = new System .Drawing.Size ( 192, 94 ); this.tabPage1.TabIndex = 0 ; this.tabPage1.Text = "tabPage1"; // // textBox2 // this.textBox2.Location = new System .Drawing.Point ( 16, 16 ); this.textBox2.Name = "textBox2"; this.textBox2.Size = new System .Drawing.Size ( 160, 20 ); this.textBox2.TabIndex = 1 ; this.textBox2.Text = "textBox2"; // // button3 // this.button3.Location = new System .Drawing.Point ( 32, 56 ); this.button3.Name = "button3"; this.button3.Size = new System .Drawing.Size ( 128, 24 ); this.button3.TabIndex = 0 ; this.button3.Text = "button3"; // toutes ces lignes correspondent ceci :
page
300
// // tabPage2 // this.tabPage2.BackColor = System .Drawing.Color.Turquoise ; this.tabPage2.Controls.Add (this .button4 ); this.tabPage2.Controls.Add (this .listBox1 ); this.tabPage2.Location = new System .Drawing.Point ( 4, 22 ); this.tabPage2.Name = "tabPage2"; this.tabPage2.Size = new System .Drawing.Size ( 192, 94 ); this.tabPage2.TabIndex = 1 ; this.tabPage2.Text = "tabPage2"; // // button4 // this.button4.Location = new System .Drawing.Point ( 8, 32 ); this.button4.Name = "button4"; this.button4.Size = new System .Drawing.Size ( 64, 24 ); this.button4.TabIndex = 1 ; this.button4.Text = "button4"; // // listBox1 // this.listBox1.Location = new System .Drawing.Point ( 80, 8 ); this.listBox1.Name = "listBox1"; this.listBox1.Size = new System .Drawing.Size ( 96, 69 ); this.listBox1.TabIndex = 0 ; // toutes ces lignes correspondent ceci :
// // label1 // this.label1.BackColor = System .Drawing.Color.MediumAquamarine ; this.label1.Location = new System .Drawing.Point ( 48, 8 ); this.label1.Name = "label1"; this.label1.Size = new System .Drawing.Size ( 88, 16 ); this.label1.TabIndex = 1 ; this.label1.Text = "label1"; // les 6 lignes prcdentes correspondent ceci :
page
301
// // WinForm // this.AutoScaleBaseSize = new System .Drawing.Size ( 5, 13 ); this.ClientSize = new System .Drawing.Size ( 432, 269 ); this.Controls.Add (this .tabControl1 ); this.Controls.Add (this .groupBox1 ); this.Controls.Add (this .panel2 ); this.Controls.Add (this .label1 ); this.Controls.Add (this .panel1 ); this.Menu = this .mainMenu1 ; this.Name = "WinForm"; this.Text = "WinForm"; this.panel1.ResumeLayout ( false ); this.panel2.ResumeLayout ( false ); this.groupBox1.ResumeLayout ( false ); this.tabControl1.ResumeLayout ( false ); this.tabPage1.ResumeLayout ( false ); this.tabPage2.ResumeLayout ( false ); this.ResumeLayout ( false ); // toutes ces lignes correspondent ceci :
} #endregion
1.3 Influence de la proprit parent sur l'affichage visuel d'un contrle Dans l'IHM prcdente, programmons par exemple la modification de la proprit Parent du contrle textBox1 en raction au click de souris sur les Button button1 et button2.
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
302
Il faut abonner le gestionnaire du click de button1 "private void button1_Click" , au dlgu button1.Click : this.button1.Click += new System.EventHandler(this.button1_Click); De mme il faut abonner le gestionnaire du click de button2 "private void button2_Click" , au dlgu button2.Click : this.button2.Click += new System.EventHandler(this.button2_Click);
Le RAD engendre automatiquement les gestionnaires: private void button1_Click ( object sender, System .EventArgs e ){ } private void button1_Click ( object sender, System .EventArgs e ){ } Les lignes d'abonnement sont engendres dans la mthode InitializeComponent ( ) : private void InitializeComponent ( ) { .... this.button1.Click += new System.EventHandler(this.button1_Click); this.button2.Click += new System.EventHandler(this.button2_Click); } Le code et les affichages obtenus (le textBox1 est positionn en X=16 et Y=76 sur son parent) :
// Gestionnaire du click sur button1 : private void button1_Click ( object sender, System .EventArgs e ){ textBox1.Parent = panel1 ; } Lorsque l'on clique sur le button1, texteBox1 a pour parent panel1 :
page
303
// Gestionnaire du click sur button2 : private void button2_Click ( object sender, System .EventArgs e ) { textBox1.Parent = this; } Lorsque l'on clique sur le button2, texteBox1 a pour parent la fiche elle-mme :
Le contrle pictureBox1 permet d'afficher des images : ico, bmp, gif, png, jpg, jpeg
// chargement d'un fichier image dans le pictureBox1 par un click sur button3 : private void InitializeComponent ( ) { .... this.button1.Click += new System.EventHandler(this.button1_Click); this.button2.Click += new System.EventHandler(this.button2_Click); this.button3.Click += new System.EventHandler(this.button3_Click); } private void button3_Click ( object sender, System .EventArgs e ) { pictureBox1.Image = Image.FromFile("csharp.jpg") ; } Lorsque l'on clique sur le button3, l'image "csharp.jpg" est charge dans pictureBox1 :
----->
1.4 Des graphiques dans les formulaires avec le GDI+ Nous avons remarqu que C# possde un contrle permettant l'affichage d'images de diffrents formats, qu'en est-il de l'affichage de graphiques construits pendant l'excution ? Le GDI+ rpond cette question. Le Graphical Device Interface+ est la partie de NetFrameWork qui fournit les graphismes
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
304
vectoriels deux dimensions, les images et la typographie. GDI+ est une interface de priphrique graphique qui permet aux programmeurs d'crire des applications indpendantes des priphriques physiques (cran, imprimante,...). Lorsque l'on dessine avec GDI+, on utilise des mthodes de classes situes dans le GDI+, donnant des directives de dessin, ce sont ces mthodes qui, via le CLR du NetFrameWork, font appel aux pilotes du priphrique physique, les programmes ainsi conus ne dpendent alors pas du matriel sur lequel ils s'afficheront.
Pour dessiner des graphiques sur n'importe quel priphrique d'affichage, il faut un objet Graphics. Un objet de classe System.Drawing.Graphics est associ une surface de dessin, gnralement la zone cliente d'un formulaire (objet de classe Form). Il n'y a pas de constructeur dans la classe Graphics :
Comme le dessin doit avoir lieu sur la surface visuelle d'un objet visuel donc un contrle, c'est cet objet visuel qui fournit le fond, le GDI+ fournit dans la classe System.Windows.Forms.Control la mthode CreateGraphics qui permet de crer un objet de type Graphics qui reprsente le "fond de dessin" du contrle :
Syntaxe : public Graphics CreateGraphics( ); Afin de comprendre comment utiliser un objet Graphics construisons un exemple fictif de code dans lequel on suppose avoir instanci ObjVisuel un contrle (par exemple : une fiche, un panel,...), on utilise deux mthodes dessin de la classe Graphics pour dessiner un trait et un rectangle :
page
305
Code C#
Graphics fond = ObjVisuel.CreateGraphics ( );
Explication
Obtention d'un fond de dessin sur ObjVisuel (cration d'un objet Graphics associ ObjVisuel) Cration d'un objet de pinceau de couleur noire et d'paisseur 2 pixels Utilisation du pinceau blackPen pour tracer une ligne droite sur le fond d'ObjVisuel entre les deux points A(10,10) et B(50,50).
fond.FillRectangle ( Brushes.SeaGreen,5,70,100,50 );
Utilisation d'une couleur de brosse SeaGreen, pour remplr l'intrieur d'un rectangle spcifi par une paire de coordonnes (5,70), une largeur(100 pixels) et une hauteur (50 pixels).
Ces quatre instructions ont permis de dessiner le trait noir et le rectangle vert sur le fond du contrle ObjVisuel reprsent ci-dessous par un rectangle fond blanc :
Note technique de Microsoft L'objet Graphics retourn doit tre supprim par l'intermdiaire d'un appel sa mthode Dispose lorsqu'il n'est plus ncessaire.
page
306
La classe Graphics implmente l'interface IDisposable : public sealed class Graphics : MarshalByRefObject, IDisposable Les objets Graphics peuvent donc tre librs par la mthode Dispose( ). Afin de respecter ce conseil d'optimisation de gestion de la mmoire, nous rajoutons dans notre code l'appel la mthode Dispose de l'objet Graphics. Nous prenons comme ObjVisuel un contrle de type panel que nous nommons panelDessin ( private System.Windows.Forms.Panel panelDessin ):
//... Graphics fond = panelDessin.CreateGraphics ( ); Pen blackPen = new Pen ( Color.Black, 2 ); fond.DrawLine ( blackPen, 10f, 10f, 50f, 50f ); fond.FillRectangle ( Brushes.SeaGreen,5,70,100,50 ); fond.Dispose( ); //...suite du code o l'objet fond n'est plus utilis
Nous pouvons aussi utiliser l'instruction using dj vue qui libre automatiquement l'objet par appel sa mthode Dispose :
//... using( Graphics fond = panelDessin.CreateGraphics ( ) ) { Pen blackPen = new Pen ( Color.Black, 2 ); fond.DrawLine ( blackPen, 10f, 10f, 50f, 50f ); fond.FillRectangle ( Brushes.SeaGreen,5,70,100,50 ); } //...suite du code o l'objet fond n'est plus utilis
1.5 Le dessin doit tre persistant Reprenons le dessin prcdent et affichons-le la demande. Nous supposons disposer d'un formulaire nomm WinForm1 contenant un panneau nomm panelDessin ( private System.Windows.Forms.Panel panelDessin ) et un bouton nomm buttonDessin ( private System.Windows.Forms.Button buttonDessin )
page
307
Nous programmons un gestionnaire de l'vnement click du buttonDessin, dans lequel nous copions le code de traage de notre dessin : private void buttonDessin_Click(object sender, System.EventArgs e) Graphics fond = panelDessin.CreateGraphics ( ); Pen blackPen = new Pen ( Color.Black, 2 ); fond.DrawLine ( blackPen, 10f, 10f, 50f, 50f ); fond.FillRectangle ( Brushes.SeaGreen,5,70,100,50 ); fond.Dispose( ); } {
Lorsque nous cliquons sur le bouton buttonDessin, le trait noir et le rectangle vert se dessine sur le fond du panelDessin :
Faisons apparatre une fentre de bloc-note contenant du texte, qui masque partiellement notre formulaire WinForm1 qui passe au second plan comme ci-dessous :
Si nous nous refocalisons sur le formulaire en cliquant sur lui par exemple, celui-ci repasse au premier plan, nous constatons que notre dessin est abm. Le rectangle vert est amput de la partie qui tait recouverte par la fentre de bloc-note. Le formulaire s'est bien redessin, mais
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
308
Il faut cliquer une nouvelle fois sur le bouton pour lancer le redessinement des tracs :
Il existe un moyen simple permettant d'effectuer le redessinement de nos tras lorsque le formulaire se redessine lui-mme automatiquement : il nous faut "consommer" l'vnement Paint du formulaire qui se produit lorsque le formulaire est redessin (ceci est d'ailleurs valable pour n'importe quel contrle). La consommation de l'vnement Paint s'effectue grce au gestionnaire Paint de notre formulaire WinForm1 :
private void WinForm1_Paint (object sender, System.Windows.Forms.PaintEventArgs e) { Graphics fond = panelDessin.CreateGraphics ( ); Pen blackPen = new Pen ( Color.Black, 2 ); fond.DrawLine ( blackPen, 10f, 10f, 50f, 50f ); fond.FillRectangle ( Brushes.SeaGreen,5,70,100,50 ); fond.Dispose( ); }
Le RAD a enregistr (abonn) le gestionnaire WinForm1_Paint auprs du dlgu Paint dans le corps de lamthode InitializeComponent : private void InitializeComponent( ) { ... this.Paint += new System.Windows.Forms.PaintEventHandler ( this.WinForm1_Paint ); ... }
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
309
Nous crivons une mthode TracerDessin permettant de dessiner sur le fond de la fiche, sur le fond des deux panel et d'crire du texte sur le fond d'un panel. La mthode TracerDessin est appele dans le gestionnaire de l'vnement Paint du formulaire lors de son redessinement de la fiche afin d'assurer la persistance de tous les tras. Voici le code que nous crons : private void InitializeComponent( ) { ... this.Paint += new System.Windows.Forms.PaintEventHandler ( this.WinForm_Paint ); ... }
private void WinForm_Paint (object sender, System.Windows.Forms.PaintEventArgs e) { TracerDessin ( e.Graphics ) ; /* Explications sur l'appel de la mthode TracerDessin : Le paramtre e de type PaintEventArgs contient les donnes relatives l'vnement Paint en particulier une proprit Graphics qui renvoie le graphique utilis pour peindre sur la fiche c'est pourquoi e.Graphics est pass comme fond en paramtre notre mthode de dessin. */ } private void TracerDessin ( Graphics x ){ string Hdcontext ; Hdcontext = x.GetType ( ) .ToString ( ); label1.Text = "Graphics x : " + Hdcontext.ToString ( ); x.FillRectangle ( Brushes.SeaGreen,5,70,100,50 ); using( Graphics g = this .CreateGraphics ( ) ) { g.FillRectangle ( Brushes.SkyBlue,5,10,100,50 ); } using( Graphics g = panel1.CreateGraphics ( ) ) { g.FillEllipse ( Brushes.MediumSlateBlue,5,10,40,30 ); } using( Graphics h = panel2.CreateGraphics ( ) ) { h.FillEllipse ( Brushes.Tomato,10,15,120,80 ); h.DrawString ("Bonjour" , new Font (this .Font.FontFamily.Name,18 ) ,Brushes.Lime,15,25 ); Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
310
} }
Illustrons les actions de dessin de chaque ligne de code de la mthode TracerDessin : private void TracerDessin ( Graphics x ) {
Panel2
On dessine deux rectangles sur le fond de la fiche, nous notons que ces deux rectangles ont une intersection non vide avec le panel2 (jaune fonc)et que cela n'altre pas le dessin du panel. En effet le panel est un contrle et donc se redessine lui-mme. Nous en dduisons que le fond graphique est situ "en dessous" du dessin des contrles.
page
311
La premire instruction dessine l'ellipse rouge, la seconde crit le texte "Bonjour" en vert sur le fond du panel2.
page
312
page
313
this.textBox1 = new System.Windows.Forms.TextBox( ); this.button1 = new System.Windows.Forms.Button( ); this.SuspendLayout( ); // // listBox1 // this.listBox1.Items.AddRange ( new object[] { "zro", "un", "deux", "trois", "quatre", "cinq", "FIN" } ); this.listBox1.Location = new System.Drawing.Point(224, 8); this.listBox1.Name = "listBox1"; this.listBox1.Size = new System.Drawing.Size(144, 95); this.listBox1.TabIndex = 9; this.listBox1.SelectedIndexChanged += new System.EventHandler ( this.listBox1_SelectedIndexChanged ); // // textBox1 // this.textBox1.Location = new System.Drawing.Point(8, 72); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(200, 20); this.textBox1.TabIndex = 8; this.textBox1.Text = "textBox1"; this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged); // // button1 // this.button1.Location = new System.Drawing.Point(24, 24); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(144, 32); this.button1.TabIndex = 7; this.button1.Text = "button1"; this.button1.Paint += new System.Windows.Forms.PaintEventHandler(this.button1_Paint); // // WinForm1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(384, 117); this.Controls.Add(this.listBox1); this.Controls.Add(this.textBox1); this.Controls.Add(this.button1); this.Name = "WinForm1"; this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.Text = "WinForm1"; this.Paint += new System.Windows.Forms.PaintEventHandler(this.WinForm1_Paint); this.ResumeLayout(false); } #endregion //-- dessin persistant sur le fond de la fiche par gestionnaire Paint: private void WinForm1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { Graphics g = e.Graphics; g.FillRectangle ( Brushes.SeaGreen,5,70,100,50 ); g.FillRectangle ( Brushes.SkyBlue,5,10,100,50 ); } Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
314
//-- dessin persistant sur le fond du bouton par gestionnaire Paint: private void button1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { Graphics x = e.Graphics; x.FillRectangle ( Brushes.SkyBlue,5,5,20,20 ); } //-- dessin non persistant sur le fond du ListBox par gestionnaire SelectedIndexChange : private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { Rectangle Rect = listBox1.GetItemRectangle(listBox1.SelectedIndex); int Haut = listBox1.ItemHeight; textBox1.Text= "x="+Rect.X.ToString()+" y="+Rect.Y.ToString()+" h="+Haut.ToString(); using ( Graphics k = listBox1.CreateGraphics( ) ) { k.FillRectangle(Brushes.SkyBlue,Rect.X,Rect.Y,Haut,Haut); } } //-- dessin non persistant sur le fond du TextBox par gestionnaire TextChanged : private void textBox1_TextChanged(object sender, System.EventArgs e) { using (Graphics k = textBox1.CreateGraphics( ) ) { k.FillRectangle(Brushes.SkyBlue,5,5,10,10); } } } } Aprs excution, slection de la 3me ligne de la liste et ajout d'un texte au clavier dans le TextBox :
page
315
1. similitude et diffrence
Afin de ne pas alourdir louvrage dj volumineux, nous ne dveloppons pas de cours spcial pour les exceptions en C# : le langage C# hrite strictement de Java pour la syntaxe et le fonctionnement de base des exceptions et de la simplicit de Delphi dans les types dexceptions. Cest pourquoi nous renvoyons le lecteur au chapitre 9.3.2 (exceptions en java) pour une tude complte de la notion dexception, de leur gestion, de la hirarchie, de lordre dinterception et du redclenchement dune exception. Nous figurons ci-dessous un tableau rcapitulant les similitudes dans chacun des trois langages :
Delphi
try - ...
<lignes de code protger>
Java
try { - ...
<lignes de code protger>
C#
try { - ...
<lignes de code protger>
- ... }
- ... }
fonctionnement identique C#
page
316
Seul Java possde deux catgories d'exceptions : les exceptions vrifies et les exceptions non vrifies. C# comme Delphi possde un mcanisme plus simple qui est quivalent celui de Java dans le cas des exceptions non vrifies (implicites) ( la propagation de l'exception est implicitement prise en charge par le systme d'excution).
Il est possible de construire de nouvelles classes d'exceptions personnalises en hritant d'une des classes du langage, ou minima de la classe de base Exception. Il est aussi possible de lancer une exception personnalise (comme si c'tait une exception propre au langage) n'importe quel endroit dans le code d'une mthode d'une classe, en instanciant un objet d'exception personnalis et en le prfixant par le mot clef (raise pour Delphi et throw pour Java et C#). Le mcanisme gnral d'interception des exceptions travers des gestionnaires d'exceptions tryexcept ou trycatch s'applique tous les types d'exceptions y compris les exceptions personnalises.
Delphi
Type MonExcept = class (Exception) .... End;
MonExcept hrite par construction de la proprit public Message de sa mre, en lecture et criture.
Java
class MonExcept extends Exception { public MonExcept (String s) { super(s); .......... } }
C#
class MonExcept : Exception { public MonExcept (string s) : base(s) { ............. } }
MonExcept hrite par construction de la proprit public Message de sa mre, en lecture seulement.
page
317
Delphi
Type MonExcept = class (Exception) .......... End; classA = class Procedure Truc; end; Implementation
Java
class MonExcept extends Exception { public MonExcept (String s) { super(s); .......... } }
C#
class MonExcept : Exception { public MonExcept (string s) : base(s) { ............. } } class classA { void Truc ( ) { .. throw new MonExcept ( 'salut' ); .. } }
class classA { void Truc ( ) throws MonExcept { .. Procedure classA.Truc; throw new MonExcept ( 'salut' ); begin .. .. } raise MonExcept.Create ( 'salut' ); } .. end;
Delphi
Type MonExcept = class (Exception) .......... End; classB = class Procedure Meth; end; Implementation
Java
class MonExcept extends Exception { public MonExcept (String s) { super(s); .......... } } class classB { void Meth ( ) {
)
C#
class MonExcept : Exception { public MonExcept (string s) : base(s) { ............. } } class classB { void Meth ( ) { page
318
.. try {
.. // code protger
.. try {
.. // code protger
} catch (MonExcept e) {
.. // code de raction l'exception
} catch (MonExcept e) {
.. // code de raction l'exception
} } }
3. Un exemple de traitement en C#
Enonc : Nous nous proposons de
mettre en oeuvre les concepts prcdents sur un exemple simulant un traitement de fichier. L'application est compose d'un bloc principal <programme> qui appelle une suite de blocs imbriqus.
Les classes en jeu et les blocs de programmes (mthodes) acteurs dans le traitement des exceptions :
<programme> <ActionsSurFichier> ..<AfficheFichier> ...<ChercheElement> ..............<OuvrirFichier> --> exception ..............<LireElement> --> exception ..........<FermerFichier> --> exception
Les trois blocs du dernier niveau les plus internes <OuvrirFichier>, <LireElement> et <FermerFichier> peuvent lancer chacun une exception selon le schma ci-aprs :
page
319
La dmarche
Les ventuelles exceptions lances par les blocs <OuvrirFichier>,<LireElement> et <FermerFichier> doivent pouvoir se propager aux blocs de niveaux englobant afin d'tre interceptables n'importe quel niveau.
page
320
On propose de crer une classe gnrale d'exception EFichierError hritant de la classe des Exception, puis 3 classes d'exception hritant de cette classe EFichierError :
Chaque bloc le plus interne peut lancer (lever) une exception de classe diffrente et la propage au niveau suprieur :
Nous proposons par exemple d'intercepter les exceptions dans les deux blocs <ActionsSurFichier> et <AfficheFichier> :
page
321
Le bloc <AfficherFichier>
Ce bloc interceptera une exception de type EFichierError, puis la redclenchera aprs traitement :
Le bloc <ActionsSurFichier>
Ce bloc interceptera une exception de l'un des trois types EOuvertureError, ELectureError ou EFermetureError :
page
322
class EOuvertureError : EFichierError { public EOuvertureError ( int x ): base ("Impossible d'ouvrir le fichier !" ,x ) { } } class ELectureError : EFichierError{ public ELectureError ( int x ): base ("Impossible de lire le fichier !" ,x ) { } } class EFermetureError : EFichierError{ public EFermetureError ( int x ): base ("Impossible de fermer le fichier !" ,x ) { } }
//-----------------------------------------------------------------------------public class PrTransmettre { void TraitementGen ( String s ) { System .Console.WriteLine ("traitement general de l''erreur: " + s ); } void TraitementSpecif ( String s ) { System .Console.WriteLine ("traitement specifique de l''erreur: " + s ); } void OuvrirFichier ( ) { System .Console.WriteLine (" >> Action ouverture..."); GenererIncident ( 1 ); System .Console.WriteLine (" >> Fin ouverture."); } Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
323
void LireElement ( ) { System .Console.WriteLine (" >> Action lecture..."); GenererIncident ( 2 ); System .Console.WriteLine (" >> Fin lecture."); }
void FermerFichier ( ) { System .Console.WriteLine (" >> Action fermeture..."); GenererIncident ( 3 ); System .Console.WriteLine (" >> Fin fermeture."); }
//-----------------------------------------------------------------------------------------void GenererIncident ( int TypeIncident ) { int n ; Random nbr = new Random ( ); switch ( TypeIncident ) { case 1 : n = nbr.Next ( ) % 4 ; if ( n == 0 ) throw new EOuvertureError ( TypeIncident ); break; case 2 : n = nbr.Next ( ) % 3 ; if ( n == 0 ) throw new ELectureError ( TypeIncident ); break; case 3 : n = nbr.Next ( ) % 2 ; if ( n == 0 ) throw new EFermetureError ( TypeIncident ); break; Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
324
} } //-----------------------------------------------------------
void ActionsSurFichier ( ) { System .Console.WriteLine ("Debut du travail sur le fichier."); try { System .Console.WriteLine ("........."); AfficherFichier ( ); } catch( EOuvertureError E ) { TraitementSpecif ( E.Message ); } catch( ELectureError E ) { TraitementSpecif ( E.Message ); } catch( EFermetureError E ) { TraitementSpecif ( E.Message ); } System.Console.WriteLine ("Fin du travail sur le fichier."); } //----------------------------------------------------------------void AfficherFichier ( ) { try { ChercheElement ( ); } catch( EFichierError E ) { TraitementGen ( E.Message ); throw E ; } } public static void Main ( string [ ] arg ) { PrTransmettre Obj = new PrTransmettre ( ); try { Obj.ActionsSurFichier ( ); } catch( EFichierError E ) { System .Console.WriteLine ( " Autre type d'Erreur gnrale Fichier !"); } System .Console.ReadLine ( ); } } }
page
325
Plan gnral:
page
326
Diffrences entre flux et fichiers Un fichier est une ensemble de donnes structures possdant un nom (le nom du fichier) stockes gnralement sur disque (dans une mmoire de masse en gnral) dans des enregistrement sous forme d'octets. Les fichiers sont en criture ou en lecture et ont des organisations diverses comme l'accs squentiels, l'accs direct, l'accs index, en outre les fichiers sont organiss sur le disque dans des rpertoires nomms selon des chemins d'accs. Un flux est un tube de connexion entre deux entrepts pouvant contenir des donnes. Ces entits entreposant les donnes peuvent tre des mmoire de masse (disques) ou des zones diffrentes de la mmoire centrale, ou bien des rseaux diffrents (par exemple changes de donnes entre sockets). En C#, il faut utiliser un flux pour accder aux donnes d'un fichier stock sur disque, ce flux connecte les donnes du fichier sur le disque une zone prcise de la mmoire : le tampon mmoire.
Le Framework.Net dispose d'une famille de classes permettant la manipulation de donnes externes : System.IO Espace de noms System.IO contient des classes qui permettent la lecture et l'criture dans des fichiers, contient des classes qui permettent la lecture et l'criture dans des flux , contient des classes qui permettent la cration, le renommage, le dplacement de fichiers et de rpertoires sur disque.
page
327
1.1 La manipulation des rpertoires La classe System.IO.Directory hrite directement de System.Object et ne rajoute que des mthodes static pour la cration, la suppression, le dplacement et le positionnement de rpertoires :
a) Cration d'un rpertoire sur disque, s'il n'existe pas au pralable : string cheminRep = @"D:\MonRepertoire"; if ( !Directory.Exists( cheminRep )) { Directory.CreateDirectory( cheminRep ); } Ces 3 lignes de code crent un rpertoire MonRepertoire la racine du disque D.
b) Suppression d'un rpertoire et de tous ses sous-rpertoires sur disque : string cheminRep = @"D:\MonRepertoire"; if ( Directory.Exists( cheminRep )) { Directory.Delete( cheminRep, true ); } Ces 3 lignes de code suppriment sur le disque D, le rpertoire MonRepertoire et tous ses sous-rpertoires.
c) Dplacement d'un rpertoire et de tous ses sous-rpertoires sur disque : string cheminRep = "D:\\MonRepertoire"; string cheminDest = "C:\\NouveauRepertoire"; if ( Directory.Exists( cheminRep )) { Directory.Move( cheminRep, cheminDest ); } Ces 3 lignes de code dplacent le rpertoire MonRepertoire et tous ses sous-rpertoires du disque D vers le disque C sous le nouveau nom NouveauRepertoire.
page
328
d) Accs au rpertoire disque de travail en cours de l'application : string cheminRepAppli = Directory.GetCurrentDirectory( ); Attention, ce rpertoire est le dernier rpertoire en cours utilis par l'application, il n'est pas ncessairement celui du processus qui a lanc l'application, ce dernier peut tre obtenu partir de la classe Application par la proprit "static string StartupPath" : string cheminLancerAppli = Application.StartupPath ;
La classe System.IO.DirectoryInfo qui hrite directement de System.Object sert aussi la cration, la suppression, au dplacement et au positionnement de rpertoires, par rapport la classe Directory, certaines mthodes sont transformes en proprits afin d'en avoir une utilisation plus souple. Conseil de Microsoft : "si vous souhaitez rutiliser un objet plusieurs fois, l'utilisation de la mthode d'instance de DirectoryInfo la place des mthodes static correspondantes de la classe Directory peut tre prfrable". Le lecteur choisira selon l'application qu'il dveloppe la classe qui lui procure le plus de confort. Afin de bien comparer ces deux classes nous reprenonsavec la classe DirectoryInfo, les mmes exemples de base de la classe Directory. a) Cration d'un rpertoire sur disque, s'il n'existe pas au pralable : string cheminRep = @"D:\MonRepertoire"; DirectoryInfo Repertoire = new DirectoryInfo( cheminRep ); if ( !Repertoire.Exists ) { Repertoire.Create( ); } Ces lignes de code crent un rpertoire MonRepertoire la racine du disque D.
b) Suppression d'un rpertoire et de tous ses sous-rpertoires sur disque : string cheminRep = "D:\\MonRepertoire"; DirectoryInfo Repertoire = new DirectoryInfo( cheminRep ); if ( Repertoire.Exists ) { Repertoire.Delete( true ); } Ces lignes de code suppriment sur le disque D, le rpertoire MonRepertoire et tous ses sous-rpertoires.
c) Dplacement d'un rpertoire et de tous ses sous-rpertoires sur disque : string cheminRep = @"D:\MonRepertoire";
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
329
string cheminDest = @"C:\NouveauRepertoire"; DirectoryInfo Repertoire = new DirectoryInfo( cheminRep ); if ( Repertoire.Exists ) { Repertoire.MoveTo( cheminDest ); } Ces lignes de code dplacent le rpertoire MonRepertoire et tous ses sous-rpertoires du disque D vers le disque C sous le nouveau nom NouveauRepertoire.
1.2 La manipulation des chemins d'accs disque La classe System.IO.Path hrite directement de System.Object et ne rajoute que des mthodes et des champs static pour la manipulation de chanes string contenant des chemins d'accs disque ( soit par exemple partir d'une string contenant un chemin comme "c:\rep1\rep2\rep3\appli.exe", extraction du nom de fichier appli.exe, du rpertoire c:\rep1\rep2\rep3, de l'extension .exe, etc... ). Ci-dessous nous donnons quelques mthodes static pratiques courantes de la classe permettant les manipulations usuelles d'un exemple de chemin d'accs disque sous Windows : string chemin = "C:\\rep1\\rep2\\appli.exe"; ou bien string chemin = @"C:\rep1\rep2\appli.exe";
Appel de la mthode static sur la string chemin System.IO.Path.GetDirectoryName ( chemin ) System.IO.Path.GetExtension ( chemin ) System.IO.Path.GetFileName ( chemin ) System.IO.Path.GetFileNameWithoutExtension ( chemin ) System.IO.Path.GetFullPath ("C:\\rep1\\rep2\\..\\appli.exe") System.IO.Path.GetPathRoot ( chemin )
rsultat obtenu prs l'appel C:\rep1\rep2 .exe appli.exe appli C:\rep1\appli.exe C:\
1.3 La manipulation des fichiers La classe System.IO.File hrite directement de System.Object et ne rajoute que des mthodes static pour la manipulation, la lecture et l'criture de fichiers sur disque :
330
Exemple de cration d'un fichier et de ses rpertoires sur le disque en combinant les classes Directory, Path et File : string cheminRep = @"d:\temp\rep1\fichier.txt"; // cration d'un rpertoire et d'un sous-rpertoire s'ils n'existent pas dj if ( !Directory.Exists( Path.GetDirectoryName(cheminRep ) ) ) Directory.CreateDirectory( Path.GetDirectoryName(cheminRep ) ); // effacer le fichier s'il existe dj if (File.Exists(cheminRep )) File.Delete(cheminRep ); // cration du fichier " fichier.txt " sur le disque D : dans " D:\temp\rep1" FileStream fs = File.Create( cheminRep ); Les lignes de code C# prcdentes crent sur le disque dur D: , les rpertoires temp et rep1, puis le fichier.txt selon l'architecture disque ci-aprs : D:
page
331
Rciproquement, une application peut aprs traitement, dlivrer en sortie des rsultats, on dit crire, dans un fichier, vers une autre application ou dans une zone mmoire. Les flux servent connecter entre elles ces diverses sources de donnes.
2.1 Les flux avec C# En C# toutes ces donnes sont changes en entre et en sortie travers des flux (Stream). Un flux est une sorte de tuyau de transport squentiel de donnes.
Un flux est unidirectionnel : il y a donc des flux d'entre et des flux de sortie. L'image ci-aprs montre qu'afin de jouer un son stock dans un fichier, l'application C# ouvre en entre, un flux associ au fichier de sons et lit ce flux squentiellement afin ensuite de traiter les sons (modifier ou jouer le son) :
page
332
La mme application peut aussi traiter des images partir d'un fichier d'images et renvoyer ces images dans le fichier aprs traitement. C# ouvre un flux en entre sur le fichier image et un flux en sortie sur le mme fichier, l'application lit squentiellement le flux d'entre (octet par octet par exemple) et crit squentiellement dans le flux de sortie :
D'une manire gnrale, NetFramework met notre disposition dans l'espace de noms System.IO une classe abstraite de flux de donnes considres comme des squences d'octets, la classe System.IO.Stream. Un certain nombre de classes drivent de la classe Stream et fournissent des implmentations spcifiques en particulier les classes :
Utilisation pratique consacre aux flux connects sur des fichiers. consacre aux flux connects sur des zones mmoires. consacre aux flux connects sur des accs rseau.
Nous allons plus particulirement tudier quelques exemples d'utilisation de flux connects sur des fichiers et plus prcisment des fichiers de textes.
2.2 Les flux et les fichiers de caractres avec C# Eu gard l'importance des fichiers comportant des donnes textuelles ( bases de caractres) le NetFramework dispose de classe de flux chargs de l'ecriture et de la lecture de caractres plus spcialises que la classe System.IO.FileStream. Les classes de base abstraites de ces flux de caractres se dnomment System.IO.TextReader pour la classe permettant la cration de flux de lecture squentielle de caractres et System.IO.TextWriter pour la classe permettant la cration de flux d'criture squentielle de caractres.
Premier pas dans .Net avec C# - (maj. 15.08.2005
page
333
Les classes concrtent et donc pratiques, qui sont fournies par NetFramework et qui implmentent ces deux classes abstraites sont : System.IO.StreamReader qui drive et implmente System.IO.TextReader. System.IO.StreamWriter qui drive et implmente System.IO.TextWriter.
asynchrone Ceci peut tre pnalisant si l'application travaille sur de grandes quantits de donnes et surtout lorsque plusieurs entres/sorties doivent avoir lieu "en mme temps", dans cette ventualit NetFramework fournit des entres/sorties multi-threades (qui peuvent avoir lieu "en mme temps") avec les mthodes asynchrones de lecture BeginRead et EndRead et d'criture BeginWrite et EndWrite ). Dans le cas d'utilisation de mthodes asynchrones, le thread principal peut continuer effectuer d'autres tches : le traitement des entres/sorties est alors "parallle".
Tampon ( Buffer )
Les flux du NetFramework sont tous par dfauts "buffriss" contrairement Java. Dans le NetFramework, un flux (un objet de classe Stream) est une abstraction d'une squence d'octets, telle qu'un fichier, un priphrique d'entre/sortie, un canal de communication processus interne, ou un socket TCP/IP. En C#, un objet flux de classe Stream possde une proprit de longueur Length qui indique combien d'octets peuvent tre traits par le flux. En outre un objet flux possde aussi une mthode SetLength( long val ) qui dfinit la longueur du flux : cette mmoire intermdiaire associe au flux est aussi appele mmoire tampon ( Buffer en Anglais) du flux. Lorsqu'un flux travaille sur des caractres, on peut faire que l'application lise ou crive les caractres les uns aprs les autres en rglant la longueur du flux 1 caractre (correspondance avec les flux non buffriss de Java) :
page
334
On peut aussi faire que l'application lise ou crive les caractres par groupes en rglant la longueur du flux n caractres (correspondance avec les flux buffriss de Java) :
2.3 Cration d'un fichier de texte avec des donnes Exemple d'utilisation des 2 classes prcdentes. Supposons que nous ayons l'architecture suivante sur le disque C:
Il est possible de crer un nouveau fichier sur le disque dur et d'ouvrir un flux en criture sur ce fichier en utilisant le constructeur de la classe System.IO.StreamWriter : StreamWriter fluxWrite = new StreamWriter(@"c:\temp\rep1\essai.txt"); Voici le rsultat obtenu par la ligne de code prcdente sur le disque dur :
Le programme C# ci-dessous permet de crer le fichier texte nomm essai.txt et d'crire des lignes de texte dans ce fichier avec la mthode WriteLine(...) de la classe StreamWriter, puis de lire le contenu selon le schma ci-aprs avec la mthode ReadLine( ) de la classe StreamReader :
Code source C# correspondant : if (!File.Exists( @"c:\temp\rep1\essai.txt" )) { // cration d'un fichier texte et ouverture d'un flux en criture sur ce fichier StreamWriter fluxWrite = new StreamWriter(@"c:\temp\rep1\essai.txt"); Console.WriteLine("Fichier essai.text cr sur le disque dur.");
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
335
Console.WriteLine("Il n'crase pas les donnes dj prsentes"); // criture de lignes de texte dans le fichier travers le flux : for ( int i = 1; i<10; i++) fluxWrite.WriteLine("texte stock par programme ligne N : "+i); fluxWrite.Close( ); // fermeture du flux imprative pour sauvegarde des donnes } Console.WriteLine("Contenu du fichier essai.txt dj prsent :"); // cration et ouverture d'un flux en lecture sur ce fichier StreamReader fluxRead = new StreamReader(@"c:\temp\rep1\essai.txt"); // lecture des lignes de texte dans le fichier travers le flux string ligne; while ( ( ligne = fluxRead.ReadLine()) != null ) Console.WriteLine(ligne); fluxRead.Close();// fermeture du flux
2.4 Copie des donnes d'un fichier texte dans un autre Nous utilisons l''instruction using qui dfinit un bloc permettant d'instancier un objet local au bloc la fin duquelle un objet est dsallou. Un bloc using instanciant un objet de flux en lecture : using ( StreamReader fluxRead = new StreamReader( ...)) { ....BLOC.... } Un bloc using instanciant un objet de flux en criture : using (StreamWriter fluxWrite = new StreamWriter( ...)) { ....BLOC.... } Code source C# crant le fichier essai.txt : if ( !File.Exists( @"c:\temp\rep1\essai.txt" )) {
using (StreamWriter fluxWrite = new treamWriter(@"c:\temp\rep1\essai.txt")) { Console.WriteLine("Fichier essai.text cr sur le disque dur."); Console.WriteLine("Il n'crase pas les donnes dj prsentes"); // criture de lignes de texte dans le fichier travers le flux : for ( int i = 1; i<10; i++) fluxWrite.WriteLine("texte stock par programme ligne N : "+i); } // fermeture et dsallocation de l'objet fluxWrite } Code source C# lisant le contenu du fichier essai.txt : Console.WriteLine("Contenu du fichier 'essai.text' dj prsent :"); using ( StreamReader fluxRead = new StreamReader(@"c:\temp\rep1\essai.txt")) { string ligne;
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
336
// lecture des lignes de texte dans le fichier travers le flux : while ((ligne = fluxRead.ReadLine()) != null ) Console.WriteLine(ligne); Console.WriteLine("\nRecopie en cours ..."); } // fermeture et dsallocation de l'objet fluxRead Code source C# recopiant le contenu du fichier essai.txt, crant le fichier CopyEssai.txt et recopiant le contenu de essai.txt : using ( StreamReader fluxRead = new StreamReader(@"c:\temp\rep1\essai.txt")) { StreamWriter fluxWrite = new StreamWriter(@"c:\temp\rep1\CopyEssai.txt"); string ligne; // on lit dans essai.txt travers fluxRead et on crit dans // CopyEssai.txt travers fluxWrite : while ((ligne = fluxRead.ReadLine()) != null ) fluxWrite.WriteLine("copie < "+ligne+" >"); fluxWrite.Close();// fermeture de fluxWrite }// fermeture et dsallocation de l'objet fluxRead Code source C# lisant le contenu du fichier CopyEssai.txt : using ( StreamReader fluxRead = new StreamReader("copyEssai.txt")) { Console.WriteLine("\nContenu de la copie 'copyEssai.txt' :"); string ligne; // lecture des lignes de texte du nouveau fichier copi fichier travers le flux : while ((ligne = fluxRead.ReadLine()) != null ) Console.WriteLine(ligne); } // fermeture et dsallocation de l'objet fluxRead
Rsultat d'excution du code prcdent : Contenu du fichier 'essai.text' dj prsent : texte stock par programme ligne N : 1 texte stock par programme ligne N : 2 ..... texte stock par programme ligne N : 9 Recopie en cours ... Contenu de la copie 'copyEssai.txt' : copie < texte stock par programme ligne N : 1 > copie < texte stock par programme ligne N : 2 > ..... copie < texte stock par programme ligne N : 9 >
page
337
Ci-dessous les lments crire en mode console d'abord, puis ensuite crez vous-mmes une interface interactive. Soit les diagrammes de classe suivants :
page
338
page
339
Les informations affrentes un salari de l'entreprise sont stockes dans un fichier Fiches qui est un objet de classe FichierDeSalaries qui se trouve stock sur le disque dur sous le nom de fichierSalaries.txt. Le programme travaille en mmoire centrale sur une image de ce fichier qui est range dans un ArrayList nomm ListeSalaries : ArrayList ListeSalaries = new ArrayList ( ) ; FichierDeSalaries Fiches = new FichierDeSalaries ("fichierSalaries.txt" , ListeSalaries );
page
340
///le constructeur de la classe employ au mrite : public Salarie ( int IDentifiant, string Nom, tring Prenom, CategoriePerso Categorie, string Insee, int Merite, int Indice, double CoeffPrime ) { FCodeEmploye = IDentifiant ; FNom = Nom ; FPrenom = Prenom ; FCategorie = Categorie ; FInsee = Insee ; FMerite = Merite ; FIndice = Indice ; FCoeffPrime = CoeffPrime ; FIndiceDetenu = DateTime.Now ; switch ( FCategorie ) { case CategoriePerso.Cadre_Sup : FBasePrime = 2000 ; break; case CategoriePerso.Cadre : FBasePrime = 1000 ; break; case CategoriePerso.Maitrise : FBasePrime = 500 ; break; case CategoriePerso.Agent : FBasePrime = 200 ; break; } } ///le constructeur de la classe employ sans mrite : public Salarie ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee ): this( IDentifiant, Nom, Prenom, Categorie, Insee,0,0,0 ) { } protected double EvaluerPrimeCadreSup ( int coeffMerite ) { return ( 100 + coeffMerite * 8 ) * FCoeffPrime * FBasePrime + FIndice * 7 ; } protected double EvaluerPrimeCadre ( int coeffMerite ) { return ( 100 + coeffMerite * 6 ) * FCoeffPrime * FBasePrime + FIndice * 5 ; } protected double EvaluerPrimeMaitrise ( int coeffMerite ) { return ( 100 + coeffMerite * 4 ) * FCoeffPrime * FBasePrime + FIndice * 3 ; } protected double EvaluerPrimeAgent ( int coeffMerite ) { return ( 100 + coeffMerite * 2 ) * FCoeffPrime * FBasePrime + FIndice * 2 ; } /// proprit abstraite donnant le montant du salaire /// (virtual automatiquement) abstract public double MontantPaie { get ; } /// proprit identifiant le salari dans l'entreprise : public int IDentifiant { get { return FCodeEmploye ; } } /// proprit nom du salari :
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
341
public string Nom { get { return FNom ; } set { FNom = value ; } } /// proprit nom du salari : public string Prenom { get { return FPrenom ; } set { FPrenom = value ; } } /// proprit catgorie de personnel du salari : public CategoriePerso Categorie { get { return FCategorie ; } set { FCategorie = value ; } } /// proprit n; de scurit sociale du salari : public string Insee { get { return FInsee ; } set { FInsee = value ; } } /// proprit de point de mrite du salari : public virtual int Merite { get { return FMerite ; } set { FMerite = value ; } } /// proprit classement indiciaire dans la hirarchie : public int Indice_Hierarchique { get { return FIndice ; } set { FIndice = value ; //--Maj de la date de dtention du nouvel indice : IndiceDepuis = DateTime.Now ; } } /// proprit coefficient de la prime en %: public double Coeff_Prime { get { return FCoeffPrime ; } set { FCoeffPrime = value ; } } /// proprit valeur de la prime : public double Prime { get { switch ( FCategorie ) { case CategoriePerso.Cadre_Sup : return EvaluerPrimeCadreSup ( FMerite ); case CategoriePerso.Cadre : return EvaluerPrimeCadre ( FMerite ); case CategoriePerso.Maitrise : return EvaluerPrimeMaitrise ( FMerite ); case CategoriePerso.Agent : return EvaluerPrimeAgent ( FMerite ); default : return EvaluerPrimeAgent ( 0 ); } } }
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
342
/// date laquelle l'indice actuel a t obtenu : public DateTime IndiceDepuis { get { return FIndiceDetenu ; } set { FIndiceDetenu = value ; } } } // fin classe Salarie /// <summary> /// Classe du personnel mensualis. Implmente la proprit abstraite /// MontantPaie dclare dans la classe de base (mre). /// </summary> class SalarieMensuel : Salarie { /// attributs du salaire annuel : private double FPrime ; private double FRemunerationTotal ; ///le constructeur de la classe (salari au mrite) : public SalarieMensuel ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, int Merite, int Indice, double CoeffPrime, double RemunerationTotal ) :base ( IDentifiant, Nom, Prenom, Categorie, Insee, Merite, Indice, CoeffPrime ) { FPrime = this .Prime ; FRemunerationTotal = RemunerationTotal ; } /// implmentation de la proprit donnant le montant du salaire : public override double MontantPaie { get { return ( FRemunerationTotal + this .Prime ) / 12 ; } } /// proprit de point de mrite du salari : public override int Merite { get { return FMerite ; } set { FMerite = value ; FPrime = this .Prime ; } } } // fin classe SalarieMensuel /// <summary> /// Classe du personnel horaire. Implemente la proprit abstraite /// MontantPaie dclare dans la classe de base (mre). /// </summary> class SalarieHoraire : Salarie { /// attributs permettant le calcul du salaire : private double FPrime ; private double FTauxHoraire ;
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
343
private double FHeuresTravaillees ; ///le constructeur de la classe (salari non au mrite): public SalarieHoraire ( int IDentifiant, string Nom, string Prenom, string Insee, double TauxHoraire ): base ( IDentifiant, Nom, Prenom, CategoriePerso.Autre, Insee ) { FTauxHoraire = TauxHoraire ; FHeuresTravaillees = 0 ; FPrime = 0 ; } /// nombre d'heures effectues : public double HeuresTravaillees { get { return FHeuresTravaillees ; } set { FHeuresTravaillees = value ; } } /// implmentation de la proprit donnant le montant du salaire : public override double MontantPaie { get { return FHeuresTravaillees * FTauxHoraire + FPrime ; } } } // fin classe SalarieHoraire class FichierDeSalaries { private string Fchemin ; private ArrayList FListeEmployes ; // liste des nouveaux employs entrer dans le fichier private ArrayList indexCadreSup ; // Table d'index des cadres suprieurs du fichier // mthode static affichant un objet Salarie la console : public static void AfficherUnSalarie ( Salarie Employe ) { // pour l'instant un salari mensualis seulement } // constructeur de la classeFichierDeSalaries public FichierDeSalaries ( string chemin, ArrayList Liste ) { } // mthode de cration de la table d'index des cadre_sup : public void CreerIndexCadreSup ( ) { } // mthode convertissant le champ string catgorie en la constante enum associe private CategoriePerso strToCategorie ( string s ) { } // mthode renvoyant un objet SalarieMensuel de rang fix dans le fichier private Salarie EditerUnSalarie ( int rang ) { SalarieMensuel perso ; ........... perso = new SalarieMensuel ( IDentifiant, Nom, Prenom, Categorie, Insee, Merite, Indice, CoeffPrime, RemunerationTotal ); ........... return perso ;
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
344
} // mthode affichant sur la console partir de la table d'index : public void EditerFichierCadreSup ( ) { ........... foreach( int ind in indexCadreSup ) { AfficherUnSalarie ( EditerUnSalarie ( ind ) ); } ........... } // mthode affichant sur la console le fichier de tous les salaris : public void EditerFichierSalaries ( ) { } // mthode crant et stockant des salaris dans le fichier : public void StockerSalaries ( ArrayList ListeEmploy ) { ........... // si le fichier n'existe pas => cration du fichier sur disque : fichierSortie = File.CreateText ( Fchemin ); fichierSortie.WriteLine ("Fichier des personnels"); fichierSortie.Close (); ........... // ajout dans le fichier de toute la liste : ........... foreach( Salarie s in ListeEmploy ) { } ........... } } // fin classe FichierDeSalaries Implmenter les classes avec le programme de test suivant :
class ClassUsesSalarie { /// <summary> /// Le point d'entre principal de l'application. /// </summary> static void InfoSalarie ( SalarieMensuel empl ) { FichierDeSalaries.AfficherUnSalarie ( empl ); double coefPrimeLoc = empl.Coeff_Prime ; int coefMeriteLoc = empl.Merite ; //--impact variation du coef de prime for( double i = 0.5 ; i < 1 ; i += 0.1 ) { empl.Coeff_Prime = i ;
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
345
Console .WriteLine (" coeff prime : " + empl.Coeff_Prime ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } Console .WriteLine (" -----------------------"); empl.Coeff_Prime = coefPrimeLoc ; //--impact variation du coef de mrite for( int i = 0 ; i < 10 ; i ++ ) { empl.Merite = i ; Console .WriteLine (" coeff mrite : " + empl.Merite ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } empl.Merite = coefMeriteLoc ; Console .WriteLine ("======================================="); } [STAThread] static void Main ( string [] args ) { SalarieMensuel Employe1 = new SalarieMensuel ( 123456, "Euton" , "Jeanne" , CategoriePerso.Cadre_Sup, "2780258123456" ,6,700,0.5,50000 ); SalarieMensuel Employe2 = new SalarieMensuel ( 123457, "Yonaize" , "Mah" , CategoriePerso.Cadre, "1821113896452" ,5,520,0.42,30000 ); SalarieMensuel Employe3 = new SalarieMensuel ( 123458, "Ziaire" , "Marie" , CategoriePerso.Maitrise, "2801037853781" ,2,678,0.6,20000 ); SalarieMensuel Employe4 = new SalarieMensuel ( 123459, "Louga" , "Belle" , CategoriePerso.Agent, "2790469483167" ,4,805,0.25,20000 ); ArrayList ListeSalaries = new ArrayList (); ListeSalaries.Add ( Employe1 ); ListeSalaries.Add ( Employe2 ); ListeSalaries.Add ( Employe3 ); ListeSalaries.Add ( Employe4 ); foreach( SalarieMensuel s in ListeSalaries ) InfoSalarie ( s ); Console .WriteLine (">>> Promotion indice de " + Employe1.Nom + " dans 2 secondes."); Thread.Sleep ( 2000 ); Employe1.Indice_Hierarchique = 710 ; InfoSalarie ( Employe1 ); //-------------------------------------------// FichierDeSalaries Fiches = new FichierDeSalaries ("fichierSalaries.txt" ,ListeSalaries ); Console .WriteLine (">>> Attente 3 s pour cration de nouveaux salaris"); Thread.Sleep ( 3000 ); Employe1 = new SalarieMensuel ( 123460, "Miett" , "Hamas" , CategoriePerso.Cadre_Sup, "1750258123456" ,4,500,0.7,42000 ); Employe2 = new SalarieMensuel ( 123461, "Kong" , "King" , CategoriePerso.Cadre, "1640517896452" ,4,305,0.62,28000 ); Employe3 = new SalarieMensuel ( 123462, "Zaume" , "Philippo" , CategoriePerso.Maitrise, "1580237853781" ,2,245,0.8,15000 );
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
346
Employe4 = new SalarieMensuel ( 123463, "Micoton" , "Mylne" , CategoriePerso.Agent, "2850263483167" ,4,105,0.14,12000 ); ListeSalaries = new ArrayList (); ListeSalaries.Add ( Employe1 ); ListeSalaries.Add ( Employe2 ); ListeSalaries.Add ( Employe3 ); ListeSalaries.Add ( Employe4 ); Fiches.StockerSalaries ( ListeSalaries ); Fiches.EditerFichierSalaries (); Fiches.CreerIndexCadreSup (); Fiches.EditerFichierCadreSup (); System .Console.ReadLine (); } } } Exemple de rsultats obtenus avec le programme de test prcdent : fichierSalaries.txt : Fichier des personnels 123456 Euton Jeanne *Cadre_Sup 2780258123456 6 710 15/02/2004 19:52:38 0,5 152970 16914,1666666667 123457 Yonaize Mah *Cadre 1821113896452 5 520 15/02/2004 19:52:36 0,42 57200 7266,66666666667 123458 Ziaire Marie *Maitrise 2801037853781 2 678 15/02/2004 19:52:36
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
347
0,6 34434 4536,16666666667 123459 Louga Belle *Agent 2790469483167 4 805 15/02/2004 19:52:36 0,25 7010 2250,83333333333 123460 Miett Hamas *Cadre_Sup 1750258123456 4 500 15/02/2004 19:52:41 0,7 188300 19191,6666666667 123461 Kong King *Cadre 1640517896452 4 305 15/02/2004 19:52:41 0,62 78405 8867,08333333333 123462 Zaume Philippo *Maitrise 1580237853781 2 245 15/02/2004 19:52:41 0,8 43935 4911,25 123463 Micoton Mylne *Agent
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
348
2850263483167 4 105 15/02/2004 19:52:41 0,14 3234 1269,5 Rsultats console : Employ n°123456: Euton / Jeanne n° SS : 2780258123456 catgorie : Cadre_Sup indice hirarchique : 700 , dtenu depuis : 15/02/2004 19:52:36 coeff mrite : 6 coeff prime : 0,5 montant prime annuelle : 152900 montant paie mensuelle: 16908,3333333333 coeff prime : 0,5 montant prime annuelle : 152900 montant paie mensuelle: 16908,3333333333 coeff prime : 0,6 montant prime annuelle : 182500 montant paie mensuelle: 19375 coeff prime : 0,7 montant prime annuelle : 212100 montant paie mensuelle: 21841,6666666667 coeff prime : 0,8 montant prime annuelle : 241700 montant paie mensuelle: 24308,3333333333 coeff prime : 0,9 montant prime annuelle : 271300 montant paie mensuelle: 26775 coeff prime : 1 montant prime annuelle : 300900 montant paie mensuelle: 29241,6666666667 ----------------------coeff mrite : 0 montant prime annuelle : 104900 montant paie mensuelle: 12908,3333333333 coeff mrite : 1 montant prime annuelle : 112900 montant paie mensuelle: 13575 coeff mrite : 2 montant prime annuelle : 120900 montant paie mensuelle: 14241,6666666667 coeff mrite : 3 montant prime annuelle : 128900 montant paie mensuelle: 14908,3333333333 coeff mrite : 4 montant prime annuelle : 136900
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
349
montant paie mensuelle: 15575 coeff mrite : 5 montant prime annuelle : 144900 montant paie mensuelle: 16241,6666666667 coeff mrite : 6 montant prime annuelle : 152900 montant paie mensuelle: 16908,3333333333 coeff mrite : 7 montant prime annuelle : 160900 montant paie mensuelle: 17575 coeff mrite : 8 montant prime annuelle : 168900 montant paie mensuelle: 18241,6666666667 coeff mrite : 9 montant prime annuelle : 176900 montant paie mensuelle: 18908,3333333333 ======================================= Employ n°123457: Yonaize / Mah n° SS : 1821113896452 catgorie : Cadre indice hirarchique : 520 , dtenu depuis : 15/02/2004 19:52:36 coeff mrite : 5 coeff prime : 0,42 montant prime annuelle : 57200 montant paie mensuelle: 7266,66666666667 coeff prime : 0,5 montant prime annuelle : 67600 montant paie mensuelle: 8133,33333333333 coeff prime : 0,6 montant prime annuelle : 80600 montant paie mensuelle: 9216,66666666667 coeff prime : 0,7 montant prime annuelle : 93600 montant paie mensuelle: 10300 coeff prime : 0,8 montant prime annuelle : 106600 montant paie mensuelle: 11383,3333333333 coeff prime : 0,9 montant prime annuelle : 119600 montant paie mensuelle: 12466,6666666667 coeff prime : 1 montant prime annuelle : 132600 montant paie mensuelle: 13550 ----------------------coeff mrite : 0 montant prime annuelle : 44600 montant paie mensuelle: 6216,66666666667 coeff mrite : 1 montant prime annuelle : 47120 montant paie mensuelle: 6426,66666666667 coeff mrite : 2
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
350
montant prime annuelle : 49640 montant paie mensuelle: 6636,66666666667 coeff mrite : 3 montant prime annuelle : 52160 montant paie mensuelle: 6846,66666666667 coeff mrite : 4 montant prime annuelle : 54680 montant paie mensuelle: 7056,66666666667 coeff mrite : 5 montant prime annuelle : 57200 montant paie mensuelle: 7266,66666666667 coeff mrite : 6 montant prime annuelle : 59720 montant paie mensuelle: 7476,66666666667 coeff mrite : 7 montant prime annuelle : 62240 montant paie mensuelle: 7686,66666666667 coeff mrite : 8 montant prime annuelle : 64760 montant paie mensuelle: 7896,66666666667 coeff mrite : 9 montant prime annuelle : 67280 montant paie mensuelle: 8106,66666666667 ======================================= Employ n°123458: Ziaire / Marie n° SS : 2801037853781 catgorie : Maitrise indice hirarchique : 678 , dtenu depuis : 15/02/2004 19:52:36 coeff mrite : 2 coeff prime : 0,6 montant prime annuelle : 34434 montant paie mensuelle: 4536,16666666667 coeff prime : 0,5 montant prime annuelle : 29034 montant paie mensuelle: 4086,16666666667 coeff prime : 0,6 montant prime annuelle : 34434 montant paie mensuelle: 4536,16666666667 coeff prime : 0,7 montant prime annuelle : 39834 montant paie mensuelle: 4986,16666666667 coeff prime : 0,8 montant prime annuelle : 45234 montant paie mensuelle: 5436,16666666667 coeff prime : 0,9 montant prime annuelle : 50634 montant paie mensuelle: 5886,16666666667 coeff prime : 1 montant prime annuelle : 56034 montant paie mensuelle: 6336,16666666667 ----------------------Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
351
coeff mrite : 0 montant prime annuelle : 32034 montant paie mensuelle: 4336,16666666667 coeff mrite : 1 montant prime annuelle : 33234 montant paie mensuelle: 4436,16666666667 coeff mrite : 2 montant prime annuelle : 34434 montant paie mensuelle: 4536,16666666667 coeff mrite : 3 montant prime annuelle : 35634 montant paie mensuelle: 4636,16666666667 coeff mrite : 4 montant prime annuelle : 36834 montant paie mensuelle: 4736,16666666667 coeff mrite : 5 montant prime annuelle : 38034 montant paie mensuelle: 4836,16666666667 coeff mrite : 6 montant prime annuelle : 39234 montant paie mensuelle: 4936,16666666667 coeff mrite : 7 montant prime annuelle : 40434 montant paie mensuelle: 5036,16666666667 coeff mrite : 8 montant prime annuelle : 41634 montant paie mensuelle: 5136,16666666667 coeff mrite : 9 montant prime annuelle : 42834 montant paie mensuelle: 5236,16666666667 ======================================= Employ n°123459: Louga / Belle n° SS : 2790469483167 catgorie : Agent indice hirarchique : 805 , dtenu depuis : 15/02/2004 19:52:36 coeff mrite : 4 coeff prime : 0,25 montant prime annuelle : 7010 montant paie mensuelle: 2250,83333333333 coeff prime : 0,5 montant prime annuelle : 12410 montant paie mensuelle: 2700,83333333333 coeff prime : 0,6 montant prime annuelle : 14570 montant paie mensuelle: 2880,83333333333 coeff prime : 0,7 montant prime annuelle : 16730 montant paie mensuelle: 3060,83333333333 coeff prime : 0,8 montant prime annuelle : 18890 montant paie mensuelle: 3240,83333333333
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
352
coeff prime : 0,9 montant prime annuelle : 21050 montant paie mensuelle: 3420,83333333333 coeff prime : 1 montant prime annuelle : 23210 montant paie mensuelle: 3600,83333333333 ----------------------coeff mrite : 0 montant prime annuelle : 6610 montant paie mensuelle: 2217,5 coeff mrite : 1 montant prime annuelle : 6710 montant paie mensuelle: 2225,83333333333 coeff mrite : 2 montant prime annuelle : 6810 montant paie mensuelle: 2234,16666666667 coeff mrite : 3 montant prime annuelle : 6910 montant paie mensuelle: 2242,5 coeff mrite : 4 montant prime annuelle : 7010 montant paie mensuelle: 2250,83333333333 coeff mrite : 5 montant prime annuelle : 7110 montant paie mensuelle: 2259,16666666667 coeff mrite : 6 montant prime annuelle : 7210 montant paie mensuelle: 2267,5 coeff mrite : 7 montant prime annuelle : 7310 montant paie mensuelle: 2275,83333333333 coeff mrite : 8 montant prime annuelle : 7410 montant paie mensuelle: 2284,16666666667 coeff mrite : 9 montant prime annuelle : 7510 montant paie mensuelle: 2292,5 ======================================= >>> Promotion indice de Euton dans 2 secondes. Employ n°123456: Euton / Jeanne n° SS : 2780258123456 catgorie : Cadre_Sup indice hirarchique : 710 , dtenu depuis : 15/02/2004 19:52:38 coeff mrite : 6 coeff prime : 0,5 montant prime annuelle : 152970 montant paie mensuelle: 16914,1666666667 coeff prime : 0,5 montant prime annuelle : 152970 montant paie mensuelle: 16914,1666666667 coeff prime : 0,6
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
353
montant prime annuelle : 182570 montant paie mensuelle: 19380,8333333333 coeff prime : 0,7 montant prime annuelle : 212170 montant paie mensuelle: 21847,5 coeff prime : 0,8 montant prime annuelle : 241770 montant paie mensuelle: 24314,1666666667 coeff prime : 0,9 montant prime annuelle : 271370 montant paie mensuelle: 26780,8333333333 coeff prime : 1 montant prime annuelle : 300970 montant paie mensuelle: 29247,5 ----------------------coeff mrite : 0 montant prime annuelle : 104970 montant paie mensuelle: 12914,1666666667 coeff mrite : 1 montant prime annuelle : 112970 montant paie mensuelle: 13580,8333333333 coeff mrite : 2 montant prime annuelle : 120970 montant paie mensuelle: 14247,5 coeff mrite : 3 montant prime annuelle : 128970 montant paie mensuelle: 14914,1666666667 coeff mrite : 4 montant prime annuelle : 136970 montant paie mensuelle: 15580,8333333333 coeff mrite : 5 montant prime annuelle : 144970 montant paie mensuelle: 16247,5 coeff mrite : 6 montant prime annuelle : 152970 montant paie mensuelle: 16914,1666666667 coeff mrite : 7 montant prime annuelle : 160970 montant paie mensuelle: 17580,8333333333 coeff mrite : 8 montant prime annuelle : 168970 montant paie mensuelle: 18247,5 coeff mrite : 9 montant prime annuelle : 176970 montant paie mensuelle: 18914,1666666667 ======================================= >>> Attente 3 s pour cration de nouveaux salaris Fichier des personnels 123456 Euton Jeanne
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
354
*Cadre_Sup 2780258123456 6 710 15/02/2004 19:52:38 0,5 152970 16914,1666666667 123457 Yonaize Mah *Cadre 1821113896452 5 520 15/02/2004 19:52:36 0,42 57200 7266,66666666667 123458 Ziaire Marie *Maitrise 2801037853781 2 678 15/02/2004 19:52:36 0,6 34434 4536,16666666667 123459 Louga Belle *Agent 2790469483167 4 805 15/02/2004 19:52:36 0,25 7010 2250,83333333333 123460 Miett Hamas *Cadre_Sup 1750258123456 4 500 15/02/2004 19:52:41 0,7 188300
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
355
19191,6666666667 123461 Kong King *Cadre 1640517896452 4 305 15/02/2004 19:52:41 0,62 78405 8867,08333333333 123462 Zaume Philippo *Maitrise 1580237853781 2 245 15/02/2004 19:52:41 0,8 43935 4911,25 123463 Micoton Mylne *Agent 2850263483167 4 105 15/02/2004 19:52:41 0,14 3234 1269,5 ++> *Cadre_Sup : 5 ++> *Cadre_Sup : 49 Employ n°123456: Euton / Jeanne n° SS : 2780258123456 catgorie : Cadre_Sup indice hirarchique : 710 , dtenu depuis : 15/02/2004 19:52:38 coeff mrite : 6 coeff prime : 0 montant prime annuelle : 4970 montant paie mensuelle: 414,208333333333 Employ n°123460: Miett / Hamas n° SS : 1750258123456 catgorie : Cadre_Sup indice hirarchique : 500 , dtenu depuis : 15/02/2004 19:52:41 coeff mrite : 4 coeff prime : 0 montant prime annuelle : 3500
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
356
page
357
Plan gnral:
page
358
Introduction
Nous avons vu au chapitre prcdent que les fichiers simples peuvent tre accds par des flux, ds que l'organisation de l'information dans le fichier est fortement structure les flux ne sont plus assez puissants pour fournir un accs souple aux donnes en particulier pour des applications architecture multi-tiers (client/serveur, internet,) et spcialement aux bases de donnes. ADO .NET est un regroupement de types (classes, interfaces, ) dans l'espace de nom System.Data construits par Microsoft afin de manipuler des donnes structures dans le .NET Framework. Le modle ADO .NET du .NET Framework fournit au dveloppeur un ensemble d'lments lui permettant de travailler sur des donnes aussi bien en mode connect qu'en mode dconnect (ce dernier mode est le mode prfrentiel d' ADO .NET car c'est celui qui est le plus adapt aux architectures multi-tiers). ADO .NET est indpendant du mode de stockage des donnes : les classes s'adaptent automatiquement l'organisation ADO .NET permet de traiter des donnes situes dans des bases de donnes selon le modle relationnel mais il supporte aussi les donnes organises selon le modle hirarchique. ADO .NET change toutes ses informations au format XML L'entit la plus importante d' ADO .NET permettant de grer les donnes en local dans une mmoire cache compltement dconnecte de la source de donnes (donc indpendante de cette source) est le DataSet en fait la classe System.Data.DataSet et la collection de classes qui lui sont lies.
Un DataSet est en fait une collection de tables reprsente par un objet de collection de la classe DataTableCollection et une collection de relations entres ces tables tables reprsente par un objet de collection de la classe DataRelationCollection.
Premier pas dans .Net avec C# - (maj. 15.08.2005
page
359
Une DataTableCollection est une collection (famille) d'une ou plusieurs DataTable qui reprsentent chacunes une table dans le DataSet. Une table gnrale est une entit compose de : q Colonnes q Lignes Un objet de classe DataTable est compos en particulier des objets suivants : q Une proprit Columns = Collection de colonnes (collection d'objets DataColumn) q Une proprit Rows = Collection de lignes (collection d'objets DataRow)
Le schma gnral de la table d'un objet DataTable est donn par la famille des colonnes (l'objet Columns) chaque objet DataColumn de la collection reprsente une colonne de la table :
page
360
Pour rsumer, un DataSet contient pour l'essentiel deux types d'objets : 1) des objets DataTable inclus dans une DataTableCollection 2) des objets DataRelation inclus dans une DataRelationCollection
Une DataRelationCollection est une collection d'une ou plusieurs DataRelation qui reprsentent chacunes une relation entre deux DataTable dans le DataSet.
page
361
Voici une reprsentation fictive mais figurative d'un DataSet contenant 4 DataTable et 2 DataRelation : La DataTableCollection du DataSet :
La DataRelationCollection du DataSet :
page
362
Cration d'un DataSet (un magasin). Cration de 2 DataTable (une table client, une table achats). Dfinition du schma de colonnes de chaque table par des DataColumn. Dfinition d'une clef primaire. Ajout des 2 tables au DataSet. Ajout d'une relation entre les deux tables.
Ajouter, supprimer ou modifier des donnes ligne par ligne dans chaque table.
page
363
page
364
page
365
Voici affich dans un autre composant visuel DataGrid, la table des Achats avec ses quatre colonnes remplies par le programme prcdent :
Le composant DataGrid destin afficher une table de donnes, est remplac dans la version 2.0 par le DataGridView, mais est tojours prsent et utilisable. private System.Windows.Forms.DataGrid DataGridSimple; Voici deux faons de lier ce composant visuel la premire table (la table Clients) du DataSet du programme de gestion du Magasin :
// Lie directement par le nom de la table le DataGrid au DataSet. DataGridSimple.SetDataBinding(unDataSet, "Clients"); // Lie indirectement par le rang, le DataGrid au DataSet sur la premire table : DataGridSimple.SetDataBinding(unDataSet, unDataSet.Tables[0].TableName); Enfin pour terminer la description des actions pratiques d'un DataSet, indiquons qu'il est possible de sauvegarder le schma structur (relation, clef primaire, tables,) d'un DataSet dans un fichier de schmas au format XSL; il est aussi possible de sauvegarder toutes les donnes et leur structuration dans un fichier au format XML.
Premier pas dans .Net avec C# - (maj. 15.08.2005
page
366
Fichier "Donnees.xsl" obtenu au format XSL <?xml version="1.0" standalone="yes"?> <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 maxOccurs="unbounded"> <xs:element name="Clients"> <xs:complexType> <xs:sequence> <xs:element name="ClientID" type="xs:int" /> <xs:element name="ClientNom" type="xs:string" /> <xs:element name="Pay" type="xs:boolean" minOccurs="0" /> <xs:element name="Achats" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="ClientID" type="xs:int" minOccurs="0" /> <xs:element name="Article" type="xs:string" minOccurs="0" /> <xs:element name="MontantAchat" type="xs:decimal" minOccurs="0" /> <xs:element name="DateAchat" type="xs:dateTime" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> <xs:unique name="Constraint1"> <xs:selector xpath=".//Clients" /> <xs:field xpath="ClientID" /> </xs:unique> <xs:unique name="Constraint2" msdata:PrimaryKey="true"> <xs:selector xpath=".//Clients" /> <xs:field xpath="ClientID" /> <xs:field xpath="ClientNom" /> </xs:unique>
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
367
<xs:keyref name="Liste_x0020_des_x0020_achats" refer="Constraint1" msdata:IsNested="true"> <xs:selector xpath=".//Achats" /> <xs:field xpath="ClientID" /> </xs:keyref> </xs:element> </xs:schema>
Fichier "Donnees.xml" obtenu format XML <?xml version="1.0" standalone="yes"?> <NewDataSet> <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 maxOccurs="unbounded"> <xs:element name="Clients"> <xs:complexType> <xs:sequence> <xs:element name="ClientID" type="xs:int" /> <xs:element name="ClientNom" type="xs:string" /> <xs:element name="Pay" type="xs:boolean" minOccurs="0" /> <xs:element name="Achats" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="ClientID" type="xs:int" minOccurs="0" /> <xs:element name="Article" type="xs:string" minOccurs="0" /> <xs:element name="MontantAchat" type="xs:decimal" minOccurs="0" /> <xs:element name="DateAchat" type="xs:dateTime" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> <xs:unique name="Constraint1"> <xs:selector xpath=".//Clients" /> <xs:field xpath="ClientID" /> </xs:unique> <xs:unique name="Constraint2" msdata:PrimaryKey="true"> <xs:selector xpath=".//Clients" /> <xs:field xpath="ClientID" /> <xs:field xpath="ClientNom" /> </xs:unique> <xs:keyref name="Liste_x0020_des_x0020_achats" refer="Constraint1" msdata:IsNested="true"> <xs:selector xpath=".//Achats" />
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
368
<xs:field xpath="ClientID" /> </xs:keyref> </xs:element> </xs:schema> <Clients> <ClientID>1001</ClientID> <ClientNom>Legrand</ClientNom> <Pay>true</Pay> <Achats> <ClientID>1001</ClientID> <Article>cuisinire</Article> <MontantAchat>456.74</MontantAchat> <DateAchat>2005-12-12T00:00:00.0000000+01:00</DateAchat> </Achats> </Clients> <Clients> <ClientID>1002</ClientID> <ClientNom>Fischer</ClientNom> <Pay>true</Pay> <Achats> <ClientID>1002</ClientID> <Article>radio</Article> <MontantAchat>297.58</MontantAchat> <DateAchat>2005-02-25T00:00:00.0000000+01:00</DateAchat> </Achats> <Achats> <ClientID>1002</ClientID> <Article>tlvison</Article> <MontantAchat>715.1</MontantAchat> <DateAchat>2005-07-19T00:00:00.0000000+02:00</DateAchat> </Achats> <Achats> <ClientID>1002</ClientID> <Article>tlvison</Article> <MontantAchat>447.55</MontantAchat> <DateAchat>2005-08-16T00:00:00.0000000+02:00</DateAchat> </Achats> <Achats> <ClientID>1002</ClientID> <Article>cuisinire</Article> <MontantAchat>92.64</MontantAchat> <DateAchat>2005-09-23T00:00:00.0000000+02:00</DateAchat> </Achats> <Achats> <ClientID>1002</ClientID> <Article>cuisinire</Article> <MontantAchat>171.07</MontantAchat> <DateAchat>2005-01-23T00:00:00.0000000+01:00</DateAchat> </Achats> </Clients> <Clients>
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
369
<ClientID>1003</ClientID> <ClientNom>Dupont</ClientNom> <Pay>false</Pay> <Achats> <ClientID>1003</ClientID> <Article>aspirateur</Article> <MontantAchat>445.89</MontantAchat> <DateAchat>2005-02-11T00:00:00.0000000+01:00</DateAchat> </Achats> </Clients> <Clients> <ClientID>1004</ClientID> <ClientNom>Durand</ClientNom> <Pay>false</Pay> <Achats> <ClientID>1004</ClientID> <Article>tlvison</Article> <MontantAchat>661.47</MontantAchat> <DateAchat>2005-11-15T00:00:00.0000000+01:00</DateAchat> </Achats> </Clients> <Clients> <ClientID>1005</ClientID> <ClientNom>Lamiel</ClientNom> <Pay>true</Pay> </Clients> <Clients> <ClientID>1006</ClientID> <ClientNom>Renoux</ClientNom> <Pay>false</Pay> <Achats> <ClientID>1006</ClientID> <Article>cuisinire</Article> <MontantAchat>435.17</MontantAchat> <DateAchat>2005-06-22T00:00:00.0000000+02:00</DateAchat> </Achats> <Achats> <ClientID>1006</ClientID> <Article>cuisinire</Article> <MontantAchat>491.3</MontantAchat> <DateAchat>2005-12-25T00:00:00.0000000+01:00</DateAchat> </Achats> <Achats> <ClientID>1006</ClientID> <Article>cuisinire</Article> <MontantAchat>388.81</MontantAchat> <DateAchat>2005-10-13T00:00:00.0000000+02:00</DateAchat> </Achats> <Achats> <ClientID>1006</ClientID> <Article>radio</Article>
Premier pas dans .Net avec C# - (maj. 15.08.2005
)
page
370
page
371
Calcul de la valeur absolue d'un nombre rel ..p.374 Rsolution de l'quation du second degr dans R .....p.376 Calcul des nombres de Armstrong .....p.378 Calcul de nombres parfaits ........p.380 Calcul du pgcd de 2 entiers (mthode Euclide) .....p.382 Calcul du pgcd de 2 entiers (mthode Egyptienne) .......p.384 Calcul de nombres premiers (boucles while et dowhile) ......p.386 Calcul de nombres premiers (boucles for) .....p.388 Calcul du nombre d'or .....p.390 Conjecture de Goldbach ......p.392 Mthodes d'oprations sur 8 bits ........p.394 Chanes palindromes 2versions ........p.398 Convertir une date numrique en lettres ........p.401 Convertir un nombre crit en chiffres romains ........p.403 Tri bulles tableau d'entiers .........p.404 Tri par insertion tableau d'entiers ......p.406 Recherche linaire dans un tableau non tri ......p.409 Recherche linaire dans un tableau dj tri .....p.413 Recherche dichotomique dans un tableau dj tri .....p.416 Dans la majorit des exemples de traduction d'algorithmes simples nous reprenons volontairement des exemples dj traits dans le livre les fondements de Java, le lecteur remarquera la similarit du code C# et du code Java pour le mme exercice. Lorsque cela est le cas nous avons fourni le code sans explication autre que celle de l'nonc, lorsqu'il y a dissemblance du code C# avec le code Java, nous avons fourni des explications spcifiques au code C#.
EXERCICES
page
372
Problme de la rfrence circulaire...p.419 Classe de salaris dans une entreprise fictive ..p.421 Classe de salaris dans un fichier de l'entreprise ....p.432 Construction d'un ensemble de caractres .....p.440 Construction d'un ensemble gnrique .......p.446 Construction d'une IHM de jeu de puzzle ......p.454 Les exercices utilisent des classes spcifiques au langage C#, si le lecteur veut traduire ces exemples en code Java ou en code Delphi, il doit soit chercher dans les packages Java ou Delphi des classes possdant les mmes fonctionnalits soit les construire lui-mme.
EXERCICES
page
373
Algorithme
Calcul de la valeur absolue d'un nombre rel
Objectif : Ecrire un programme C# servant calculer la valeur absolue d'un nombre rel x partir de la dfinition de la valeur absolue. La valeur absolue du nombre rel x est le nombre rel |x| : |x| = x , si x 0 |x| = -x si x < 0 Spcifications de lalgorithme :
lire( x ); si x 0 alors crire( '|x| =', x) sinon crire( '|x| =', -x) fsi
Implantation en C#
Ecrivez avec les deux instructions diffrentes "if...else.." et "...?.. : ...", le programme C# complet correspondant l'affichage ci-dessous :
EXERCICES
page
374
Classe C# solution
Une classe C# solution du problme avec un if...else :
using System; namespace CsExosAlgo1 { class ApplicationValAbsolue1 { static void Main(string[ ] args) { double x; System.Console.Write("Entrez un nombre x = "); x = Double.Parse( System.Console.ReadLine( ) ) ; if (x<0) System.Console.WriteLine("|x| = "+(-x)); else System.Console.WriteLine("|x| = "+x); } } }
Explication sur l'instruction : x = Double.Parse ( System.Console.ReadLine( ) ) ; Le gestionnaire d'entre sortie C# partir de la classe Console renvoie travers la mthode ReadLine() une valeur saisie au clavier de type string. Il est donc obligatoire si l'on veut rcuperer un nombre rel au clavier (ici double x;) de transtyper le rel tap correctement sous forme de chane au clavier, et de le convertir en un rel de type double ici, grce la mthode Parse de la classe enveloppe Double du type double. Remarquons que cette version simple ne protge pas des ereurs de saisie. Pour tre plus robuste le programme devrait intercepter l'exception leve par une ventuelle erreur de saisie signale par une exception du type FormatException :
try { x = Double.Parse( System.Console.ReadLine( ) ) ; } catch ( FormatException ) { //...traitement de l'erreur de saisie }
EXERCICES
page
375
Algorithme
Algorithme de rsolution de l'quation du second degr dans R.
Objectif : On souhaite crire un programme C# de rsolution dans R de l'quation du second degr : Ax2 + Bx +C = 0 Il s'agit ici d'un algorithme trs classique provenant du cours de mathmatique des classes du secondaire. L'exercice consiste essentiellement en la traduction immdiate Spcifications de lalgorithme :
Algorithme Equation Entre: A, B, C Rels Sortie: X1 , X2 Rels Local: Rels dbut lire(A, B, C); Si A=0 alors dbut{A=0} Si B = 0 alors Si C = 0 alors crire(R est solution) Sinon{C 0} crire(pas de solution) Fsi Sinon {B 0} X1 C/B; crire (X1) Fsi fin Sinon {A 0}dbut B2 - 4*A*C ; Si < 0 alors crire(pas de solution) Sinon { 0} Si = 0 alors X1 -B/(2*A); crire (X1) Sinon{ 0} X1 (-B + )/(2*A); X2 (-B - )/(2*A); crire(X1 , X2 ) Fsi Fsi fin Fsi FinEquation
Implantation en C#
Ecrivez le programme C# qui est la traduction immdiate de cet algorithme dans le corps de la mthode Main.
EXERCICES
page
376
Conseil : On utilisera la mthode static Sqrt(double x) de la classe Math pour calculer la racine carr d'un nombre rel :
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationEqua2 { static void Main (string[ ] arg) { double a, b, c, delta ; double x, x1, x2 ; System.Console.Write("Entrer une valeur pour a : ") ; a = Double.Parse( System.Console.ReadLine( ) ) ; System.Console.Write("Entrer une valeur pour b : ") ; b = Double.Parse( System.Console.ReadLine( ) ) ; System.Console.Write("Entrer une valeur pour c : ") ; c = Double.Parse( System.Console.ReadLine( ) ) ; if (a ==0) { if (b ==0) { if (c ==0) { System.Console.WriteLine("tout reel est solution") ; } else {// c 0 System.Console.WriteLine("il n'y a pas de solution") ; } } else { // b 0 x = -c/b ; System.Console.WriteLine("la solution est " + x) ; } } else { // a 0 delta = b*b - 4*a*c ; if (delta < 0) { System.Console.WriteLine("il n'y a pas de solution dans les reels") ; } else { // delta 0 x1 = (-b + Math.Sqrt(delta))/ (2*a) ; x2 = (-b - Math.Sqrt(delta))/ (2*a) ; System.Console.WriteLine("il y deux solutions egales a " + x1 + " et " + x2) ; } } } } }
EXERCICES
page
377
Algorithme
Calcul des nombres de Armstrong
Objectif : On dnomme nombre de Armstrong un entier naturel qui est gal la somme des cubes des chiffres qui le composent. Exemple : 153 = 1 + 125 + 27, est un nombre de Armstrong. Spcifications de lalgorithme :
On sait qu'il n'existe que 4 nombres de Armstrong, et qu'ils ont tous 3 chiffres (ils sont compris entre 100 et 500). Si l'on qu'un tel nombre est crit ijk (i chiffre des centaines, j chiffres des dizaines et k chiffres des units), il suffit simplement d'envisager tous les nombres possibles en faisant varier les chiffres entre 0 et 9 et de tester si le nombre est de Armstrong.
Implantation en C#
Ecrivez le programme C# complet qui fournisse les 4 nombres de Armstrong :
EXERCICES
page
378
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationArmstrong { static void Main(string[ ] args) { int i, j, k, n, somcube; System.Console.WriteLine("Nombres de Armstrong:"); for(i = 1; i<=9; i++) for(j = 0; j<=9; j++) for(k = 0; k<=9; k++) { n = 100*i + 10*j + k; somcube = i*i*i + j*j*j + k*k*k; if (somcube == n) System.Console.WriteLine(n); } } } }
EXERCICES
page
379
Algorithme
Calcul de nombres parfaits
Objectif : On souhaite crire un programme C# de calcul des n premiers nombres parfaits. Un nombre est dit parfait sil est gal la somme de ses diviseurs, 1 compris. Exemple : 6 = 1+2+3 , est un nombre parfait.
Spcifications de lalgorithme :
l'algorithme retenu contiendra deux boucles imbriques. Une boucle de comptage des nombres parfaits qui s'arrtera lorsque le dcompte sera atteint, la boucle interne ayant vocation calculer tous les diviseurs du nombre examin d'en faire la somme puis de tester l'galit entre cette somme et le nombre.
Algorithme Parfait Entre: n N Sortie: nbr N Local: somdiv, k, compt N dbut lire(n); compt 0; nbr 2; Tantque(compt < n) Faire somdiv 1; Pour k 2 jusqu nbr-1 Faire Si reste(nbr par k) = 0 Alors // k divise nbr somdiv somdiv + k Fsi Fpour ; Si somdiv = nbr Alors ecrire(nbr) ; compt compt+1; Fsi; nbr nbr+1 Ftant FinParfait
Implantation en C#
Ecrivez le programme C# complet qui produise le dialogue suivant lcran (les caractres gras reprsentent ce qui est crit par le programme, les italiques ce qui est entr au clavier) :
EXERCICES
page
380
Entrez combien de nombre parfaits : 4 6 est un nombre parfait 28 est un nombre parfait 496 est un nombre parfait 8128 est un nombre parfait
class ApplicationParfaits { static void Main(string[ ] args) { .. } } La mthode Main calcule et affiche les nombres parfaits.
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationParfaits { static void Main (string[ ] args) { int compt = 0, n, k, somdiv, nbr; System.Console.Write("Entrez combien de nombre parfaits : "); n = Int32.Parse( System.Console.ReadLine( ) ) ; nbr = 2; while (compt != n) { somdiv = 1; k = 2; while(k <= nbr/2 ) { if (nbr % k = = 0) somdiv += k ; k++; } if (somdiv = = nbr) { System.Console.WriteLine(nbr+" est un nombre parfait"); compt++; } nbr++; } } } }
La saisie de l'entier int n; s'effectue par transtypage grce la mthode Parse de la classe Net Framework Int32 du type int.
Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
381
Algorithme
Calcul du pgcd de 2 entiers (mthode Euclide)
Objectif : On souhaite crire un programme de calcul du pgcd de deux entiers non nuls, en C# partir de lalgorithme de la mthode d'Euclide. Voici une spcification de l'algorithme de calcul du PGCD de deux nombres (entiers strictement positifs) a et b, selon cette mthode : Spcifications de lalgorithme :
Algorithme Pgcd Entre: a,b N* x N* Sortie: pgcd N Local: r,t N x N dbut lire(a,b); Si ba Alors t a; a b; b t Fsi; Rpter r a mod b ; ab; br jusqu r = 0; pgcd a; ecrire(pgcd) FinPgcd
Implantation en C#
Ecrivez le programme C# complet qui produise le dialogue suivant la console (les caractres gras reprsentent ce qui est crit par le programme, les italiques ce qui est entr au clavier) : Entrez le premier nombre : 21 Entrez le deuxime nombre : 45 Le PGCD de 21 et 45 est : 3
EXERCICES
page
382
class ApplicationEuclide { static void Main(string[ ] args) { .. } static int pgcd (int a, int b) { .. } } La mthode pgcd renvoie le pgcd des deux entiers p et q .
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationEuclide { static void Main (string[ ] args) { System.Console.Write("Entrez le premier nombre : "); int p = Int32.Parse( System.Console.ReadLine( ) ) ; System.Console.Write("Entrez le deuxime nombre : "); int q = Int32.Parse( System.Console.ReadLine( ) ) ; if (p*q!=0) System.Console.WriteLine("Le pgcd de "+p+" et de "+q+" est "+pgcd(p,q)); else System.Console.WriteLine("Le pgcd n'existe pas lorsque l'un des deux nombres est nul !"); } static int pgcd (int a , int b) { int r,t ; if ( b>a) { t = a; a = b; b = t; } do { r = a % b; a = b; b = r; } while(r !=0); return a ; } } }
EXERCICES
page
383
Algorithme
Calcul du pgcd de 2 entiers (mthode Egyptienne) Objectif : On souhaite crire un programme de calcul du pgcd de deux entiers non nuls, en C# partir de lalgorithme de la mthode dite "gyptienne " Voici une spcification de l'algorithme de calcul du PGCD de deux nombres (entiers strictement positifs) p et q, selon cette mthode : Spcifications de lalgorithme :
Lire (p, q ) ; Tantque p q faire Si p > q alors ppq sinon qqp FinSi FinTant; Ecrire( " PGCD = " , p )
Implantation en C#
Ecrivez le programme C# complet qui produise le dialogue suivant la console (les caractres gras reprsentent ce qui est crit par le programme, les italiques ce qui est entr au clavier) : Entrez le premier nombre : 21 Entrez le deuxime nombre : 45 Le PGCD de 21 et 45 est : 3
class ApplicationEgyptien { static void Main(String[ ] args) { .. } static int pgcd (int p, int q) { .. } } La mthode pgcd renvoie le pgcd des deux entiers p et q .
EXERCICES
page
384
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationEgyptien { static void Main (string[ ] args) { System.Console.Write("Entrez le premier nombre : "); int p = Int32.Parse( System.Console.ReadLine( ) ) ; System.Console.Write("Entrez le deuxime nombre : "); int q = Int32.Parse( System.Console.ReadLine( ) ) ; if ( p*q != 0 ) System.Console.WriteLine("Le pgcd de "+p+" et de "+q+" est "+pgcd(p,q)); else System.Console.WriteLine("Le pgcd n'existe pas lorsque l'un des deux nombres est nul !"); } static int pgcd (int p, int q) { while ( p != q) { if (p > q) p -= q; else q -= p; } return p; } } }
EXERCICES
page
385
Algorithme
Calcul de nombres premiers (boucles while et dowhile)
Objectif : On souhaite crire un programme C# de calcul et d'affichage des n premiers nombres premiers. Un nombre entier est premier sil nest divisible que par 1 et par lui-mme On oprera une implantation avec des boucles while et do...while. Exemple : 31 est un nombre premier Spcifications de lalgorithme :
Algorithme Premier Entre: n N Sortie: nbr N Local: Est_premier {Vrai , Faux} divis,compt N2; dbut lire(n); compt 1; ecrire(2); nbr 3; Tantque(compt < n) Faire divis 3; Est_premier Vrai; Rpter Si reste(nbr par divis) = 0 Alors Est_premier Faux Sinon divis divis+2 Fsi jusqu (divis > nbr / 2)ou (Est_premier=Faux); Si Est_premier =Vrai Alors ecrire(nbr); compt compt+1 Fsi; nbr nbr+1 Ftant FinPremier
EXERCICES
page
386
Implantation en C#
Ecrivez le programme C# complet qui produise le dialogue suivant la console (les caractres gras reprsentent ce qui est crit par le programme, les italiques ce qui est entr au clavier) : Combien de nombres premiers : 5 2 3 5 7 11
Classe C# solution
Avec une boucle while et une boucle do...while imbrique :
On tudie la primalit de tous les nombres systmatiquement using System; namespace CsExosAlgo1 { class ApplicationComptPremiers1 { static void Main(string[ ] args) { int divis, nbr, n, compt = 0 ; bool Est_premier; System.Console.Write("Combien de nombres premiers : "); n = Int32.Parse( System.Console.ReadLine( ) ) ; System.Console.WriteLine( 2 ); nbr = 3; while (compt < n-1) { divis = 2 ; Est_premier = true; do { if (nbr % divis == 0) Est_premier=false; else divis = divis+1 ; } while ((divis <= nbr/2) && (Est_premier == true)); if (Est_premier) { compt++; System.Console.WriteLine( nbr ); } nbr++ ; } } } }
EXERCICES
page
387
Algorithme
Calcul de nombres premiers (boucles for)
Objectif : On souhaite crire un programme C# de calcul et d'affichage des n premiers nombres premiers. Un nombre entier est premier sil nest divisible que par 1 et par lui-mme. On oprera une implantation avec des boucles for imbriques. Exemple : 23 est un nombre premier
Implantation en C#
Ecrivez le programme C# complet qui produise le dialogue suivant la console (les caractres gras reprsentent ce qui est crit par le programme, les italiques ce qui est entr au clavier) :
EXERCICES
page
388
Classe C# solution
Avec deux boucles for imbriques :
On tudie la primalit des nombres uniquement impairs using System; namespace CsExosAlgo1 { class ApplicationComptPremiers2 { static void Main(string[ ] args) { int divis, nbr, n, compt = 0 ; bool Est_premier; System.Console.Write("Combien de nombres premiers : "); n = Int32.Parse( System.Console.ReadLine( ) ) ; System.Console.WriteLine( 2 ); //-- primalit uniquement des nombres impairs for( nbr = 3; compt < n-1; nbr += 2 ) { Est_premier = true; for (divis = 2; divis<= nbr/2; divis++ ) if ( nbr % divis == 0 ) { Est_premier = false; break; } if (Est_premier) { compt++; System.Console.WriteLine( nbr ); } } } } }
La mthode Main affiche la liste des nombres premiers demands. Le fait de n'tudier la primalit que des nombres impairs acclre la vitesse d'excution du programme, il est possible d'amliorer encore cette vitesse en ne cherchant que les diviseurs dont le carr est infrieur au nombre ( test : jusqu (divis2 > nbr )ou (Est_premier=Faux) ).
EXERCICES
page
389
Algorithme
Calcul du nombre d'or
Objectif : On souhaite crire un programme C# qui calcule le nombre d'or utilis par les anciens comme nombre idal pour la sculpture et l'architecture. Si l'on considre deux suites numriques (U) et (V) telles que pour n strictement suprieur 2 :
et On montre que la suite (V) tend vers une limite appele nombre d'or (nbr d'Or = 1,61803398874989484820458683436564). Spcifications de lalgorithme :
n,Un ,Un1 ,Un2 : sont des entiers naturels Vn ,Vn1 , : sont des nombres rels lire( ); // prcision demande Un2 1; Un1 2; Vn1 2; n 2; // rang du terme courant Itration n n + 1; Un Un1 + Un2 ; Vn Un / Un1 ; si |Vn - Vn1| alors Arrt de la boucle ; // la prcision est atteinte sinon Un2 Un1 ; Un1 Un ; Vn1 Vn ; fsi fin Itration ecrire (Vn , n);
Ecrire un programme fond sur la spcification prcdente de l'algorithme du calcul du nombre d'or. Ce programme donnera une valeur approche avec une prcision fixe de du nombre d'or. Le programme indiquera en outre le rang du dernier terme de la suite correspondant.
Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
390
Implantation en C#
On entre au clavier un nombre rel ci-dessous 0.00001, pour la prcision choisie (ici 5 chiffres aprs la virgule), puis le programme calcule et affiche le Nombre d'or (les caractres gras reprsentent ce qui est crit par le programme, les italiques ce qui est entr au clavier) : Prcision du calcul ? : 0.00001 Nombre d'Or = 1.6180328 // rang=14
Classe C# solution
Avec un boucle for :
using System; namespace CsExosAlgo1 { class AppliNombredOr { static void Main(string[ ] args) { int n, Un, Un1=2, Un2=1 ; double Vn,Vn1=2, Eps ; System.Console.Write("Prcision du calcul ? : "); //-- prcision demande (exemple 1e-4 ou 1e-2) : Eps = Double.Parse( System.Console.ReadLine( ) ) ; for (n=2; ; n++) //n est le rang du terme courant { Un = Un1 + Un2; Vn =(double)Un / (double)Un1; if (Math.Abs(Vn - Vn1) <= Eps) break; else { Un2 = Un1; Un1 = Un; Vn1 = Vn; } } System.Console.WriteLine("Nombre d'Or = " + Vn+" // rang="+n); } } }
Remarquons que nous proposons une boucle for ne contenant pas de condition de rebouclage dans son en-tte (donc en apparence infinie), puisque nous effectuerons le test "si |Vn - Vn1| <= Eps alors Arrt de la boucle" qui permet l'arrt de la boucle. Dans cette ventualit , la boucle for devra donc contenir dans son corps, une instruction de rupture de squence.
EXERCICES
page
391
Algorithme
Conjecture de Goldbach
Objectif : On souhaite crire un programme C# afin de vrifier sur des exemples, la conjecture de GoldBach (1742), soit : "Tout nombre pair est dcomposable en la somme de deux nombres premiers". Dans cet exercice nous rutilisons un algorithme dj trait (algorithme du test de la primalit d'un nombre entier), nous rappelons ci-aprs un algorithme indiquant si un entier "nbr" est premier ou non :
Algorithme Premier Entre: nbr N Local: Est_premier {Vrai , Faux} divis,compt N2 ; dbut lire(nbr); divis 3; Est_premier Vrai; Rpter Si reste(nbr par divis) = 0 Alors Est_premier Faux Sinon divis divis+2 Fsi jusqu (divis > nbr / 2)ou (Est_premier=Faux); Si Est_premier = Vrai Alors ecrire(nbr est premier) Sinon ecrire(nbr n'est pas premier) Fsi FinPremier
EXERCICES
page
392
Pour n = 10, on gnre les couples : (1,9), (2,8), (3,7), (4,6), (5,5) on constate que la conjecture est vrifie, et on crit : 10 = 3 + 7 10 = 5 + 5 Conseils : Il faudra traduire cet algorithme en fonction recevant comme paramtre d'entre le nombre entier dont on teste la primalit, et renvoyant un boolen true ou false selon que le nombre entr a t trouv ou non premier On crira la mthode boolenne EstPremier pour dterminer si un nombre est premier ou non, et la mthode generCouples qui gnre les couples rpondant la conjecture.
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationGoldBach { static void Main(string[ ] args) { int n; System.Console.WriteLine("Entrez un nombre pair (0 pour finir) :"); while ( (n = Int32.Parse( System.Console.ReadLine( ) )) !=0 ){ generCouples(n); } } static bool EstPremier(int m) { int k ; for (k = 2 ; k <= m / 2 ; k++) { if (m % k == 0) { return false; } } return true; } static void generCouples(int n) { if (n % 2 ==0) { for (int a = 1; a <= n/2; a++) { int b; b = n - a; if ( EstPremier(a) && EstPremier(b) ) { System.Console.WriteLine(n+" = "+a+" + "+b); } } } else System.Console.WriteLine("Votre nombre n'est pas pair !"); } } }
EXERCICES
page
393
Algorithme
Mthodes d'oprations sur 8 bits
Objectif : On souhaite crire une application de manipulation interne des bits d'une variable entire non signe sur 8 bits. Le but de l'exercice est de construire une famille de mthodes de travail sur un ou plusieurs bits d'une mmoire. L'application construire contiendra 9 mthodes :
1. Une mthode BitSET permettant de mettre 1 un bit de rang fix. 2. Une mthode BitCLR permettant de mettre 0 un bit de rang fix. 3. Une mthode BitCHG permettant de remplacer un bit de rang fix par son complment. 4. Une mthode SetValBit permettant de modifier un bit de rang fix. 5. Une mthode DecalageD permettant de dcaler les bits d'un entier, sur la droite de n positions (introduction de n zros gauche). 6. Une mthode DecalageG permettant de dcaler les bits d'un entier, sur la gauche de n positions (introduction de n zros droite). 7. Une mthode BitRang renvoyant le bit de rang fix d'un entier. 8. Une mthode ROL permettant de dcaler avec rotation, les bits d'un entier, sur la droite de n positions (rintroduction gauche). 9. Une mthode ROR permettant de dcaler avec rotation, les bits d'un entier, sur la gauche de n positions (rintroduction droite).
EXERCICES
page
394
Implantation en C#
La conception de ces mthodes ne dpendant pas du nombre de bits de la mmoire oprer on choisira le type int comme base pour une mmoire. Ces mthodes sont classiquement des outils de manipulation de l'information au niveau du bit. Lors des jeux de tests pour des raisons de simplicit de lecture il est conseill de ne rentrer que des valeurs entires portant sur 8 bits. Il est bien de se rappeler que le type primaire int est un type entier sign sur 32 bits (reprsentation en complment deux).
class Application8Bits { static void Main(string [ ] args){ .. } static int BitSET (int nbr, int num) { .. } static int BitCLR (int nbr, int num) { .. } static int BitCHG (int nbr, int num) { .. } static int SetValBit (int nbr, int rang, int val) { .. static int DecalageD (int nbr, int n) { .. } static int DecalageG (int nbr, int n) { .. } static int BitRang (int nbr, int rang) { .. } static int ROL (int nbr, int n) { .. } static int ROR (int nbr, int n) { .. } }
Classe C# solution
using System; namespace CsExosAlgo1 { class Application8Bits { // Int32.MAX_VALUE : 32 bit = 2147483647 // Int64.MAX_VALUE : 64 bits = 9223372036854775808 static void Main(string[] args){ int n,p,q,r,t; n =9;// 000...1001 System.Console.WriteLine("n=9 : n="+toBinaryString(n)); p = BitCLR(n,3);// p=1 System.Console.WriteLine("BitCLR(n,3) ="+toBinaryString(p)); q = BitSET(n,2);// q=13 System.Console.WriteLine("BitSET(n,2) ="+toBinaryString(q)); r = BitCHG(n,3);// r=1 Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
395
System.Console.WriteLine("BitCHG(n,3) ="+toBinaryString(r)); t = BitCHG(n,2);// t=13 System.Console.WriteLine("BitCHG(n,2) ="+toBinaryString(t)); System.Console.WriteLine("p = "+p+", q = "+q+", r = "+r+", t = "+t); n =-2147483648;//1000.....00 entier minimal System.Console.WriteLine("n=-2^31 : n="+toBinaryString(n)); p=ROL(n,3);// 000...000100 = p=4 System.Console.WriteLine("p = "+p); n =-2147483648+1;//1000.....01 entier minimal+1 System.Console.WriteLine("n=-2^31+1 : n="+toBinaryString(n)); p=ROL(n,3);// 000...0001100 = p=12 System.Console.WriteLine("p = "+p); n =3;//0000.....0 11 System.Console.WriteLine("n=3 : n="+toBinaryString(n)); p=ROR(n,1);//100000...001 = p=-2147483647 System.Console.WriteLine("ROR(n,1) = "+p+"= "+toBinaryString(p)); p=ROR(n,2);// 11000...000 = p= -1073741824 System.Console.WriteLine("ROR(n,2) = "+p+"= "+toBinaryString(p)); p=ROR(n,3);// 011000...000 = p= +1610612736 =2^30+2^29 System.Console.WriteLine("ROR(n,3) = "+p+"= "+toBinaryString(p)); }
static string toBinaryString ( int n ) { // renvoie l'criture de l'entier n en reprsentation binaire string [ ] hexa = { "0000","0001","0010","0011","0100", "0101","0110","0111","1000","1001","1010", "1011","1100","1101","1110","1111" }; string s = string.Format("{0:x}",n), res = ""; for ( int i = 0; i < s.Length; i++ ) { char car=(char)s[i]; if ((car <= '9')&&(car >= '0')) res = res+hexa[(int)car-(int)'0']; if ((car <= 'f')&&(car >= 'a')) res = res+hexa[(int)car-(int)'a'+10]; } return res; } static int BitSET(int nbr, int num) { // positionne 1 le bit de rang num int mask; mask =1<< num; return nbr | mask; } static int BitCLR(int nbr, int num) { // positionne 0 le bit de rang num int mask; mask = ~ (1<< num); return nbr & mask; } static int BitCHG(int nbr, int num) { // complmente le bit de rang num (0 si bit=1, 1 si bit=0) int mask; mask =1<< num; return nbr ^ mask; } Premier pas dans .Net avec C# - - ( rv. 15.08.2005
EXERCICES
page
396
static int DecalageD (int nbr, int n) { // dcalage sans le signe de n bits vers la droite return nbr >> n ; } static int DecalageG (int nbr, int n) { // dcalage de 2 bits vers la gauche return nbr << n ; } static int BitRang (int nbr, int rang) { //renvoie le bit de rang fix return(nbr >> rang ) %2; } static int SetValBit (int nbr, int rang,int val) { //positionne val le bit de rang fix return val ==0 ? BitCLR( nbr , rang) : BitSET( nbr , rang) ; } static int ROL (int nbr, int n) { //dcalage gauche avec rotation int C; int N = nbr; for(int i=1; i<=n; i++) { C = BitRang(N,31); N = N <<1; N = SetValBit(N,0,C); } return N ; } static int ROR (int nbr,int n) { //dcalage droite avec rotation int C; int N = nbr; for(int i=1; i<=n; i++) { C = BitRang (N,0); N = N >> 1; N = SetValBit (N,31,C); } return N ; }
} //--Application8Bits }
EXERCICES
page
397
Travail effectuer : Ecrire les mthode compresser et Inverser , il est demand d'crire deux versions de la mthode Inverser. La premire version de la mthode Inverser construira une chane locale la mthode caractre par caractre avec une boucle for un seul indice. La deuxime version de la mthode Inverser modifiera les positions des caractres ayant des positions symtriques dans la chane avec une boucle for deux indices et en utilisant un tableau de char.
EXERCICES
page
398
Classe C# solution
Le code de la mthode compresser :
static string compresser ( string s ) { String strLoc = ""; for( int i = 0 ; i < s.Length ; i ++ ) { if( s[i] !=' ' && s[i] !=' , ' && s[i] !=' \ '' && s[i] !=' . ' ) strLoc += s[i] ; } return strLoc ; }
La mthode compresser limine les caractres non recevables comme : blanc, virgule, point et apostrophe de la String s passe en paramtre. Remarquons que l'instruction strLoc +=s[i] permet de concatner les caractres recevables de la chane locale strLoc, par balayage de la String s depuis le caractre de rang 0 jusqu'au caractre de rang s.Length-1. La rfrence de String strLoc pointe chaque tour de la boucle for vers un nouvel objet cr par l'oprateur de concatnation + La premire version de la mthode Inverser :
static string inverser ( string s ) { String strLoc = ""; for( int i = 0 ; i < s.Length ; i ++ ) strLoc = s[i] + strLoc ; return strLoc ; }
EXERCICES
page
399
EXERCICES
page
400
static string mois ( int num ) static string jours ( int num ) static string numjour ( int num ) static void scanDate ( string date, out int jour, out int val, out int mois ) static string ConvertDate ( string date )
Si le paramtre est un entier compris entre 1 et 12, elle renvoie le nom du mois (janvier,.,dcembre), sinon elle renvoie ###. Si le paramtre est un entier compris entre 1 et 7, elle renvoie le nom du jour (lundi,.,dimanche), sinon elle renvoie ###. Si le paramtre est un entier compris entre 1 et 31, elle le sous forme d'une string, sinon elle renvoie ###. Reoit en entre une date numrique formate avec des sparateurs (/,.-) et ressort les 3 entiers la composant. Reoit en entre une date numrique formate avec des spaarteurs (/,.-) et renvoie sa conversion en forme littrale.
EXERCICES
page
401
Classe C# solution
class Dates { enum LesJours { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche }; static string [ ] LesMois = { "janvier" , "fvrier" , "mars" , "avril" ,"mai" , "juin" , "juillet" , "aot" , "septembre" , "octobre" , "novembre" , "dcembre" }; static string mois ( int num ) { if( num >= 0 && num < 12 ) Tous les noms du type enum return LesMois[num] ; sont automatiquement stocks else return "mois ####"; dans le tableau de string tablej . } static string jours ( int num ) { string [ ] tablej = Enum.GetNames ( typeof ( LesJours )); if( num >= 0 && num < 7 ) return tablej [num] ; else return "jour ####"; } static string numjour ( int num ) { if( num >= 1 && num <= 31 ) return num.ToString ( ) ; else return "n ####"; } static void scanDate ( string date, out int jour, out int val, out int mois ) { string delimStr = "/.:-"; char [ ] delimiteurs = delimStr.ToCharArray ( ); La mthode Split de la classe string [ ] strInfos = date.Split ( delimiteurs , 3 ); string est trs utile ici, elle jour = Convert.ToInt32 ( strInfos[0] ) - 1 ; constitue un scanner de chane : val = Convert.ToInt32 ( strInfos[1] ); mois = Convert.ToInt32 ( strInfos[2] ) - 1 ; elle dcoupe automatiquement } la chane en 3 lments dans un static string ConvertDate ( string date ) tableau de string. { int lejour, laval, lemois ; scanDate ( date, out lejour, out laval, out lemois ); return jours ( lejour ) + " " + numjour ( laval ) + " " + mois ( lemois ); } static void Main ( string [ ] args ) { System .Console.WriteLine ( ConvertDate ("3-2-05")); System .Console.WriteLine ( ConvertDate ("5/7/1")); System .Console.WriteLine ( ConvertDate ("05.07.01")); System .Console.ReadLine ( ); } }
EXERCICES
page
402
static void Main ( string [ ] args ) { System .Console.WriteLine ( recurRom ("MCDXCIV" ,0 )); System .Console.WriteLine ( iterRom ("MCDXCIV")); System .Console.ReadLine (); } }
EXERCICES
page
403
Algorithme
Tri bulles sur un tableau d'entiers
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationTriBulle { static int[ ] table ; // le tableau trier, par exemple 19 lments de l'index 1 l'index 19 static void AfficherTable ( ) { // Affichage du tableau int n = table.Length-1; for ( int i = 1; i <= n; i++) System.Console.Write (table[i]+" , "); System.Console.WriteLine ( ); } static void InitTable ( ) { int[ ] tableau = { 0 ,25, 7 , 14 , 26 , 25 , 53 , 74 , 99 , 24 , 98 , 89 , 35 , 59 , 38 , 56 , 58 , 36 , 91 , 52 }; table = tableau; } static void Main(string[ ] args) { InitTable ( ); System.Console.WriteLine ("Tableau initial :"); AfficherTable ( ); TriBulle ( ); System.Console.WriteLine ("Tableau une fois tri :"); Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
404
AfficherTable ( ); System.Console.Read(); } static void TriBulle ( ) { // sous-programme de Tri bulle : on trie les lments du n1 au n19 int n = table.Length-1; for ( int i = n; i>=1; i--) for ( int j = 2; j <= i; j++) if (table[j-1] > table[j]){ int temp = table[j-1]; table[j-1] = table[j]; table[j] = temp; } /* Dans le cas o l'on dmarre le tableau l'indice zro on change les bornes des indices i et j: for ( int i = n; i >= 0; i--) for ( int j = 1; j <= i; j++) if ....... reste identique */ } } }
Tableau initial (n1 au n19): 25 , 7 , 14 , 26 , 25 , 53 , 74 , 99 , 24 , 98 , 89 , 35 , 59 , 38 , 56 , 58 , 36 , 91 , 52 Tableau une fois tri (n1 au n19) : 7 , 14 , 24 , 25 , 25 , 26 , 35 , 36 , 38 , 52 , 53 , 56 , 58 , 59 , 74 , 89 , 91 , 98 , 99
Autre version depuis l'indice zro : Tableau initial (n0 au n19): 0, 25 , 7 , 14 , 26 , 25 , 53 , 74 , 99 , 24 , 98 , 89 , 35 , 59 , 38 , 56 , 58 , 36 , 91 , 52 Tableau une fois tri (n0 au n19) : 0, 7 , 14 , 24 , 25 , 25 , 26 , 35 , 36 , 38 , 52 , 53 , 56 , 58 , 59 , 74 , 89 , 91 , 98 , 99
EXERCICES
page
405
Algorithme
Tri par insertion sur un tableau d'entiers
Objectif : Ecrire un programme C# implmentant l'algorithme du tri par insertion. Spcifications de lalgorithme :
Algorithme Tri_Insertion local: i , j , n, v Entiers naturels Entre : Tab Tableau d'Entiers naturels de 0 n lments Sortie : Tab Tableau d'Entiers naturels de 0 n lments (le mme tableau) { dans la cellule de rang 0 se trouve une sentinelle charge d'viter de tester dans la boucle tantque .. faire si l'indice j n'est pas infrieur 1, elle aura une valeur infrieure toute valeur possible de la liste } dbut pour i de2 jusqu n faire// la partie non encore trie (ai, ai+1, ... , an) v Tab[ i ] ; // l'lment frontire : ai ji; // le rang de l'lment frontire Tantque Tab[ j-1 ]> v faire//on travaille sur la partie dj trie (a1, a2, ... , ai) Tab[ j ] Tab[ j-1 ]; // on dcale l'lment j j-1; // on passe au rang prcdent FinTant ; Tab[ j ] v //on recopie ai dans la place libre fpour Fin Tri_Insertion
Classe C# solution
On utilise une sentinelle place dans la cellule de rang 0 du tableau, comme le type d'lment du tableau est un int, nous prenons comme valeur de la sentinelle une valeur ngative trs grande par rapport aux valeurs des lments du tableau; par exemple le plus petit lment du type int, soit la valeur Integer.MIN_VALUE.
EXERCICES
page
406
using System; namespace CsExosAlgo1 { class ApplicationTriInsert { static int[] table ; // le tableau trier, par exemple 19 lments /* Tri avec sentinelle : * dans la cellule de rang 0 se trouve une sentinelle (Int32.MinValue) * charge d'viter de tester dans la boucle tantque .. faire * si l'indice j n'est pas infrieur 1, elle aura une valeur * infrieure toute valeur possible de la liste. */ static void AfficherTable ( ) { // Affichage du tableau int n = table.Length-1; for ( int i = 1; i <= n; i++) System.Console.Write (table[i]+" , "); System.Console.WriteLine ( ); } static void InitTable ( ) { // sentinelle dans la cellule de rang zro int[ ] tableau = { Int32.MinValue ,25, 7 , 14 , 26 , 25 , 53 , 74 , 99 , 24 , 98 , 89 , 35 , 59 , 38 , 56 , 58 , 36 , 91 , 52 }; table = tableau; } static void Main(string[ ] args) { InitTable ( ); System.Console.WriteLine ("Tableau initial :"); AfficherTable ( ); TriInsert ( ); System.Console.WriteLine ("Tableau une fois tri :"); AfficherTable ( ); System.Console.Read(); } static void TriInsert ( ) { // sous-programme de Tri par insertion : on trie les lments du n1 au n19 int n = table.Length-1; for ( int i = 2; i <= n; i++) { // la partie non encore trie (ai, ai+1, ... , an) int v = table[i]; // l'lment frontire : ai int j = i; // le rang de l'lment frontire while (table[ j-1 ] > v) { //on travaille sur la partie dj trie (a1, a2, ... , ai) table[ j ] = table[ j-1 ]; // on dcale l'lment j = j-1; // on passe au rang prcdent } table[ j ] = v ; //on recopie ai dans la place libre } } } }
EXERCICES
page
407
EXERCICES
page
408
Algorithme
Recherche linaire dans une table non trie
Objectif : Ecrire un programme C# effectuant une recherche squentielle dans un tableau linaire (une dimension) non tri
EXERCICES
page
409
i1; Tantque (i n) et alors (t[i] Elt) faire i i+1 finTant; si i n alors rang i sinon rang -1 Fsi
Traduire chacune des quatre versions sous forme d'une mthode C#. Proposition de squelette de classe C# implanter :
class ApplicationRechLin { static void AfficherTable ( int[] table ) // Affichage du tableau } static void InitTable ( ) { // remplissage du tableau } } {
static int RechSeq1( int[] t, int Elt ) { // Version Tantque avec "et alors" (oprateur et optimis) static int RechSeq2( int[] t, int Elt ) { // Version Tantque avec "et" (oprateur non optimis) static int RechSeq3( int[] t, int Elt ) { // Version Tantque avec sentinelle en fin de tableau static int RechSeq4( int[] t, int Elt ) { // Version Pour avec instruction de sortie break } } }
static void Main(string[ ] args) { InitTable ( ); System.Console.WriteLine("Tableau initial :"); AfficherTable (table ); int x = Int32.Parse(System.Console.ReadLine( )), rang; //rang = RechSeq1( table, x ); //rang = RechSeq2( table, x ); //rang = RechSeq3( tableSent, x ); rang = RechSeq4( table, x ); if (rang > 0) System.Console.WriteLine("Elment "+x+" trouv en : "+rang); Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
410
Classe C# solution
Les diffrents sous-programmes C# implantant les 4 versions d'algorithme de recherche linaire (table non trie) :
static int RechSeq1( int[] t, int Elt ) { int i = 1; int n = t.Length-1; while ((i <= n) && (t[i] != Elt)) i++; if (i<=n) return i; else return -1; } static int RechSeq3( int[] t, int Elt ) { int i = 1; int n = t.Length-2; t[n+1]= Elt ; //sentinelle while ((i <= n) & (t[i] != Elt)) i++; if (i<=n) return i; else return -1; } static int RechSeq2( int[] t, int Elt ) { int i = 1; int n = t.Length-1; while ((i < n) & (t[i] != Elt)) i++; if (t[i] == Elt) return i; else return -1; } static int RechSeq4( int[] t, int Elt ) { int i = 1; int n = t.Length-1; for(i = 1; i <= n ; i++) if (t[i] == Elt) break; if (i<=n) return i; else return -1; }
EXERCICES
page
411
int n = table.Length-1; for ( int i = 1; i <= table.Length-1; i++) tableSent[i] = table[i]; } static int RechSeq1( int[] t, int Elt ) { // Version Tantque avec "et alors" (oprateur et optimis) int i = 1; int n = t.Length-1; while ((i <= n) && (t[i] != Elt)) i++; if (i<=n) return i; else return -1; } static int RechSeq2( int[] t, int Elt ) { // Version Tantque avec "et" (oprateur non optimis) int i = 1; int n = t.Length-1; while ((i < n) & (t[i] != Elt)) i++; if (t[i] == Elt) return i; else return -1; } static int RechSeq3( int[] t, int Elt ) { // Version Tantque avec sentinelle en fin de tableau int i = 1; int n = t.Length-2; t[n+1]= Elt ; // sentinelle rajoute while ((i <= n) & (t[i] != Elt)) i++; if (i<=n) return i; else return -1; } static int RechSeq4( int[] t, int Elt ) { // Version Pour avec instruction de sortie break int i = 1; int n = t.Length-1; for(i = 1; i <= n ; i++) if (t[i] == Elt) break; if (i<=n) return i; else return -1; } static void Main(String[ ] args) { InitTable ( ); System.Console.WriteLine("Tableau initial :"); AfficherTable (table ); int x = Int32.Parse(System.Console.ReadLine( )), rang; //rang = RechSeq1( table, x ); //rang = RechSeq2( table, x ); //rang = RechSeq3( tableSent, x ); rang = RechSeq4( table, x ); if (rang > 0) System.Console.WriteLine("Elment "+x+" trouv en : "+rang); else System.Console.WriteLine("Elment "+x+" non trouv !"); System.Console.Read(); } }}
EXERCICES
page
412
Algorithme
Recherche linaire dans table dj trie Objectif : Ecrire un programme C# effectuant une recherche squentielle dans un tableau linaire (une dimension) tri avant recherche. Spcifications de lalgorithme :
Soit t un tableau d'entiers de 1..n lments rangs par ordre croissant par exemple. On recherche le rang (la place) de l'lment Elt dans ce tableau. L'algorithme renvoie le rang (la valeur -1 est renvoye lorsque l'lment Elt n'est pas prsent dans le tableau t)
On peut reprendre sans changement les algorithmes prcdents travaillant sur un tableau non tri.
On peut aussi utiliser le fait que le dernier lment du tableau est le plus grand lment et s'en servir comme une sorte de sentinelle. Ci-dessous deux versions utilisant cette dernire remarque. Version Tantque :
si t[n] Elt alors i 1; Tantque t[i] < Elt faire i i+1; finTant; si t[i] = Elt alors Renvoyer rang i // retour du rsultat et sortie du programme Fsi Fsi; Renvoyer rang -1 // retour du rsultat et sortie du programme
Version pour :
si t[n] Elt alors rang -1 pour i 1 jusqu n-1 faire Sortirsi t[i] Elt //sortie de boucle fpour; si t[i] = Elt alors Renvoyer rang i // retour du rsultat et sortie du programme Fsi Fsi; Renvoyer rang -1 // retour du rsultat et sortie du programme
EXERCICES
page
413
Ecrire chacune des mthodes associes ces algorithmes (prendre soin d'avoir tri le tableau auparavant par exemple par une methode de tri), squelette de classe propos :
Classe C# solution
Les deux mthodes C# implantant les 2 versions d'algorithme de recherche linaire (table trie) :
static int RechSeqTri1( int[] t, int Elt ) { int i = 1; int n = t.Length-1; if (t[n] >= Elt) { while (t[i] < Elt) i++; if (t[i] == Elt) return i ; } return -1; }
static int RechSeqTri2( int[] t, int Elt ) { int i = 1; int n = t.Length-1; if (t[n] >= Elt) { for (i = 1; i <= n; i++) if (t[i] == Elt) return i; } return -1; }
static void AfficherTable (int[] t ) { // Affichage du tableau int n = t.Length-1; for ( int i = 1; i <= n; i++) System.Console.Write(t[i]+" , "); System.Console.WriteLine( ); } static void InitTable ( ) { // remplissage du tableau la cellule de rang 0 est inutilise int[ ] tableau = { 0, 14 , 35 , 84 , 49 , 50 , 94 , 89 , 58 , 61 , 47 , 39 , 58 , 57 , 99 , 12 , 24 , 9 , 81 , 80 }; table = tableau; } static void TriInsert ( ) { // sous-programme de Tri par insertion : int n = table.Length-1; for ( int i = 2; i <= n; i++) { int v = table[i]; int j = i; while (table[ j-1 ] > v) { table[ j ] = table[ j-1 ]; j = j-1; } table[ j ] = v ; Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
414
} } static int RechSeqTri1( int[] t, int Elt ) { int i = 1; int n = t.Length-1; if (t[n] >= Elt) { while (t[i] < Elt) i++; if (t[i] == Elt) return i ; } return -1; } static int RechSeqTri2( int[] t, int Elt ) { int i = 1; int n = t.Length-1; if (t[n] >= Elt) { for (i = 1; i <= n; i++) if (t[i] == Elt) return i; } return -1; } static void Main(string[ ] args) { InitTable ( ); System.Console.WriteLine("Tableau initial :"); AfficherTable (table ); TriInsert ( ); System.Console.WriteLine("Tableau tri :"); AfficherTable (table ); int x = Int32.Parse(System.Console.ReadLine( )), rang; //rang = RechSeqTri1( table, x ); rang = RechSeqTri2( table, x ); if (rang > 0) System.Console.WriteLine("Elment "+x+" trouv en : "+rang); else System.Console.WriteLine("Elment "+x+" non trouv !"); System.Console.Read(); } } }
EXERCICES
page
415
Algorithme
Recherche dichotomique dans une table
Objectif : effectuer une recherche dichotomique dans un tableau linaire dj tri. Spcifications de lalgorithme :
Soit t un tableau d'entiers de 1..n lments tris par ordre croissant. On recherche le rang (la place) de l'lment Elt dans ce tableau. L'algorithme renvoie le rang (la valeur -1 est renvoye lorsque l'lment Elt n'est pas prsent dans le tableau t)
Au lieu de rechercher squentiellement du premier jusqu'au dernier, on compare l'lment Elt chercher au contenu du milieu du tableau. Si c'est le mme, on retourne le rang du milieu, sinon l'on recommence sur la premire moiti (ou la deuxime) si l'lment recherch est plus petit (ou plus grand) que le contenu du milieu du tableau. Version itrative
bas, milieu, haut, rang : entiers bas 1; haut N; Rang -1; repter milieu (bas + haut) div 2; si x = t[milieu] alors Rang milieu sinon si t[milieu] < x alors bas milieu + 1 sinon haut milieu-1 fsi fsi jusqu ( x = t[milieu] ) ou ( bas haut )
Implanter une mthode en C# que vous nommerez RechDichoIter qui recevant en entre un tableau et un lment chercher, renverra le rang de l'lment selon les spcifications ci-haut. N'oubliez pas de trier le tableau avant d'invoquer la mthode de recherche. Ci-dessous une suggestion de rdaction de la mthode Main :
static void Main(string[ ] args) { InitTable ( ); System.Console.WriteLine("Tableau initial :"); AfficherTable ( ); Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
416
TriInsert ( ); System.Console.WriteLine("Tableau tri :"); AfficherTable ( ); int x = Int32.Parse(System.Console.ReadLine( )), rang; rang = RechDichoIter( table, x ); if (rang 0) System.Console.WriteLine("Elment "+x+" trouv en : "+rang); else System.Console.WriteLine("Elment "+x+" non trouv !"); System.Console.Read(); }
Classe C# solution
using System; namespace CsExosAlgo1 { class ApplicationRechDicho { static int[ ] table; // le tableau examiner cellules de 1 19 static void AfficherTable ( ) { // Affichage du tableau int n = table.Length-1; for ( int i = 1; i <= n; i++) System.Console.Write (table[i]+" , "); System.Console.WriteLine( ); } static void InitTable ( ) { // La cellule de rang zro est inutilise (examen sur 19 lments) int[ ] tableau = { 0 , 53 , 77 , 11 , 72 , 28 , 43 , 65 , 83 , 39 , 73 , Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
417
82 , 69 , 65 , 4 , 95 , 46 , 12 , 87 , 75 }; table = tableau; } static void TriInsert ( ) { // sous-programme de Tri par insertion : int n = table.Length-1; for ( int i = 2; i <= n; i++) { int v = table[i]; int j = i; while (table[ j-1 ] v) { table[ j ] = table[ j-1 ]; j = j-1; } table[ j ] = v ; } } static int RechDichoIter( int[ ] t, int Elt ) { int n = t.Length-1; int bas = 1, haut = n, milieu ; int Rang = -1; do{ milieu = (bas + haut) / 2; if ( Elt == t[milieu]) Rang = milieu ; else if ( t[milieu] < Elt ) bas = milieu + 1 ; else haut = milieu-1 ; } while ( ( Elt != t[milieu] ) & ( bas <= haut ) ); return Rang; } static void Main(string[ ] args) { InitTable ( ); System.Console.WriteLine("Tableau initial :"); AfficherTable ( ); TriInsert ( ); System.Console.WriteLine("Tableau tri :"); AfficherTable ( ); int x = Int32.Parse(System.Console.ReadLine( )), rang; rang = RechDichoIter( table, x ); if (rang 0) System.Console.WriteLine("Elment "+x+" trouv en : "+rang); else System.Console.WriteLine("Elment "+x+" non trouv !"); System.Console.Read(); } } }
Remarque : Ces exercices sont purement acadmiques et servent apprendre utiliser le langage sur des algorithmes classiques, car la classe Array contient dj une mthode static de recherche dichotomique dans un tableau t tri au pralable par la mthode static Sort de la mme classe Array : public static int BinarySearch ( Array t , object elt ); public static void Sort( Array t );
Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
418
La classe ClasseA possde une rfrence ObjB un objet public de classe ClasseB, possde un attribut Table qui est un tableau de 50 000 entiers, lorsqu'un objet de ClasseA est construit il incrmente de un le champ static compteur de la classe TestRefCirculaire et instancie la rfrence ObjB. La classe ClasseB possde une rfrence ObjA un objet public de classe ClasseA, possde un attribut Table qui est un tableau de 50 000 entiers, lorsqu'un objet de ClasseB est construit il incrmente de un le champ static compteur de la classe TestRefCirculaire et instancie la rfrence ObjA. La classe TestRefCirculaire ne possde que un attribut de classe : le champ public entier compteur initialis zro au dpart, et la mthode principale Main de lancement de l'application.
Implmentez ces trois classes en ne mettant dans le corps de la mthode Main qu'une seule instruction consistant instancier un objet local de classe ClasseA, puis excuter le programme et expliquez les rsultats obtenus.
Programme C# solution
Code source demand :
using System; namespace ConsoleGarbageRefCirc { class ClasseA { Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
419
public ClasseB ObjB; public int[ ]Table = new int[50000]; public ClasseA( ) { TestRefCirculaire.compteur++; System.Console.WriteLine("Cration objet ClasseA n "+TestRefCirculaire.compteur); ObjB = new ClasseB( ); } } class ClasseB { public ClasseA ObjA; public int[ ]Table = new int[50000]; public ClasseB( ) { TestRefCirculaire.compteur++; System.Console.WriteLine("Cration objet ClasseB n "+TestRefCirculaire.compteur); ObjA = new ClasseA( ); } } class TestRefCirculaire { public static int compteur=0; [STAThread] static void Main(string[ ] args) { ClasseA ObjA = new ClasseA( ); } } }
Rsultats d'excution :
Explications : L' instanciation d'un objetA provoque l'instanciation d'un objetB qui lui mme provoque l'instanciation d'un autre objetA qui son tour instancie un objetB etc A chaque instanciation d'un objet de ClasseA ou de ClasseB, un tableau de 50 000 entiers est rserv dans la pile d'excution, nous voyons sur l'exemple que la 7055me instanciation a t fatale car la pile a t sature ! L'instanciation d'un seul objet a provoqu la saturation de la mmoire car les ClasseA et ClasseB sont lies par une association de double rfrence ou rfrence circulaire, il faut donc faire attention ce genre de configuration.
Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
420
La classe Salarie implmente l'interface IpositionHierarchie et les classes SalarieMensuel et SalarieHoraire hritent toutes deux de la classe Salarie :
EXERCICES
page
421
/// <summary> /// Interface dfinissant les proprits de position d'un /// salari dans la hirarchie de l'entreprise. /// </summary>
/// <summary> /// Classe de base abstraite pour le personnel. Cette classe n'est /// pas instanciable. /// </summary>
abstract class Salarie : IPositionHierarchie { /// attributs identifiant le salari : private int FCodeEmploye ; private string FPrenom ; private CategoriePerso FCategorie ; private string FNom ; private string FInsee ; protected int FMerite ; private int FIndice ; private double FCoeffPrime ; private double FBasePrime ; private DateTime FIndiceDetenu ; ///le constructeur de la classe Salarie , pay au mrite : public Salarie ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, int Merite, int Indice, double CoeffPrime ) { } ///le constructeur de la classe Salarie , pay sans mrite : Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
422
public Salarie ( ..... )... { } protected double EvaluerPrimeCadreSup ( int coeffMerite ) { return ( 100 + coeffMerite * 8 ) * FCoeffPrime * FBasePrime + FIndice * 7 ; } protected double EvaluerPrimeCadre ( int coeffMerite ) { return ( 100 + coeffMerite * 6 ) * FCoeffPrime * FBasePrime + FIndice * 5 ; } protected double EvaluerPrimeMaitrise ( int coeffMerite ) { return ( 100 + coeffMerite * 4 ) * FCoeffPrime * FBasePrime + FIndice * 3 ; } protected double EvaluerPrimeAgent ( int coeffMerite ) { return ( 100 + coeffMerite * 2 ) * FCoeffPrime * FBasePrime + FIndice * 2 ; } /// proprit abstraite donnant le montant du salaire /// (virtual automatiquement) abstract public double MontantPaie { } /// proprit identifiant le salari dans l'entreprise (lecture) : public int IDentifiant { } /// proprit nom du salari (lecture /ecriture) : public string Nom { } /// proprit prnom du salari (lecture /ecriture) : public string Prenom { } /// proprit catgorie de personnel du salari (lecture /ecriture) : public CategoriePerso Categorie { } /// proprit n de scurit sociale du salari (lecture /ecriture) : public string Insee { } /// proprit de point de mrite du salari (lecture /ecriture) :: public virtual int Merite { } /// proprit classement indiciaire dans la hirarchie (lecture /ecriture) : public int Indice_Hierarchique { //--lors de l'criture : Maj de la date de dtention du nouvel indice } /// proprit coefficient de la prime en % (lecture /ecriture) : public double Coeff_Prime { } /// proprit valeur de la prime selon la catgorie (lecture) : public double Prime { } /// date laquelle l'indice actuel a t obtenu (lecture /ecriture) : public DateTime IndiceDepuis { } } Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
423
/// <summary> /// Classe du personnel mensualis. Implmente la proprit abstraite /// MontantPaie dclare dans la classe de base (mre). /// </summary>
class SalarieMensuel : Salarie { /// attributs du salaire annuel : private double FPrime ; private double FRemunerationTotal ; ///le constructeur de la classe (salari au mrite) : public SalarieMensuel ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, int Merite, int Indice, double CoeffPrime, double RemunerationTotal )... { // la prime est calcule } /// implmentation de la proprit donnant le montant du salaire (lecture) : public .... double MontantPaie { } /// proprit de point de mrite du salari (lecture /ecriture) : public ... int Merite { } }
/// <summary> /// Classe du personnel horaire. Implmente la proprit abstraite /// MontantPaie dclare dans la classe de base (mre). /// </summary>
class SalarieHoraire : Salarie { /// attributs permettant le calcul du salaire : private double FPrime ; private double FTauxHoraire ; private double FHeuresTravaillees ; ///le constructeur de la classe (salari non au mrite): public SalarieHoraire ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, double TauxHoraire ) ... { } /// nombre d'heures effectues (lecture /ecriture) : public double HeuresTravaillees { } /// implmentation de la proprit donnant le montant du salaire (lecture) : public override double MontantPaie { } } } Premier pas dans .Net avec C# - - ( rv. 15.08.2005
EXERCICES
page
424
Implmenter les classes avec le programme de test suivant : class ClassUsesSalarie { static void InfoSalarie ( SalarieMensuel empl ) { Console .WriteLine ("Employ n" + empl.IDentifiant + ": " + empl.Nom + " / " + empl.Prenom ); Console .WriteLine (" n SS : " + empl.Insee ); Console .WriteLine (" catgorie : " + empl.Categorie ); Console .WriteLine (" indice hirarchique : " + empl.Indice_Hierarchique + " , dtenu depuis : " + empl.IndiceDepuis ); Console .WriteLine (" coeff mrite : " + empl.Merite ); Console .WriteLine (" coeff prime : " + empl.Coeff_Prime ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); double coefPrimeLoc = empl.Coeff_Prime ; int coefMeriteLoc = empl.Merite ; //--impact variation du coef de prime for( double i = 0.5 ; i < 1 ; i += 0.1 ) { empl.Coeff_Prime = i ; Console .WriteLine (" coeff prime : " + empl.Coeff_Prime ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } Console .WriteLine (" -----------------------"); empl.Coeff_Prime = coefPrimeLoc ; //--impact variation du coef de mrite for( int i = 0 ; i < 10 ; i ++ ) { empl.Merite = i ; Console .WriteLine (" coeff mrite : " + empl.Merite ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } empl.Merite = coefMeriteLoc ; Console .WriteLine ("======================================="); } [STAThread] static void Main ( string [] args ) { SalarieMensuel Employe1 = new SalarieMensuel ( 123456, "Euton" , "Jeanne" , CategoriePerso.Cadre_Sup, "2780258123456" ,6,700,0.5,50000 ); SalarieMensuel Employe2 = new SalarieMensuel ( 123457, "Yonaize" , "Mah" , CategoriePerso.Cadre, "1821113896452" ,5,520,0.42,30000 ); SalarieMensuel Employe3 = new SalarieMensuel ( 123457, "Ziaire" , "Marie" , CategoriePerso.Maitrise, "2801037853781" ,2,678,0.6,20000 ); SalarieMensuel Employe4 = new SalarieMensuel ( 123457, "Louga" , "Belle" , CategoriePerso.Agent, "2790469483167" ,4,805,0.25,20000 ); ArrayList ListeSalaries = new ArrayList (); ListeSalaries.Add ( Employe1 ); ListeSalaries.Add ( Employe2 ); ListeSalaries.Add ( Employe3 ); ListeSalaries.Add ( Employe4 ); foreach( SalarieMensuel s in ListeSalaries ) Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
425
InfoSalarie ( s ); Console .WriteLine (">>> Promotion indice de " + Employe1.Nom + " dans 2 secondes."); Thread.Sleep ( 2000 ); Employe1.Indice_Hierarchique = 710 ; InfoSalarie ( Employe1 ); System .Console.ReadLine (); } }
Rsultats obtenus avec le programme de test prcdent : Employ n123456: Euton / Jeanne nSS : 2780258123456 catgorie : Cadre_Sup indice hirarchique : 700 , dtenu depuis : 15/06/2004 19:24:23 coeff mrite : 6 coeff prime : 0,5 montant prime annuelle : 152900 montant paie mensuelle: 16908,3333333333 coeff prime : 0,5 montant prime annuelle : 152900 montant paie mensuelle: 16908,3333333333 coeff prime : 0,6 montant prime annuelle : 182500 montant paie mensuelle: 19375 coeff prime : 0,7 montant prime annuelle : 212100 montant paie mensuelle: 21841,6666666667 coeff prime : 0,8 montant prime annuelle : 241700 montant paie mensuelle: 24308,3333333333 coeff prime : 0,9 montant prime annuelle : 271300 montant paie mensuelle: 26775 coeff prime : 1 montant prime annuelle : 300900 montant paie mensuelle: 29241,6666666667 ----------------------coeff mrite : 0 montant prime annuelle : 104900 montant paie mensuelle: 12908,3333333333 coeff mrite : 1 montant prime annuelle : 112900 montant paie mensuelle: 13575 coeff mrite : 2 montant prime annuelle : 120900 montant paie mensuelle: 14241,6666666667 coeff mrite : 3 montant prime annuelle : 128900 montant paie mensuelle: 14908,3333333333 coeff mrite : 4 montant prime annuelle : 136900 montant paie mensuelle: 15575 coeff mrite : 5 montant prime annuelle : 144900 montant paie mensuelle: 16241,6666666667 coeff mrite : 6 montant prime annuelle : 152900 montant paie mensuelle: 16908,3333333333 coeff mrite : 7 montant prime annuelle : 160900 montant paie mensuelle: 17575 coeff mrite : 8 montant prime annuelle : 168900 montant paie mensuelle: 18241,6666666667 coeff mrite : 9
EXERCICES
page
426
montant prime annuelle : 176900 montant paie mensuelle: 18908,3333333333 ======================================= Employ n123457: Yonaize / Mah nSS : 1821113896452 catgorie : Cadre indice hirarchique : 520 , dtenu depuis : 15/06/2004 19:24:23 coeff mrite : 5 coeff prime : 0,42 montant prime annuelle : 57200 montant paie mensuelle: 7266,66666666667 tout le tableau : coeff mrite : montant prime annuelle montant paie mensuelle: ======================================= tous les autres salaris ======================================= >>> Promotion indice de Euton dans 2 secondes. Employ n123456: Euton / Jeanne nSS : 2780258123456 catgorie : Cadre_Sup indice hirarchique : 710 , dtenu depuis : 15/06/2004 19:24:25 coeff mrite : 6 coeff prime : 0,5 montant prime annuelle : 152970 montant paie mensuelle: 16914,1666666667 coeff prime : 0,5 montant prime annuelle : 152970 montant paie mensuelle: 16914,1666666667 coeff prime : 0,6 montant prime annuelle : 182570 montant paie mensuelle: 19380,8333333333 coeff prime : 0,7 montant prime annuelle : 212170 montant paie mensuelle: 21847,5 coeff prime : 0,8 montant prime annuelle : 241770 montant paie mensuelle: 24314,1666666667 coeff prime : 0,9 montant prime annuelle : 271370 montant paie mensuelle: 26780,8333333333 coeff prime : 1 montant prime annuelle : 300970 montant paie mensuelle: 29247,5 ----------------------coeff mrite : 0 montant prime annuelle : 104970 montant paie mensuelle: 12914,1666666667 coeff mrite : 1 montant prime annuelle : 112970 montant paie mensuelle: 13580,8333333333 coeff mrite : 2 montant prime annuelle : 120970 montant paie mensuelle: 14247,5 coeff mrite : 3 montant prime annuelle : 128970 montant paie mensuelle: 14914,1666666667 coeff mrite : 4 montant prime annuelle : 136970 montant paie mensuelle: 15580,8333333333 coeff mrite : 5 montant prime annuelle : 144970 montant paie mensuelle: 1
EXERCICES
page
427
Programme C# solution
Les classes et interface de base : using System ; using System.Collections ; using System.Threading ; namespace cci { enum CategoriePerso { Cadre_Sup,Cadre,Maitrise,Agent,Autre } /// <summary> /// Interface dfinissant les proprits de position d'un /// salari dans la hirarchie de l'entreprise. /// </summary> interface IPositionHierarchie { int Indice_Hierarchique { get ; set ; } double Coeff_Prime { get ; set ; } DateTime IndiceDepuis { get ; set ; } } /// <summary> /// Classe de base abstraite pour le personnel. Cette classe n'est /// pas instanciable. /// </summary> abstract class Salarie : IPositionHierarchie { /// attributs identifiant le salari : private int FCodeEmploye ; private string FPrenom ; private CategoriePerso FCategorie ; private string FNom ; private string FInsee ; protected int FMerite ; private int FIndice ; private double FCoeffPrime ; private double FBasePrime ; private DateTime FIndiceDetenu ; ///le constructeur de la classe employ au mrite : public Salarie ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, int Merite, int Indice, double CoeffPrime ) { FCodeEmploye = IDentifiant ; FNom = Nom ; FPrenom = Prenom ; FCategorie = Categorie ; FInsee = Insee ; Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
428
FMerite = Merite ; FIndice = Indice ; FCoeffPrime = CoeffPrime ; FIndiceDetenu = DateTime.Now ; switch ( FCategorie ) { case CategoriePerso.Cadre_Sup : FBasePrime = 2000 ; break; case CategoriePerso.Cadre : FBasePrime = 1000 ; break; case CategoriePerso.Maitrise : FBasePrime = 500 ; break; case CategoriePerso.Agent : FBasePrime = 200 ; break; } } ///le constructeur de la classe employ sans mrite : public Salarie ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee ): this( IDentifiant, Nom, Prenom, Categorie, Insee, 0, 0, 0 ) { } protected double EvaluerPrimeCadreSup ( int coeffMerite ) { return ( 100 + coeffMerite * 8 ) * FCoeffPrime * FBasePrime + FIndice * 7 ; } protected double EvaluerPrimeCadre ( int coeffMerite ) { return ( 100 + coeffMerite * 6 ) * FCoeffPrime * FBasePrime + FIndice * 5 ; } protected double EvaluerPrimeMaitrise ( int coeffMerite ) { return ( 100 + coeffMerite * 4 ) * FCoeffPrime * FBasePrime + FIndice * 3 ; } protected double EvaluerPrimeAgent ( int coeffMerite ) { return ( 100 + coeffMerite * 2 ) * FCoeffPrime * FBasePrime + FIndice * 2 ; } /// proprit abstraite donnant le montant du salaire /// (virtual automatiquement) abstract public double MontantPaie { get ; } /// proprit identifiant le salari dans l'entreprise : public int IDentifiant { get { return FCodeEmploye ; } } /// proprit nom du salari : public string Nom { get { return FNom ; } set { FNom = value ; } } /// proprit prnom du salari : public string Prenom { get { return FPrenom ; } set { FPrenom = value ; } } /// proprit catgorie de personnel du salari : public CategoriePerso Categorie { get { return FCategorie ; } set { FCategorie = value ; } } Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
429
/// proprit n° de scurit sociale du salari : public string Insee { get { return FInsee ; } set { FInsee = value ; } } /// proprit de point de mrite du salari : public virtual int Merite { get { return FMerite ; } set { FMerite = value ; } } /// proprit classement indiciaire dans la hirarchie : public int Indice_Hierarchique { get { return FIndice ; } set { FIndice = value ; //--Maj de la date de dtention du nouvel indice : IndiceDepuis = DateTime.Now ; } } /// proprit coefficient de la prime en %: public double Coeff_Prime { get { return FCoeffPrime ; } set { FCoeffPrime = value ; } } /// proprit valeur de la prime : public double Prime { get { switch ( FCategorie ) { case CategoriePerso.Cadre_Sup : return EvaluerPrimeCadreSup ( FMerite ); case CategoriePerso.Cadre : return EvaluerPrimeCadre ( FMerite ); case CategoriePerso.Maitrise : return EvaluerPrimeMaitrise ( FMerite ); case CategoriePerso.Agent : return EvaluerPrimeAgent ( FMerite ); default : return EvaluerPrimeAgent ( 0 ); } } } /// date laquelle l'indice actuel a t obtenu : public DateTime IndiceDepuis { get { return FIndiceDetenu ; } set { FIndiceDetenu = value ; } } } /// <summary> /// Classe du personnel mensualis. Implmente la proprit abstraite /// MontantPaie dclare dans la classe de base (mre). /// </summary> class SalarieMensuel : Salarie { /// attributs du salaire annuel : private double FPrime ; private double FRemunerationTotal ;
EXERCICES
page
430
///le constructeur de la classe (salari au mrite) : public SalarieMensuel ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, int Merite, int Indice, double CoeffPrime, double RemunerationTotal ): base ( IDentifiant, Nom, Prenom, Categorie, Insee, Merite, Indice, CoeffPrime ) { FPrime = this .Prime ; FRemunerationTotal = RemunerationTotal ; } /// implmentation de la proprit donnant le montant du salaire : public override double MontantPaie { get { return ( FRemunerationTotal + this .Prime ) / 12 ; } } /// proprit de point de mrite du salari : public override int Merite { get { return FMerite ; } set { FMerite = value ; FPrime = this .Prime ; } } } /// <summary> /// Classe du personnel horaire. Implemente la proprit abstraite /// MontantPaie dclare dans la classe de base (mre). /// </summary> class SalarieHoraire : Salarie { /// attributs permettant le calcul du salaire : private double FPrime ; private double FTauxHoraire ; private double FHeuresTravaillees ; ///le constructeur de la classe (salari non au mrite): public SalarieHoraire ( int IDentifiant, string Nom, string Prenom, CategoriePerso Categorie, string Insee, double TauxHoraire ) : base ( IDentifiant, Nom,Prenom, Categorie, Insee ) { FTauxHoraire = TauxHoraire ; FHeuresTravaillees = 0 ; FPrime = 0 ; } /// nombre d'heures effectues : public double HeuresTravaillees { get { return FHeuresTravaillees ; } set { FHeuresTravaillees = value ; } } /// implmentation de la proprit donnant le montant du salaire : public override double MontantPaie { get { return FHeuresTravaillees * FTauxHoraire + FPrime ; } } } }
EXERCICES
page
431
namespace cci { class FichierDeSalaries { private string Fchemin ; private ArrayList FListeEmployes ; // liste des nouveaux employs entrer dans le fichier private ArrayList indexCadreSup ; // Table d'index des cadres suprieurs du fichier // mthode static affichant un objet Salarie la console : public static void AfficherUnSalarie ( Salarie Employe ) { // pour l'instant un salari mensualis seulement } // constructeur de la classeFichierDeSalaries public FichierDeSalaries ( string chemin, ArrayList Liste ) { } // mthode de cration de la table d'index des cadre_sup : public void CreerIndexCadreSup ( ) {
EXERCICES
page
432
} // mthode convertissant le champ string catgorie en la constante enum associe private CategoriePerso strToCategorie ( string s ) { } // mthode renvoyant un objet SalarieMensuel de rang fix dans le fichier private Salarie EditerUnSalarie ( int rang ) { SalarieMensuel perso ; ........... perso = new SalarieMensuel ( IDentifiant, Nom, Prenom, Categorie, Insee, Merite, Indice, CoeffPrime, RemunerationTotal ); ........... return perso ; } // mthode affichant sur la console partir de la table d'index : public void EditerFichierCadreSup ( ) { ........... foreach( int ind in indexCadreSup ) { AfficherUnSalarie ( EditerUnSalarie ( ind ) ); } ........... } // mthode affichant sur la console le fichier de tous les salaris : public void EditerFichierSalaries ( ) { } // mthode crant et stockant des salaris dans le fichier : public void StockerSalaries ( ArrayList ListeEmploy ) { ........... // si le fichier n'existe pas => cration du fichier sur disque : fichierSortie = File.CreateText ( Fchemin ); fichierSortie.WriteLine ("Fichier des personnels"); fichierSortie.Close ( ); ........... // ajout dans le fichier de toute la liste : ........... foreach( Salarie s in ListeEmploy ) { } ........... } }
EXERCICES
page
433
int coefMeriteLoc = empl.Merite ; //--impact variation du coef de prime for( double i = 0.5 ; i < 1 ; i += 0.1 ) { empl.Coeff_Prime = i ; Console .WriteLine (" coeff prime : " + empl.Coeff_Prime ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } Console .WriteLine (" -----------------------"); empl.Coeff_Prime = coefPrimeLoc ; //--impact variation du coef de mrite for( int i = 0 ; i < 10 ; i ++ ) { empl.Merite = i ; Console .WriteLine (" coeff mrite : " + empl.Merite ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } empl.Merite = coefMeriteLoc ; Console .WriteLine ("======================================="); } [STAThread] static void Main ( string [ ] args ) { SalarieMensuel Employe1 = new SalarieMensuel ( 123456, "Euton" , "Jeanne" , CategoriePerso.Cadre_Sup, "2780258123456" ,6,700,0.5,50000 ); SalarieMensuel Employe2 = new SalarieMensuel ( 123457, "Yonaize" , "Mah" , CategoriePerso.Cadre, "1821113896452" ,5,520,0.42,30000 ); SalarieMensuel Employe3 = new SalarieMensuel ( 123458, "Ziaire" , "Marie" , CategoriePerso.Maitrise, "2801037853781" ,2,678,0.6,20000 ); SalarieMensuel Employe4 = new SalarieMensuel ( 123459, "Louga" , "Belle" , CategoriePerso.Agent, "2790469483167" ,4,805,0.25,20000 ); ArrayList ListeSalaries = new ArrayList (); ListeSalaries.Add ( Employe1 ); ListeSalaries.Add ( Employe2 ); ListeSalaries.Add ( Employe3 ); ListeSalaries.Add ( Employe4 ); foreach( SalarieMensuel s in ListeSalaries ) InfoSalarie ( s ); Console .WriteLine (">>> Promotion indice de " + Employe1.Nom + " dans 2 secondes."); Thread.Sleep ( 2000 ); Employe1.Indice_Hierarchique = 710 ; InfoSalarie ( Employe1 ); //-------------------------------------------// FichierDeSalaries Fiches = new FichierDeSalaries ("fichierSalaries.txt" ,ListeSalaries ); Console .WriteLine (">>> Attente 3 s pour cration de nouveaux salaris"); Thread.Sleep ( 3000 ); Employe1 = new SalarieMensuel ( 123460, "Miett" , "Hamas" , CategoriePerso.Cadre_Sup, "1750258123456" ,4,500,0.7,42000 ); Employe2 = new SalarieMensuel ( 123461, "Kong" , "King" , CategoriePerso.Cadre, "1640517896452" ,4,305,0.62,28000 ); Employe3 = new SalarieMensuel ( 123462, "Zaume" , "Philippo" , CategoriePerso.Maitrise, "1580237853781" ,2,245,0.8,15000 ); Employe4 = new SalarieMensuel ( 123463, "Micoton" , "Mylne" , CategoriePerso.Agent, "2850263483167" ,4,105,0.14,12000 ); ListeSalaries = new ArrayList (); ListeSalaries.Add ( Employe1 ); Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
434
ListeSalaries.Add ( Employe2 ); ListeSalaries.Add ( Employe3 ); ListeSalaries.Add ( Employe4 ); Fiches.StockerSalaries ( ListeSalaries ); Fiches.EditerFichierSalaries ( ); Fiches.CreerIndexCadreSup ( ); Fiches.EditerFichierCadreSup ( ); System .Console.ReadLine ( ); } }
Pour tester le programme prcdent, on donne le fichier des salaris fichierSalaries.txt suivant :
Fichier des personnels 123456 Euton Jeanne *Cadre_Sup 2780258123456 6 710 15/02/2004 19:52:38 0,5 152970 16914,1666666667 123457 Yonaize Mah *Cadre 1821113896452 5 520 15/02/2004 19:52:36 0,42 57200 7266,66666666667 123458 Ziaire Marie *Maitrise 2801037853781 2 678 15/02/2004 19:52:36 0,6 34434 4536,16666666667 123459 Louga Belle *Agent 2790469483167 4 805 15/02/2004 19:52:36 0,25 7010 2250,83333333333 123460 Miett Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
435
Hamas *Cadre_Sup 1750258123456 4 500 15/02/2004 19:52:41 0,7 188300 19191,6666666667 123461 Kong King *Cadre 1640517896452 4 305 15/02/2004 19:52:41 0,62 78405 8867,08333333333 123462 Zaume Philippo *Maitrise 1580237853781 2 245 15/02/2004 19:52:41 0,8 43935 4911,25 123463 Micoton Mylne *Agent 2850263483167 4 105 15/02/2004 19:52:41 0,14 3234 1269,5 ===============================
namespace cci { class FichierDeSalaries { private string Fchemin ; private ArrayList FListeEmployes ; private ArrayList indexCadreSup ; Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
436
// mthode static affichant un objet Salarie la console : public static void AfficherUnSalarie ( Salarie Employe ) { if( Employe is SalarieMensuel ) { SalarieMensuel empl = ( Employe as SalarieMensuel ); Console .WriteLine ("Employ n + empl.IDentifiant + ": " + empl.Nom + " / " + empl.Prenom ); Console .WriteLine (" n SS : " + empl.Insee ); Console .WriteLine (" catgorie : " + empl.Categorie ); Console .WriteLine (" indice hirarchique : " + empl.Indice_Hierarchique + " , dtenu depuis : " + empl.IndiceDepuis ); Console .WriteLine (" coeff mrite : " + empl.Merite ); Console .WriteLine (" coeff prime : " + empl.Coeff_Prime ); Console .WriteLine (" montant prime annuelle : " + empl.Prime ); Console .WriteLine (" montant paie mensuelle: " + empl.MontantPaie ); } } // constructeur de la classeFichierDeSalaries public FichierDeSalaries ( string chemin, ArrayList Liste ) { Fchemin = chemin ; FListeEmployes = Liste ; StockerSalaries ( FListeEmployes ); } // mthode de cration de la table d'index des cadre_sup : public void CreerIndexCadreSup ( ) { // Ouvre le fichier pour le lire StreamReader fichierEntree = File.OpenText ( Fchemin ); string Ligne ; int indexLigne = 0 ; indexCadreSup = new ArrayList ( ); while (( Ligne = fichierEntree.ReadLine ()) != null) { indexLigne ++ ; if("*" + CategoriePerso.Cadre_Sup.ToString () == Ligne ) { Console .WriteLine ("++> " + Ligne + " : " + indexLigne ); indexCadreSup.Add ( indexLigne - 3 ); } } fichierEntree.Close (); } // mthode convertissant le champ string catgorie en la constante enum associe private CategoriePerso strToCategorie ( string s ) { switch( s ) { case "*Cadre_Sup":return CategoriePerso.Cadre_Sup ; case "*Cadre":return CategoriePerso.Cadre ; case "*Maitrise":return CategoriePerso.Maitrise ; case "*Agent":return CategoriePerso.Agent ; case "*Autre":return CategoriePerso.Autre ; default : return CategoriePerso.Autre ; } } // mthode renvoyant un objet SalarieMensuel de rang fix dans le fichier private Salarie EditerUnSalarie ( int rang ) { int compt = 0 ; string Ligne ; int IDentifiant = 0 ; string Nom = "" , Prenom = ""; Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
437
CategoriePerso Categorie = CategoriePerso.Autre ; string Insee = ""; int Merite = 0, Indice = 0 ; DateTime delai = DateTime.Now ; double CoeffPrime = 0, RemunerationTotal = 0, MontantPaie = 0 ; SalarieMensuel perso ; StreamReader f = File.OpenText ( Fchemin ); //System .IFormatProvider format = new System .Globalization.CultureInfo ("fr-FR" , true ); while (( Ligne = f.ReadLine ( )) != null) { compt ++ ; if ( compt == rang ) { IDentifiant = Convert.ToInt32 ( Ligne ); Nom = f.ReadLine ( ); Prenom = f.ReadLine ( ); Categorie = strToCategorie ( f.ReadLine ( )); Insee = f.ReadLine ( ); Merite = Convert.ToInt32 ( f.ReadLine ( )); Indice = Convert.ToInt32 ( f.ReadLine ( )); delai = DateTime.Parse ( f.ReadLine ( ) ); CoeffPrime = Convert.ToDouble ( f.ReadLine ( )); RemunerationTotal = Convert.ToDouble ( f.ReadLine ( )); MontantPaie = Convert.ToDouble ( f.ReadLine ( )); break; } } f.Close ( ); perso = new SalarieMensuel ( IDentifiant, Nom, Prenom, Categorie, Insee, Merite, Indice, CoeffPrime, RemunerationTotal ); perso.IndiceDepuis = delai ; return perso ; } // mthode affichant sur la console partir de la table d'index : public void EditerFichierCadreSup ( ) { StreamReader fichierEntree = File.OpenText ( Fchemin ); if( indexCadreSup == null) CreerIndexCadreSup (); foreach( int ind in indexCadreSup ) { AfficherUnSalarie ( EditerUnSalarie ( ind ) ); } fichierEntree.Close ( ); } // mthode affichant sur la console le fichier de tous les salaris : public void EditerFichierSalaries ( ) { // Ouvre le fichier pour le lire StreamReader fichierEntree = File.OpenText ( Fchemin ); string Ligne ; while (( Ligne = fichierEntree.ReadLine ( )) != null) { Console .WriteLine ( Ligne ); } fichierEntree.Close ( ); } // mthode crant et stockant des salaris dans le fichier : public void StockerSalaries ( ArrayList ListeEmploy ) { StreamWriter fichierSortie ; Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
438
if ( ! File.Exists ( Fchemin )) { // cration du fichier sur disque : fichierSortie = File.CreateText ( Fchemin ); fichierSortie.WriteLine ("Fichier des personnels"); fichierSortie.Close ( ); } // ajout dans le fichier de tout le : fichierSortie = File.AppendText ( Fchemin ); if ( FListeEmployes.Count != 0 ) foreach( Salarie s in ListeEmploy ) { fichierSortie.WriteLine ( s.IDentifiant ); fichierSortie.WriteLine ( s.Nom ); fichierSortie.WriteLine ( s.Prenom ); fichierSortie.WriteLine ( '*'+ s.Categorie.ToString ( )); fichierSortie.WriteLine ( s.Insee ); if( s is SalarieMensuel ) { SalarieMensuel sLoc = ( s as SalarieMensuel ); fichierSortie.WriteLine ( sLoc.Merite ); fichierSortie.WriteLine ( sLoc.Indice_Hierarchique ); fichierSortie.WriteLine ( sLoc.IndiceDepuis ); fichierSortie.WriteLine ( sLoc.Coeff_Prime ); fichierSortie.WriteLine ( sLoc.Prime ); } else fichierSortie.WriteLine (( s as SalarieHoraire ) .HeuresTravaillees ); fichierSortie.WriteLine ( s.MontantPaie ); } fichierSortie.Close ( ); } }
EXERCICES
page
439
IeventEnsemble est une interface qui est donne pour dcrire les deux vnements auxquels un ensemble doit tre sensible : interface IEventEnsemble { event EventHandler OnInserer; event EventHandler OnEnlever; }
Question :
complter dans le squelette de programme ci-aprs, la classe ensemble de caractre setOfChar.
using System; using System.Collections; Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
440
namespace cci { interface IEventEnsemble { event EventHandler OnInserer; event EventHandler OnEnlever; } public class setOfChar : CollectionBase, IEventEnsemble { public event EventHandler OnInserer; public event EventHandler OnEnlever; public static setOfChar operator + ( setOfChar e1, setOfChar e2) { // surcharge de l'oprateur d'addition tendu aux ensembles de char . } //-- les constructeurs de la classe servent initialiser l'ensemble : public setOfChar( ) { . } public setOfChar(string s) : this(s.ToCharArray( )) { . } public setOfChar(char[ ] t) { . } public char[ ] ToArray( ) { // renvoie les lments de l'ensemble sous forme d'un tableau de char . } public override string ToString ( ) { // renvoie les lments de l'ensemble sous forme d'une chane . } public int Card { // cardinal de l'ensemble . } protected virtual void Inserer( object sender, EventArgs e ) { // lance l'vnement OnInserer . } protected virtual void Enlever( object sender, EventArgs e ) { // lance l'vnement OnEnlever . } public char this[ int index ] { // indexeur de l'ensemble . Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
441
} public int Add( char value ) { // ajoute un lment l'ensemble . } public void Remove( char value ) { // enlve un lment l'ensemble s'il est prsent . } public bool Contains( char value ) { // true si value est dans type setOfChar, false sinon. . }
La mthode protected virtual void OnInsert est prsente dans la classe CollectionBase, elle sert excuter des actions avant l'insertion d'un nouvel lment dans la collection. Elle va servir de base au lancement de l'vnement OnInserer. La mthode protected virtual void OnRemoveest prsente dans la classe CollectionBase, elle sert excuter des actions lors de la suppression d'un lment dans la collection. Elle va servir de base au lancement de l'vnement OnEnlever.
EXERCICES
page
442
for(int i=0; i<EnsCar.Card; i++) Console.Write( EnsCar[i]+"," ); Console.WriteLine(); // on enlve un lment dans l'ensemble EnsCar.Remove( 'd' ); // Contenu de l'ensemble avec foreach Console.WriteLine( "Contenu de l'ensemble aprs enlvement de l'lment d :" ); foreach(char elt in EnsCar) Console.Write(elt+"," ); Console.ReadLine(); } } }
Rsultats de l'excution :
public static setOfChar operator + ( setOfChar e1, setOfChar e2) { return new setOfChar(e1.ToString( )+e2.ToString( )); } Ce constructeur permet de charger public setOfChar( ) l'ensemble par une chane s en { } appelant le constructeur du dessous par conversion de la chane s en tableau de public setOfChar(string s) : this(s.ToCharArray( )) char (mthode ToCharArray). { } public setOfChar(char[ ] t) { foreach(char car in t) this.Add(car); } public char[ ] ToArray( ) { char[ ] t = new char[this.Count]; for(int i=0; i<this.Count; i++) t[i]=this[i]; return t; }
Le seul constructeur qui a un corps et permettant de charger l'ensemble avec un tableau de caractres.
EXERCICES
page
443
public override string ToString( ) { return new string(this.ToArray( )); } public int Card { get { return this.Count; } } protected virtual void Inserer( object sender, EventArgs e ) if ( OnInserer != null ) OnInserer( sender , e ); } protected virtual void Enlever( object sender, EventArgs e ) if ( OnEnlever != null ) OnEnlever( sender , e ); } public char this[ int index ] { get { return( (char) List[index] ); } set { List[index] = value; } } public int Add( char value ) { return ( List.Add( value ) ); } public void Remove( char value ) if (Contains(value)) List.Remove( value ); } public bool Contains( char value ) return ( List.Contains( value ) ); } { OnInsert appelle la mthode Inserer qui lance l'ventuel gestionnaire d'vnement OnInserer. { OnRemove appelle la mthode Inserer qui lance l'ventuel gestionnaire d'vnement OnEnlever. { {
protected override void OnInsert ( int index, Object value ) Inserer( this , EventArgs.Empty ); } protected override void OnRemove( int index, Object value ) Enlever ( this , EventArgs.Empty ); } }
Si l'on veut liminer la redondance d'lment (un lment n'est prsent qu'une seule fois dans un ensemble) il faut agir sur la mthode d'ajout (add) et vrifier que l'ajout est possible. L'ajout est possible si l'lment ajouter n'est pas dj contenu dans la liste :
Premier pas dans .Net avec C# - - ( rv. 15.08.2005
EXERCICES
page
444
Comme la mthode add renvoie le rang d'insertion de l'lment, nous lui faisons renvoyer la valeur -1 lorsque l'lment n'est pas ajout parce qu'il est dj prsent dans la liste :
public int Add( char value ) { if (!Contains(value) ) return ( List.Add ( value ) ); else return -1; }
Nous pouvons aussi prvoir d'envoyer un message l'utilisateur de la classe sous forme d'une fentre de dialogue l'avertissant du fait qu'un lment tait dj prsent et qu'il n'a pas t rajout. Nous utilisons la classe MessageBox de C# qui sert afficher un message pouvant contenir du texte, des boutons et des symboles : System.Object |__ System.Windows.Forms.MessageBox Plus particulirement, nous utilisons une des surcharges de la mthode Show : public static DialogResult Show( string TexteMess , string caption );
Voici dans cette ventualit, ce que provoque l'excution de la sixime ligne du code cidessous sur la deuxime demande d'insertion de l'lment 'd' dans l'ensemble EnsCar1 :
EnsCar1.Add('a'); EnsCar1.Add('b'); EnsCar1.Add('c'); EnsCar1.Add('d'); EnsCar1.Add('e'); EnsCar1.Add('d');
EXERCICES
page
445
Conseils :
Par rapport l'exercice prcdent, il faut faire attention l'ajout d'un lment du mme type que tous ceux qui sont dj prsents dans l'ensemble et refuser un nouvel lment qui n'est pas strictement du mme type. Il faut donc utiliser le mcanisme de reflexion de C# (connatre le type dynamique d'un objet lors de l'excution) contenu dans la classe Type. Nous proposons un squelette dtaill de la classe setOfObject complter, les mthodes qui sont identiques celle de l'exercice prcdent ont t mise avec leur code, les autres sont dfinir par le lecteur. Nous souhaitons disposer de plusieurs surcharges du constructeur d'ensemble gnrique :
constructeur public setOfObject( ) public setOfObject (object[ ] t) public setOfObject (Array t) public setOfObject (ArrayList t) public setOfObject (string s) fonctionnalit Construit un ensemble vide
(de n'importe quel type)
Nous proposons enfin, de prvoir un champ protg nomm FtypeElement qui contient le type de l'lment de l'ensemble qui peut varier au cours du temps Car lorsque l'ensemble est vide il n'a pas de type d'lment c'est l'ajout du premier lment qui dtermine le type de l'ensemble et donc des futurs autres lments introduire. Ce champ protg devra tre accessible en lecture seulement par tout utilisateur de la classe.
Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
446
Code complter :
public class setOfObject : CollectionBase, IEventEnsemble { protected Type FTypeElement; public Type TypeElement { get { return FTypeElement; } } public event EventHandler OnInserer; public event EventHandler OnEnlever; public static setOfObject operator + ( setOfObject e1, setOfObject e2) { .. } public setOfObject() { } public setOfObject(object[] t) { .. } public setOfObject(Array t) { .. } public setOfObject (ArrayList t) .. { } public setOfObject(string s) { .. } public virtual object[] ToArray() { .. } public override string ToString() { .. } public int Card { get { return this.Count; } } protected virtual void Inserer( object sender, EventArgs e ) { if ( OnInserer != null ) OnInserer( sender , e ); } protected virtual void Enlever( object sender, EventArgs e ) { if ( OnEnlever != null ) OnEnlever( sender , e ); } Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
447
public object this[ int index ] { get { return( List[index] ); } set { List[index] = value; } } public int Add( object value ) { /* ajoute un lment l'ensemble sinon renvoie-1 si dj prsent ou bien renvoie -2 si l'lment n'est pas du mme type que les autres */ .. } public void Remove( object value ) { // enlve un lment l'ensemble s'il est prsent et de mme type .. } public bool Contains( object value ) { // true si value est dans type setOfChar, false sinon. return( List.Contains( value ) ); } protected override void OnInsert( int index, Object value ) { Inserer( this , EventArgs.Empty ); } protected override void OnRemove( int index, Object value ) { Enlever( this , EventArgs.Empty ); } }
EXERCICES
page
448
public setOfObject ( ) { FTypeElement = null; } public setOfObject(object[ ] t) { foreach(object elt in t) this.Add(elt); } public setOfObject(Array t) { foreach(object elt in t) this.Add(elt); } public setOfObject(ArrayList t) : this(t.ToArray( ) ) { } public setOfObject(string s) { char[ ] t = s.ToCharArray( ); foreach(char elt in t) this.Add(elt); } public virtual object[ ] ToArray( ) { object[ ] t = new object[this.Count]; for(int i=0; i<this.Count; i++) t[i]=this[i]; return t; } public override string ToString( ) { return Convert.ToString(this.ToArray()); } public int Card { get { return this.Count; } }
Le constructeur recopie les caractres de la string dans l'ensemble dont le type est donc char.
protected virtual void Inserer( object sender, EventArgs e ) { if ( OnInserer != null ) OnInserer( sender , e ); } protected virtual void Enlever( object sender, EventArgs e ) { if ( OnEnlever != null ) OnEnlever( sender , e ); } public object this[ int index ] { get { return( List[index] ); } set { List[index] = value; } }
EXERCICES
page
449
public int Add( object value ) { // ajoute un lment l'ensemble sinon -1 ou -2 if (this.Count==0) FTypeElement = value.GetType( ); If (value.GetType( ) ==TypeElement) { if (!Contains(value) ) return( List.Add( value ) ); else { MessageBox.Show("L'lment "+value.ToString()+", est dj prsent !", "Ensemble : insertion impossible (-1)"); return -1; } } else { MessageBox.Show("L'lment "+value.ToString()+", n'est pas du mme type !", "Ensemble : insertion impossible (-2)"); return -2; } } public void Remove( object value ) { // enlve un lment l'ensemble s'il est prsent et de mme type if (value.GetType( )==FTypeElement && Contains(value)) List.Remove( value ); } public bool Contains( object value ) { // true si value est dans type setOfChar, false sinon. return( List.Contains( value ) ); } protected override void OnInsert( int index, Object value ) { Inserer( this , EventArgs.Empty ); } protected override void OnRemove( int index, Object value ) { Enlever( this , EventArgs.Empty ); } }
Extrait de code de test o E1, E2 et EnsCar sont des setOfObject //-- chargement par ajout lment par lment : E1.Add('a'); E1.Add('b'); E1.Add('c'); E1.Add('d'); E1.Add('e'); //-- chargement par string : E2 = new setOfObject("xyztu#"); EnsCar = E1 + E2 ; foreach (object elt in EnsCar) Console.WriteLine(" "+ elt ); Console.WriteLine("type d'lment de l'ensemble : "+EnsCar.TypeElement); On teste sur le type d'lment char.
EXERCICES
page
450
Extrait de code de test o E1, E2 et EnsCar sont des setOfObject //-- chargement par ajout lment par lment : E1.Add(45); E1.Add(-122); E1.Add(666); E1.Add(45); On teste sur le type d'lment int.
E1.Add(-9002211187785);
E1.Add(12);
//-- chargement par ajout d'un tableau d'entiers : int[ ] table=new int[ ]{ 125,126,127}; E2 = new setOfObject(table); EnsCar = E1 + E2 ; foreach (object elt in EnsCar) Console.WriteLine(" "+ elt ); Console.WriteLine("type d'lment de l'ensemble : "+EnsCar.TypeElement);
EXERCICES
page
451
EnsCar=Ensble1+Ensble2; EnsCar.OnEnlever += new EventHandler(EnsbleOnEnlever); Console.WriteLine("card(EnsCar)="+EnsCar.Card+" ; "); Console.WriteLine(); // Affichage du Contenu de l'ensemble avec for : Console.WriteLine( "Contenu de l'ensemble EnsCar :" ); Console.WriteLine( "=========== for =============" ); for(int i=0; i<EnsCar.Card; i++) Console.WriteLine(" "+ EnsCar[i] ); // Affichage du Contenu de l'ensemble avec foreach : Console.WriteLine( "========= foreach ===========" ); foreach(object elt in EnsCar) Console.WriteLine(" "+ elt ); // Affichage du type d'lment de l'ensemble Console.WriteLine("type d'lment de l'ensemble : "+EnsCar.TypeElement); } private static void ChargerEnsInt( setOfObject E1,out setOfObject E2) { //-- chargement par ajout lment par lment : E1.Add(45); E1.Add(-122); E1.Add(666); Appel au constructeur : E1.Add(45); E1.Add(-9002211187785); public setOfObject(Array t) E1.Add(12); //-- chargement par ajout d'un tableau d'entiers : int[ ] table=new int[ ] { 125,126,127 }; E2 = new setOfObject(table); } private static void ChargerEnsChar( setOfObject E1,out setOfObject E2) { //-- chargement par ajout lment par lment : E1.Add('a'); E1.Add('b'); E1.Add('c'); E1.Add('d'); Appel au constructeur : E1.Add('e'); //-- chargement par string : public setOfObject(string s) E2 = new setOfObject("xyztu#"); } private static void ChargerEnsTChar( setOfObject E1,out setOfObject E2) { //-- chargement par ajout lment par lment : E1.Add('a'); E1.Add('b'); E1.Add('c'); E1.Add('d'); E1.Add('e'); //-- chargement par tableau de char : Appel au constructeur : char[ ] table = new char [ ] { 'x','y','z','t','u','#'}; E2=new setOfObject(table); public setOfObject(Array t) } private static void ChargerEnsArrayList( setOfObject E1,out setOfObject E2) { //-- chargement par ajout lment par lment : E1.Add("un"); E1.Add("deux"); Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
452
E1.Add("trois"); E1.Add("quatre"); E1.Add("cinq"); //-- chargement par ArrayList de string : ArrayList t = new ArrayList( ); t.Add("six"); t.Add("sept"); t.Add("fin."); E2=new setOfObject(t); }
Appel au constructeur :
public setOfObject(ArrayList t) : this (t.ToArray( ) )
private static void ChargerEnsArrayString( setOfObject E1,out setOfObject E2) { //-- chargement par ajout lment par lment : E1.Add("rat"); E1.Add("chien"); E1.Add("chat"); E1.Add("vache"); //-- chargement par tableau de string : string[ ] table = new string[3]; table[0]="voiture"; Appel au constructeur : table[1]="bus"; table[2]="fin."; public setOfObject(object[ ] t) E2=new setOfObject(table); } //--un type numr enum Jours { Dimanche, Lundi, Mardi, Mercredi, Jeudi, Vendredi, Samedi } private static void ChargerJours( setOfObject E1,out setOfObject E2) { //-- chargement par ajout lment par lment : E1.Add(Jours.Vendredi); E1.Add(Jours.Samedi); E1.Add(Jours.Dimanche); //-- chargement par tableau de TypePerso : Jours[ ]table = new Jours[4]; table[0]=Jours.Lundi; table[1]=Jours.Mardi; table[2]=Jours.Mercredi; Appel au constructeur : table[3]=Jours.Jeudi; E2=new setOfObject(table); public setOfObject(Array t) } [STAThread] public static void Main( ) { Console.WriteLine("------------ 1 -----------------"); version(1); Console.WriteLine("----------- 2 ------------------"); version(2); Console.WriteLine("----------- 3 ------------------"); version(3); Console.WriteLine("----------- 4 ------------------"); version(4); Console.WriteLine("----------- 5 ------------------"); version(5); Console.WriteLine("----------- 6 ------------------"); version(6); } }
EXERCICES
page
453
Sur les 9 cases une seule est disponible, le jeu consiste ne dplacer qu'une seule lettre la fois et uniquement dans la case reste libre. Par exemple dans la configuration de dpart cihaut seules les lettres G, E, C , B peuvent tre dplaces vers la case centrale reste libre.
4 choix de dplacements sont possibles : si l'on dplace la lettre C, on obtient :
EXERCICES
page
454
L'interface construire en C# doit permettre un utilisateur de jouer au taquin dans les conditions de contraintes du jeu, il utilisera la souris pour cliquer sur une lettre afin de la dplacer vers la case libre (le programme doit grer la possibilit pour une case d'tre dplace ou non et doit tester chaque dplacement si le joueur a gagn ou non).
L'IHM affichera la liste des lettres lue depuis le coin suprieur gauche jusqu'au coin infrieur droit du tableau (la case libre sera reprsente par un #)
etc Au final :
Lorsque le joueur a trouv la bonne combinaison et rang les lettres dans le bon ordre, prvoir de lui envoyer une fentre de dialogue de flicitation et un menu lui permettant de rejouer ou de quitter le jeu:
EXERCICES
page
455
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Runtime.InteropServices;
namespace WinAppliPuzzle { /// <summary> /// Description rsume de Form1. /// </summary> public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.MainMenu mainMenu1; private System.Windows.Forms.Label labelHorloge; private System.Windows.Forms.MenuItem menuItemjeu; private System.Windows.Forms.MenuItem menuItemson; private System.Windows.Forms.MenuItem menuItemrelancer; private System.Windows.Forms.MenuItem menuItemquitter; private System.Windows.Forms.Timer timerTemps; private System.ComponentModel.IContainer components; private const String correct="ABCDEFGH#"; private String modele=correct; private int trou=9; private Point Ptrou=new Point(176,176); private System.Windows.Forms.PictureBox pictureBoxSon; private System.Windows.Forms.Label labelModele; private System.Windows.Forms.Button buttonA; private System.Windows.Forms.Button buttonB; private System.Windows.Forms.Button buttonC; private System.Windows.Forms.Button buttonF; private System.Windows.Forms.Button buttonE; private System.Windows.Forms.Button buttonD; private System.Windows.Forms.Button buttonH; private System.Windows.Forms.Button buttonG; private System.Windows.Forms.Panel panelFond; private bool Sonok=false; private int TestDeplace(int num) { switch (num) { case 1:if(trou==2 | trou==4) return trou; break; case 2:if(trou==1 | trou==3 | trou==5) return trou; break; case 3:if(trou==2 | trou==6) return trou; break; case 4:if(trou==1 | trou==5 | trou==7) return trou; break; case 5:if(trou==2 | trou==4 | trou==6 | trou==8) return trou; break; case 6:if(trou==3 | trou==5 | trou==9) return trou; break; case 7:if(trou==4 | trou==8) return trou; break; case 8:if(trou==5 | trou==7 | trou==9) return trou; break; case 9:if(trou==6 | trou==8) return trou; break; } return -1; } Un exemple d'utilisation de fonction non CLS compatible et dpendant de [DllImport("user32.dll",EntryPoint="MessageBeep")] la plateforme Win32. (envoyer un son public static extern bool MessageBeep(uint uType); sur le haut-parleur du PC) .
EXERCICES
page
456
private void DeplaceVers(Button source, int but) { int T,L; if(but>0) { T = source.Location.Y; L = source.Location.X; source.Location = Ptrou; Ptrou = new Point(L,T); if (Sonok) MessageBeep(0); } } private void MajModele(Button source, int but) { if(but>0) { char[ ] s = modele.ToCharArray(); s[(int)(source.Tag)-1]='#'; s[but-1]=(char)(source.Text[0]); trou = (int)(source.Tag); source.Tag=but; modele=""; for(int i=0; i<s.Length;i++) modele = modele+s[i]; } } public void Tirage( ) { buttons_Click(buttonF, buttons_Click(buttonE, buttons_Click(buttonB, buttons_Click(buttonA, buttons_Click(buttonD, buttons_Click(buttonG, buttons_Click(buttonH, buttons_Click(buttonB, buttons_Click(buttonE, buttons_Click(buttonC, buttons_Click(buttonA, buttons_Click(buttonE, }
new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( )); new EventArgs( ));
Ecrivez une autre redisposition alatoire des boutons. Ici nous lanons une squence modifiant l'existant en utilisant les gestionnaires de click de souris comme si l'utilisateur avait cliqu 12 fois sur l'IHM.
public Form1( ) { // // Requis pour la prise en charge du Concepteur Windows Forms // InitializeComponent( ); // // TODO : ajoutez le code du constructeur aprs l'appel InitializeComponent // } /// <summary> /// Nettoyage des ressources utilises. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose( ); } } Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
457
base.Dispose( disposing ); } #region Windows Form Designer generated code /// <summary> /// Mthode requise pour la prise en charge du concepteur - ne modifiez pas /// le contenu de cette mthode avec l'diteur de code. /// </summary> private void InitializeComponent() { this.components = new System.ComponentModel.Container( ); System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1)); this.mainMenu1 = new System.Windows.Forms.MainMenu( ); this.menuItemjeu = new System.Windows.Forms.MenuItem( ); this.menuItemson = new System.Windows.Forms.MenuItem( ); this.menuItemrelancer = new System.Windows.Forms.MenuItem( ); this.menuItemquitter = new System.Windows.Forms.MenuItem( ); this.panelFond = new System.Windows.Forms.Panel( ); this.buttonH = new System.Windows.Forms.Button( ); this.buttonG = new System.Windows.Forms.Button( ); this.buttonF = new System.Windows.Forms.Button( ); this.buttonE = new System.Windows.Forms.Button( ); this.buttonD = new System.Windows.Forms.Button( ); this.buttonC = new System.Windows.Forms.Button( ); this.buttonB = new System.Windows.Forms.Button( ) ; this.buttonA = new System.Windows.Forms.Button( ) ; this.pictureBoxSon = new System.Windows.Forms.PictureBox( ); this.labelModele = new System.Windows.Forms.Label( ); this.timerTemps = new System.Windows.Forms.Timer(this.components); this.labelHorloge = new System.Windows.Forms.Label( ); this.panelFond.SuspendLayout( ); this.SuspendLayout( ); // // mainMenu1 // this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.menuItemjeu } ); // // menuItemjeu // this.menuItemjeu.Index = 0; this.menuItemjeu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.menuItemson, this.menuItemrelancer, this.menuItemquitter } ); this.menuItemjeu.Text = "Jeu"; // // menuItemson // this.menuItemson.Index = 0; this.menuItemson.Text = "Son off"; this.menuItemson.Click += new System.EventHandler(this.menuItemson_Click); // // menuItemrelancer Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
458
// this.menuItemrelancer.Index = 1; this.menuItemrelancer.Text = "Relancer"; this.menuItemrelancer.Click += new System.EventHandler(this.menuItemrelancer_Click); // // menuItemquitter // this.menuItemquitter.Index = 2; this.menuItemquitter.Text = "Quitter"; this.menuItemquitter.Click += new System.EventHandler(this.menuItemquitter_Click); // // panelFond // this.panelFond.BackColor = System.Drawing.SystemColors.Info; this.panelFond.Controls.Add(this.buttonH); this.panelFond.Controls.Add(this.buttonG); this.panelFond.Controls.Add(this.buttonF); this.panelFond.Controls.Add(this.buttonE); this.panelFond.Controls.Add(this.buttonD); this.panelFond.Controls.Add(this.buttonC); this.panelFond.Controls.Add(this.buttonB); this.panelFond.Controls.Add(this.buttonA); this.panelFond.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.panelFond.Location = new System.Drawing.Point(8, 32); this.panelFond.Name = "panelFond"; this.panelFond.Size = new System.Drawing.Size(264, 264); this.panelFond.TabIndex = 0; // // buttonH // this.buttonH.BackColor = System.Drawing.Color.Tan; this.buttonH.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonH.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonH.Location = new System.Drawing.Point(92, 176); this.buttonH.Name = "buttonH"; this.buttonH.Size = new System.Drawing.Size(80, 80); this.buttonH.TabIndex = 16; this.buttonH.Tag = 8; this.buttonH.Text = "H"; this.buttonH.Click += new System.EventHandler(this.buttons_Click); this.buttonH.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonG // this.buttonG.BackColor = System.Drawing.Color.Tan; this.buttonG.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonG.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonG.Location = new System.Drawing.Point(8, 176); this.buttonG.Name = "buttonG"; this.buttonG.Size = new System.Drawing.Size(80, 80); this.buttonG.TabIndex = 15; this.buttonG.Tag = 7; this.buttonG.Text = "G"; this.buttonG.Click += new System.EventHandler(this.buttons_Click); this.buttonG.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonF // Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
459
this.buttonF.BackColor = System.Drawing.Color.Tan; this.buttonF.Cursor = System.Windows.Forms.Cursors.Default; this.buttonF.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonF.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonF.Location = new System.Drawing.Point(176, 92); this.buttonF.Name = "buttonF"; this.buttonF.Size = new System.Drawing.Size(80, 80); this.buttonF.TabIndex = 14; this.buttonF.Tag = 6; this.buttonF.Text = "F"; this.buttonF.Click += new System.EventHandler(this.buttons_Click); this.buttonF.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonE // this.buttonE.BackColor = System.Drawing.Color.Tan; this.buttonE.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonE.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonE.Location = new System.Drawing.Point(92, 92); this.buttonE.Name = "buttonE"; this.buttonE.Size = new System.Drawing.Size(80, 80); this.buttonE.TabIndex = 13; this.buttonE.Tag = 5; this.buttonE.Text = "E"; this.buttonE.Click += new System.EventHandler(this.buttons_Click); this.buttonE.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonD // this.buttonD.BackColor = System.Drawing.Color.Tan; this.buttonD.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonD.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonD.Location = new System.Drawing.Point(8, 92); this.buttonD.Name = "buttonD"; this.buttonD.Size = new System.Drawing.Size(80, 80); this.buttonD.TabIndex = 12; this.buttonD.Tag = 4; this.buttonD.Text = "D"; this.buttonD.Click += new System.EventHandler(this.buttons_Click); this.buttonD.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonC // this.buttonC.BackColor = System.Drawing.Color.Tan; this.buttonC.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonC.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonC.Location = new System.Drawing.Point(176, 8); this.buttonC.Name = "buttonC"; this.buttonC.Size = new System.Drawing.Size(80, 80); this.buttonC.TabIndex = 11; this.buttonC.Tag = 3; this.buttonC.Text = "C"; this.buttonC.Click += new System.EventHandler(this.buttons_Click); this.buttonC.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonB Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
460
// this.buttonB.BackColor = System.Drawing.Color.Tan; this.buttonB.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonB.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonB.Location = new System.Drawing.Point(92, 8); this.buttonB.Name = "buttonB"; this.buttonB.Size = new System.Drawing.Size(80, 80); this.buttonB.TabIndex = 10; this.buttonB.Tag = 2; this.buttonB.Text = "B"; this.buttonB.Click += new System.EventHandler(this.buttons_Click); this.buttonB.MouseMove += new System.Windows.Forms.MouseEventHandler(this.buttons_MouseMove); // // buttonA // this.buttonA.BackColor = System.Drawing.Color.Tan; this.buttonA.Font = new System.Drawing.Font("Times New Roman", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonA.ForeColor = System.Drawing.Color.SaddleBrown; this.buttonA.Location = new System.Drawing.Point(8, 8); this.buttonA.Name = "buttonA"; this.buttonA.Size = new System.Drawing.Size(80, 80); this.buttonA.TabIndex = 9; this.buttonA.Tag = 1; this.buttonA.Text = "A"; this.buttonA.Click += new System.EventHandler(this.buttons_Click); this.buttonA.MouseMove += new System.Windows.Forms.MouseEventHandler (this.buttons_MouseMove); // // pictureBoxSon // this.pictureBoxSon.Image = ((System.Drawing.Image)(resources.GetObject("pictureBoxSon.Image"))); this.pictureBoxSon.Location = new System.Drawing.Point(128, 8); this.pictureBoxSon.Name = "pictureBoxSon"; this.pictureBoxSon.Size = new System.Drawing.Size(16, 16); this.pictureBoxSon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; this.pictureBoxSon.TabIndex = 1; this.pictureBoxSon.TabStop = false; // // labelModele // this.labelModele.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.labelModele.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelModele.ForeColor = System.Drawing.Color.Chocolate; this.labelModele.Location = new System.Drawing.Point(16, 8); this.labelModele.Name = "labelModele"; this.labelModele.Size = new System.Drawing.Size(88, 16); this.labelModele.TabIndex = 2; this.labelModele.Text = "ABCDEFGH#"; this.labelModele.TextAlign = System.Drawing.ContentAlignment.TopCenter; // // timerTemps // this.timerTemps.Enabled = true; this.timerTemps.Interval = 1000; this.timerTemps.Tick += new System.EventHandler(this.timer1_Tick); // // labelHorloge // Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
461
this.labelHorloge.BackColor = System.Drawing.Color.Aqua; this.labelHorloge.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.labelHorloge.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelHorloge.Location = new System.Drawing.Point(192, 4); this.labelHorloge.Name = "labelHorloge"; this.labelHorloge.Size = new System.Drawing.Size(72, 20); this.labelHorloge.TabIndex = 4; this.labelHorloge.Text = "00:00:00"; this.labelHorloge.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(280, 305); this.Controls.Add(this.labelHorloge); this.Controls.Add(this.labelModele); this.Controls.Add(this.pictureBoxSon); this.Controls.Add(this.panelFond); this.Menu = this.mainMenu1; this.Name = "Form1"; this.Text = "Mini puzzle type taquin"; this.Load += new System.EventHandler(this.Form1_Load); this.panelFond.ResumeLayout(false); this.ResumeLayout(false); } #endregion /// <summary> /// Point d'entre principal de l'application. /// </summary> [STAThread] static void Main( ) { Application.Run(new Form1()); } private void buttons_Click(object sender, EventArgs e) { int but = TestDeplace((int)((sender as Button).Tag) ); DeplaceVers((sender as Button), but); MajModele((sender as Button),but); labelModele.Text = modele; if(modele.CompareTo(correct)==0) MessageBox.Show("Bravo vous avez trouv !", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void timer1_Tick(object sender, System.EventArgs e) { DateTime dt = DateTime.Now; labelHorloge.Text=dt.ToString("T"); }
Gestionnaire centralis du click de souris sur les 8 boutons reprsentant les lettres dans le tableau.
Gestionnaire centralis du Move de souris sur les 8 boutons reprsentant les lettres dans le tableau.
private void buttons_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { if (TestDeplace((int)((sender as Button).Tag) )>0) (sender as Button).Cursor = System.Windows.Forms.Cursors.Hand; else (sender as Button).Cursor = System.Windows.Forms.Cursors.No; } //-- les gestionnaires des click dans le menu : Premier pas dans .Net avec C# - - ( rv. 15.08.2005
)
EXERCICES
page
462
private void menuItemson_Click(object sender, System.EventArgs e) { Console.WriteLine(menuItemson.Text); if(menuItemson.Text.IndexOf("off")>0) { menuItemson.Text = "Son on"; Sonok = false; pictureBoxSon.Visible = false; } else { menuItemson.Text = "Son off"; Sonok = true; pictureBoxSon.Visible = true; } } private void menuItemrelancer_Click(object sender, System.EventArgs e) { Tirage( ); } private void menuItemquitter_Click(object sender, System.EventArgs e) { Close( ); } private void Form1_Load(object sender, System.EventArgs e) { Tirage( ); Sonok = true; } } }
Remarque
Nous avons utilis la classe System.Windows.Forms.Timer afin d'afficher l'heure systmedans le jeu : System.Object |__ System.MarshalByRefObject |__ System.ComponentModel.Component |__ System.Windows.Forms.Timer Cette classe trs semblable la classe TTimer de Delphi, implmente une minuterie dclenchant un vnement Tick (vnement OnTimer en Delphi)selon un intervalle dfini par l'utilisateur (prconisation Microsoft : un Timer doit tre utilis dans une fentre). Ci-dessous le gestionnaire de l'vnement Tick :
EXERCICES
page
463
Les valeurs de date de type DateTime sont mesures en units de 100 nanosecondes et exprimes sous forme d'un entier long.
EXERCICES
page
464
Bibliographie
Livres papier vendus par diteur
Livres C# en franais
J.Richter, programmez microsoft .Net Framework, microsof press-Dunod, Paris (2002) R.Standefer, ASP Net web training auto-formation, OEM-Eyrolles, Paris (2002) G.Leblanc, solutions dveloppeur C# et .NET, Eyrolles, Paris (2002) B.Bischof, Langages .Net guide des quivalences, Eyrolles, Paris (2002) S.Gross, cook book C#, Micro application, Paris (2002) C.Eberhardt, le langage C#, campus press, Paris (2002) H.Berthet , Visual C# concepts et mise en oeuvre, Ed.ENI, Nantes (2002) M.Williams, manuel de rfrence microsof visual C# .Net, microsof press-Dunod, Paris (2003) Kit de formation dvelopper des applications windows avec visual C# .Net, microsof pressDunod, Paris (2003) Kit de formation dvelopper des applications web avec visual C# .Net, microsof press-Dunod, Paris (2003) V.Billotte, M.Thevenet, Le langage C#, Ed. Micro-Application, Paris (2002) J.Gabillaud, ADO.NET l'accs aux donnes, Ed.ENI, Nantes (2004)
Site de l'association des dveloppeurs francophones contenant de nombreux cours et tutoriels en ligne gratuits pour C++, Delphi, Java, C#, UML etc.. : http://www.developpez.com
EXERCICES
page
465