Contents involved in the file:
Design disclosureEventType
How the compiler implements events
Design the type of listening event
Explicitly implement the event
Event:Definition The event member type allows the type to notify other objects that specific things have occurred.
CLR EventsModel is based on delegation. Delegation is a type of safe way of calling callback methods. Objects rely on calling methods to receive notifications to which they subscribe.
The type of event member defined requires the ability to provide the following functions:
The method can register its attention to the event
The method can unregister its attention to the event
When the event occurs, the registered method will receive a notification
This article is written as an emailApplication program as an example. When an email arrives, the user wants it forwarded to a fax machine or pager for processing. First design the MainlManager type to receive incoming emails, which exposes the NewMain event. Objects of other types (Fax or Pager) register interest in the event. This event is triggered when MailManager receives a new email, causing the email to be distributed to each registered object, each of which has its own way of handling the email.
1.1 Design the type of event to be disclosed
Step one: Define the type to accommodate all additional information that needs to be sent to the event notification recipient
This type usually contains a set of Private fields as well as some read-only public properties that expose these fields.
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 }
Step 2: Define event members
class MailManager { public event EventHandler<NewMailEventArgs> NewMail; }
where NewMail is the event name. The event member type is EventHandler
public delegate void EventHandler
So the method prototype must have the following form: void MethodName(Object sender,NewMailEventArgs e);The reason why the eventmoderequires that all event processingthe return type of the program is void, is Because several callback methods may be called after an event is raised, but there is no way to obtain the return values of all methods. Returning void does not allow the callback method to have a return value.
Step 3: Define the method responsible for triggering the event to notify the event registration object
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 }
The above method uses the Volatile.Read() method to ensure that the thread Safety, mainly consider the following two situations:
1. Directly judge NewMail!=null, but before calling NewMail, another thread may remove a delegate from the delegation chain, making it empty, thus A (NullReferenceException) exception occurred.
2. Some people may also save it in a temporary variable without using Volatile. It is theoretically possible, but if the compiler optimizes the code and removes the temporary variable, it will be the same as the first case. Same.
Using Volatile.Read will force NewMail to read when this call occurs. The reference must be copied to the temp variable, which is a perfect solution. But this will not happen in a single thread.
The fourth step defines the method to convert the input into the expected event
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 }
This method points out a letter New mail has arrived at MailManager.
1.2 How the compiler implements events
In the MailManager class we define the event member itself in one sentence: public event EventHandler
C#The compiler will convert it to the following code:
//一个被初始化为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); }
In this example, the add and remove method accessibility are both public because the event NewMail is declared as public, and the event Accessibility determines what code can register and unregister attention to the event. But in any case only the type itself can access the above delegate field NewMail. In addition to the generated code above, the compiler also generates event definition entries in the managed assembly's metadata. Contains some flags and basic delegate types. The CLR itself does not use this metadata information; only accessor methods are required by the 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 }
The above is the detailed content of C# series of article events. For more information, please follow other related articles on the PHP Chinese website!