Csharp Part 1
Csharp Part 1
Csharp Part 1
Rm di Scala
Mise à jour d’octobre 2007
Pour les mises à jour et les autres cours pdf du même auteur,
consultez le site suivant : http://www.discala.net
SOMMAIRE
IHM avec C#
Programmation événementielle ……………………………..……. P.293
Les événements en C # ……………………………………………. P.317
Propriétés et indexeurs …….…………………………………...… P.338
Fenêtres et ressources mémoires ……………..…………….…… P.366
Contrôles dans les formulaires ………………..……………….... P.405
Exceptions comparées à Delphi et java …………..…………….. P.429
Données simples flux et fichiers …………..……………………. P.433
Bibliographie ……………………………………………………….………….…..
Exercices ……………………………………………………………………….……..
94 pages d'exercices avec solutions
Pour pouvoir s’initier à C# avec ce cours et à peu de frais dans un premier temps, il faut télécharger gratuitement sur
le site de CodeGear, l’environnement Borland studio Delphi 2006 édition personnelle, ou aller sur le site de
Microsoft et télécharger gratuitement Visual C# express, ou bien utiliser la denière version de l'environnemnt open
source Sharpdevelop qui fonctionne sous Net framework run-time..
A mon épouse Dominique pour son soutien et sa patience qui me permettent de consacrer de nombreuses heures à la
construction du package et des cours inclus et surtout qui a eu la constance de relire entièrement toutes les pages de la
version initiale de l'ouvrage, alors que l'informatique n'est pas sa tasse de thé.
A michel Veuillerot ex-Manager européen Information Technology and Telecom Architecture and Delivery Services
chez Eastman Kodak, qui a relu attentivement la version précédente de l’ouvrage et testé tous les exemples.
• Au club des développeurs francophones qui héberge gratuitement un site miroir du précédent et qui recommande
le package pédagogique ( http://rmdiscala.developpez.com/cours/ ) à ses visiteurs débutants.
Remerciements : (anticipés)
Aux lecteurs qui trouveront nécessairement encore des erreurs, des oublis, et autres imperfections et qui voudront bien
les signaler à l’auteur afin d’améliorer le cours, e-mail : csharplivre@discala.net
Site de consultation et de téléchargement des autres ouvrages en pdf ( Bases de l'informatique, Java 2 ) :
http://www.discala.net
Une stratégie différente de répartition de l'information et de son traitement est proposée 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 idées fondatrices que nous énonçons ci-dessous :
Une disparition progressive des différences 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 concentrées sur un seul serveur pourront être réparties sur
plusieurs machines qui proposeront chacune un service adapté aux informations qu'elles
détiennent.
A la place d'une seule application, l'utilisateur aura accès à une fédération d'applications
distantes ou locales capables de coopérer entre elles pour divers usages de traitement.
L'utilisateur n'aurait plus la nécessité d'acheter un logiciel, il louerait plutôt les services d'une
action spécifique.
Le micro-ordinateur reste l'intermédiaire incontournable de cette stratégie, 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 développeurs d'applications .NET un vaste ensemble de composants afin de faire de
la programmation par composant unifiée au sens des protocoles (comme l’utilisation du
protocole SOAP) et diversifiée quant aux lieux où se trouvent les composants.
Afin de mettre en place cette nouvelle stratégie, microsoft procède par étapes. Les fondations de
l'architecture .NET sont posées par l'introduction d'un environnement de développement et
d'exécution des applications .NET. Cet environnement en version stabilisée depuis 2002 avec une
révision majeure en 2005, porte la dénomination de .NET Framework, il est distribué
gratuitement par microsoft sur toutes les versions de Windows (98, Me,..., Xp,...). La dernière
version de .NET Framework est directement intégrée à Windows Vista.
L'outil Visual Studio .NET contient l'environnement RAD de développement pour l'architecture
.NET. Visual Studio .NET permet le développement d'applications classiques Windows ou
Internet.
Les versions gratuites soit "Express" de Microsoft, soit l'environnement complet enversion
professionnelle de Visual Studio .NET, soit sharpDevelop de l'open source démocratisent les
outils de programmation.
Elle comporte plusieurs couches les unes abstraites, les autres en code exécutable :
La première couche CLS est composée des spécifications communes à tous les langages qui
veulent produire des applications .NET qui soient exécutables dans cet environnement et les
langages eux-même. Le CLS est une sorte de sous-ensemble minimal de spécifications autorisant
une interopérabilité complète entre tous les langages de .NET les règles minimales (il y en a en
fait 41 ) sont :
• Les langages de ..NET doivent savoir utiliser tous les composants du CLS
La seconde couche est un ensemble de composants graphiques disponibles dans Visual Studio
.NET qui permettent de construire des interfaces homme-machine orientées Web (services Web)
ou bien orientées applications classiques avec IHM.
Les données sont accédées dans le cas des services Web à travers les protocoles qui sont des
standards de l'industrie : HTTP, XML et SOAP.
La troisième couche est constituée 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 même fonction que la
bibliothèque des classes de Java.
La librairie de classe de .NET Framework est organisée en nom d'espace hierarchisés, exemple
ci-dessous de quelques espaces de nom de la hiérarchie System :
• La classe Console qui se trouve dans l'espace de noms "System" se déclare comme "System.Console".
• La classe DataAdapter qui se trouve dans l'espace de noms "System.Data.Common " se déclare comme
"System.Data. Common.DataAdapter ".
Rappelons qu'un ordinateur ne sait exécuter que des programmes écrits en instructions machines
compréhensibles par son processeur central. C# comme pascal, C etc... fait partie de la famille des
langages évolués (ou langages de haut niveau) qui ne sont pas compréhensibles immédiatement
par le processeur de l'ordinateur. Il est donc nécesaire d'effectuer une "traduction" d'un
programme écrit en langage évolué afin que le processeur puisse l'exécuter.
Les deux voies utilisées pour exécuter un programme évolué sont la compilation ou
l'interprétation :
La première 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'interpréteur de p-machine pascal, le
reste de l'environnement de développement (éditeurs, compilateurs,...) étant écrit en pascal était
fourni et fonctionnait dès que la p-machine était opérationnelle sur la plate-forme cible.
Donc dans le cas d'une p-machine le programme source est compilé, mais le
programme cible est exécuté par l'interpréteur de la p-machine.
Beaucoup de langages possèdent pour une plate-forme fixée des interpréteurs ou des compilateurs,
moins possèdent une p-machine, Java de Sun est l'un de ces langages. Tous les langages de la
plateforme .NET fonctionnent selon ce principe, C# conçu par microsoft en est le dernier, un
programme C# compilé en p-code, s'exécute sur la p-machine virtuelle incluse dans le CLR.
Compilation native
La compilation native consiste en la traduction du source C# (éventuellement préalablement
traduit instantanément en code intermédiare) en langage binaire exécutable sur la plate-forme
concernée. Ce genre de compilation est équivalent à n'importe quelle compilation d'un langage
dépendant de la plate-forme, l'avantage est la rapidité d'exécution des instructions machines
par le processeur central. La stratégie de développement multi-plateforme de .Net, fait que
Microsoft ne fournit pas pour l’instant, de compilateur C# natif, il faut aller voir sur le net les
entreprises vendant ce type de produit.
ATTENTION
Bien que se terminant par le suffixe exe, un programme issu d'une compilation sous .NET
n'est pas un exécutable en code natif, mais un bytecode en MSIL; ce qui veut dire que vous ne
pourrez pas faire exécuter directement sur un ordinateur qui n'aurait pas la machine virtuelle
.NET, un programme PE "xxx.exe" ainsi construit.
Ci-dessous le schéma d'un programme source Exemple.cs traduit par le compilateur C# sous
.NET en un programme cible écrit en bytecode nommé Exemple.exe
• à travers le CTS (Common Type System) qui implémente le CLS (Common Language
Specification), le CLR assure la sécurité de compatibilité des types connus mais
syntaxiquement différents selon les langages utilisés.
Une fois le programme source C# traduit en bytecode MSIL, la machine virtuelle du CLR se
charge de l'exécuter sur la machine physique à travers son système d'exploitation (Windows,
Unix,...)
On peut mentalement considérer qu'avec cette technique vous obtenez un programme C# cible
compilé en deux passages :
• le second passage étant le compilateur JIT lui-même qui optimise et traduit localement à
la volée et à chaque appel de méthode, le bytecode MSIL en instructions du processeur de
la plate-forme. Ce qui donne au bout d'un temps très bref, un code totalement traduit en
instruction du processeur de la plateforme, selon le schéma ci-après :
AOT (ahead-of-time) est une technique de compilation locale de tout le bytecode MSIL avant
exécution (semblable à la compilation native). Le compilateur AOT du CLR compile, avant une
quelconque exécution et en une seule fois, toutes les lignes de code MSIL et génère des images
d’exécutables à destination du CLR.
Tout est objet dans C#, en outre C# est un langage fortement typé. Comme en Delphi et en Java
vous devez déclarer un objet C# ou une variable C# avec son type avant de l'utiliser. C# dispose
de types valeurs intrinsèques qui sont définis à partir des types de base du CLS (Common
Language Specification).
Struct
Les classes encapsulant les types élémentaires 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
référence pour un objet de type valeur et lorsqu'un objet de type valeur est passé comme paramètre
il est passé par valeur.
Dans .NET Framework les classes-structures de type valeur sont déclarées comme structures et
ne sont pas dérivables, les classes de type référence sont déclarées comme des classes classiques et
sont dérivables.
Afin d'éclairer le lecteur prenons par exemple un objet x instancié à partir d'une classe de type
référence et un objet y instancié à partir d'un classe de type valeur contenant les mêmes membres
que la classe par référence. Ci-dessous le schéma d'allocation de chacun des deux objets :
Déclaration de classe-structure :
instanciation :
struct StructAmoi {
int b;
void meth(int a){ StructAmoi y = new StructAmoi ( ) ;
b = 1000+a;
}
}
Déclaration de classe :
instanciation :
class ClassAmoi {
int b;
void meth(int a) { ClassAmoi x = new ClassAmoi ( ) ;
b = 1000+a;
}
}
Les classes-structures de type valeur peuvent comme les autres classes posséder un constructeur
explicite, qui comme pour toute classe C# doit porter le même nom que celui de la classe-
structure.
Exemple ci-desssous d'une classe-structure dénommée 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 référence.
En reprenant l'exemple de la classe précédente on instancie et on utilise un objet Rec :
Menulang Rec = new Menulang ( Nomlang , FiltreLang );
Rec.MenuTexte = "Entrez" ;
Rec.Filtre = "*.ent" ;
Decimal réeel = entier* 10n (au maximum 28 décimales exactes) 128 bits
Le type System.Int32 est le type valeur entier signé sur 32 bits dans le CLS.
Voici selon 4 langages de .NET Framework ( VB, C#, C++, J# ) la déclaration 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 premières déclarations comportent syntaxiquement le mot clef struct ou Structure
Programmer objet .Net avec C#- ( rév.17.10.2007 ) - Rm di Scala page 14
indiquant le mode de gestion par valeur donc sur la pile des objets de ce type. La dernière
déclaration en J# compatible syntaxiquement avec Java, utilise une classe qui par contre gère ses
objets par référence dans le tas. C'est le CLR qui va se charger de maintenir une cohérence
interne entre ces différentes variantes; ici on peut raisonnablement supposer que grâce au
mécanisme d'emboîtage (Boxing) le CLR allouera un objet par référence 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 déclarer un ensemble de constantes de base
comme en pascal. En C#, chaque énumération de type enum, possède 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 défaut des éléments de l'énumération. Par défaut, le premier
énumérateur a la valeur 0, et l'énumérateur de rang n a la valeur n-1.
Soit par exemple un type énuméré jour :
1°) Il est possible de déclarer classiquement une variable du type jour comme un objet de type
jour, de l'instancier et de l'affecter :
2°) Il est possible de déclarer d'une manière plus courte la même 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);
Résultat de ces 3 lignes de code affiché sur la console :
unJour = lundi , place = 0
Dans cette éventualité faire attention, la comparaison de deux variables de deux types
différents, affectées 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 énuméré qui est testé).
L'exemple ci-dessous illustre cette remarque :
Rappelons qu'en C# toute variable qui sert de conteneur à une valeur d'un type élémentaire précis
doit préalablement avoir été déclarée sous ce type.
Remarque importante
Exemples :
45l ou 45L représente la valeur 45 en entier signé sur 64 bits.
45f ou 45F représente la valeur 45 en virgule flottante simple précision sur 32 bits.
45d ou 45D représente la valeur 45 en virgule flottante double précision sur 64 bits.
Transtypage opérateur ( )
Les conversions de type en C# sont identiques pour les types numériques aux conversions utilisées
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 immédiate ou une valeur contenue dans une variable il faut utiliser l'opérateur de
transtypage noté ( ). Nous nous sommes déjà servi de la fonctionnalité de transtypage explicite au
paragraphe précédent 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 élémentaires peuvent être transtypés à l'exception du type bool qui ne peut
pas être converti en un autre type (différence avec le C).
• Les conversions peuvent être restrictives quant au résultat; par exemple le transtypage du
réel 5.27e-2 en entier ( x = (int)5.27e-2) mettra l'entier zéro dans x.
Comme en Java, une variable C# peut contenir soit une valeur d'un type élémentaire, soit une
référence à un objet. Les variables jouent le même rôle que dans les langages de programmation
classiques impératifs, leur visibilité est étudiée dans le prochain chapitre.
Les identificateurs de variables en C# se décrivent comme ceux de tous les langages de
programmation :
Attention C# fait une différence entre majuscules et minuscules, c'est à dire que la variable
BonJour n'est pas la même que la variable bonjour ou encore la variable Bonjour. En plus des
lettres, les caractères suivants sont autorisés pour construire une identificateur C# : "$" , "_" , "µ"
et les lettres accentuées.
fonctionnement de l'exemple :
Lorsque la variable car est l'un des caractères '0', '1', ... ,'9', la variable Valeur est égale à la valeur
numérique associée (il s'agit d'une conversion car = '0' ---> Valeur = 0, car = '1' ---> Valeur = 1, ...
, car = '9' ---> Valeur = 9).
• const int x ; // erreur , le compilateur n'accepte pas une constante non initialisée.
• const int x = 1000 ; // x est déclarée comme constante entière initialisée à 1000.
- Les constantes qualifiées par readonly sont uniquement des variables membre de classes, elles
peuvent être initialisées dans le constructeur de la classe (et uniquement dans le constructeur) :
-Rappelons enfin pour mémoire les constantes de base d'un type énuméré ( cf. enum )
1. Priorité d'opérateurs en C#
Les opérateurs du C# sont très semblables à ceux de Java et donc de C++, ils sont détaillés par
famille, plus loin. Ils sont utilisés comme dans tous les langages impératifs pour manipuler,
séparer, comparer ou stocker des valeurs. Les opérateurs ont soit un seul opérande, soit deux
opérandes, il n'existe en C# qu'un seul opérateur à trois opérandes (comme en Java) l'opérateur
conditionnel " ? : ".
Dans le tableau ci-dessous les opérateurs de C# sont classés par ordre de priorité croissante (0 est
le plus haut niveau, 13 le plus bas niveau). Ceci sert lorsqu'une expression contient plusieurs
opérateurs à indiquer l'ordre dans lequel s'effectueront les opérations.
• Par exemple sur les entiers l'expression 2+3*4 vaut 14 car l'opérateur * est plus prioritaire que
l'opérateur +, donc l'opérateur * est effectué en premier.
• Lorsqu'une expression contient des opérateurs de même priorité alors C# effectue les
évaluations de gauche à droite. Par exemple l'expression 12/3*2 vaut 8 car C# effectue le
parenthésage automatique de gauche à droite ((12/3)*2).
0 () [ ] . new
1 ! ~ ++ --
2 * / %
3 + -
4 << >>
6 == !=
7 &
9 |
10 &&
11 ||
12 ?:
Les opérateurs d'affectation seront mentionnés plus loin comme cas particulier de l'instruction
d'affectation.
Ces opérateurs sont binaires (à deux opérandes) exceptés les opérateurs de signe positif ou négatif.
Ils travaillent tous avec des opérandes de types entiers ou réels. Le résultat de l'opération est
converti automatiquement en valeur du type des opérandes.
L'opérateur " % " de reste n'est intéressant que pour des calculs sur les entiers longs, courts,
signés ou non signés : il renvoie le reste de la division euclidienne de 2 entiers.
int x et int 2
y=x/2; y = 2 // type int
résultat : int
conversion implicite
y=b/2; erreur de conversion : interdit
impossible (float b --> int y)
conversion implicite
y = b / 2.0 ; erreur de conversion: interdit
impossible (float b --> int y)
float b et int 2
a=b/2; a = 2.5 // type float
résultat : float
int x et int 2
résultat : int
a=x/2; a = 2.0 // type float
conversion automatique
int 2 --> float 2.0
int x et float 2f
a = x / 2f ; a = 2.5 // type float
résultat : float
Pour l'instruction précédente " 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 résultat de la division qui est converti en int.
L'objectif de ces opérateurs est l'optimisation de la vitesse d'exécution du bytecode MSIL dans le
CLR (cette optimisation n'est pas effective dans le version actuelle du MSIL) mais surtout la
reprise syntaxique aisée de code source Java et C++.
int k = 5 , n ;
n=5 k=6
n = k++ ;
Exemple 2 :
int k = 5 , n ;
n = -1 k=6
n = k++ - k ;
Dans l'instruction k++ - k nous avons le calcul suivant : la valeur de k (k=5) est utilisée comme
premier opérande de la soustraction, puis elle est incrémentée (k=6), la nouvelle valeur de k est
maintenant utilisée comme second opérande de la soustraction ce qui revient à calculer n = 5-6 et
donne n = -1 et k = 6.
Exemple 3 :
int k = 5 , n ;
n=0 k=6
n = k - k++ ;
Dans l'instruction k - k++ nous avons le calcul suivant : la valeur de k (k=5) est utilisée comme
premier opérande de la soustraction, le second opérande de la soustraction est k++ c'est la valeur
actuelle de k qui est utilisée (k=5) avant incrémentation de k, ce qui revient à calculer n = 5-5 et
donne n = 0 et k = 6.
Exemple 4 : Utilisation de l'opérateur de post-incrémentation en combinaison avec un autre
opérateur unaire.
int nbr1, z , t , u , v ;
nbr1 = 10 ;
v = 10 nbr1 = 11
v = nbr1++
nbr1 = 10 ;
z = -11 nbr1 = 10
z = ~ nbr1 ;
nbr1 = 10 ;
t = -11 nbr1 = 11
t = ~ nbr1 ++ ;
nbr1 = 10 ;
u = -11 nbr1 = 11
u = ~ (nbr1 ++) ;
Remarquons que les expressions "~nbr1 ++ " et "~ (nbr1 ++)" produisent les mêmes effets, ce
qui est logique puisque lorsque deux opérateurs (ici ~ et ++ ) ont la même priorité, l'évaluation a
lieu de gauche à droite.
pré-incrémentation : ++k
la valeur de k est d'abord augmentée de un ensuite utilisée dans l'instruction.
Exemple1 :
int k = 5 , n ;
Exemple 2 :
int k = 5 , n ;
n=0 k=6
n = ++k - k ;
Dans l'instruction ++k - k nous avons le calcul suivant : le premier opérande de la soustraction
étant ++k c'est donc la valeur incrémentée de k (k=6) qui est utilisée, cette même valeur sert de
second opérande à la soustraction ce qui revient à calculer n = 6-6 et donne n = 0 et k = 6.
Exemple 3 :
int k = 5 , n ;
n = -1 k=6
n = k - ++k ;
Dans l'instruction k - ++k nous avons le calcul suivant : le premier opérande de la soustraction est
k (k=5), le second opérande de la soustraction est ++k, k est immédiatement incrémenté (k=6) et
c'est sa nouvelle valeur incrémentée qui est utilisée, ce qui revient à calculer n = 5-6 et donne n = -
1 et k = 6.
post-décrémentation : k--
la valeur de k est d'abord utilisée telle quelle dans l'instruction, puis elle est diminuée de un à la
fin.
Exemple1 :
int k = 5 , n ;
Reprenez avec l'opérateur - - des exemples semblables à ceux fournis pour l'opérateur ++ afin
d'étudier le fonctionnement de cet opérateur (étudiez (- -k - k) et (k - - -k)).
3. Opérateurs de comparaison
Ces opérateurs employés dans une expression renvoient un résultat de type booléen (false ou
true). Nous en donnons la liste sans autre commentaire car ils sont strictement identiques à tous
les opérateurs classiques de comparaison de n'importe quel langage algorithmique (C, pascal,
etc...). Ce sont des opérateurs à deux opérandes.
4. Opérateurs booléens
Ce sont les opérateurs classiques de l'algèbre de boole { { V, F }, ! , & , | } où { V, F } représente
l'ensemble {Vrai,Faux}. Les connecteurs logiques ont pour syntaxe en C# : ! , & , |, ^:
V V F V F V
V F F F V V
F V V F V V
F F V F F F
Remarque :
C# dispose de 2 clones des opérateurs binaires & et | . Ce sont les opérateurs && et || qui se
différentient de leurs originaux & et | par leur mode d'exécution optimisé (application de
théorèmes de l'algèbre de boole) :
Théorème
∀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'opérateur & évalue toujours l'expression q, C# à des fins d'optimisation de la vitesse
d'exécution du bytecode MSIL dans le CLR , propose un opérateur « et » noté && qui a la
même table de vérité que l'opérateur & mais qui applique ce théorème.
L'opérateur ou optimisé : | |
Théorème
∀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'opérateur | évalue toujours l'expression q, C# à des fins d'optimisation de la vitesse
d'exécution du bytecode dans la machine virtuelle C#, propose un opérateur ou noté || qui
∀p ∈ { V, F } , ∀q∈ { V, F } , p ||q = p |q
Mais dans p||q , q n'est évalué que si p = F.
En résumé:
Nous allons voir ci-après une autre utilisation des opérateurs &et | sur des variables ou des
valeurs immédiates en tant qu'opérateur bit-level.
Les tables de vérités des opérateurs "&", " | " et celle du ou exclusif " ^ " au niveau du bit sont
identiques aux tables de verité booléennes ( seule la valeur des constantes V et F change, V est
remplacé par le bit 1 et F par le bit 0) .
p q ~p p&q p|q p ^ q
1 1 0 1 1 0
1 0 0 0 1 1
0 1 1 0 1 1
0 0 1 0 0 0
Afin de bien comprendre ces opérateurs, le lecteur doit bien connaître les différents codages des
entiers en machine (binaire pur, binaire signé, complément à deux) car les entiers C# sont codés
en complément à deux et la manipulation bit à bit nécessite une bonne compréhension de ce
codage.
Afin que le lecteur se familiarise bien avec ces opérateurs de bas niveau nous détaillons un
exemple pour chacun d'entre eux.
soit à représenter le nombre -14 dans la variable i de type int (entier signé sur 32 bits)
codage de |-14|= 14
complément à 1
addition de 1
• Etude de l'instruction : j = ~ i
j = ~ i ; // complémentation des bits de i
~i
Tous les bits 1 sont transformés en 0 et les bits 0 en 1, puis le résultat est stocké dans j qui
contient la valeur 13 (car 000...01101 représente +13 en complément à deux).
~ i >> 2
Tous les bits sont décalés 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 libérés (ici le bit 31 et le bit 30), puis le résultat est stocké dans j qui contient la
valeur -4 (car 1111...11100 représente -4 en complément à deux).
~ i << 2
Tous les bits sont décalés 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 libérés (ici
le bit 0 et le bit 1), puis le résultat est stocké dans j contient la valeur -56 (car 11...1001000
représente -56 en complément à deux).
using System;
namespace CsAlgorithmique
{
class AppliOperat_Arithme
{
static void Main(string[ ] args)
{
int x = 4, y = 8, z = 3, t = 7, calcul ;
calcul = x * y - z + t ;
System.Console.WriteLine(" x * y - z + t = "+calcul);
calcul = x * y - (z + t) ;
System.Console.WriteLine(" x * y - (z + t) = "+calcul);
calcul = x * y % z + t ;
System.Console.WriteLine(" x * y % z + t = "+calcul);
calcul = (( x * y) % z ) + t ;
System.Console.WriteLine("(( x * y) % z ) + t = "+calcul);
calcul = x * y % ( z + t ) ;
System.Console.WriteLine(" x * y % ( z + t ) = "+calcul);
calcul = x *(y % ( z + t ));
System.Console.WriteLine(" x *( y % ( z + t)) = "+calcul);
}
}
}
using System;
namespace CsAlgorithmique
{
class AppliOperat_BitBoole
{
static void Main(String[ ] args)
{
int x, y, z ,t, calcul=0 ;
x = 4; // 00000100
y = -5; // 11111011
z = 3; // 00000011
t = 7; // 00000111
calcul = x & y ;
System.Console.WriteLine(" x & y = "+calcul);
calcul = x & z ;
System.Console.WriteLine(" x & z = "+calcul);
calcul = x & t ;
System.Console.WriteLine(" x & t = "+calcul);
calcul = y & z ;
System.Console.WriteLine(" y & z = "+calcul);
calcul = x | y ;
System.Console.WriteLine(" x | y = "+calcul);
calcul = x | z ;
System.Console.WriteLine(" x | z = "+calcul);
calcul = x | t ;
System.Console.WriteLine(" x | t = "+calcul);
calcul = y | z ;
System.Console.WriteLine(" y | z = "+calcul);
calcul = z ^ t ;
System.Console.WriteLine(" z ^ t = "+calcul);
System.Console.WriteLine(" ~x = "+~x+", ~y = "+~y+", ~z = "+~z+", ~t = "+~t);
}
}
}
x&y =0 x|z =7
x&z =0 x|t =7
x&t =4 y | z = -5
y&z =3 z^t =4
x | y = -1 ~x = -5, ~y = 4, ~z = -4, ~t = -8
using System;
namespace CsAlgorithmique
{
class AppliOperat_BitDecalage
{
static void Main(String[ ] args)
{
int x,y, calcul = 0 ;
x = -14; // 1...11110010
y = x;
calcul = x >>2; // 1...11111100
System.Console.WriteLine(" x >> 2 = "+calcul);
calcul = y <<2 ; // 1...11001000
System.Console.WriteLine(" y <<2 = "+calcul);
uint x1,y1, calcul1 = 0 ;
x1 = 14; // 0...001110
y1 = x1;
calcul1 = x1 >> 2; // 0...000011
System.Console.WriteLine(" x1 >> 2 = "+calcul1);
calcul1 = y1 <<2 ; // 0...00111000
System.Console.WriteLine(" y1 <<2 = "+calcul1);
}
}
}
using System;
namespace CsAlgorithmique
{
class AppliOperat_Boole
{
static void Main(String[ ] args)
{
int x = 4, y = 8, z = 3, t = 7, calcul=0 ;
bool bool1 ;
bool1 = x < y;
System.Console.WriteLine(" x < y = "+bool1);
bool1 = (x < y) & (z == t) ;
System.Console.WriteLine(" (x < y) & (z = = t) = "+bool1);
bool1 = (x < y) | (z == t) ;
System.Console.WriteLine(" (x < y) | (z = = t) = "+bool1);
bool1 = (x < y) && (z == t) ;
System.Console.WriteLine(" (x < y) && (z = = t) = "+bool1);
bool1 = (x < y) || (z == t) ;
System.Console.WriteLine(" (x < y) || (z = = t) = "+bool1);
bool1 = (x < y) || ((calcul=z) == t) ;
System.Console.WriteLine(" (x < y) || ((calcul=z) == t) = "+bool1+" ** calcul = "+calcul);
bool1 = (x < y) | ((calcul=z) == t) ;
System.Console.WriteLine(" (x < y) | ((calcul=z) == t) = "+bool1+" ** calcul = "+calcul);
System.Console.Read();
}
}
}
Une large partie de la norme ANSI du langage C est reprise dans C# , ainsi que la norme
Delphi.
• Les commentaires sur une ligne débutent par //.... comme en Delphi
Ici, nous expliquons les instructions C# en les comparant à pascal-delphi. Voici la syntaxe
d'une instruction en C#:
instruction :
instruction complète :
int a, b = 12;
{ int x , y = 8 ;
{ int z =12;
x=z;
a=x+1;
{ int u = 1 ;
y=u-b;
}
} schéma d'imbrication des 3 blocs
}
2 - l'affectation
C# est un langage de la famille des langages hybrides, il possède la notion d'instruction
d'affectation.
x=y;
// x doit obligatoirement être un identificateur de variable.
Affectation simple
L'affectation peut être utilisée 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
int a , b = 56 ; a = ??? b = 56
a = (b = 12)+8 ; a = 20 b = 12
Il s'agit plus d'un raccourci syntaxique que d'un opérateur nouveau (sa traduction en MSIL
est exactement la même : la traduction de a op= b devrait être plus courte en instructions p-
code que a = a op b).
Ci-dessous le code MSIL engendré par i = i+5; et i +=5; est effectivement identique :
Code MSIL engendré Instruction C#
IL_0077: ldloc.1
IL_0078: ldc.i4.5 i=i+5;
IL_0079: add
IL_007a: stloc.1
IL_007b: ldloc.1
IL_007c: ldc.i4.5
IL_007d: add i += 5 ;
IL_007e: stloc.1
int a , b = 56 ; a = ??? b = 56
a = -8 ; a = -8 b = 56
a += b ; a = 48 b = 56
b *= 3 ; a = 48 b = 168
Remarques :
Ci-dessous le code MSIL engendré par "table[ f(i) ] = table[ f(i) ] +9 ;" et "table[ f(i) ] += 9 ;"
n'est pas le même :
Code MSIL engendré Instruction C#
IL_008e: ldloc.3
IL_008f: ldarg.0
table[ f(i) ]
IL_0090: ldloc.1
IL_0091: call instance int32 exemple.WinForm::f(int32)
IL_0096: ldelem.i4
IL_0097: ldc.i4.s 9
table[ f(i) ] = table[ f(i) ] + 9 ; (suite)
IL_0099: add
IL_009a: stelem.i4
table[ f(i) ] += 9 ;
IL_009b: ldloc.3
IL_009c: dup
IL_009d: stloc.s CS$00000002$00000000
IL_009f: ldarg.0 table[ f(i) ]
IL_00a0: ldloc.1
IL_00a1: call instance int32 exemple.WinForm::f(int32)
IL_00a6: dup
IL_00a7: stloc.s CS$00000002$00000001
IL_00a9: ldloc.s CS$00000002$00000000 +=
IL_00ab: ldloc.s CS$00000002$00000001
IL_00ad: ldelem.i4
IL_00ae: ldc.i4.s 9
table[ f(i) ] += 9 ;
IL_00b0: add
IL_00b1: stelem.i4
Au total, 14 instructions MSIL dont un seul appel :
Dans l'exemple qui précède, il y a réellement gain sur le temps d'exécution de l'instruction table[
f(i) ] += 9, si le temps d'exécution de l'appel à f(i) à travers l'instruction MSIL < call instance int32
exemple.WinForm::f(int32) > , est significativement long devant les temps d'exécution des opérations
ldloc et stloc.
En fait d'une manière générale en C# comme dans les autres langages, il est préférable 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 précédent, la simplicité consisterait à utiliser
une variable locale x et à stocker la valeur de f(i) dans cette variable :
1 - l'instruction conditionnelle
Syntaxe :
• if ( Expr ) Instr ;
Pascal-Delphi C#
var a , b , c : integer ;
int a , b , c ;
....
....
if b=0 then c := 1
if ( b = = 0 ) c =1 ;
else begin
else {
c := a / b;
c = a / b;
writeln("c = ",c);
System.Console.WriteLine ("c = " + c);
end;
}
c := a*b ;
if ((c = a*b) != 0) c += b;
if c <>0 then c:= c+b
else c = a;
else c := a
• L'instruction " if ((c = a*b) != 0) c +=b; else c = a; " contient une affectation intégrée
dans le test afin de vous montrer les possibilités de C# : la valeur de a*b est rangée
dans c avant d'effectuer le test sur c.
Pascal-Delphi C#
if Expr1 then
if ( Expr1 ) {
begin
if ( Expr2 ) InstrA ;
if Expr2 then InstrA
}
end
else InstrB
else InstrB
2 - l'opérateur conditionnel
Il s'agit ici comme dans le cas des opérateurs d'affectation d'une sorte de raccourci entre
l'opérateur conditionnel if...else et l'affectation. Le but étant encore d'optimiser le MSIL
engendré.
Syntaxe :
Exemple :
int a,b,c ;
c = a = = 0 ? b : a+1 ;
Si l'expression est true l'opérateur renvoie la première valeur, (dans l'exemple c vaut la valeur
de b)
Si l'expression est false l'opérateur renvoie la seconde valeur (dans l'exemple c vaut la valeur
de a+1).
Sémantique de l'exemple avec un if..else :
if (a = = 0) c = b; else c = a+1;
IL_0007: ldloc.0
IL_0008: brfalse.s IL_000f
IL_000a: ldloc.0 Opérateur conditionnel :
IL_000b: ldc.i4.1
IL_000c: add
IL_000d: br.s IL_0010
c = a == 0 ? b : a+1 ;
IL_000f: ldloc.1
IL_0010: stloc.2 une seule opération de stockage pour c :
IL_0010: stloc.2
IL_0011: ldloc.0
IL_0012: brtrue.s IL_0018
IL_0014: ldloc.1
IL_0015: stloc.2 Instruction conditionnelle :
IL_0016: br.s IL_001c
IL_0018: ldloc.0 if (a = = 0) c = b; else c = a+1;
IL_0019: ldc.i4.1
IL_001a: add
IL_001b: stloc.2 deux opérations de stockage pour c :
IL_0015: stloc.2
IL_001b: stloc.2
Le code MSIL engendré a la même structure classique de code de test pour les deux
instructions, la traduction de l'opérateur sera légèrement plus rapide que celle de l'instructions
car, il n'y a pas besoin de stocker deux fois le résultat du test dans la variable c (qui ici, est
représentée par l'instruction MSIL stloc.2)
3 - l'opérateur switch...case
Syntaxe :
switch :
bloc switch :
Sémantique :
• 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
immédiate du type byte, char, int, short, string ou bien enum.
Exemple de switch..case..break
Pascal-Delphi C#
char x ;
var x : char ;
....
....
switch (x)
case x of
{
'a' : InstrA;
case 'a' : InstrA ; break;
'b' : InstrB;
case 'b' : InstrB ; break;
else InstrElse
default : InstrElse; break;
end;
}
Dans ce cas le déroulement de l'instruction switch après déroutement vers le bon case, est
interrompu par le break qui renvoie la suite de l'exécution après la fin du bloc switch. Une
telle utilisation correspond à une utilisation de if...else imbriqués (donc une utilisation
structurée) mais devient plus lisible que les if ..else imbriqués, elle est donc fortement
conseillée dans ce cas.
Exemples :
switch (x+1)
{ case 11 : System.Console.WriteLine (">> case 11");
break;
case 12 : System.Console.WriteLine (">> case 12"); >> case 11
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"); >> case 12
break;
default : System.Console.WriteLine (">> default")");
break;
}
C# - switch C# - if...else
int x = 10;
case 12 : else
if (x+1= = 12)
System.Console.WriteLine (">> case 12");
break; System.Console.WriteLine (">> case 12");
default : else
System.Console.WriteLine (">> default");
System.Console.WriteLine (">> default");
break;
Bien que la syntaxe du switch …break soit plus contraignante que celle du case…of de Delphi, le
fait que cette instruction apporte commme le case…of une structuration du code, conduit à une
amélioration du code et augmente sa lisibilité. Lorsque cela est possible, il est donc conseillé de
l'utiliser d'une manière générale comme alternative à des if...then…else imbriqués.
1 - l'instruction while
Syntaxe :
Où expression est une expression renvoyant une valeur booléenne (le test de l'itération).
Sémantique :
Identique à celle du pascal (instruction algorithmique tantque .. faire .. ftant) avec le même
défaut de fermeture de la boucle.
Pascal-Delphi C#
Où expression est une expression renvoyant une valeur booléenne (le test de l'itération).
Sémantique :
L'instruction "do Instr while ( Expr )" fonctionne comme l'instruction algorithmique répéter
Instr jusquà non Expr.
Sa sémantique peut aussi être expliquée à l'aide d'une autre instruction C#, le while( ) :
Programmer objet .Net avec C#- ( rév.17.10.2007 ) - Rm di Scala page 46
do Instr while ( Expr ) <=> Instr ; while ( Expr ) Instr
Pascal-Delphi C#
do
repeat
{
InstrA ;
InstrA ;
InstrB ; ...
InstrB ; ...
until not Expr
} while ( Expr )
3 - l'instruction for(...)
Syntaxe :
Sémantique :
Une boucle for contient 3 expressions for (Expr1 ; Expr2 ; Expr3 ) Instr, d'une manière
générale chacune de ces expressions joue un rôle différent 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-après une sémantique minimale :
Expr1 ;
while ( Expr2 )
for (Expr1 ; Expr2 ; Expr3 ) Instr { Instr ;
Expr3
}
Pascal-Delphi C#
i := 10; k := i;
while (i>-450) do
for ( i = 10, k = i ;i>-450 ; k += i , i -= 15)
begin
{
InstrA ;
InstrA ;
InstrB ; ...
InstrB ; ...
k := k+i;
}
i := i-15;
end
i := n; int i, j ;
while i<>1 do
if i mod 2 = 0 then i := i div 2 for ( i = n, j ;i !=1 ; j = i % 2 == 0 ? i /=2 : i++);
else i := i+1 // pas de corps de boucle !
• Le premier exemple montre une boucle for classique avec la variable de contrôle "i"
(indice de boucle), sa borne initiale "i=1" et sa borne finale "10", le pas
d'incrémentation séquentiel étant de 1.
• Le second exemple montre une boucle toujours contrôlée par une variable "i", mais
dont le pas de décrémentation séquentiel est de -15.
• Le troisème exemple montre une boucle aussi contrôlée par une variable "i", mais dont
la variation n'est pas séquentielle puisque la valeur de i est modifiée selon sa parité ( i
% 2 == 0 ? i /=2 : i++).
Voici une boucle ne possédant pas de variable de contrôle(f(x) est une fonction déjà déclarée) :
//inverse d'une suite de caractère dans un tableau par permutation des deux
extrêmes
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 dernière boucle ce sont les variations de i et de j qui contrôlent la boucle.
Syntaxe :
Sémantique :
Une instruction break ne peut se situer qu'à l'intérieur du corps d'instruction d'un bloc switch
ou de l'une des trois itérations while, do..while, for.
Lorsque break est présente dans l'une des trois itérations while, do..while, for :
break interrompt l'exécution de la boucle dans laquelle elle se trouve, l'exécution se
poursuit après le corps d'instruction.
Explications
Si la valeur de la variable elt est présente dans le tableau table, l’expression (elt=
=table[i]) est true et break est exécutée (arrêt de la boucle et exécution de if (i = = 8)...
).
Sémantique :
Une instruction continue ne peut se situer qu'à l'intérieur du corps d'instruction de l'une des
trois itérations while, do..while, for.
Lorsque continue est présente dans l'une des trois itérations while, do..while, for :
• Si continue n'est pas suivi d'une étiquette elle interrompt l'exécution de la séquence des
instructions situées après elle, l'exécution se poursuit par rebouclage de la boucle. Elle
agit comme si l'on venait d'exécuter la dernière instruction du corps de la boucle.
• Si continue est suivi d'une étiquette elle fonctionne comme un goto (utilisation
déconseillée en programmation moderne, c'est pourquoi nous n'en dirons pas plus !)
Explications
Attention
Nous avons déjà signalé plus haut que l'équivalence suivante entre un for et un while
Expr1 ;
while ( Expr2 )
for (Expr1 ; Expr2 ; Expr3 ) Instr { Instr ;
Expr3
}
valide dans le cas général, était mise en défaut si le corps d'instruction contenait un continue.
Voyons ce qu'il en est en reprenant l'exemple précédent. Essayons d'écrire la boucle while qui
lui serait équivalente selon la définition générale. Voici ce que l'on obtiendrait :
i = 0; n = 0 ;
for ( i = 0, n = 0 ; i<8 ; i++ , k = 2*n )
while ( i<8 )
{ if ( ta[i] = = 0 ) continue ;
{ if ( ta[i] = = 0 ) continue ;
tb[n] = ta[i];
tb[n] = ta[i];
n++;
n++;
}
i++ ; k = 2*n;
}
Une boucle while strictement équivalente au for précédent pourrait être la suivante :
i = 0; n = 0 ;
for ( i = 0, n = 0 ; i<8 ; i++ , k = 2*n ) while ( i<8 )
{ if ( ta[i] = = 0 ) continue ; { if ( ta[i] = = 0 )
tb[n] = ta[i]; { i++ ; k = 2*n;
n++; continue ;
} }
tb[n] = ta[i];
n++;
i++ ; k = 2*n;
}
Avant d'utiliser les possibilités offertes par les classes et les objets en C#, apprenons à utiliser
et exécuter des applications simples C# ne nécessitant pas la construction de nouveaux objets,
ni de navigateur pour s'exécuter
Comme C# est un langage entièrement orienté objet, un programme C# est composé de
plusieurs classes, nous nous limiterons à une seule classe.
Exemple2
class Application2
{
static void Main(string[ ] args)
{ // recherche séquentielle 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.Console.WriteLine("valeur : "+elt+" pas trouvée.");
else System.Console.WriteLine ("valeur : "+elt+" trouvée au rang :"+i);
}
}
Après 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" prêt à être exécuté
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 intégrant dans une seule classe (comme nous venons de le faire avec les deux exemples
précédents) et exécutez votre programme.
Donc par la suite dans ce document lorsque nous emploierons le mot méthode sans autre adjectif,
il s'agira d'une méthode de classe, comme nos applications ne possèdent qu'une seule classe, nous
pouvons assimiler ces méthodes aux fonctions de l'application et ainsi retrouver une utilisation
classique de C# en mode application.
Attention, il est impossible en C# de déclarer une méthode à l'intérieur d'une autre
méthode comme en pascal; toutes les méthodes sont au même niveau de déclaration :
ce sont les méthodes de la classe !
Syntaxe :
corps de fonction :
• Une méthode peut renvoyer un résultat d'un type C# quelconque en particulier d'un des
types élémentaires (int, byte, short, long, bool, double, float, char...) et nous verrons
plus loin qu'elle peut renvoyer un résultat de type objet comme en Delphi. Ce mot clef
ne doit pas être omis.
En C#, ces trois modes de transmission (ou de passage) des paramètres (très semblables à Delphi)
sont implantés.
En C# tous les paramètres sont passés par défaut par valeur (lorsque le paramètre est un objet,
c'est en fait la référence de l'objet qui est passée par valeur). Pour ce qui est de la vision
algorithmique de C#, le passage par valeur permet à une variable d'être passée comme paramètre
d'entrée.
Lors de l'appel d'un paramètre passé par référence, le mot clef ref doit obligatoirement précéder
le paramètre effectif qui doit obligatoirement avoir été initialisé auparavant :
En C# pour indiquer un passage par résultat on précède la déclaration du paramètre formel du mot
Programmer objet .Net avec C#- ( rév.17.10.2007 ) - Rm di Scala page 63
out :
static int methode1(int a , out char b) {
//.......
return a+b;
}
Lors de l'appel d'un paramètre passé par résultat, le mot clef out doit obligatoirement précéder le
paramètre effectif qui n'a pas besoin d'avoir été initialisé :
Remarque :
Le choix de passage selon les types élimine les inconvénients dûs à l'encombrement
mémoire et à la lenteur de recopie de la valeur du paramètre 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 passée par référence.
Les méthodes de type fonction en C#, peuvent renvoyer un résultat de n'importe quel type et
acceptent des paramètres de tout type.
Une méthode- fonction ne peut renvoyer qu'un seul résultat comme en Java, mais l'utilisation
des passages de paramètres par référence ou par résultat, permet aussi d'utiliser les
paramètres de la fonction comme des variables de résultats comme en Delphi.
En C# comme en Java le retour de résultat est passé grâce au mot clef return placé n'importe
où dans le corps de la méthode.
C# autorise le passage d’un nombre de paramètres variables dans une méthode, il utilise une
syntaxe spécifique pour cette opération.
params
Le mot clef params permet de spécifier que le nombre des paramètres formels d’une
méthode est variable. Ces paramètres sont obligatoirement déclarés dans un tableau.
Contrainte et utilisation
• Le mot clef params doit être le dernier de la liste des paramètres formels (aucune
déclaration de paramètre après params n’est recevable).
• Un seul mot clef params est autorisé dans la liste des paramètres formels.
• Il peut y avoir des paramètres formels en nombre fixe, en sus des paramètres en nombre
variable.
• Le passage ne peut s’effectuer que par valeur (pas de ref, pas de out)
• La liste des paramètres effectifs passés par params peut être vide.
Exemple :
class Exercice
{
public static void paramsVariable( int nbr, params int[ ] listeParam )
{
Console.WriteLine("Premier paramètre : "+nbr);
for (int i = 0 ; i < listeParam.Length; i++)
Console.WriteLine("Paramètre suivant : "+listeParam[i]);
}
Résultats de l’exécution :
Visibilité de bloc
C# est un langage à structure de blocs (comme pascal et C ) dont le principe général de
visibilité est :
Le masquage des variables n'existe que pour les variables déclarées dans des méthodes :
Il est interdit de redéfinir une variable déjà déclarée dans une méthode
soit :
int f (int x, int a ) - Dans la méthode f, elle est masquée par le paramètre
{ return 3*x-a; du même nom qui est utilisé pour évaluer l'expression
} 3*x-a.
Contrairement à ce que nous avions signalé plus haut nous n'avons pas présenté un exemple
fonctionnant sur des méthodes de classes (qui doivent obligatoirement être précédées du mot
clef static), mais sur des méthodes d'instances dont nous verrons le sens plus loin en POO.
Remarquons avant de présenter le même exemple cette fois-ci sur des méthodes de classes,
que quelque soit le genre de méthode la visibilité des variables est identique.
static int g (int x ) - Elle est visible dans la méthode g et dans la méthode f.
{ return 3*x-a; C'est elle qui est utilisée dans la méthode g pour évaluer
} l'expression 3*x-a.
static int f (int x, int a ) - Dans la méthode f, elle est masquée par le paramètre
{ return 3*x-a; du même nom qui est utilisé pour évaluer l'expression
} 3*x-a.
Les variables définies dans une méthode (de classe ou d'instance) suivent les règles classiques
de la visibilité du bloc dans lequel elles sont définies :
Elles sont visibles dans toute la méthode et dans tous les blocs imbriqués dans cette méthode et
seulement à ce niveau (les autres méthodes de la classe ne les voient pas), c'est pourquoi on
emploie aussi le terme de variables locales à la méthode.
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 = 9; Toutes les remarques précédentes restent valides puisque
b = 5-a+i*b; l'exemple ci-contre est quasiment identique au précédent.
} Nous avons seulement rajouté dans le bloc if la
définition d'une nouvelle variable interne a à ce bloc.
else b = 5-a+i*b;
} C# produit une erreur de compilation int b = 8, a = 9; sur
la variable a, en indiquant que c'est une redéfinition de
Programmer objet .Net avec C#- ( rév.17.10.2007 ) - Rm di Scala page 69
return 3*x-a+b; variable à l'intérieur de la méthode f, car nous avions
} déjà défini une variable a ({ int a=7;...) dans le bloc
} englobant for {...}.
Comparaison C# , java : la même classe à gauche passe en Java mais fournit 6 conflits en C#
La classe string
Le type de données String (chaîne de caractère) est une classe de type référence dans l'espace de
noms System de .NET Framework. Donc une chaîne de type string est un objet qui n'est utilisable
qu'à travers les méthodes de la classe string. Le type string est un alias du type System.String
dans .NET
Un littéral de chaîne est une suite de caractères entre guillemets : " abcdef " est un exemple
de littéral de String.
Toutefois un objet string de C# est immuable (son contenu ne change pas)
• Etant donné que cette classe est très utilisée les variables de type string bénéficient d'un statut
d'utilisation aussi souple que celui des autres types élémentaires par valeurs. On peut les
considérer comme des listes de caractères Unicode numérotés de 0 à n-1 (si n figure le nombre
de caractères de la chaîne).
Déclaration d'une variable String avec String str1 = " abcdef ";
initialisation
Instancie un nouvel objet : "abcdef "
Remarque
En fait l'opérateur [ ] 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 compréhension de C#, qu'il est possible d'accéder en
lecture seulement à chaque caractère d'une chaîne, mais qu'il est impossible de modifier un
caractère grâce à l'indexeur.
char car = ch1[7] ; // l'indexeur renvoie le caractère '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 !!
Attention :
Les méthodes d'insertion, suppression, etc…ne modifient pas la chaîne objet qui invoque la
méthode mais renvoient un autre objet de chaîne différent, obtenu après action de la
méthode sur l'objet initial.
char [ ] tCarac ;
tCarac = str1.ToCharArray( ) ;
tCarac = "abcdefghijk".ToCharArray( );
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 différent de ch");
System.Object
|__System.Convert
méthode de classe static :
Le code suivant est correct, il permet de stocker un caractère char dans une string :
char car = 'r';
string s;
s = Convert.ToString (car);
Remarque :
La classe Convert contient un grand nombre de méthodes de conversion de types.
Microsoft indique que cette classe : "constitue une façon, indépendante 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 à différentes techniques
pour la conversion des types de données, la classe Convert assure que toutes les
conversions communes sont disponibles dans un format générique."
2°) On peut concaténer avec l'opérateur +, des char à une chaîne string déjà existante et affecter
le résultat à une String :
string s1 , s2 ="abc" ;
char c = 'e' ;
s1 = s2 + 'd' ;
s1 = s2 + c ;
Car il faut qu'au moins un des deux opérandes de l'opérateur + soit du type string :
Le littéral 'e' est de type char,
Le littéral "e" est de type string (chaîne ne contenant qu'un seul caractère)
Pour plus d'information sur toutes les méthodes de la classe string consulter la documentation
de .Net framework.
Chaque dimension d'un tableau en C# est définie par une valeur ou longueur, qui est un nombre ou
une variable N entier (char, int, long,…) dont la valeur est supérieur ou égal à zéro. 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 numérotées de 0 à n :
• Il n'y a pas de mot clef spécifique pour la classe tableaux, mais l'opérateur symbolique
[ ] indique qu'une variable de type fixé est un tableau.
• La taille d'un tableau doit obligatoirement avoir été définie avant que C# accepte que
vous l'utilisiez !
Les tableaux de C# sont des objets d'une classe dénommée Array qui est la classe de base
d'implémentation des tableaux dans le CLR de .NET framework (localisation :
System.Array) Cette classe n'est pas dérivable pour l'utilisateur : "public abstract class
Array : ICloneable, IList, ICollection, IEnumerable".
C'est en fait le compilateur qui est autorisé à implémenter une classe physique de tableau. Il
faut utiliser les tableaux selon la démarche ci-dessous en sachant que l'on dispose en plus des
propriétés et des méthodes de la classe Array si nécessaire (longueur, tri, etc...)
Voici une image de ce qui est présent en mémoire centrale après ces instructions :
Exemple :
int [ ] table1 = {17,-9, 4, 3, 57};
int taille;
taille = table1.Length; // taille = 5
Attention
Il est possible de déclarer une référence de tableau, puis de l'initialiser après uniquement ainsi :
int [ ] table1 ; // crée une référence table1 de type tableau de type int
table1 = new int {17,-9, 4, 3, 57}; // instancie un tableau de taille 5 éléments reférencé par table1
…
table1 = new int {5, 0, 8}; // instancie un autre tableau de taille 3 éléments reférencé par table1
…
table1 = new int {1, 2, 3, 4}; // instancie un autre tableau de taille 3 éléments reférencé par table1
Les cellules du tableau précédent sont perdues dès que l'on instancie table1 vers un nouveau
tableau, leurs emplacements en mémoire centrale sont rendus au système de gestion de la
mémoire (Garbage Collector).
table2[0] = '?' ;
table2[4] = 'a' ;
table2[14] = '#' ; <--- est une erreur de dépassement de la taille
for (int i = 0 ; i<= table2.Length-1; i++)
table2[i] =(char)('a'+i);
// après la boucle: table2 = {'a', 'b', 'c' ,'d', 'e', 'f'}
Remarque :
Dans une classe exécutable la méthode Main reçoit en paramètre un tableau
de string nommé args qui correspond en fait aux éventuels paramètres de
l'application elle-même:
static void Main(string [ ] args)
table2 = table1
Toute modification sur table1 ou table2 a lieu sur les mêmes cellules :
Instructions Image en mémoire centrale
int [ ]table1 = new int [9];
table2 = table1;
Donc table1(4) vaut aussi "-1", car il pointe vers le même tableau
qui ne se trouve qu'en un seul exemplaire en mémoire centrale.
table1.CopyTo(table2,0);
Ce schéma montre bien qu'un tel tableau T est constitué de tableaux unidimensionnels, les
tableaux composés de cases blanches contiennent des pointeurs (références). Chaque case blanche
est une référence vers un autre tableau unidimensionnel, seules les cases grisées contiennent les
informations utiles de la structure de données : les éléments de même type du tableau T.
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];
Etc….
Attention
Dans le cas d'un tableau déchiqueté, le champ Length de la classe Array, contient la taille du sous-tableau uni-
dimensionnel associé à la référence.
Soit la déclaration :
Soit la déclaration :
C# initialise les tableaux par défaut à 0 pour les int, byte, ... et à null pour les objets.
On peut simuler une matrice avec un tableau déchiqueté dont tous les sous-tableaux ont
exactement la même 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 ci-
dessous ne sera pas compilé par C# :
Conseil
L'exemple précédent montre à l'évidence que si l'on souhaite réellement utiliser des
matrices en C#, il est plus simple d'utiliser la notion de tableau multi-dimensionnel
[ , ] que celle de tableau en escalier [ ] [ ].
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 différent de t2");
Après exécution on obtient :
t1<>t2
t1 différent de t2
Ces deux objets (les tableaux) sont différents (leurs références pointent vers des blocs
différents)bien que le contenu de chaque objet soit le même.
public static void Copy ( Array t1 , Array t2, int long) : méthode de classe qui copie dans un
tableau t2 déjà existant et déjà instancié, long éléments du tableau t1 depuis son premier élément
(si l'on veut une copie complète du tableau t1 dans t2, il suffit que long représente le nombre total
d'éléments soit long = t1.Length).
Attention
Dans le cas où le tableau t1 contient des références qui pointent vers des objets :
la recopie dans un autre tableau à travers les méthode Clone ou Copy ne recopie que les
références, mais pas les objets pointés, voici un "clone" du tableau t1 de la figure précédente dans
le tableau t2 :
Code source d'utilisation de ces deux méthodes sur un tableau unidimensionnel et sur une
matrice :
//-- tableau à une dimension :
Les instructions itératives for( …), while, do…while précédemment vues permettent le parcours
d'un tableau élément par élément à travers l'indice de tableau. Il existe une instruction d'itération
spécifique foreach…in qui énumère les éléments d'une collection, en exécutant un ensemble
d'actions pour chaque élément de la collection.
Syntaxe
Donc tout objet de cette classe (un tableau) est susceptible d'être parcouru par un instruction
foreach…in. Mais les éléments ainsi parcourus ne peuvent être utilisés qu'en lecture, ils ne
peuvent pas être modifiés, ce qui limite d'une façon importante la portée de l'utilisation d'un
foreach…in.
Lorsque T est un tableau multi-dimensionnel microsoft indique : … les éléments sont parcourus
de manière que les indices de la dimension la plus à droite soient augmentés en premier, suivis de
ceux de la dimension immédiatement à gauche, et ainsi de suite en continuant vers la gauche.
Dans l'exemple ci-après où une matrice table est instanciée 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
Avantage : la simplicité d'écriture, toujours la même quelle que soit le type du tableau.
Inconvénient : on ne peut qu'énumérer en lecture les éléments d'un tableau.
L'espace de noms System.Collections contient des interfaces et des classes qui permettent de
manipuler des collections d'objets. Plus précisément, les données structurées classiques que l'on
utilise en informatique comme les listes, les piles, les files d'attente,… sont représentées dans .Net
framework par des classes directement utilisables du namespace System.Collections.
Quatre interfaces de cet espace de noms jouent un rôle fondamental : IEnumerable, IEnumerator,
ICollection et IList selon les diagrammes d'héritage et d'agrégation suivants :
IEnumerable :
contient une seule méthode qui renvoie un énumérateur (objet de type IEnumerator) qui
peut itérer sur les élément d'une collection (c'est une sorte de pointeur qui avance dans la
collection, comme un pointeur de fichier se déplace sur les enregistrements du fichier) :
public IEnumerator GetEnumerator( );
IEnumerator :
Propriétés
public object Current {get;} Obtient l'élément en cours pointé actuellement par l'énumérateur dans la
collection.
Méthodes
public bool MoveNext( ); Déplace l'énumérateur d'un élément il pointe maintenant vers l'élément
suivant dans la collection (renvoie false si l'énumérateur est après le dernier élément de la collection sinon
renvoie true).
public void Reset( ); Déplace l'énumérateur au début de la collection, avant le premier élément (donc si
l'on effectue un Current on obtiendra la valeur null, car après un Reset( ), l'énumérateur ne pointe pas devant
le premier élément de la collection mais avant ce premier élément !).
public object SyncRoot {get;} Fournit un objet qui peut être utilisé pour synchroniser
(verrouiller ou déverrouiller) l'accès à ICollection.
Méthode
public void CopyTo ( Array table, Copie les éléments de ICollection dans un objet de type
int index) Array (table), commençant à un index fixé.
IList :
Propriétés
public bool IsFixedSize {get;} : indique si IList est de taille fixe.
public bool IsReadOnly {get;} : indique si IList est en lecture seule.
Les classes implémentant l'interface IList sont indexables par l'indexeur [ ].
public bool Contains( object elt ); Indique si IList contient l'élément elt en son sein.
public int IndexOf( object elt ); Indique le rang de l'élément elt dans IList.
Ce qui nous donne après exécution 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];
public virtual void Insert(int index, object value); Insère un élément dans ArrayList à l'index spécifié.
PROPRIETE
ArrayList liste ;
…
object obj;
….
int k = liste.Add( obj );
…
object x = liste[k] ;
…
liste[k] = new object() ;
Voici un exemple simple de vecteur de chaînes utilisant quelques unes des méthodes précédentes :
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 ] );
}
Rappelons qu'une liste linéaire (ou liste chaînée) est un ensemble ordonné d'éléments de même
type (structure de donnée homogène) auxquels on accède séquentiellement. Les opérations
minimales effectuées sur une liste chaînée sont l'insertion, la modification et la suppression d'un
élément quelconque de la liste.
Les listes peuvent être uni-directionnelles, elles sont alors parcourues séquentiellement dans un
seul sens :
ou bien bi-directionnelles dans lesquelles chaque élément possède deux liens de chaînage, l'un sur
l'élément qui le suit, l'autre sur l'élément qui le précède, le parcours s'effectuant en suivant l'un ou
l'autre sens de chaînage :
La classe ArrayList peut servir à une implémentation de la liste chaînée uni ou bi-directionnelle;
un ArrayList contient des éléments de type dérivés d'Object, la liste peut donc être hétérogène, cf
exercice sur les listes chaînées.
Une méthode de hachage calcule la place d'un élément dans une structure de données de collection à partir
de sa clef. La recherche d'un élément grâce à sa clef est une recherche "associative". Nous nommons
Dictionnaire une telle collection.
TAD Dictionnaire
Utilise : EnsClé, EnsElement
Champs : (a1,.....,an) suite finie dans EnsElement
Opérations :
Clef : EnsElement Æ EnsClé
Ajouter : EnsElement x Collection Æ Collection
Supprimer : EnsClé x Collection Æ Collection
Rechercher : EnsClé x Collection Æ EnsElement
Fin TAD Dictionnaire
Comme la place d'un élément dans la collection Dictionnaire est obtenue à partir d'une fonction de calcul
sur la clef de cet élément (fonction dite de hachage ou hashcode), l'idéal serait que cette fonction de
hachage soit bijective afin d'avoir une place unique par chaque clef d'élément de la collection.
Toutefois dans la réalité le nombre des éléments du Dictionnaire est très grand et si en mémoire externe
(disque dur) il n' y a pas de problème de place pour ranger ces éléments, il n'en n'est pas de même en
mémoire centrale où la place est plus restreinte.
Par exemple, ranger des clients selon leur clef = nom + prénom, soit sur un maximum de 30 lettres, ce qui
nous donne 3026 possibilités de clefs différentes (de l'ordre de 1038 clefs), il n'est donc pas possible d'utiliser
une structure de donnée statique pour réserver une place en mémoire centrale pour chaque clef. Nous allons
définir une structure classique pour stocker ces données associatives.
Table de hachage
On décide de découper l'ensemble des éléments en N sous-ensembles (on fixe par exemple N=1000) et de
définir une structure de donnée statique de type table dont chaque cellule accède à un sous-ensemble de la
collection, cette table se dénomme table de hachage.
On construit une fonction de hachage que nous nommons Hashcode et qui à chaque clef d'un élément de
Pour toute clef k ∈ EnsClé , l'entier Hashcode( k ) est appelé la valeur de hachage ou code de hachage
primaire de cette clef.
TAD Dictionnaire
Utilise : EnsClé, EnsElement
Champs : (a1,.....,an) suite finie dans EnsElement
Opérations :
Clef : EnsElement Æ EnsClé
Hashcode : EnsClé Æ [0 , N-1]
Ajouter : EnsElement x Collection Æ Collection
Supprimer : EnsClé x Collection Æ Collection
Rechercher : EnsClé x Collection Æ EnsElement
Fin TAD Dictionnaire
Collision primaire
La fonction Hashcode ( Clef ( element ) ) n'est pas bijective et même pas injective, puisque plusieurs
éléments distincts peuvent avoir le même code de hachage primaire.
On dit qu'il y a collision entre deux éléments elt_1 et elt_2 lorsqu'ils sont dans le même compartiment (ils
appartiennent au même sous-ensemble) :
Hashcode ( Clef ( elt_1 ) ) = Hashcode ( Clef ( elt_2 ) )
et
Clef ( elt_1 ) ≠ Clef ( elt_2 )
Ce qui veut dire que la clef d'un élément est insuffisante pour accéder aux informations sur l'élément, car la
collision fait que plusieurs éléments sont situés dans la même zone associé au hashcode de cette clef, il est
nécessaire de résoudre le problème posé par cette collision.
Les éléments en collision (dans le même sous-ensemble de hachage) sont rangés sous forme de liste
chaînée.
Les algorithmes ajouter, supprimer et rechercher un élément x, sont semblables à ceux qui permettent de
manipuler les listes chaînées et sont proportionnels au pire, au nombre d'éléments en collision. Ils sont
précédés par un accès fonctionnel direct dans la table à l'aide de Hashcode ( Clef ( x ) ).
Ajouter
Soit à insérer l'élément x entre l'élément t et l'élément u dans le compartiment de collision contenant les
éléments a, t, u, b et q.
Rechercher
Soit à rechercher l'élément b dans le compartiment de collision contenant les éléments a, t, u, b et q, par
une recherche linéaire classique.
Remarque
Des combinaisons différentes ou identiques de lettres peuvent donner le même hashcode.
ravale et avaler ont le même hashcode, car ces deux chaînes sont constituées des mêmes
lettres mais dans un autre ordre.
Il faut que N soit assez grand pour ne pas avoir des listes trop grandes et que les valeurs
obtenus à partir des clefs soit le plus possible équiréparties.
Représente une collection de paires clé/valeur qui sont organisées en fonction du code de hachage de la clé,
cette classe assure automatiquement la gestion des collisions.
Chaque élément est une paire clé/valeur stockée dans un objet DictionaryEntry. Une clé ne peut pas être
référence Null (Nothing en Visual Basic), contrairement à une valeur.
Signature Description
La table de hachage
public virtual object this [ object key ] Rechercher un élément par sa clef
Obtient ou définit la valeur associée à la clé spécifiée. unique.
public virtual void Add ( object key, object value ) Ajouter un élément et sa clef unique.
Ajoute un élément avec la clé et la valeur spécifiées dans
Hashtable.
Exercice :
Construire une classe ClasseHash dérivant de Hashtable permettant de stocker des informations
sur des objets de type Etudiant :
ICloneable Contrat d'une seuleméthode pour cloner (recopier entièrement) un objet : object clone( )
Cette classe n'est pas utile pour la gestion d'une liste chaînée classique non rangée à cause de son
tri automatique selon les clefs.
DictionaryEntry Défini un unique couple (clef, valeur) qui peut être stocké ou retrouvé.
public struct DictionaryEntry
SortedList liste ;
….
liste.Add( objKey, objValue) ;
....
object x = liste[objKey ] ;
....
liste[objKey ] = new object( ) ;
2°) Le couple ( une cellule de Keys , la cellule associée dans Values ) peut
aussi être considéré comme un DictionaryEntry = (Key ,Value), le
SortedList peut alors être considéré aussi comme une collection d'objets de
type DictionaryEntry :
Dans ce cas l'accès aux éléments d'un SortedList peut aussi s'effectuer avec
une boucle foreach sur chaque DictionaryEntry contenu dans le
SortedList.
Les principales méthodes permettant de manipuler les éléments d'un SortedList sont :
public virtual void CopyTo( Array array, Copie les éléments du SortedList dans une instance
int arrayIndex ); Array array unidimensionnelle à l'index arrayIndex
spécifié (valeur de l'index dans array où la copie
public virtual object GetByIndex( int index ); Obtient la valeur à l'index spécifié de la liste SortedList.
public virtual object GetKey( int index ); Obtient la clé à l'index spécifié de SortedList.
PROPRIETE
ICloneable Contrat d'une seuleméthode pour cloner (recopier entièrement) un objet : object clone( )
La classe "public class Stack : ICollection, IEnumerable, ICloneable" représente une pile Lifo :
public virtual object Peek ( ); Renvoie la référence de l'objet situé au sommet de la pile.
public virtual object Pop( ); Dépile la pile (l'objet au sommet est enlevé et renvoyé)
public virtual void Push( object elt ); Empile un objet au sommet de la pile.
public virtual object [ ] ToArray( ); Recopie toute la pile dans un tableau d'objet depuis le sommet jusqu'au
fond de la pile (dans l'ordre du dépilement).
ICloneable Contrat d'une seuleméthode pour cloner (recopier entièrement) un objet : object clone( )
La classe "public class Queue : ICollection, IEnumerable, ICloneable" représente une file Fifo :
public virtual object Peek ( ); Renvoie la référence de l'objet situé au sommet de la file.
public virtual object Dequeue( ); L'objet au début de la file est enlevé et renvoyé.
public virtual void Enqueue ( object elt ); Ajoute un objet à la fin de la file.
public virtual object [ ] ToArray( ); Recopie toute la file dans un tableau d'objet depuis le début de la fifo
jusqu'à la fin de la file.
En effet la méthode 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# réagit comme Java, en acceptant la compilation, mais en générant 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 différents !
Il nous faut donc construire une méthode ToArray qui effectue le transtypage de chaque cellule du
tableau d'object et renvoie un tableau de string, or nous savons que la méthode de classe Array
nommée Copy un tableau t1 vers un autre tableau t2 en effectuant éventuellement le transtypage
des cellules : Array.Copy(t1 , t2 , t1.Length)
Nous avons mis le qualificateur new car cette méthode masque la méthode mère de la classe
Stack, nous avons maintenant une pile Lifo de string.
Construisons d'une manière identique à la construction précédente, une file de string possédant
une méthode getArray permettant d'ajouter immédiatement dans la file tout un tableau de string et
la méthode ToArray redéfinie :
Nous livrons immédiatement le code source de cette classe et celui de la classe d'appel :
class Class
class Fifo : Queue {
{ static void Main ( string[ ] args ) {
public virtual void getArray ( string[ ] t ) { string [ ] t1 = {"aaa","bbb","ccc","ddd","eee","fff","fin"};
foreach(string s in t) string [ ] t2 ;
this. Enqueue (s); Fifo filFifo = new Fifo ( );
} filFifo.getArray(t1);
public new virtual string [ ] ToArray ( ){ t2 = filFifo.ToArray( ); aaa
string[ ] t = new string [this.Count]; foreach (string s in t2) bbb
Array.Copy( base.ToArray( ), t , this.Count ); System.Console.WriteLine(s); ccc
return t ; System.Console.ReadLine(); ddd
} } eee
} fff
fin
CollectionBase
Il existe une classe abstraite de gestion d'une collection d'objets nommée CollectionBase, elle
est située dans le namespace System.Collections.
La classe CollectionBase :
Interface Description
IList Représente une collection dont chaque élément est accessible par un index.
Cette structure peut être atteinte soit comme un ArrayList, soit comme un IList.
Si l'on souhaite construire une collection personnalisée bénéficiant des fonctionnalités de base
offertes par .Net, il faut hériter de la classe CollectionBase :
class MaCollection : CollectionBase { … }
DictionaryBase
Il existe une classe abstraite de gestion d'une collection d'objets rangés sous forme de de
dictionnaire (paire de valeur <clef,valeur>), nommée DictionaryBase, elle est située dans le
namespace System.Collections.
Dans ce cas l'accès aux éléments d'un MonDictionnaire peut s'effectuer avec une boucle foreach
sur chaque DictionaryEntry contenu dans le MonDictionnaire.
foreach (DictionaryEntry couple in this ) {
couple.Key.... (accès à la clef)
couple.Value....(accès à la valeur associée à la clef)
}
La propriété Dictionary permet plus de manipulations sur les données puisqu'elle possède les
méthodes Add, Contains, Remove et Clear qui agissent directement sur les données de l'objet de
classe MonDictionnaire.
class Principale
{
public static void Main(string[] args)
{
MonDictionnaire dico = new MonDictionnaire();
dico.Dictionary.Add(10, "rmd1");
dico.Dictionary.Add(30, "rmd2");
dico.Dictionary.Add(20, "rmd3");
dico.Dictionary.Add(50, "rmd4");
dico.Dictionary.Add(40, "rmd5");
dico.afficherDico();
dico.afficherMe();
Console.ReadLine();
}
}
Résultats obtenus :