Sommaire | |
Le langage | |
§ | Les variables et les constantes |
§ | Les types de base et les déclarations |
§ | Les tableaux |
§ | Les objets |
§ | Les classes |
§ | Les fonctions |
§ | Propriétés et indexeurs |
§ | Héritage de classes |
§ | Classe abstraite |
§ | Les exceptions |
Programmation avancée | |
§ | Surcharge des opérateurs |
Sections [masquer] 1Les variables o 1.1Déclarationo 1.2Utilisation |
o 1.3Portée d'une variableo 1.4Durée de vie d'une variableo 1.5Variable en lecture seule 2Les constantes o 2.1Déclarationo 2.2Types des constantes o 2.3Portée et durée de vie d'une constante |
Une variable réserve une place en mémoire pour stocker des données : résultats d'expressions, données lues depuis un fichier, Elle est associée à un nom. [modifier]Déclaration
Une variable possède un nom et un type. Ce type détermine ce que peut stocker la variable : un nombre, une chaîne de caractère, un objet d'une classe particulière
La syntaxe est la suivante :
type nom [ = expression ] ;
Exemples :
double prix_unitaire; int quantite = 50; string article = "Pommes"; bool rabais = false;
Assigner une variable (initialiser) à la déclaration n'est pas obligatoire. Dans ce cas, elle possède la valeur par défaut correspondant au type :
§ la valeur par défaut des types numériques et caractères (char) est zéro (0),
§ la valeur par défaut du type booléen (bool) est false,
§ la valeur par défaut des types références (objet, chaîne de caractère, tableaux) est null, § la valeur par défaut des types énumérés (enum) est celle qui correspond au type sous-jacent (int par défaut).
Il est possible de regrouper la déclaration de plusieurs variables du même type en utilisant une virgule pour séparer les différentes variables.
Exemple :
int quantite = 50, nombre, quantite_en_stock = 100 ;
Ou en plaçant une variable par ligne pour plus de clarté :
int quantite = 50, nombre,
quantite_en_stock = 100 ; [modifier]Utilisation
Une variable est utilisée pour stocker des résultats intermédiaires, des données qui serviront ultérieurement. La valeur d'une variable peut être modifiée autant de fois que nécessaire.
Exemple :
prix_unitaire = 1.50; if (rabais) prix_unitaire = prix_unitaire - 0.20; double prix_total = prix_unitaire * quantite ; [modifier]Portée d'une variable
La portée d'une variable est l'ensemble des emplacements dans le code où cette variable est accessible. En dehors de cette portée, l'utilisation du même nom correspondra à une autre variable ou plus souvent à aucune variable (erreur de compilation dans ce cas).
La portée d'une variable correspond au bloc d'accolades où elle est déclarée. Une variable membre d'une classe est déclarée au niveau de la classe et est accessible depuis les autres membres de la classe (méthodes, indexeurs, propriétés, ). Une variable locale est déclarée à l'intérieur d'un bloc d'accolades (celui de la méthode/propriété membre, ou un bloc interne à celle-ci).
Exemple :
public class Exemple
{
// variable membre de la classe :
int nombre = 5; // portée : la classe Exemple
public int MethodeUne()
{
// variable locale à la méthode : int nombre = 10; // portée : la méthode MethodeUne()
return nombre + // la variable locale this.nombre; // la variable membre
// retourne 10 + 5 , c'est à dire 15
} public int MethodeDeux()
{
System.Console.WriteLine("Nombre = " + nombre); // la variable membre
// Nombre = 5
{ int nombre = 20; // variable locale au bloc d'accolades System.Console.WriteLine("Nombre = " + nombre); // la variable locale
// Nombre = 20
} // la variable locale est hors de portée
System.Console.WriteLine("Nombre = " + nombre); // la variable membre
// Nombre = 5
} }
La durée de vie d'une variable est la période durant laquelle une variable existe, c'est à dire conserve son emplacement en mémoire et durant laquelle elle peut donc stocker des données. [modifier]Variable en lecture seule
Une variable peut être déclarée en lecture seule en utilisant le mot-clé readonly.
Exemple :
readonly double taux_tva = 19.6;
Il n'est pas obligatoire d'initialiser une variable en lecture seule lors de sa déclaration. Ce qui permet de déterminer la valeur en fonction d'autres données, ou de lire sa valeur depuis un fichier par exemple, et d'empêcher sa modification après affectation.
Exemple :
class Facture
{ public readonly double taux_tva; public Facture(bool taux1)
{ if (taux1) taux_tva = 19.6; else taux_tva = 5.5;
} }
Une constante nommée est associée à une valeur pour toute la durée de l'application. Sa valeur ne peut changer.
La syntaxe est similaire à celle de la déclaration d'une variable, excepté que le mot clé const précède la déclaration, et que l'initialisation à la déclaration est obligatoire :
const type nom = expression ;
Exemple :
const double prix_unitaire_unique = 1.50 ; const double taux_euro_en_francs = 6.55957 ;
Comme les variables il est également possible de regrouper la déclaration de plusieurs constantes du même type :
const double prix_unitaire_unique = 1.50 , taux_euro_en_francs = 6.55957 ;
N.B.: Une constante est implicitement statique. Il est donc inutile d'ajouter le mot-clé static, sinon le compilateur génère une erreur.
Les mêmes règles que celles pour les variables s'appliquent également aux constantes.
En C#, il existe deux catégories de type :
§ Les types valeurs qui stockent directement la valeur des données (un entier, une chaîne de caractères, une structure),
§ Les types références qui stockent une référence vers la valeur des données (un tableau, un objet, une interface). Les variables de ce type se distinguent par le fait qu'elles valent initialement null et qu'il faut explicitement leur allouer de l'espace mémoire avec new. Les données référencées par ces variables sont donc soumises au garbage collector quand plus aucune référence n'existe, afin de libérer la place mémoire occupée.
Un type de base est un type simple valeur.
[masquer]
1 Liste des types de base
2 Syntaxe des chaînes de caractères
3 Auto-boxing/unboxing
4 Types nullable
5 Valeur par défaut
6 Obtenir le type
Type | Classe | Description | Exemples |
bool | Booléen (vrai ou faux : true ou false) | true | |
char | Caractère Unicode (16 bits) | 'A' | |
sbyte | System.SByte | Entier signé sur 8 bits (1 octet) | -128 |
byte | Entier non signé sur 8 bits (1 octet) | 255 | |
short | System.Int16 | Entier signé sur 16 bits | -129 |
ushort | System.UInt16 | Entier non signé sur 16 bits | 1450 |
int | System.Int32 | Entier signé sur 32 bits | -100000 |
uint | System.UInt32 | Entier non signé sur 32 bits | 8000000 |
long | System.Int64 | Entier signé sur 64 bits | -2565018947302L |
ulong | System.UInt64 | Entier non signé sur 64 bits | 8000000000000L |
float | System.Single | Nombre à virgule flottante sur 32 bits | 3.14F |
double | System.Double | Nombre à virgule flottante sur 64 bits | 3.14159 |
decimal | System.Decimal | 3.1415926M | |
string | System.String | Chaîne de caractères | "Exemple" "C:\\windows\\system32" @"C:\windows\system32" |
Comme illustré par l'exemple du tableau ci-dessus, une chaîne de caractères peut avoir deux syntaxes différentes :
§ Syntaxe avec caractères d'échappement : La chaîne est entourée par les guillemets et l'anti-slash introduit un caractère spécial (\n, \t ) ou empêche son interprétation (guillemet \" et antislash \\)
§ Syntaxe verbatim : La chaîne est précédée d'un caractère arobase, et l'anti-slash n'est pas interprété. Si un guillemet doit faire partie de la chaîne de caractère, il faut le doubler "".
Exemples :
"un guillemet \", un anti-slash \\, un retour à la ligne\n" @"un guillemet "", un anti-slash \, un retour à la ligne "
La syntaxe verbatim simplifie la frappe des chemins de fichiers qui utilisent le caractère anti-slash comme séparateur :
@"C:\program files\monapp\" [modifier]Auto-boxing/unboxing
Chaque type de données correspond à une classe de l'espace de nom System. La conversion entre le type et une instance de cette classe est implicite et invisible.
Ainsi, on peut appeler des méthodes sur les types simples, affecter une constante où une classe de type est attendue (et vice versa),
Exemple :
int a = 25; string message = 36.ToString(); // convertit 36 en chaîne de caractères,
// méthode définie dans la classe
System.Int32
UInt32 b = 50;
[modifier]Types nullable
Le langage C#2.0 introduit la possibilité pour les types simples de valoir null. Cette fonctionnalité permet une meilleure interopérabilité avec les bases de données qui utilisent ce type de données.
Pour qu'une variable puisse valoir null (nullable), il faut que le type soit suivi d'un point d'interrogation. Par exemple :
int? numero = null;
Il est donc possible de tester qu'une variable de ce type vaut null :
Le nouvel opérateur ?? (double point d'interrogation) permet de sélectionner l'opérande de gauche s'il ne vaut pas null, ou l'opérande de droite sinon :
valeur_ou_null??valeur_si_null
Cet opérateur est donc pratique à utiliser avec les types nullables pour obtenir une valeur par défaut :
Console.WriteLine("Numéro : "+( numero??50 ));
L'opérateur default retourne la valeur par défaut du type spécifié. Il s'agit de la valeur quand une variable de ce type n'est pas initialisée (0 pour les nombres, null pour les types références).
Exemple:
public int absolu(int? valeur)
{
if (valeur==null) return default(int); else return (valeur<0) ? -valeur : valeur; }
L'utilité de cet opérateur est plus évident avec l'utilisation detypes génériques.
L'opérateur typeof retourne une instance de la classe pour le type spécifié entre parenthèses.
Exemple:
Type t=typeof(int);
L'utilité de cet opérateur est plus évident avec l'utilisation detypes génériques.
Un tableau regroupe plusieurs données du même type dans un ensemble ordonné, indexé par un entier. En C#, comme avec la plupart des langages de programmation modernes, le premier élément porte l'index 0.
[masquer]
1 Déclaration d'un tableau
2 Allocation d'un tableau
3 Pré-initialisation
4 Accès aux éléments
5 Taille d'un tableau
6 Tableaux multi-dimensionnels
Les crochets ajoutés à la fin d'un type indique qu'il s'agit d'un tableau.
Exemple :
int[] entiers;
La variable entiers est un tableau de nombres entiers. Le nombre d'éléments du tableau n'est pas spécifié à la déclaration, mais lors de l'allocation du tableau.
Les tableaux font partie des types références. Il n'est donc pas alloué par défaut (référence null).
new type[taille]
Exemple :
int[] entiers = new int[10]; // un tableau de 10 entiers
Il est également possible de l'allouer ailleurs que dans la déclaration :
int[] entiers;
entiers = new int[10]; // un tableau de 10 entiers
Une fois la place du tableau réservée en mémoire, celui-ci ne contient que des valeurs par défaut, c'est à dire que chaque élément d'un tableau de numériques (entiers, réels, ) vaut 0, pour un tableau de bool chaque élément vaut false, et pour un tableau de références (objet, interface, tableau) chaque élément vaut null. [modifier]Pré-initialisation
Il est également possible de définir directement les valeurs que le tableau contient. Ces valeurs doivent être comprises entre des accolades et séparées par une virgule. Le compilateur détermine le nombre d'éléments à allouer d'après la liste d'éléments spécifiée à la suite de l'instruction d'allocation.
Exemples :
§ A la déclaration du tableau :
int[] entiers = new int[] { 10,15,20,25,30,35,40,45 };
§ Hors déclaration :
entiers = new int[] { 10,15,20,25,30,35,40,45 };
Seule la déclaration peut omettre l'instruction d'allocation du tableau avant les accolades :
int[] entiers = { 10,15,20,25,30,35,40,45 };
Dans ce cas, le compilateur alloue implicitement un tableau du même type que la variable déclarée, pour le nombre d'éléments placés entre les accolades.
L'accés (lecture et écriture) aux éléments du tableau se fait en utilisant le nom du tableau suivi des crochets encadrant l'index de l'élément accédé :
entiers[0] = 7; entiers[1] = 13; Console.WriteLine("1er entier = " + entiers[0] ); |
L'index spécifié est en fait une expression. Il est donc possible d'utiliser une variable comme index, par exemple, pour parcourir les éléments du tableau :
for(int i=0 ; i<2 ; i++)
Console.WriteLine("entier n°" + i + " = " + entiers[ i ] );
Console.WriteLine("Le tableau contient " + entiers.Length + " entiers :");
for(int i=0 ; i<entiers.Length ; i++) // i : index dans le tableau Console.WriteLine(" ["+i+"] = " + entiers[i] );
Si l'index n'est pas nécessaire durant le parcours du tableau, il est plus simple d'utiliser foreach :
Console.WriteLine("Le tableau contient " + entiers.Length + " entiers :");
foreach(int n in entiers) // n : élément du tableau
Console.WriteLine(" " + n );
Les tableaux vus jusqu'à présent étaient des tableaux uni-dimensionnels : ils n'ont qu'une seule dimension, c'est à dire un seul index.
Certaines applications nécessitent des tableaux à deux indices ou davantage. Par exemple, une image est représentée par un tableau de couleurs indexé par l'abscisse (x) et l'ordonnée (y).
Un tableau multi-dimensionnel utilise la même syntaxe, en séparant les indices par une virgule.
int[,] image = new int[ 32, 16 ];
// un tableau de 32*16 entiers, soit 512 entiers image[1,5] = 0;
Un tableau de tableaux (appelé jagged array ou tableau déchiqueté) utilise la syntaxe des tableaux uni-dimensionnels en utilisant un tableau comme type de base :
int[][] ensembles = new int[10][]; // un tableau de 10 tableaux d'entiers ensembles[0] = new int[11]; // dont le premier contient 11 entiers ensembles[1] = new int[] { 2, 3 }; // le deuxième : 2 entiers (2 et 3)
// ensembles[2] à ensembles[9] valent null car non alloués ensembles[1][1] = 4; // remplace la valeur 3 par 4
Chacun des sous-tableaux doit être alloué séparemment, et rien n'oblige à ce qu'ils aient tous la même dimension (d'où l'appellation de tableau déchiqueté).
Il est également possible de combiner les possibilités :
int[,][] donnees = new int[10,20][];
donnees[1,5] = new int[25];
donnees[1,5][10] = 12;
Un objet est un type référence. Il s'agit d'une instance declasse.
L'auto-boxing/unboxing permet d'utiliser le type object pour n'importe quel type de valeur :
object obj; obj = 10; // int -> Int32 -> object obj = "Une chaîne"; // string -> String -> object [modifier]Créer un nouvel objet
Créer un nouvel objet est appelé instancier une classe et utilise l'opérateur new :
classe variable=new classe(arguments );
Cet opérateur effectue les opérations suivantes :
§ allocation d'une zone mémoire d'une taille suffisante pour accueillir les attributs définis dans laclassespécifiée,
§ initialisation des attributs à leur valeur par défaut (définie dans la classe ou 0 sinon), § appel au constructeur acceptant les arguments spécifiés.
L'appel peut échouer pour plusieurs raisons :
§ la classe spécifiée n'a pas pu être chargée,
§ l'espace mémoire libre est insuffisant, § une exception a été lancée par le constructeur.
Dans tous les cas uneexceptionest lancée.
La notion de classe est à la base de la programmation orientée objet. Elle définit un type d'objet. Ce type rassemble des variables (créées pour chaque objet et appelées « attributs ») et des fonctions (appelées dans ce cas « méthodes »).
Ces attributs et méthodes sont les « membres » de la classe. Ils peuvent avoir chacun un niveau de protection différent.
Sections [masquer] 1Déclarer une classe o 1.1Membres d'une classe o 1.2Membres statiques de classe et membres d'instanceo 1.3Classe statiqueo 1.4Instance d'une classeo 1.5La référence nulle 2 Niveaux de protection 3 Constructeur o 3.1Exemple o 3.2Constructeur par défauto 3.3Constructeur statique |
La déclaration d'une classe utilise le mot clé class :
class nom_de_la_classe
{
}
Exemple :
class Fraction
{
int numerateur, denominateur;
void multiplier(Fraction autre)
{
this.numerateur *= autre.numerateur; this.denominateur *= autre.denominateur;
} }
Les membres d'une classe sont de différents types :
§ Les fonctions, appelées méthodes, traitent les données, appellent d'autres méthodes, retournent éventuellement une valeur ;
§ Les variables, appelées attributs, stockent les données ;
§ Les propriétés sont des méthodes spéciales utilisées comme des variables.
Un membre statique de classe est un membre déclaré avec le mot clé static. Il est définit une seule fois en mémoire, et il n'est pas nécessaire de créer un nouvel objet pour qu'il soit créé et utilisable.
Un membre d'instance est déclaré sans le mot clé static. Il n'est créé que pour chaque objet (une instance) créé par l'opérateur new.
Exemple :
public class UnExemple
{ int numero; // Défini pour chaque objet static int numero_suivant; // Défini pour la classe
}
Console.WriteLine("N° suivant = " + UnExemple.numero_suivant );
Console.WriteLine("N° = " + UnExemple.numero );
// Erreur de compilation, car la variable n'est pas définie pour la classe
UnExemple InstanceDUnExemple = new UnExemple();
Console.WriteLine("N° = " + InstanceDUnExemple.numero );
Une classe statique ne contient que des membres statiques, et ne peut être instanciée. Le mot clé static précède la déclaration de cette classe.
Exemple :
public static class UneClasseStatique
{
public static void Afficher(string message)
{
//
}
}
UneClasseStatique.Afficher("Un exemple de message");
Exemple :
UnExemple objetExemple; // par défaut : null// Equivaut à UnExemple objetExemple = null; objetExemple = new UnExemple();
Le mot clé new est suivi du nom de la classe et d'une liste de paramètres entre parenthèses. Il s'agit en fait d'un appel auconstructeur(abordé plus loin dans ce chapitre) initialisant l'objet créé. [modifier]La référence nulle
Toute variable de type objet est en fait une référence initialisée à null par défaut. Cette référence peut être utilisée quel que soit le type d'objet.
Il est souvent nécessaire de tester si la référence est nulle avant d'accéder à un membre de l'objet référencé. Si le test n'est pas fait et que la référence est nulle, uneexceptionest levée.
Le niveau de protection est spécifié par un mot clé placé avant la déclaration d'une classe ou d'un membre de classe (attribut, méthode, propriété, ) :
public
Accès autorisé à tous ; private
Accès depuis la classe seulement ; protected
Accès depuis la classe et ses sous-classes seulement ; internal
Accès depuis l'assembly seulement.
Exemple :
public class Fraction
{ private int numerateur, denominateur;
public void multiplier(Fraction autre)
{ }
}
En général, les règles de l'encapsulation sont les suivantes :
§ Toute méthode est publique, à moins qu'elle ne soit destinée à être exclusivement appelée dans la classe, auquel cas est peut être privée ou protégée,
§ Les attributs sont privés. Si un accès est nécessaire, il faut ajouter des méthodes d'accès («accesseurs»), voire unepropriété.
Un constructeur est une méthode spéciale portant le nom de la classe, ne retournant aucune valeur, chargée d'initialiser l'instance. Un constructeur peut avoir des paramètres permettant de définir les valeurs initiales des attributs du nouvel objet.
Pour initialiser une fraction :
public class Fraction
{ private int numerateur, denominateur;
// Constructeur public Fraction(int numerateur, int denominateur)
{
// this est utilisé pour désigner les attributs de l'objet
// plutôt que les paramètres// recopie des paramètres : this.numerateur = numerateur; this.denominateur = denominateur;
}
public void multiplier(Fraction autre)
{ }
}
Utilisation :
Fraction estimation_pi = new Fraction( 355 , 113 ); [modifier]Constructeur par défaut
Le constructeur par défaut est celui qui n'a aucun paramètre. Si aucun constructeur n'est déclaré dans une classe, le compilateur crée un constructeur par défaut qui ne fait rien, laissant les attributs à leur valeur par défaut (dépendant du type : 0 pour les numériques, null pour tous les autres).
En fait, ce constructeur par défaut appelle le constructeur sans argument (qu'il soit par défaut ou explicitement implémenté) de la classe de base (System.Object par défaut). Voir lechapitre sur l'héritage de classe. [modifier]Constructeur statique
Il est possible d'exécuter un code lors de la première utilisation de la classe, cette initialisation peut être vue comme un constructeur statique
public class Fraction{
private static int valeur;
static Fraction(){ valeur = 1;
}
}
Une fonction étant toujours déclarée dans une classe, elles sont plutôt appelées « méthodes ».
Sections [masquer] 1 Déclarer une méthode 2 Appeler une méthode 3 Passage de paramètres à une méthodeo 3.1Passage par valeuro 3.2Paramètre outo 3.3Paramètre ref § 3.3.1C# 4.0Méthode COM: ref optionnelo 3.4Nombre variable de paramètres 4 Surcharge de méthode La syntaxe de déclaration est la suivante :
Où accès est optionnel et définit le mode d'accès (public, privé ou protégé), le type de méthode (statique ou d'instance). Voirle chapitre sur les classes. type_retour spécifie le type de la valeur retournée par la fonction. Ce type peut être void (vide) pour indiquer que la fonction ne retourne aucune valeur. La fonction retourne une valeur en utilisant le mot clé return. L'instruction return interrompt l'exécution de la fonction, retourne la valeur de l'expression qui suit, et l'exécution du code ayant appelé la fonction se poursuit. La syntaxe est : return expression; Si la fonction ne retourne rien, l'instruction return n'est pas obligatoire, mais si elle est utilisée, aucune expression ne doit être spécifiée. Si la fonction ne retourne rien on appelera cette fonction une procédure Exemple : // Fonction qui retourne la concaténation // de la chaîne de caractères a à la chaîne de caractères b string Concatener(string a, string b) { return a+b; } [modifier]Appeler une méthodeL'appel à une méthode se fait en spécifiant son nom, suivi des arguments entre parenthèses : nom_methode ( expression, ) Si la fonction retourne une valeur, l'appel à celle-ci peut être utilisé dans une expression. Exemple : string c = Concatener ( "abc" , "def" ); // c vaut "abcdef" Dans cet exemple, la méthode appelée est nommée Concatener car située dans la même classe que l'appel. Cependant, toute méthode s'applique à un objet. Celui-ci doit être spécifié avant le nom de la fonction, et séparé par le caractère point ( . ). Par défaut, il s'agit de l'objet this (objet courant). [modifier]Passage de paramètres à une méthodeLes paramètres passés à une méthode peuvent être passés de trois manière différentes : § un paramètre référençant une variable pour y mettre une valeur de retour : paramètre out, § un paramètre référençant une variable pour transmettre une information et y mettre une valeur de retour : paramètre ref. [modifier]Passage par valeurIl s'agit d'un paramètre servant à transmettre une information à la méthode appelée, comme dans les exemples précédents. Si la méthode modifie sa valeur, la modification est locale à la fonction uniquement. Un paramètre normal n'est précédé d'aucun mot-clé particulier. Exemple : private void methodeTest(int nombre) { Console.WriteLine("fonctionTest : le nombre vaut " + nombre); nombre = 100; Console.WriteLine("fonctionTest : le nombre vaut " + nombre); } private void testAppel() { int n = 5; Console.WriteLine("testAppel : le nombre vaut " + n); methodeTest(n); // Appel à la méthode Console.WriteLine("testAppel : le nombre vaut " + n); // On peut passer une expression ou une constante methodeTest( 25 + 5 ); // Appel à la méthode } Ce programme affiche : testAppel : le nombre vaut 5 fonctionTest : le nombre vaut 5 fonctionTest : le nombre vaut 100 testAppel : le nombre vaut 5 fonctionTest : le nombre vaut 30 fonctionTest : le nombre vaut 100 [modifier]Paramètre outUn paramètre out ne sert à la fonction qu'à retourner une valeur. L'argument transmis doit référencer une variable ou un élément de tableau. Ce paramètre est précédé du mot-clé out à la fois lors de sa déclaration, et lors de l'appel à la méthode. La variable référencée n'a pas besoin d'être initialisée auparavant. La fonction doit obligatoirement affecter une valeur à la variable référencée. Exemple : private void methodeTest(out int resultat) { //Console.WriteLine("fonctionTest : le nombre vaut " + resultat); resultat = 100; //Console.WriteLine("fonctionTest : le nombre vaut " + resultat); } private void testAppel() Console.WriteLine("testAppel : le nombre vaut " + n); // On ne peut pas passer une expression ou une constante //methodeTest( 25 + 5 ); // <- erreur de compilation } Ce programme affiche : testAppel : le nombre vaut 100 [modifier]Paramètre refUn paramètre ref est une combinaison des deux types de paramètres précédents. L'argument transmis doit référencer une variable ou un élément de tableau qui doit être initialisé auparavant. Ce paramètre est précédé du mot-clé ref à la fois lors de sa déclaration, et lors de l'appel à la méthode. La méthode n'est pas obligée de modifier la valeur contenue dans la variable référencée. Exemple : private void methodeTest(ref int resultat) { Console.WriteLine("fonctionTest : le nombre vaut " + resultat); resultat = 100; Console.WriteLine("fonctionTest : le nombre vaut " + resultat); } private void testAppel() { int n = 5; Console.WriteLine("testAppel : le nombre vaut " + n); methodeTest(ref n); // Appel à la méthode Console.WriteLine("testAppel : le nombre vaut " + n); // On ne peut pas passer une expression ou une constante //methodeTest( 25 + 5 ); // <- erreur de compilation } Ce programme affiche : testAppel : le nombre vaut 5 fonctionTest : le nombre vaut 5 fonctionTest : le nombre vaut 100 testAppel : le nombre vaut 100 [modifier]C# 4.0 Méthode COM : ref optionnelL'appel à une méthode d'interface COM utilisant un paramètre ref peut ne pas utiliser explicitement de variable. Exemple : La méthode COM suivante : void Increment(ref int x); Peut être appelée sans le mot clé ref : Increment(0); Ce qui est équivalent à : int x = 0; Increment(ref x); Un paramètre ref est en général modifié par la méthode appelée. Ne pas utilier le mot clé ref lors de l'appel signifie que la nouvelle valeur retournée par la méthode est ignorée. string message = String.Format("A={0} et B={1}", a, b); Ce genre de méthode possède un nombre de paramètres obligatoires, comme une méthode normale, suivis d'un nombre variable de paramètres (voire aucun). Ces paramètres en nombre variable sont en fait transmis sous la forme d'untableau. Une telle méthode utilise le mot clé params pour le dernier paramètre qui doit être un tableau. Exemples de méthodes : public string MessageDeLog(string format, params object[] parametres) { return "LOG: "+String.Format(format, parametres); } public double Moyenne(params double[] nombres) { double sum=0; if (nombres.Length==0) return 0; foreach(double d in nombres) sum += d; return sum / nombres.Length; } Pour le paramètre marqué params, il est possible de transmettre soit une liste d'argument, soit un tableau contenant ces arguments. Exemples d'appels : double a = Moyenne(3.0, 2.0, 5.14, 8.22, 6.37); // Equivaut à : double b = Moyenne( new double[]{ 3.0, 2.0, 5.14, 8.22, 6.37 } ); [modifier]Surcharge de méthode Une méthode peut être surchargée (overload en anglais), c'est à dire qu'il peut exister au sein de la même classe plusieurs méthodes portant le même nom, à condition qu'elles soient différenciables par leur signature. La signature d'une méthode correspond aux types et nombre de paramètres acceptés par celle-ci. Exemple : public int Ajouter ( int valeur1, int valeur2 ) { return valeur1 + valeur2; } public double Ajouter ( double valeur1, double valeur2 ) { return valeur1 + valeur2; } Le compilateur détermine la méthode à appeler en fonction du type des arguments passés à la méthode. Exemple : Console.WriteLine("(entiers) 2 + 5 = " + Ajouter( 2, 5 )); Console.WriteLine("(réels) 2.0 + 5.0 = " + Ajouter( 2.0, 5.0 )); Exemple : Ce code contient une erreur volontaire ! Les deux méthodes ont la même signature. public double Longueur ( string chaine ) { return (double) chaine.Length; } public int Longueur ( string chaine ) { return (int) chaine.Length; } Propriétés et indexeurs
[modifier]Les propriétésUne propriété est une valeur qui peut être lue ou modifiée, comme une variable. Ces deux opérations sont en fait réalisées par les accesseurs get et set. Si l'un de ces deux accesseurs est manquant, la propriété sera alors soit en lecture seule, soit en écriture seule. [modifier]Syntaxe
set // propriété modifiée { code utilisant le paramètre prédéfini "value" } } Le code contenu dans chaque bloc est le même que celui que l'on placerait pour implémenter les méthodes suivantes :
[modifier]Exemple private string _message; public string Message { get { return _message; } set { _message = value; } } Utilisation : Message = "Test de la propriété" ; // <- accesseur set Console.WriteLine( Message ); // <- accesseur get Message += " et message ajouté"; // <- accesseurs get et set § type get_Nom_propriété() § void set_Nom_propriété(type value) La classe ne peut donc avoir de méthodes portant l'une de ces deux signatures. [modifier]Les indexeursUn indexeur est une propriété spéciale qui permet d'utiliser une instance de la classe comme untableau, en utilisant les crochets. [modifier]Syntaxe
Type_élément Type de chaque élément du tableau virtuel. Type_index Type de l'indice spécifié entre crochets. index Variable contenant la valeur de l'indice de l'élément lu ou modifié. L'index peut avoir un autre type que int. C'est le cas des tables de hashage de l'espace de nom System.Collections. [modifier]Exemple public class TableauVirtuel { public string this[int index] { // lecture seule car pas d'accesseur set get { return "Elément"+index.ToString(); } } } TableauVirtuel tab=new TableauVirtuel(); Console.WriteLine("tab[15] = " + tab[15] ); // affiche Elément15 Héritage de classesUneclassepeut hériter d'une autre, c'est à dire posséder les mêmes méthodes et attributs que celle-ci et en avoir des supplémentaires. Cette nouvelle classe est appelée « classe fille » ou « sous-classe ». Elle hérite d'une « classe mère » ou « classe de base » ou « super-classe ». Ces spécificités de la sous-classe sont les seules qui ont besoin d'être définies dans celle-ci. Les autres fonctionnalités sont héritées de la super-classe. Sections[masquer] 1 Syntaxe 2 Constructeurs 3 Référence d'instance 4 Surcharge de méthodes et de propriétéso 4.1Surcharge sans polymorphismeo 4.2Surchargeavec polymorphismeo 4.3Résumé 5 Classe sans héritière [modifier]Syntaxe La relation d'héritage est définie à la déclaration de la classe par le symbole deux-points suivi du nom de la super-classe. Exemple de super-classe : public class Vehicule { int roues, places, kilometrage; public Vehicule() { } } Exemple de sous-classe : public class Automobile : Vehicule { string couleur; public Automobile() { } } [modifier]ConstructeursLe constructeur de la sous-classe appelle toujours celui de la classe de base, implicitement ou explicitement. Si rien n'est spécifié, le compilateur génère un appel implicite au constructeur de la classe de base ne comportant aucun paramètre. C'est pourquoi ce constructeur est nommé « constructeur par défaut ». Si la classe de base ne possède aucun constructeur par défaut, ou si un autre serait plus approprié, il est possible de spécifier explicitement le constructeur à appeler. Le mot-clé base désigne la classe de base. Exemple : public class Vehicule { int roues, places, kilometrage; // Constructeur "protégé" : l'accès est limité// à cette classe et aux sous-classes protected Vehicule(int roues,int places) { this.roues = roues; this.places = places; } } public class Automobile : Vehicule { string couleur; public Automobile(string couleur) : base( 4, 5 ) // appel au constructeur de Vehicule // 4 roues et 5 places { this.couleur = couleur; } // mais permet d'illustrer qu'il est possible d'appeler un // autre constructeur de la même classe public Automobile() : this( "indéfinie" ) // couleur indéfinie par défaut { } } [modifier]Référence d'instanceUne référence à une instance de la sous-classe peut être stockée par une référence du même type : Automobile voiture = new Automobile(); Il est également possible d'utiliser une référence du type de la classe de base : Vehicule voiture = new Automobile(); [modifier]Surcharge de méthodes et de propriétésDans cette section, la surcharge de propriétés est similaire à la surcharge de méthodes. Ce paragraphe discutera de la surcharge de méthodes, mais tout ceci s'applique également aux propriétés. [modifier]Surcharge sans polymorphismeLa surcharge d'une méthode consiste à créer une méthode dans une sous-classe ayant le même nom et les mêmes types d'arguments (la même signature) qu'une méthode de la classe de base, afin de modifier son comportement. Exemple : public class Vehicule { private int poids; public Vehicule(int poids) { this.poids = poids; } public string Description() { return "Véhicule de "+poids+" tonnes"; } } public class Automobile : Vehicule { private string couleur; public Automobile(int poids,string couleur) : base(poids) { this.couleur = couleur; } // méthode surchargée public string Description() { return base.Description()+" de couleur "+couleur; } } Cependant, le compilateur génère un avertissement (warning CS0108) dans la classe dérivée. Il faut spécifier que l'on surcharge une méthode, en utilisant le mot clé new : public class Automobile : Vehicule { private string couleur; public Automobile(int poids,string couleur) : base(poids) { this.couleur = couleur; } // méthode surchargée public new string Description() } } Utilisation : Automobile voiture = new Automobile(3, "rouge"); Console.WriteLine( voiture.Description() ); // affiche : Véhicule de 3 tonnes de couleur rouge Par contre, si on utilise une référence à la classe de base Vehicule, la méthode appelée sera celle de la classe de base : Vehicule vehicule = new Automobile(3, "rouge"); Console.WriteLine( vehicule.Description() ); // affiche : Véhicule de 3 tonnes Une conversion de la référence vers la classe réelle de l'objet permet d'appeler la méthode surchargée : Console.WriteLine( ((Automobile)vehicule).Description() ); // affiche : Véhicule de 3 tonnes de couleur rouge Cet exemple n'utilise donc pas le polymorphisme, c'est à dire que pour appeler la méthode Description, le compilateur se base sur le type de la référence plutôt que sur le type réel de l'objet référencé. [modifier]Surcharge avec polymorphismeLe polymorphisme permet d'utiliser le type réel de l'objet référencé plutôt que le type de la référence pour déterminer la méthode, la propriété ou l'indexeur à utiliser. Pour cela, il faut utiliser le mot clévirtual dans la déclaration de la méthode de la classe de base. Exemple : public class Vehicule { private int poids; public Vehicule(int poids) { this.poids = poids; } public virtual string Description() { return "Véhicule de "+poids+" tonnes"; } } Et il faut utiliser le mot clé override dans la classe dérivée : public class Automobile : Vehicule { private string couleur; public Automobile(int poids,string couleur) : base(poids) { this.couleur = couleur; } // méthode surchargée public override string Description() { return base.Description()+" de couleur "+couleur; } } Utilisation : Vehicule vehicule = new Automobile(3, "rouge"); Console.WriteLine( vehicule.Description() ); // affiche : Véhicule de 3 tonnes de couleur rouge
[modifier]Classe sans héritièreLe mot-clé sealed empêche la création de classes dérivées. Cette fonctionnalité est également utile pour les classes ne déclarant que des membres statiques. Syntaxe: sealed class nom_classe Exemple: public sealed class Ferrari : Automobile { private int consommation; public Ferrari(int litre_par_km) : base(5100, "Rouge") { consommation = litre_par_km; } } Ce type de classe ne peut avoir de méthodes abstraites (abstract) ou de membres protégés (protected) car aucune classe dérivée ne pourra être créée pour les implémenter / y accéder. Classe abstraiteUne classe abstraite possède au moins uneméthodeabstraite ou unepropriétéabstraite, c'est à dire ne comportant aucune implémentation (pas de code). Une telle classe ne peut être instanciée avec l'opérateur new. Il faut utiliser une sous-classe implémentant les méthodes abstraites de la classe de base. Sections[masquer] 1 Syntaxe 2 Implémenter les méthodes abstraites 3 Propriétés abstraites 4 Mode d'accès [modifier]Syntaxe Le mot clé abstract doit précéder la classe abstraite et toutes les méthodes abstraites. Exemple : // Classe abstraite gérant un ensemble d'objets public abstract class Ensemble { public bool AjouterSiNeContientPas(object o) { if ( ! Contient(o) ) Ajouter(o); } public bool RetirerSiContient(object o) { if ( Contient(o) ) Retirer(o); } [modifier]Implémenter les méthodes abstraitesLa classe implémentant les méthodes abstraites de la classe de base doit utiliser le mot clé override, car les méthodes abstraites sont implicitement virtuelles également : public class EnsembleTableau : Ensemble { private object[] elements = new object[0]; public override bool Contient(object o) { // recherche de l'objet for(int i = 0 ; i < elements.Length ; i++) if (elements[i] == o) return true; // objet trouvé // fin de la boucle sans avoir trouvé l'objet return false; } protected override void Ajouter(object o) { // } protected override void Retirer(object o) { // } } La classe peut ne pas implémenter toutes les méthodes abstraites de la classe de base. Dans ce cas elle est également abstraite, et laisse le soin à ses sous-classes d'implémenter les méthodes abstraites restantes. [modifier]Propriétés abstraitesIl est également possible de déclarer des propriétés abstraites. Exemple : public abstract class Ensemble { public abstract int nombre { get; } public abstract string nom { get; set; } } public class EnsembleTableau : Ensemble { private string nom_ensemble = null; private object[] elements = new object[0]; public override int nombre { get { return elements.Count; } } public override string nom { get { return nom_ensemble ; } set { nom_ensemble = value ; } } } [modifier]Mode d'accèsPuisque les méthodes et propriétés abstraites doivent être implémentées par les sous-classes, il n'est pas possible de les déclarer avec le mot clé private, ou d'utiliser le mot clé sealed avec la classe. De plus les méthodes et propriétés abstraites ne peuvent être statiques. Sections [masquer] 1 Attraper une exception 2 Libérer des ressources 3 Lancer une exception 4 Créer une classe d'exception [modifier]Attraper une exceptionUn gestionnaire d'exception attrape uneclassed'exception particulière et gère le cas d'erreur correspondant. Ce gestionnaire encadre les instructions à gérer pouvant lancer une exception. La syntaxe est la suivante :
Le bloc try est suivi d'un nombre quelconque de bloc catch (éventuellement aucun) attrapant différents types d'exception, et éventuellement d'un bloc finally qui sera toujours exécuté quoi qu'il se passe. Exemple : try { Console.Write("Entrez un nombre : "); int n = int.Parse( Console.ReadLine() ); Console.WriteLine(" 100/nombre = "+( 100/n )); } catch ( DivideByZeroException dbzex ) { Console.Error.WriteLine(" Division par zéro"); } catch ( Exception ex ) { Console.Error.WriteLine( " Une autre exception a eu lieu : "+ex.Message); } finally { Console.WriteLine( " Quel que soit le résultat, ceci est affiché"); } [modifier]Libérer des ressourcesUn bloc finally est utile pour libérer des ressources à la fin d'un traitement, qu'une erreur ait eu lieu ou non. Exemple : Bitmap bm; try { bm=new Bitmap(100,100); } finally { bm.Dispose(); // libérer les ressources } Cependant, les classes implémentant l'interfaceIDisposable ont une méthode Dispose(), et peuvent être utilisées avec le mot clé using : using( Bitmap bm = new Bitmap(100,100) ) // <- objet IDisposable { [modifier]Lancer une exceptionEn cas d'erreur dans une méthode d'un programme (arguments invalides, ), il est possible de lancer une exception en utilisant le mot clé throw. La syntaxe est la suivante : throw objet_exception; Où objet_exception est une instance de la classe Exception ou de l'une de ses sous-classes. En général, l'objet exception est alloué en même temps qu'il est lancé : throw new classe_exception(arguments); La pile d'appel est enregistrée dans l'objet exception au moment où il est créé. Dans un gestionnaire d'exceptions, il est possible de relancer l'exception attrapée en utilisant l'instruction throw sans argument. Ce qui est utile quand le gestionnaire ne gère que partiellement l'erreur qui devra être totalement traitée par un autre gestionnaire d'exceptions. Exemple : try { Console.Write("Entrez un nombre : "); int n = int.Parse( Console.ReadLine() ); Console.WriteLine(" 100/nombre = "+( 100/nombre )); } catch ( DivideByZeroException dbzex ) { Console.Error.WriteLine(" Division par zéro"); throw; // relance la même exception } [modifier]Créer une classe d'exceptionLancer une exception signale une erreur particulière. Si aucuneclassed'exception ne convient ou n'est suffisamment précise, ou si l'exception doit comporter des informations supplémentaires, il est possible de créer une nouvelle classe d'exception. Pour cela, il fautdériver la classeException ou l'une de ses sous-classes. Par convention, toutes ces sous-classes ont un nom se terminant par Exception. Exemple : public class ErreurDeScriptException : Exception { // Attributs private int ligne,colonne; private string fichier; : base(message) // appel au constructeur de la classe Exception { this.fichier = fichier; this.ligne = ligne; this.colonne = colonne; } } Ensuite, cette classe peut être utilisée comme n'importe quelle autre classe d'exception : if ( arg==null ) throw new ErreurDeScriptException( "Un argument est nécessaire", fichier_script, 10 , 5); Surcharge des opérateursLa surcharge des opérateurs permet d'utiliser les opérateurs sur d'autres types (des classes) que les types simples. Certains opérateurs ne sont pas surchargeables. Il s'agit des opérateurs en rouge dansle tableau duchapitre. Sections[masquer] 1 Syntaxe 2 Implémentation par paire 3 Opérateurs de conversionso 3.1Explicite/Implicite [modifier]Syntaxe L'opérateur surdéfini doit toujours être statique et public, sinon le compilateur génère une erreur. L'opérateur est déclaré en utilisant le mot clé operator suivi de l'opérateur surdéfini. Exemple : public class NombreComplexe { private double n_reel, n_imag; public NombreComplexe() {} public NombreComplexe(double r,double i) { this.n_reel = r; this.n_imag = i; } public static NombreComplexe operator + (NombreComplexe a,NombreComplexe b) { return new NombreComplexe ( a.n_reel + b.n_reel , a.n_imag + b.n_imag ); } } [modifier]Implémentation par paireLa surdéfinition de certains opérateurs exige également la surdéfinition d'un autre. Ces opérateurs doivent donc être surdéfinis par paire. Il s'agit des paires d'opérateurs suivantes : § operator < et operator > § operator <= et operator >= § operator == et operator != En général, l'un des deux peut être défini en fonction de l'autre : § operator < en fonction de operator > public static bool operator < (NombreComplexe a,NombreComplexe b) { § operator > en fonction de operator < public static bool operator > (NombreComplexe a,NombreComplexe b) { return b < a; } § operator <= en fonction de operator >= public static bool operator <= (NombreComplexe a,NombreComplexe b) { return b >= a; } § operator >= en fonction de operator <= public static bool operator >= (NombreComplexe a,NombreComplexe b) { return b <= a; } § operator == en fonction de operator != public static bool operator == (NombreComplexe a,NombreComplexe b) { return !( a != b ); } § operator != en fonction de operator == public static bool operator != (NombreComplexe a,NombreComplexe b) { return !( a == b ); } [modifier]Opérateurs de conversionsLes opérateurs de conversions sont déclarés dans une classe C en ajoutant une méthode utilisant la syntaxe suivante : operator type_cible(type_source valeur) où l'un des deux types est la classe C : convertir un objet de classe C vers type_cible ou un objet de type type_source vers classe C. Exemple: public class Fraction { private int numerateur, denominateur; public Fraction(int n, int d) { this.numerateur = n; this.denominateur = d; } public double GetValue() { return this.numerateur / this.denominateur; } public static implicit operator double(Fraction f) { return f.GetValue(); } public static implicit operator Fraction(int entier) { return new Fraction(entier, 1); } } Fraction f=new Fraction(1,3); double d = f; // -> 0.33333 Fraction f; f = 5; // -> 5 / 1 [modifier]Explicite/ImpliciteL'opérateur de conversion doit être déclaré avec l'un des mots-clés implicit ou explicit pour qu'il soit utilisé respectivement implicitement ou explicitement. Exemple: public class Fraction public Fraction(int n, int d) { this.numerateur = n; this.denominateur = d; } public double GetValue() { return this.numerateur / this.denominateur; } public static explicit operator Fraction(int entier) { return new Fraction(entier, 1); } } Fraction f; f = (Fraction)5; // conversion explicite -> 5 / 1 |