Maison >développement back-end >Tutoriel C#.Net >Série d'événements d'articles C#
Contenu impliqué dans le fichier :
Divulgation de conceptionÉvénementType
Comment le compilateur implémente les événements
Concevoir le type d'événement d'écoute
Mettre en œuvre explicitement l'événement
Événement : Définition Les types de membres d'événement permettent aux types de notifier d'autres objets que des événements spécifiques se sont produits. Le modèle
d'événement CLR est basé sur des délégués, qui sont un type de moyen sûr d'appeler des méthodes de rappel par lesquelles les objets reçoivent les notifications auxquelles ils sont abonnés.
définit les exigences de type pour que les membres de l'événement soient en mesure de fournir les fonctions suivantes :
La méthode peut enregistrer son attention sur l'événement
La méthode peut désenregistrer son attention sur l'événement
Lorsque l'événement se produit, la méthode enregistrée recevra une notification
Cet article est envoyé par emailAppliquer le programme à titre d'exemple. Lorsqu'un e-mail arrive, l'utilisateur souhaite qu'il soit transféré vers un télécopieur ou un téléavertisseur pour traitement. Concevez d’abord le type MainlManager pour recevoir les e-mails entrants, ce qui expose l’événement NewMain. Des objets d'autres types (Fax ou Pager) enregistrent un intérêt pour l'événement. Cet événement est déclenché lorsque MailManager reçoit un nouvel e-mail, provoquant la distribution de l'e-mail à chaque objet enregistré, chacun ayant sa propre manière de gérer l'e-mail.
1.1 Concevoir le type d'événement à exposer
Étape 1 : Définir le type pour accueillir toutes les informations supplémentaires qui doivent être envoyées au destinataire de notification d'événement
Ceci type contient généralement un ensemble de champs privés et des propriétés publiques en lecture seule qui exposent ces champs.
1 class NewMailEventArgs:EventArgs 2 { 3 private readonly string m_from, m_to, m_subject; 4 public NewMailEventArgs(string from,string to,string subject) 5 { 6 m_from = from; 7 m_to = to; 8 m_subject = subject; 9 }10 public string From { get { return m_from; } }11 public string To { get{ return m_to; } }12 public string Subject { get { return m_subject; } }13 }
Étape 2 : Définir les membres de l'événement
class MailManager { public event EventHandler<NewMailEventArgs> NewMail; }
où NewMail est le nom de l'événement. Le type de membre d'événement est EventHandler
public délégué void EventHandler
Le prototype de méthode doit donc avoir la forme suivante : void MethodName(Object sender,NewMailEventArgs e); La raison pour laquelle l'événement mode nécessite que le type de retour de tous les traitements d'événements Programs is void is Parce que plusieurs méthodes de rappel peuvent être appelées après le déclenchement d'un événement, mais qu'il n'y a aucun moyen d'obtenir les valeurs de retour de toutes les méthodes, le retour de void ne permet pas à la méthode de rappel d'avoir une valeur de retour.
Étape 3 : Définir la méthode responsable du déclenchement de l'événement pour notifier l'objet enregistré de l'événement
1 /// <summary> 2 /// 定义负责引发事件的方法来通知事件的登记对象,该方法定义在MailManager中 3 /// 如果类是密封的,该方法要声明为私有和非虚 4 /// </summary> 5 /// <param name="e"></param> 6 protected virtual void OnNewMail(NewMailEventArgs e) 7 { 8 //出于线程安全考虑,现在将委托字段的引用复制到一个临时变量中 9 EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);10 if(temp!=null)11 {12 temp(this, e);13 }14 }
La méthode ci-dessus utilise le Volatile. Méthode Read() Pour garantir la sécurité des threads, considérez principalement les deux situations suivantes :
1. Déterminez directement NewMail!=null, mais avant d'appeler NewMail, un autre thread peut supprimer un délégué de la chaîne de délégation, la rendant vide. , ce qui entraîne une exception (NullReferenceException).
2. Certaines personnes peuvent également la sauvegarder dans une variable temporaire sans utiliser Volatile. C'est théoriquement possible mais si le compilateur optimise le code et supprime la variable temporaire, ce sera pareil que dans le premier cas. .
L'utilisation de Volatile.Read forcera NewMail à lire lorsque cet appel se produit. La référence doit être copiée dans la variable temp, ce qui est une solution parfaite. Mais cela ne se fera pas en un seul fil
La quatrième étape définit la méthode pour convertir l'entrée en événement attendu
1 public void SimulateNewMail(string from,string to,string subject)2 {3 //构造一个对象来容纳想传给通知接收者的信息4 NewMailEventArgs e = new NewMailEventArgs(from, to, subject);5 //调用虚方法通知对象事件已反生6 //如果没有类型重写该方法7 //我们的对象将通知事件的所有登记对象8 OnNewMail(e);9 }
Les points de la méthode out Un nouvel e-mail est arrivé à MailManager.
1.2 Comment le compilateur implémente les événements
Dans la classe MailManager, nous définissons le membre de l'événement lui-même en une phrase : public event EventHandler
C# Le compilateur le convertira dans le code suivant :
//一个被初始化为null的私有字段 private EventHandler<NewMailEventArgs> NewMail = null; public void add_NewMail(EventHandler<NewMailEventArgs> value) { //通过循环和对CompareExchange的调用,以一种线程安全的方式向事件添加委托 //CompareExchange是把目标操作数(第1参数所指向的内存中的数) //与一个值(第3参数)比较,如果相等, //则用另一个值(第2参数)与目标操作数(第1参数所指向的内存中的数)交换 EventHandler<NewMailEventArgs> prevHandler; EventHandler<NewMailEventArgs> newMail = this.NewMail; do { prevHandler = newMail; EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Combine(prevHandler, value); newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler); } while (newMail != prevHandler); } public void remove_NewMail(EventHandler<NewMailEventArgs> value) { EventHandler<NewMailEventArgs> prevHandler; EventHandler<NewMailEventArgs> newMail = this.NewMail; do { prevHandler = newMail; EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Remove(prevHandler, value); newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler); } while (newMail != prevHandler); }
Dans cet exemple, l'accessibilité des méthodes add et delete est toutes deux publiques car l'événement NewMail est déclaré comme public. L’accessibilité d’un événement détermine quel code peut enregistrer et désenregistrer l’attention sur l’événement. Mais dans tous les cas, seul le type lui-même peut accéder au champ délégué NewMail ci-dessus. En plus du code généré ci-dessus, le compilateur génère également des entrées de définition d'événement dans les métadonnées de l'assembly géré. Contient quelques indicateurs et types de délégués de base. Le CLR lui-même n'utilise pas ces informations de métadonnées ; seules les méthodes d'accès sont requises par le runtime.
1.3 设计侦听事件的类型
如何定义一个类型来使用另一个类型提供的事件。以Fax类型为例:
internal class Fax { public Fax(MailManager mm) { //向MailManager的NewMail事件登记我们的回调方法 mm.NewMail += FaxMsg; } //新邮件到达,MailManager将调用这个方法 //sender表示MailManager对象,便于将信息回传给它 //e表示MailManager对象想传给我们的附加事件信息 private void FaxMsg(object sender, NewMailEventArgs e) { Console.WriteLine("Fax 的消息from:{0} to:{1} subject:{2}", e.From, e.To, e.Subject); } /// <summary> /// 注销 /// </summary> /// <param name="mm"></param> public void Unregister(MailManager mm) { mm.NewMail -= FaxMsg; } }
电子邮件应用程序初始化时首先构造MailManager对象,并将对该对象的引用保存到变量中。然后构造Fax对象,并将MailManager对象引用作为实参传递。在Fax构造器中,使用+=登记对NewMail事件的关注。
1.4 显式实现事件
对于System.Windows.Forms.Control类型定义了大约70个事件。每个从Control派生类型创建对象都要浪费大量内存,而大多数我们只关心少数几个事件。如何通过显式实现事件来高效的实现提供了大量事件的类思路如下:
定义事件时:公开事件的每个对象都要维护一个集合(如字典)。集合将某种事件标识符作为健,将委托列表作为值。新对象构造时集合也是空白。登记对一个事件的关注会在集合中查找事件的标识符。如果事件标识符存在,新委托就和这个事件的委托列表合并,否则就添加事件标识符和委托。
引发事件时:对象引发事件会在集合中查找事件的标识符,如果没有说明没有对象登记对这个事件的关注,所以也没委托需要回调。否则就调用与它关联的委托列表。
1 public sealed class EventKey { } 2 public sealed class EventSet 3 { 4 //定义私有字典 5 private readonly Dictionary<EventKey, Delegate> m_events = 6 new Dictionary<EventKey, Delegate>(); 7 /// <summary> 8 /// 不存在添加,存在则和现有EventKey合并 9 /// </summary> 10 public void Add(EventKey eventKey,Delegate handler)11 {12 //确保操作唯一13 Monitor.Enter(m_events);14 Delegate d;15 //根据健获取值16 m_events.TryGetValue(eventKey, out d);17 //添加或合并18 m_events[eventKey] = Delegate.Combine(d, handler);19 Monitor.Exit(m_events);20 }21 /// <summary>22 /// 删除委托,在删除最后一个委托时还需删除字典中EventKey->Delegate23 /// </summary> 24 public void Remove(EventKey eventKey,Delegate handler)25 {26 Monitor.Enter(m_events);27 Delegate d;28 //TryGetValue确保在尝试从集合中删除不存在的EventKey时不会抛出异常29 if (m_events.TryGetValue(eventKey,out d))30 {31 d = Delegate.Remove(d, handler);32 if(d!=null)33 {34 //如果还有委托,就设置新的头部35 m_events[eventKey] = d;36 }37 else38 {39 m_events.Remove(eventKey);40 }41 }42 Monitor.Exit(m_events);43 }44 /// <summary>45 /// 为指定的EventKey引发事件46 /// </summary> 47 public void Raise(EventKey eventKey,Object sender,EventArgs e)48 {49 Delegate d;50 Monitor.Enter(m_events);51 m_events.TryGetValue(eventKey, out d);52 Monitor.Exit(m_events);53 if(d!=null)54 {55 //利用DynamicInvoke,会向调用的回调方法查证参数的类型安全,56 //并调用方法,如果存在类型不匹配,就抛异常57 d.DynamicInvoke(new Object[] { sender, e });58 }59 }60 }
接下来定义类来使用EventSet
1 public class FooEventArgs : EventArgs { } 2 public class TypeWithLotsOfEvents 3 { 4 //用于管理一组"事件/委托" 5 private readonly EventSet m_eventSet = new EventSet(); 6 //受保护的属性使派生类型能访问集合 7 protected EventSet EventSet { get { return m_eventSet; } } 8 //构造一个静态只读对象来标识这个事件 9 //每个对象都有自己的哈希码,以便在对象的集合中查找这个事件的委托链表10 protected static readonly EventKey s_fooEventKey = new EventKey();11 //定义事件访问器方法,用于在集合中增删委托12 public event EventHandler<FooEventArgs> Foo13 {14 add { m_eventSet.Add(s_fooEventKey, value); }15 remove { m_eventSet.Remove(s_fooEventKey, value); }16 }17 //为这个事件定义受保护的虚方法18 protected virtual void OnFoo(FooEventArgs e)19 {20 m_eventSet.Raise(s_fooEventKey, this, e);21 }22 //定义将输入转换成这个事件的方法23 public void SimulateFoo() { OnFoo(new FooEventArgs()); }24 }
如何使用TypeWithLotsOfEvent,只需按照标准的语法向事件登记即可
1 static void Main(string[] args) 2 { 3 TypeWithLotsOfEvents twle = new TypeWithLotsOfEvents(); 4 twle.Foo += HandleFooEvent; 5 twle.SimulateFoo(); 6 Console.Read(); 7 } 8 9 private static void HandleFooEvent(object sender, FooEventArgs e)10 {11 Console.WriteLine("成功");12 }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!