C# Chapitre 7 - L'Evenementiel

Vous aimerez peut-être aussi

Télécharger au format pptx, pdf ou txt
Télécharger au format pptx, pdf ou txt
Vous êtes sur la page 1sur 45

Dominique Yolin

L’événementiel
- Cours de découverte du C#-
Plan

Les Délégués

Les Evénements

Les Exceptions
+

Délégués
Rappels et définition

En C# il est possible de surcharger des méthodes :


● C’est-à-dire permettre à des méthodes d’avoir le même nom mais prenant des
paramètres différents
● Afin de gérer ces cas, le runtime se base sur la signature des méthodes

Les signatures de méthodes contiennent les informations suivantes :


● L'identifiant de la méthode.
● La séquence (leur ordre de déclaration) des types des paramètres, sans leur
nom.
● Mais PAS le type de RETOUR
Présentation

Les délégués(delegate) permettent de créer des variables spéciales : des variables


qui « pointent » vers une méthode. (Équivalent .Net d'un pointeur de fonction)
● Comme tout objet, une méthode est chargée en mémoire et possède donc une adresse
mémoire.
● De même qu’il est possible de référencer un objet à l'aide d'une variable, il est possible de
référencer une méthode.

Le délégué permet de définir une signature de méthode et avec lui, il est possible
de pointer vers n’importe quelle méthode qui respecte cette signature.
● Le délégué est alors utilisé comme un nouveau type.

En général, on utilise un délégué quand on veut passer une méthode en


paramètres d’une autre méthode.
Une autre forme de polymorphisme

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.
Syntaxe
Création du délégué
● On définit la signature d'une méthode en ajoutant le mot clé delegate
o public delegate int CompareDelegate(object a, object b);
o Cela créer un délégué permettant de pointer vers des méthodes retournant un int et prenant 2 objets en paramètre.
o CompareDelegate est un nouveau type permettant de déclarer une variable

Création de la variable de type du délégué que l’on veut utiliser


● CompareDelegate myComp;
● CompareDelegate est bien un nouveau type et myComp est une variable de type CompareDelegate

Affectation à la variable d’une méthode respectant la signature


● int methodeDeComparaison(object a, object b) //Méthode définie quelque part dans le code
● myComp = methodeDeComparaison;

Utilisation comme avec une méthode classique


● object x,y;
● myComp(x, y);
Utilisation

Ajout et retrait de fonction


● Un délégué peut être associé à une ou plusieurs méthodes
● Dans ce cas, il les exécute les unes à la suite des autres lors de son appel
● On parle alors de Multicast Delegate
● Tous doivent posséder la même signature
● Se fait par les opérateurs = (affectation), += (ajout) et -= (retrait)

Les délégués du framework .Net


● La plupart des délégués du framework .Net sont utilisés comme callback
quand un événement se produit (timer, opération terminée, exécution d'un
thread, ...).
Exemple - préparation

Deux méthodes :
● void AfficherConsole(string message) { ... }
● void AfficherFenetre(string message) { ... }

Le délégué :
● delegate void AfficherDelegate(string message);
Exemple - utilisation

Déclaration ("variable membre" d’une classe)


● AfficherDelegate affichage;

Affectation
● affichage = AfficherConsole;
● affichage += AfficherFenetre; //On dit que le délégué peut être multicast

Utilisation
● affichage("Message affiché de deux manières différentes en un seul appel");
● affichage -= AfficherFenetre; // Ne plus afficher par fenêtre
● affichage("Un message sans fenêtre");
● affichage = AfficherFenetre; // Fenêtre seulement (affectation par =)
● affichage("Un message dans une fenêtre");
Exemple d’utilisation d’un delegate

void StockerPgRec(string url, string contenu) {code1 }


void AfficherPgRec(string url, string contenu) {code2 }

public delegate void PageRecueDelegate(string url, string contenu);


public class ConnectionHttp
{
public PageRecueDelegate PageRecue;
}
ConnectionHttp getweb = new ConnectionHttp();
getweb.PageRecue = StockerPgRec;
getweb.PageRecue += AfficherPgRec;
getweb.PageRecue("",""); // appellera les méthodes StockerPgRec et
AfficherPgRec
Les délégués anonymes

Délégué anonyme : au lieu d’utiliser une variable de type delegate qui pointe
vers une méthode, c’est comme si on écrivait directement la méthode.
● Évidemment, celle-ci doit respecter la signature du délégué
● Création dynamique de la fonction associée
o delegate( arguments ) {code }

La déclaration peut se faire directement dans l’appel d’une méthode


● Signature : typeRetour methodeAvecDelegueEnParam(TypeDuDelegate unDelegate)
● Appel : methodeAvecDelegueEnParam( delegate(int a, int b) {code} );
● Le mot-clé delegate revient en fait à créer une classe qui dérive de System.Delegate et
qui implémente la logique de base d’un délégué.
o Le C# nous masque tout ceci afin d’être au maximum efficace.
Exemple
En reprenant le délégué précédent:
● delegate void AfficherDelegate(string message);

Et une fonction prenant un delegate en parametre


● void SetAffichage(AfficherDelegate mydelegate);

On peut alors utiliser avec un delegate déclaré


● AfficherDelegate UneVariableMembreDelegate
● UneVariableMembreDelegate = AfficherConsole; //Méthode déclarée dans l’exemple 2
slides plus tôt
● SetAffichage(UneVariableMembreDelegate);

Ou utilisation de delegate anonymes


● SetAffichage(delegate(String myMsg){Console.WriteLine(myMsg);//exemple de code});
Le délégué générique Action

Délégué qui permet de pointer vers une méthode qui ne renvoie rien
et qui peut accepter jusqu’à 16 types différents.

Action<int[], string> est équivalent à créer un délégué qui ne renvoie


rien et qui prend un tableau d’entier et un string en paramètre.
Exemple Action

static char test;


static void Method1(string mystring, int aVal)
{ test = mystring[aVal]; }

static void Method2(string mystring, int aVal)


{ test = mystring[mystring.Length – 1 – aVal]; }

static void RunTheMethod(Action<String, int> myMethod, String s, int v)


{ myMethod(s, v); }

/////////////////
RunTheMethod(Method1, "Arcreane", 3); //test vaudra r
RunTheMethod(Method2, "Arcreane", 3); //test vaudra e
Le délégué générique Func<T>

Si la méthode doit renvoyer quelque chose on utilise Func

Func<int, int, double> est équivalent à créer un délégué qui prend


deux int en paramètre et renvoie un double
Exemple Func
private static void Main(string[] args) {
//On passe à la méthode Afficher la méthode à lancer et les arguments.
Afficher(Add, 25, 19);
Afficher(Sub, 52, 17);
}

//On fait une méthode générale qui prendra le delegate en paramètre.


private static void Afficher(Func<int, int, int> calcul, int i, int j) {
Console.WriteLine("{0} {1} {2} = {3}", i, calcul.Method.Name, j, calcul(i, j));
}

//Méthodes très simples qui ont toutes un type de retour et des paramètres identiques.
private static int Add(int i, int j) { return i + j; }
private static int Sub(int i, int j) { return i - j; }
Exemple avec la méthode Find des tableaux

Array.Find : méthode statique de la classe Array pour trouver un élément dans un


tableau
● Array.Find (T[], Predicate<T>) : le prédicat définit les conditions de l'élément à rechercher
● Le Predicate<T> est un délégué à une méthode qui retourne true si l’objet passé correspond aux
conditions définies
o public delegate bool Predicate<in T>(T obj);

// Creating and intializing new the String


String[] myArr = {"Sun", "Mon", "Tue", "Thu"};

string value = Array.Find(myArr, StWiLetterS); //Utilisation d’un delegate


private static bool StWiLetterS(string S) {
return S.StartsWith("S", StringComparison.Ordinal));
}
Une autre forme de polymorphisme

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 comme alternative au polymorphisme

L’héritage est la réponse spontanée aux questions d’architecture logicielle car il facilite
grandement la structuration des objets

Malheureusement il nuit parfois à la lisibilité et à la refactorisation du code


● Il arrive qu’il modifie le comportement de la classe mère de manière minime et destinée à un seul cas
d’utilisation
● Le comportement en question semble ne pas avoir été spécialisé, plutôt décliné à un usage précis.

Lorsqu’une subclass n’utilise qu’une petite partie de la classe parente ou que l’héritage
n’est qu’une réutilisation de code commun et pas parce que la subclasse étend la classe
mère il vaut mieux passer par un délégué
● En refactorisant le code on remplace l’héritage par une composition
● La subclass va présenter un délégué auquel pourront s’attacher les classes à même de répondre au
besoin
Exercice - delegates

Ajouter une classe CommunicatingClass dont hériterons toutes les classes devant
interagir avec le joueur.
● Faire hériter les classes

Ajouter 2 delegates :
● Un pour récupérer les actions du joueur (GetPlayerAction)
● L’autre pour lui faire parvenir des informations (InformPlayer)

Définir les types de messages que le joueur pourra recevoir

Instancier une interface utilisateur dans Program


● Ajouter une méthode pour permettre à toutes ces classes d’accéder à l’interface utilisateur
Les événements
Principaux problèmes du délégué

Si un objet est partagé par plusieurs classes / fonctions / threads /… (ex :


composants de l’IHM), plusieurs fonctions peuvent être associées à un de
ses délégués, et en être supprimées par une simple affectation
● Exemple d’erreur de frappe
o getweb.PageRecue += StockerPgRec;
o getweb.PageRecue = AfficherPgRec; //On supprime l’appel à StockerPgRec

L'ajout et le retrait de fonctions au délégué n'est pas thread-safe,

Conclusion, l'appel au délégué ne devrait pas être possible en dehors de la


classe ConnectionHttp.
La solution

La solution à ces problèmes : les événements


public delegate void PageRecueDelegate(string url, string contenu);
public class ConnectionHttp {
public event PageRecueDelegate PageRecue;
}

Mot clé event protège l'accès au délégué de la manière suivante :


● Il n'est plus possible d'utiliser l'affection seule (opérateur =), il faut utiliser += ou -=
● L'ajout et le retrait sont réalisés de manière synchrone,
● Il n'est pas possible d'appeler le delegate en dehors de la classe propriétaire où l'event
est déclaré (cf exemple plus loin)
● N’importe quel utilisateur peut abonner/désabonner une fonction à l’événement
Les événements : Présentation

Les événements sont un mécanisme du C# permettant à une classe d’être


notifiée d’un changement via une callback.

Ils peuvent également être déclenchés en dehors de l'application,


● par l'utilisateur (frappe au clavier, clic d'un bouton de souris, …)
● par le système (connexion réseau, …)
● par une autre application

Les classes abonnées à l’événement sont appelées classes clientes


● Elles « s’abonnent » à un événement en ajoutant leur callback à la liste des délégués à
appeler lorsque cet événement survient
● Lorsque l’événement se produit, toutes les callbacks sont appelées les unes après le autres
Utilisation des événements

Les événements sont beaucoup utilisés dans les applications en C#


● Ces événements utilisent en général une construction à base du délégué
EventHandler
● public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
● Délégué défini dans la classe System.EventHandler du framework .net

Gestion par délégué : ajouter le mot clé "event"


● public delegate void PageRecueDelegate(string url, string contenu);
● public event PageRecueDelegate PageRecue;
Exemple d’événement
public delegate void PageRecueDelegate(string url, string contenu);

public class ConnectionHttp {


public event PageRecueDelegate PageRecue;
public void Reception(string url, string page) {
if(PageRecue !=null)
PageRecue(url, page);
}
}

private void StockerPgRec(string url, string page) { code };

static void Main(string[] args) {


ConnectionHttp getweb = new ConnectionHttp();
getweb.PageRecue += StockerPgRec; //On ne peut plus faire =, seulement +=
getweb.Reception(« mon url », « Text de la page »); // appellera les méthodes
StockerPgRec
Fonctionnement interne
Le véritable membre délégué est privé alors que l'event est public

L'utilisation des opérateurs += et -= est réalisée par des appels aux accesseurs
add et remove de l'event.

Il est possible de remplacer les accesseurs par défaut créés par le compilateur.
Pour l'exemple précédent, les accesseurs par défaut sont définis ainsi :
public delegate void PageRecueDelegate(string url, string contenu);
public class ConnectionHttp {
private PageRecueDelegate _pageRecue;
public event PageRecueDelegate PageRecue {
[MethodImpl(MethodImplOptions.Synchronized)]
add // paramètre value : fonction à ajouter { _pageRecue += value; }
[MethodImpl(MethodImplOptions.Synchronized)]
remove // paramètre value : fonction à retirer { _pageRecue -= value; }
}
}
EventHandler

La gestion de l’IHM en particulier repose beaucoup sur le concept


d’événements

Dans le framework .Net, les événements utilisent une construction à


base du délégué générique EventHandler<> qui accepte 2 paramètres
● déclaration du delegate dans System.EventHandler
● public delegate void EventHandler<TEventArgs>(object sender, TEventArgs arg);
o sender : objet qui envoie l’événement
o arg : objet dont le type dérive de la classe de base EventArgs et qui transporte des données
associées à l’événement
Exemple avec EventHandler
using System.EventHandler://Pour avoir accès à EventHandler
public class PageRecueEventArgs: EventArgs {
public string Url { get; set; }
public string Text { get; set; }
}

public class ConnectionHttp {


public event EventHandler< PageRecueEventArgs > PageRecue;
public void Reception(string url, string page) {
if(PageRecue !=null) PageRecue(this, new PageRecueEventArgs{Url = url, Text = page});
}
}

private void StockerPgRec(object sender, PageRecueEventArgs e) { code };

static void Main(string[] args) {


ConnectionHttp getweb = new ConnectionHttp();
getweb.PageRecue += StockerPgRec;
getweb.Reception(« mon url », « Text de la page »); // appellera les méthodes StockerPgRec
}
Notification de changement d'une propriété
Implémentation de INotifyPropertyChanged
● l’interface INotifyPropertyChanged, notifie les changements de valeurs aux clients de liaison de données
● Quand la valeur d’une propriété change, l’objet déclenche l’événement
INotifyPropertyChanged.PropertyChanged pour signaler le changement

Exemple Implémentation de INotifyPropertyChanged


public event PropertyChangedEventHandler PropertyChanged;
public String FirsName{
set {
if (value != firstName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FirstName)));
}
firstName = value;
}
}
Structure Timer

Il existe plusieurs types de timer


● Dans la boite outil graphique pour la création d’interface
● Dans System.Timers pour l’utiliser programmatiquement

Propriétés principales
● Interval : entier représentant la durée (en millisecondes) entre deux tics. Par défaut cette
durée vaut 100ms.
● Start : méthode qui démarre le Timer.
● Stop : méthode qui arrête le Timer.
● Tick : gestionnaire d'évènements qui lance un évènement à chaque tic.

Event
● Elapsed : événement envoyé à chaque fois que l’intervalle de temps est écoulé
Exercice – Events

Créer des EventHandler et les classes EventsArg associées pour


● La mort du héros (DeathEventsArg)
● L’ouverture du coffre (ChestOpeningEventArgs)
● Le héros reçoit des dommages (EventArgs => pas besoin de spécifier)
● L’ouverture d’une porte (EventArgs => pas besoin de spécifier)

Ajouter les fonctions nécessaires pour les appeler


Les exceptions
Les différents types d'erreurs

Erreurs du compilateur
● Associées à un numéro
● Permet de googliser l’erreur pour la comprendre

Erreurs en runtime
● Remonter des infos claires pour effectuer les correctifs rapidement
● Éviter que l’utilisateur reçoive un message d’erreur incompréhensible

Origines des erreurs de runtime


● à cause d’une erreur dans le code
● dans le code externe appelé (une bibliothèque partagée, par exemple)
● de ressources de système d’exploitation non disponibles
● de conditions inattendues rencontrées par le runtime (du code qui ne peut pas être vérifié, par exemple)
● etc.
Premiers pas

La gestion des erreurs de .NET est basée sur les exceptions


● La syntaxe utilisée est semblable au C++

Si une application C# rencontre en runtime une anomalie (gérée ou


non) le framework .net remonte une exception

Les exceptions contiennent les informations sur l'erreur et son


contexte
● Les exceptions doivent toutes dériver de la classe de base System.Exception
Les blocs try – catch - finally

Gestion des exceptions : try et catch


● Encadrer le code susceptible d'être source d'erreur d’un bloc try / catch.
● Une exception levée dans le bloc try => le flux de programmation ira
directement dans la partie catch.
● Il ne peut y avoir qu’un seul bloc try mais plusieurs catchs

Blocs finally
● Pour que du code soit exécuté qu’une exception ou non soit levée (pour de la
libération de ressources par exemple)
● Il ne peut y avoir qu’un seul bloc finally
OnUnhandledException

Si aucun gestionnaire d’exceptions n’est présent pour une exception


donnée, le programme s’arrête avec un message d’erreur.

Si un workflow lève une exception qui n'est pas gérée, le


gestionnaire OnUnhandledException est appelé.
● Possibilité de fournir un gestionnaire OnUnhandledException personnalisé
Exemple

try
{
// Ouverture de la connexion à la base de données
// Exécution d’une instruction en base de données
//….
}
catch (ExceptionType1 ex) { // Gestion de l’exception spécifique 1}
catch (ExceptionType 2 ex) { // Gestion de l’exception spécifique 2}
catch (Exception ex) { // Gestion de l’exception générique}
Finally
{
// Fermeture de la base de données
// Nettoyage propre des ressources
//…
}
Lancer et intercepter des exceptions

Une exception est comme une alerte, le but est d'éviter une mauvaise opération

L'intérêt de lancer une exception est que du code ait été prévu pour la gérer,
sinon le programme s'arrête brutalement

Il est possible de lancer des exceptions, lorsqu’il est jugé nécessaire de le faire.
● throw new nomDeLexception();

Une exception est levée à partir d'une partie du code où un problème s'est
produit.
● L'exception remonte la pile jusqu'à sa prise en charge par l'application ou l'arrêt du
programme.
Créer ses propres exceptions

Pour créer ses propres exceptions, il faut dériver de la classe Exception


● public class MyException : Exception
o try {// Code dangereux }
o catch (MyException ex) { ex.DisplayError(); }

Il est possible de récupérer des exceptions de dll C++/CLI (code managé) en


C#

Pour du code managé il faut créer un wrapper permettant la « traduction »


d’un langage à l’autre
● Pour cela il sera nécessaire de recompiler le C++ avec l’option /clr
● Il y a toujours un risque de perte d’information
Détails

N’interceptez pas d’exception si vous ne pouvez pas la gérer tout en laissant l’application dans
un état connu

Les objets « Exception » contiennent des informations détaillées sur l'erreur

La gestion et la levée des exceptions fonctionnent de la même façon pour tous les langages de
programmation .NET.

Les exceptions offrent des avantages par rapport à d’autres méthodes de notification des
erreurs, comme les codes de retour.
● Les erreurs ne passent pas inaperçues, car si une exception est levée et qu’elle n’est pas gérée, le runtime
arrête l’application.
● Les valeurs non valides ne continuent pas à se propager dans le système parce que du code n’a pas pu
vérifier un code de retour d’échec
Exceptions courantes

Type d'exception Description Exemple


Classe de base pour toutes les exceptions. Aucun (utilisez une classe dérivée de cette exception).
Exception
Levée par le runtime uniquement en cas d’indexation Indexation d’un tableau en dehors de sa plage valide :
IndexOutOfRangeException incorrecte du tableau. arr[arr.Length+1]
Levée par le runtime uniquement si un objet Null est object o = null;
NullReferenceException référencé. o.ToString();

Levée par les méthodes en cas d’état non valide. Appel de Enumerator.MoveNext() après la suppression
d’un élément de la collection sous-jacente.
InvalidOperationException

Classe de base pour toutes les exceptions d’argument. Aucun (utilisez une classe dérivée de cette exception).
ArgumentException
Levée par les méthodes qui n’acceptent pas la valeur Null String s = null;
ArgumentNullException pour un argument. "Calculate".IndexOf(s);
Levée par les méthodes qui vérifient que les arguments String s = "string";
ArgumentOutOfRangeException sont inclus dans une plage donnée. s.Substring(s.Length+1);
Exercice - Exception

Créer une nouvelle classe WorkInProgressException héritant de la classe


Exception

Ajouter dans cette classe les propriétés OriginClassProp et


OriginFunctionProp

Faire un lancer de cette exception dans une méthode pas encore


complètement implémentée

Faire un try catch pour appeler cette méthode

Vous aimerez peut-être aussi