Introduction C#
Master 2 IF App
Julien Saunier
LEPSiS, INRETS/LCPC
saunier@[Link]
[Link]
Qu’est-ce c’est C# ?
• Langage proposé par Microsoft, standardisé par ECMA, ressemble
à la syntaxe de Java
– Langage orienté objet
– Une partie évolue vers les langages de programmation par composants
– Dérivé de C++ et de Java
– MFC est remplacée par librairie .NET Framework
– Actuellement: version 4.0 (avril 2010)
• Combinaison entre la force de C++ et la simplicité de Visual Basic
• Cross Platform (du moins cross-windows...)
• Cross Language
Le langage C#
• Le C# est un langage à objet procédural
• Syntaxe très proche de C++ et Java
• C'est un langage très fortement typé
• Il est épuré des constructions à risque
• Il est non déclaratif (pas d'en-tête)
• La bibliothèque est fortement structurée
– utilisation d'espaces de nommages,
– accès via la directive « using ».
Installation .NET
• Que faut-il avoir pour une application .NET ?
– Windows OS
Visual [Link]
– Linux
Mono (compatible en grande partie)
Présentation .NET & .NET Framework
• Microsoft propose 4 langages
– C#, C++,[Link],[Link]
dont C# est le premier recommandé par
Microsoft
• Autres langages supportés
– Perl, Cobol, Eiffel, J#, Oberon,Component Pascal,
Python, Smalltalk
.NET & CLR Common Language Runtime
C# versus Java
•
Java n'autorise pas la surcharge des opérateurs
•
Java n'a pas de mode unsafe permettant l'arithmétique
de pointeurs,
•
C# supporte indexers (indexeurs), delegates (délégué
ou liste de pointeurs sur fonctions) et events
(événements),
•
C# supporte les structures en plus des classes (les
structures sont des types valeur : on stocke le contenu et
non l'adresse, elles peuvent contenir des méthodes),
•
C# utilise une syntaxe intégrée au langage (DllImport)
et portable pour appeler une bibliothèque native, tandis
que Java utilise Java Native Interface.
Le langage C#
• La procédure principale doit être une méthode
statique publique d'une classe
• Elle doit s'appeler « Main »
• Les bibliothèques utilisées doivent être
référencées dans le projet
• La syntaxe peut être simplifiée avec « using »
• Les entrées/sorties de base sont accessibles
via l'objet Console (espace de nommage System)
Hello World
// Introduit les fonctionnalités de System :
using System;
// Classe de l'application :
class MonApp
{
// Fonction principale :
public static void Main()
{
// Équivalent de [Link] :
[Link]("Hello World!");
}
}
Les types de la CLR
• Les types sont définis par le Runtime
• Ils sont accessibles dans l'espace de nommage
System (System.Int32, etc.)
• Les types de données sont des objets
– méthode d'analyse,
– méthode de formatage,
– méta-information sur les types.
• Le langage fournit des alias
Les types C#
• Les types fournit par C# :
– bool (booléens),
– char (caractère Unicode 16 bits),
– string (chaîne de caractères Unicode),
– types intégraux non signés (byte, ushort, uint, ulong),
– types intégraux signés (sbyte, short, int, long),
– types flottants (float, double),
– currency (128 bits, pour les montants),
– object (type de base de tous les objets),
– void (type vide, pour les procédures).
Déclaration des variables
int i = 2;
long l = 3L;
float f = 2.12f;
double d = 3.14D;
char c = 'A';
string s = "Hello World!\n";
bool b = true;
int tab1[] = new int[3] { 7, 9, 42 };
tab1[0] = 2;
long tab2[,] = new long[5,4];
tab2[2,3] = 5L;
object o = new int();
Les structures
// Définition d'une structure :
struct Livre
{
public string nom;
public string auteur;
public string editeur;
// Possibilité de définir des méthodes (cf. classes)
// Moins souple que les objets (pas d'héritage,
stockage par valeur)
}
// Utilisation des structures :
Libre l;
[Link] = "L'Odyssée";
[Link] = "Homère";
...
Valeurs et références
• Les types .NET peuvent être des valeurs
ou des références
• Les objets de type valeur :
– dérivent de [Link],
– sont dupliqués lors des affectations,
– sont allouables directement sur la pile.
• Les objets de type référence :
– ne sont pas dupliqués lors des affectations,
– doivent être alloués dynamiquement.
Valeurs et références
• Les types valeurs :
– types simples du langage,
– structures.
• Les types références :
– string, object, tableaux,
– classes utilisateur.
• Le type string n'est pas modifiable :
– fiable mais peu performant,
– utilisation de la classe [Link].
Transtypages
// Transtypages directs :
double d = 2;
int i = (int) d; // Cast « à la C ».
// Lance une exception en cas d'échec.
// Transtypage de références :
object o = "Hello World!\n";
// Attention, les chaînes de caractères sont des références :
string s = o as string; // Réinterprétation de référence.
// Renvoie null en cas d'échec.
Les tests
if (i == 2)
// Cas si i vaut 2.
else
if (i != 5)
{
// Cas si i est différent de 5.
}
else
if (i >= 7)
{
// Cas si i est supérieur ou égal à 7.
}
else
{
// Autres cas.
}
Les branchements conditionnels
switch (i)
{
case 1:
case 2:
// Traitement si i vaut 1 ou 2.
break; // Obligatoire.
case 5:
// Traitement si i vaut 5.
goto default; // Saut à une autre étiquette.
default:
// Traitement pour les autres cas.
break;
}
// Contrairement au C, switch fonctionne aussi avec les chaînes.
La boucle do
do
{
// Code exécuté une fois au moins, puis tant
// que la condition exprimée par la ligne while :
} while (i < 7);
do
{
// Rupture de séquence :
if (j == 2)
continue; // Saut direct à la condition.
if (k > 10)
break; // Sortie de boucle.
} while (i < 10);
La boucle while
// Contrairement au do, le test est effectué en premier :
while (i < 7)
{
// Traitement effectué tant que i est inférieur à 7.
// Les ruptures de séquences break et continue sont
// toujours utilisables.
}
La boucle for
// Boucle classique « à la C++ ».
// Déclaration et initialisation de la variable muette,
// test de condition de boucle, opération d'incrémentation.
// La condition est évaluée en premier (comme pour while) :
for (int i = 0; i < 10; ++i)
{
// Code exécuté tant que i < 10, en partant de i = 0 et
// avec incrémentation de i à chaque itération.
}
// La variable muette i n'est plus définie après la boucle.
Les itérations foreach
string[] tab = new string[5];
tab[0] = "chaîne 1";
...
tab[4] = "chaîne 5";
// Itération sur l'ensemble des données du conteneur tab :
foreach (string s in tab)
{
// Code exécuté pour chaque valeur contenue dans tab.
// La valeur est stockée dans la variable muette s
// déclarée dans l'instruction foreach :
[Link](s);
}
// Le conteneur énuméré doit implémenter IEnumerable
Les sauts
foreach (string n in noms)
{
foreach (string p in prenoms)
{
if (n == "Dupont" && p == "Jean")
goto trouve;
}
}
// Traitement si non trouvé
goto fin;
trouve:
// Traitement si trouvé
fin:
Les exceptions
• La gestion des erreurs de .NET est basée
sur les exceptions
• Les exceptions doivent toutes dériver
de la classe de base [Link]
• Les exceptions contiennent les informations
sur l'erreur et son contexte
• La syntaxe utilisée est semblable au C++
• Disponibilité d'un bloc finally pour la libération
des ressources
Les exceptions
try
{
// Lancement de l'exception :
throw new [Link]("pas trouvé");
}
// Récupération de l'exception :
catch ([Link] e)
{
[Link]([Link]);
// Relancement de l'exception :
throw;
}
catch
{
// Traitement de toutes les autres exceptions
}
Les exceptions
try
{
// Code exécuté sous le contrôle du try.
}
catch
{
// Récupération des exceptions.
}
finally
{
// Traitement de fin de bloc try.
// Exécuté qu'il y ait eu une exception ou non.
// Attention, n'est pas exécuté si l'exception n'est
// traitée par aucun bloc catch !
}
Les classes en C#
• Les classes sont introduites avec « class »
• Les données membres se déclarent
comme des variables
• Les méthodes comme des fonctions inline
• Plusieurs types de mode d'accès :
– private (accessibles de la classe, par défaut),
– public (accessibles de tout le monde),
– protected (accessibles des classes dérivées),
– internal (accessibles depuis l'assemblée, cf. ci-après).
Méthodes et paramètres
• Les méthodes peuvent être surchargées
• Les paramètres sont en entrée seule par défaut :
– pour un passage par référence, utiliser « ref »,
– pour un paramètre en sortie, utiliser « out »,
– pour la valeur de retour, utiliser « return ».
• Le mode de passage des paramètres doit être
fourni à l'appel explicitement
• .NET accepte les paramètres par défaut depuis la
version 4.0
Exemple de classe
class CLivre
{
string m_nom;
string m_auteur;
string m_editeur;
public string reference()
{
return m_nom + " " + m_auteur;
}
public void echange_nom(ref string nouveau)
{
string ancien = m_nom;
m_nom = nouveau;
nouveau = ancien
}
}
Constructeurs et destructeurs
class CLivre
{
string m_nom;
string m_auteur;
CLivre(string nom, string auteur)
{
m_nom = nom;
m_auteur = auteur;
}
~CLivre()
{
// Libération des ressources non managées.
}
}
Définition partielle de classes
// Fichier [Link] :
partial class CLivre
{
CLivre(string nom, string auteur)
{
m_nom = nom;
m_auteur = auteur;
}
}
---------------------------------------------------------------------
// Fichier livre_impl.cs :
partial class CLivre
{
string m_nom;
string m_auteur;
}
Méthodes et données statiques
• Les membres statiques sont déclarés avec
le mot-clef « static »
• L'instance est accessible des membres non
statiques via le mot-clef « this »
• La méthode principale de la classe d'application
doit être publique et statique
Exemple de membres statiques
class CLivre
{
static string m_bibliotheque = "Mes livres";
public static string get_biblio()
{
return m_bibliotheque;
}
}
// Utilisation :
[Link](CLivre.get_biblio());
Les accesseurs
• C# dispose d'une syntaxe spéciale pour
définir des accesseurs :
– mots-clefs « get », « set » et « value »,
– éviter d'en abuser pour les données internes.
• Permet de définir des fonctions d'écriture
et de lecture
• S'utilise comme des propriétés classiques
Exemple de propriété C#
class Personne
{
int m_Age;
public int Age
{
get
{
return m_Age;
}
set
{
if (value >=0) m_Age = value;
}
}
}
Les indexeurs
class CBibliotheque
{
... // Implémentation d'une collection de livre
public CLivre this[string nom]
{
get
{
return get_LivreParNom(nom);
}
}
}
Cbibliotheque biblio = new Cbibliotheque (...);
CLivre l = biblio["Odyssée"];
Modules et assemblées
• L'unité de déploiement des programmes .NET
est l'assemblée
• Une assemblée peut être un binaire, une
bibliothèque, ou un ensemble de fichiers
• Les assemblées sont constituées de « modules »
• Les modules sont obtenus par compilation des
fichiers sources
• Les informations de l'assemblée sont stockées
dans son « manifeste » (source [Link])
Espaces de nommage
• Les assemblées doivent être référencées
pour être utilisées
• Il est recommandé d'utiliser les espaces de
nommage pour éviter les conflits de noms
• Les espaces de nommage sont définis avec le mot-
clef « namespace »
• On peut morceler la définition des espaces
de noms au sein d'une même assemblée
Les namespaces
namespace cours
{
namespace [Link]
{
class CLivre { ... }
}
}
namespace [Link]
{
class CBibliothèque
{
[Link] m_tab[];
...
}
}
Formatage et analyse des données
• Les méthodes de formatage telles que WriteLine
utilisent des chaînes de format
• Les formateurs ont la syntaxe {n[,l][:f]} :
– n est le numéro du paramètre à utiliser,
– l est la taille minimale du champ, aligné à droite ou à
gauche selon le signe,
– f est le type du paramètre (G, général par défaut).
• Les types fournissent une méthode ToString() et
une méthode Parse() pour écrire et lire une chaîne
Entrées/sorties
int i = 25;
double d = 3.14159;
[Link]("i = {0} et d = {1:e}", i, d );
[Link]("Saisissez un entier :");
string s = [Link]();
int lu = [Link](s);
[Link]("J'ai lu :" = [Link]());
Les fichiers
• Les fonctions de manipulation de fichiers sont
dans l'espace de nommage [Link] ;
• La classe File permet de manipuler les fichiers
• Ses méthodes Open/Create renvoient un flux
de type FileStream
• Les flux dérivent de la classe Stream
• Ils founissent les méthodes de lecture et d'écriture
(Read, Write, etc.)
Héritage
• Les classes .NET peuvent dériver d'une autre classe
• .NET ne prend pas en charge l'héritage multiple
• La classe de base est spécifiée après le nom de la
classe dérivée suivi de « : »
• L'accès à la classe de base se fait via le mot-clef
« base »
• Utile pour initialiser la classe de base dans le
constructeur de la classe dérivée
Héritage
class CDocument
{
protected string m_titre;
public CDocument(string titre)
{
m_titre = titre;
}
}
class CLivre : CDocument
{
private int m_nbPages;
public CLivre(string titre, int nbPages) : base(titre)
{
m_nbPages = nbPages;
}
}
Interfaces
• Les classes peuvent implémenter une
ou plusieurs interfaces
• Elles en dérivent comme une classe
• Les implémentations des méthodes doivent
être publiques
• Les interfaces se définissent avec le mot-clef
« interface »
• Les interfaces peuvent dériver d'une ou plusieurs
autres interfaces
Interfaces
interface IDocument
{
string titre { get; }
void get_page_info(ref double hauteur, ref double largeur);
}
class CLivre : IDocument, ILisible
{
// Implémentation de IDocument :
public string titre { ... }
public get_page_info(ref double hauteur, ref double largeur)
{
...
}
// Implémentation de ILisible :
...
}
L'interface IDisposable
• L'interface IDisposable fournit un mécanisme
générique pour libérer les ressources applicatives
• Sa méthode Dispose doit :
– libérer les ressources non managées (handles de
fichiers, les connexions de bases de données, les
sockets réseau ou les références COM, etc...);
– prévenir le ramasse-miette que la finalisation de l'objet
n'est plus nécessaire (optimisation) ;
– pouvoir être appelée plusieurs fois de manière fiable.
• Il est d'usage de centraliser le code commun avec
le destructeur dans une autre méthode Dispose
L'interface IDisposable
class CFichier : IDisposable
{
... // Ressources
public void [Link]()
{
Dispose(true); // Libère toutes les ressources.
[Link](this);
// ~CFichier est inutile
}
public ~CFichier()
{
Dispose(false);// Libère les ressources
non managées
}
}
Redéfinition de membres
• Les membres des classes de base peuvent être
redéfinis
• Ces redéfinitions ne sont accessibles que depuis la
classe qui les redéfinit et ses classes dérivées
• La redéfinition provoque un avertissement
du compilateur
• On peut lui indiquer que la redéfinition est voulue
avec le mot-clé « new »
Redéfinition de membres
class CForme
{
public void Dessine()
{
Dessine_cadre();
Dessine_hachures_cadre();
}
}
class CRond : CForme
{
new public void Dessine() // Redéfinition.
{
Dessine_cadre()
Dessine_cercle_inscrit();
}
}
Surcharge de méthodes
• Les méthodes et propriétés de la classe de base
peuvent être déclarées virtuelles ou abstraites
• Les mots-clefs utilisés sont « virtual » et
« abstract »
• La surcharge dans les classes dérivées se fait avec
le mot-clef « override »
• Les classes dérivées peuvent interdire
la surcharge par une autre classe avec « sealed »
Méthodes virtuelles
class CForme
{
virtual public void Dessine()
{
Dessine_cadre();
Dessine_hachures_cadre();
}
}
class CCercle : CForme
{
override public void Dessine() // Surcharge.
{
Dessine_cadre()
Dessine_cercle_inscrit();
}
}
Classes abstraites et scellées
// CPoint ne peut plus être dérivé :
sealed class CPoint
{
public double x = 0;
public double y = 0;
}
// CForme ne peut être instanciée :
abstract class CForme
{
abstract public void Dessine(); // Non implémenté
}
class CCarre : CForme // Doit surcharger Dessine()
{
// Implémente de manière définitive la méthode Dessine() :
sealed override public void Dessine() { ... }
}
Les opérateurs
• Il est possible de redéfinir les opérateurs du
langage dans les classes utilisateur
• Les opérateurs sont des méthodes statiques dont
le nom est précédé du mot-clef « operator »
• L'un des paramètres de l'opérateur doit être la
classe contenante
• Les opérateurs de transtypage peuvent être
explicites (« explicit »)
Redéfinition d'opérateurs
class CPoint
{
public double x = 0;
public double y = 0;
CPoint(double _x, double _y) { ... }
// Additionne les coordonnées de deux points :
public static CPoint operator+(CPoint p1, CPoint p2)
{
return new CPoint(p1.x + p2.x, p1.y + p2.y);
}
// Calcule la distance à l'origine :
public static explicit operator double(CPoint p)
{
return [Link](p.x * p.x + p.y * p.y);
}
}
Les délégués
• .NET encapsule les pointeurs de méthodes dans
des objets appelés « délégués » (« delegates »)
• Les délégués permettent l'invocation d'une
méthode de manière polymorphique :
– pas besoin de connaître la classe de la méthode ;
– seule la signature est importante pour l'initialisation ;
– l'invocation se fait en interne par Invoke.
Les délégués
• Une fois initialisés, on ne peut plus les modifier
• Nécessité de reconstruire un nouveau délégué
pour changer les méthodes et objets cibles
• Réalisé de manière transparente
par les opérateurs « = », « += », « -= »
Les délégués
class CArithmetique
{
static int somme(int i, int j) { return i + j; }
static int soustraction(int i, int j) { return i - j; }
static int multiplication(int i, int j) { return i * j; }
// Tableau d'opérations :
delegate int operation(int i, int j);
operation[] ops = new operation[3] { somme, soustraction,
multiplication };
// Utilisation :
public static int calcule(int i, int j, int op)
{
return ops[op](i, j);
}
}
Les événements
• Les événements permettent de notifier
une information à des clients via des callbacks
• Les événements utilisent des délégués
• Une liste de délégués est construite en interne
pour chaque client
• Elle est mise à jour par les clients qui fournissent
de simples délégués sur leurs callbacks
• Les événements invoquent tous les délégués
des clients lors de l'événement
Implémentation d'un événement
// Type de délégué pour les fonctions de rappel :
public delegate void MonEvenementEventHandler(object sender,
MonEvenementEventArgs);
//exemple: delegate void MouseEventHandler(
object sender, MouseEventArgs e);
class CMonServer
{
// Déclaration de l'événement :
public event MonEvenementEventHandler MonEvenement;
//exemple: event MouseEventHandler OnClick;
// Méthode de déclenchement de l'événement
// (appelée par la classe CMonServer) :
OnMonEvenement(MonEvenementEventArgs a)
{
if (MonEvenement != null)
MonEvenement(this, a);
}
}
Réception d'un événement
class CMonClient
{
// Callback de réception :
void OnMonEvenementReceived(object sender,
MonEvenementEventArgs)
{
// Traitement de l'événement
}
// Abonnement :
void AdviseToServer(CMonServer s)
{
[Link] +=
new MonEvenementEventHandler(OnMonEvenementReceived);
}
}
Threading
• Le multithreading est intégré dans .NET
• De nombreuses fonctionnalités sont disponibles
(E/S asynchrones, timers, pools de threads)
• La classe Thread permet de lancer et de contrôler
un thread
• Les fonctions de thread sont encapsulées
par des délégués
• Les synchronisations se font grâce à des objets
de [Link]
Les templates
• Le .NET FrameWork 2.0 supporte les templates
(modèles de classes et de méthodes)
• Les templates permettent de conserver le typage
fort, et sont donc plus sûrs
• Ils sont très utilisés pour réaliser des conteneurs
(utilisation du type de base object facultative)
• Contrairement au C++, les templates sont
instanciés dynamiquement
• Les templates doivent donc pouvoir être compilés
Les templates
• Le modèle des templates .NET est simplifié :
– seuls les types génériques sont pris en charge,
– les paramètres templates ne sont pas gérés,
– pas de valeur par défaut,
– pas de spécialisation partielle.
• Les types génériques peuvent être contraints :
– limitation des cas d'utilisation du template
à des cas éprouvés,
– accès aux membres et fonctionnalités des types
déclarés comme autorisés.
Exemple de template
class TSociete<T>
{
int m_nbElements;
T m_Chef;
...
T get_chef()
{
return m_Chef;
}
}
// Utilisation :
TSociete<CEmploye> PME = new TSociete<CEmploye>;
CEmploye patron = PME.get_chef();
TSociete<CFourmi> fourmiliere = new TSociete<CFourmi>;
CFourmi reine = fourmiliere.get_chef();
Contraintes et default
class TPays<T> where T : CEtreHumain
{
// TPays peut utiliser les méthodes de CEtreHumain
// car T est soit un CEtreHumain, soit une classe dérivée.
...
}
// Classe de fraction, requiert que T soit un ValueType :
class TFraction<T> where T : struct // struct signifie valeur
{
T m_numerateur;
T m_denominateur;
public TFraction()
{
m_numerateur = m_denominateur = default(T);
}
}
Les collections
• Le FrameWork .NET fournit des classes
de conteneurs dans [Link]
• Les classes fournies sont trop abstraites et
ne conviennent pas en pratique
• La version 2.0 fournit des conteneurs génériques
dans [Link]
• Les classes sont plus orientées structures de
données (List, Dictionnary, ...) et donc meilleures
• Hélas toujours partiellement implémentées
Les itérateurs
• Pour être utilisable avec foreach, les conteneurs
utilisateurs doivent :
– soit implémenter IEnumerable et sa méthode
GetEnumerator (qui retourne un IEnumerator),
– soit fournir une propriété de type IEnumerator.
• Le mot-clef yield permet de réaliser facilement des
énumérateurs :
– il mémorise la position courante dans l'énumération,
– il implémente l'interface IEnumerator.
Les itérateurs
class CSemaine : [Link]
{
string[] m_Jours = {"Lun", "Mar", "Mer", "Jeu",
"Ven", "Sam", "Dim" };
public [Link] GetEnumerator()
{
for (int i = 0; i < m_jours.Length; ++i)
{
// yield conserve la valeur courante et
// implémente l'interface IEnumerator :
yield return m_Jours[i];
}
}
}
Attributs
• Les objets .NET peuvent recevoir des attributs
• Les attributs permettent de paramétrer
leur comportement
• Fortement utilisé pour spécifier des directives
de compilation ou des conventions d'appel
• Permettent de spécifier le type de marshalling
et d'importer des fonctions non managées
Attributs
• Également utilisés pour générer du code
exécuté au niveau méta (à la compilation)
• Possibilité de définir ses propres attributs
• Possibilité de consulter les attributs dans
l'implémentation des méthodes qui les supportent
• Permettent de faire du code paramétrable
au niveau directive de compilation
Interopérabilité
• .NET fournit les mécanismes d'interopérabilité :
– import des fonctions des DLL existantes (PInvoke),
– accès aux objets COM (COM Interop),
– exposition des objets .NET en tant qu'objets COM.
• Déclaration des fonctions de DLL :
– méthodes externes, publiques et statiques,
– spécification de la fonction via des attributs.
• Création d'une assemblée d'interopérabilité et
ajout d'une référence pour les objets COM
Conclusion
• Le C# est un langage simple mais puissant
• Il est fortement typé et épuré des constructions
complexes ou à risque
• Il dispose d'un ramasse-miettes mais
reste performant
• Il s'appuie sur un environnement et
une bibliothèque très riches
• Il est ouvert vers l'existant