J'apprends les événements/délégués en C #. Puis-je vous demander votre avis sur le style de nommage/codage que j'ai choisi (tiré du livre Head First C #)?
J'enseigne cela à un ami demain et j'essaie de trouver la manière la plus élégante d'expliquer les concepts. (pensait que la meilleure façon de comprendre un sujet était d'essayer de l'enseigner!)
class Program
{
static void Main()
{
// setup the metronome and make sure the EventHandler delegate is ready
Metronome metronome = new Metronome();
// wires up the metronome_Tick method to the EventHandler delegate
Listener listener = new Listener(metronome);
metronome.OnTick();
}
}
public class Metronome
{
// a delegate
// so every time Tick is called, the runtime calls another method
// in this case Listener.metronome_Tick
public event EventHandler Tick;
public void OnTick()
{
while (true)
{
Thread.Sleep(2000);
// because using EventHandler delegate, need to include the sending object and eventargs
// although we are not using them
Tick(this, EventArgs.Empty);
}
}
}
public class Listener
{
public Listener(Metronome metronome)
{
metronome.Tick += new EventHandler(metronome_Tick);
}
private void metronome_Tick(object sender, EventArgs e)
{
Console.WriteLine("Heard it");
}
}
n.b. Le code est refactorisé de http://www.codeproject.com/KB/cs/simplesteventexample.aspx
Il y a quelques points que je voudrais mentionner:
Metronome.OnTick ne semble pas être nommé correctement. Sémantiquement, "OnTick" me dit qu'il sera appelé quand il "Tick" s, mais ce n'est pas vraiment ce qui se passe. Je l'appellerais plutôt "Go".
Cependant, le modèle généralement accepté consisterait à procéder comme suit. OnTick
est une méthode virtuelle qui déclenche l'événement. De cette façon, vous pouvez facilement remplacer le comportement par défaut dans les classes héritées et appeler la base pour déclencher l'événement.
class Metronome
{
public event EventHandler Tick;
protected virtual void OnTick(EventArgs e)
{
//Raise the Tick event (see below for an explanation of this)
var tickEvent = Tick;
if(tickEvent != null)
tickEvent(this, e);
}
public void Go()
{
while(true)
{
Thread.Sleep(2000);
OnTick(EventArgs.Empty); //Raises the Tick event
}
}
}
Aussi, je sais que c'est un exemple simple, mais s'il n'y a pas d'écouteurs attachés, votre code lancera Tick(this, EventArgs.Empty)
. Vous devez au moins inclure un garde nul pour vérifier les auditeurs:
if(Tick != null)
Tick(this, EventArgs.Empty);
Cependant, cela est toujours vulnérable dans un environnement multithread si l'écouteur n'est pas enregistré entre le garde et l'invocation. Le mieux serait de capturer les auditeurs actuels en premier et de les appeler:
var tickEvent = Tick;
if(tickEvent != null)
tickEvent(this, EventArgs.Empty);
Je sais que c'est une vieille réponse, mais comme il recueille toujours des votes positifs, voici la façon de faire les choses en C # 6. L'ensemble du concept de "garde" peut être remplacé par un appel de méthode conditionnelle et le compilateur fait en effet la bonne chose (TM) en ce qui concerne la capture des écouteurs:
Tick?.Invoke(this, EventArgs.Empty);
Microsoft a en fait écrit un ensemble complet de directives de dénomination et l'a mis dans la bibliothèque MSDN. Vous pouvez trouver les articles ici: Lignes directrices pour les noms
Mis à part les directives générales de capitalisation, voici ce qu'il a pour les "événements" sur la page Noms des membres de type :
Nommez les événements avec un verbe ou une expression verbale.
Donnez aux noms d'événements un concept d'avant et d'après, en utilisant le présent et le passé. Par exemple, un événement de fermeture déclenché avant la fermeture d'une fenêtre s'appellera Clôture et un événement déclenché après la fermeture de la fenêtre sera appelé Fermé.
N'utilisez pas de préfixes ou suffixes avant ou après pour indiquer les événements pré et post.
Nommez les gestionnaires d'événements (délégués utilisés comme types d'événements) avec le suffixe EventHandler.
Utilisez deux paramètres nommés expéditeur et e dans les signatures du gestionnaire d'événements.
Le paramètre sender doit être de type Object et le paramètre e doit être une instance ou hériter d'EventArgs.
Nommez les classes d'arguments d'événement avec le suffixe EventArgs.
Je dirais que le meilleur guide des événements en général, y compris les conventions de dénomination, est ici .
C'est la convention que j'ai adoptée, brièvement:
Intéressant de voir comment Microsoft semble rompre ses propres conventions de dénomination avec les noms de gestionnaires d'événements générés par Visual Studio.
Voir: Consignes de dénomination des événements (.NET Framework 1.1)
Un point que j'ai trouvé après avoir utilisé des événements dans .Net pendant de nombreuses années est le besoin répétitif de vérifier l'événement pour un gestionnaire nul à chaque appel. Je n'ai pas encore vu un morceau de code en direct qui fait quoi que ce soit, mais n'appelle pas l'événement s'il est nul.
Ce que j'ai commencé à faire, c'est de mettre un gestionnaire factice sur chaque événement que je crée pour éviter d'avoir à faire la vérification nulle.
public class Metronome
{
public event EventHandler Tick =+ (s,e) => {};
protected virtual void OnTick(EventArgs e)
{
Tick(this, e); // now it's safe to call without the null check.
}
}
Cela semble bon, mis à part le fait que OnTick
ne suit pas le modèle d'invocation d'événement typique. En règle générale, On[EventName]
Déclenche l'événement une seule fois, comme
protected virtual void OnTick(EventArgs e)
{
if(Tick != null) Tick(this, e);
}
Pensez à créer cette méthode et à renommer votre méthode "OnTick
" existante en "StartTick
", et au lieu d'appeler Tick
directement à partir de StartTick
, appelez OnTick(EventArgs.Empty)
de la méthode StartTick
.
Dans votre cas, cela pourrait être:
class Metronome {
event Action Ticked;
internalMethod() {
// bla bla
Ticked();
}
}
Au-dessus de l'utilisation de l'échantillonneur au-dessous de la convention, auto-descriptif;]
Source des événements:
class Door {
// case1: property change, pattern: xxxChanged
public event Action<bool> LockStateChanged;
// case2: pure action, pattern: "past verb"
public event Action<bool> Opened;
internalMethodGeneratingEvents() {
// bla bla ...
Opened(true);
LockStateChanged(false);
}
}
BTW. le mot clé event
est facultatif mais permet de distinguer les "événements" des "rappels"
Auditeur d'événements:
class AlarmManager {
// pattern: NotifyXxx
public NotifyLockStateChanged(bool state) {
// ...
}
// pattern: [as above]
public NotifyOpened(bool opened) {
// OR
public NotifyDoorOpened(bool opened) {
// ...
}
}
Et contraignant [le code semble convivial pour les humains]
door.LockStateChanged += alarmManager.NotifyLockStateChanged;
door.Moved += alarmManager.NotifyDoorOpened;
Même l'envoi manuel d'événements est "lisible par l'homme".
alarmManager.NotifyDoorOpened(true);
Parfois plus expressif peut être "verbe + ing"
dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting;
Quelle que soit la convention que vous choisissez, soyez cohérent avec elle.