事件(event)


 

事件概述

委托是一类别型能够被实例化,而事件可以当作将多播委托开始展览包装的1个对象成员(简化委托调用列表增删方法)但决不新鲜的委托,保养订阅互不影响。

 

基本功事件(event)

在.Net中声明事件应用首要词event,使用也非凡简单在信托(delegate)前边加上event:

 1     class Program
 2     {
 3         /// <summary>
 4         /// 定义有参无返回值委托
 5         /// </summary>
 6         /// <param name="i"></param>
 7         public delegate void NoReturnWithParameters();
 8         /// <summary>
 9         /// 定义接受NoReturnWithParameters委托类型的事件
10         /// </summary>
11         static event NoReturnWithParameters NoReturnWithParametersEvent;
12         static void Main(string[] args)
13         {
14             //委托方法1
15             {
16                 Action action = new Action(() =>
17                 {
18                     Console.WriteLine("测试委托方法1成功");
19                 });
20                 NoReturnWithParameters noReturnWithParameters = new NoReturnWithParameters(action);
21                 //事件订阅委托
22                 NoReturnWithParametersEvent += noReturnWithParameters;
23                 //事件取阅委托
24                 NoReturnWithParametersEvent -= noReturnWithParameters;
25             }
26             //委托方法2
27             {
28                 //事件订阅委托
29                 NoReturnWithParametersEvent += new NoReturnWithParameters(() =>
30                 {
31                     Console.WriteLine("测试委托方法2成功");
32                 });
33             }
34             //委托方法3
35             {
36                 //事件订阅委托
37                 NoReturnWithParametersEvent += new NoReturnWithParameters(() => Console.WriteLine("测试委托方法3成功"));
38             }
39             //执行事件
40             NoReturnWithParametersEvent();
41             Console.ReadKey();
42         }
43         /*
44          * 作者:Jonins
45          * 出处:http://www.cnblogs.com/jonins/
46          */
47     }

上述代码执行结果:

图片 1

 

事件发表&订阅

事件基于委托,为委托提供了一种发布/订阅机制。当使用事件时一般会油不过生三种角色:发行者订阅者。

发行者(Publisher)也叫做发送者(sender):是含有委托字段的类,它控制何时调用委托广播。

订阅者(Subscriber)也号称接受者(recevier):是措施指标的接收者,通过在发行者的信托上调用+=和-=,决定几时开端和得了监听。多少个订阅者不驾驭也可是问其它的订阅者。

来电->开辟手提式有线电话机->接电话,诸如此类1个必要,模拟订阅揭橥机制:

 1     /// <summary>
 2     /// 发行者
 3     /// </summary>
 4     public class Publisher
 5     {
 6         /// <summary>
 7         /// 委托
 8         /// </summary>
 9         public delegate void Publication();
10 
11         /// <summary>
12         /// 事件  这里约束委托类型可以为内置委托Action
13         /// </summary>
14         public event Publication AfterPublication;
15         /// <summary>
16         /// 来电事件
17         /// </summary>
18         public void Call()
19         {
20             Console.WriteLine("显示来电");
21             if (AfterPublication != null)//如果调用列表不为空,触发事件
22             {
23                 AfterPublication();
24             }
25         }
26     }
27     /// <summary>
28     /// 订阅者
29     /// </summary>
30     public class Subscriber
31     {
32         /// <summary>
33         /// 订阅者事件处理方法
34         /// </summary>
35         public void Connect()
36         {
37             Console.WriteLine("通话接通");
38         }
39         /// <summary>
40         /// 订阅者事件处理方法
41         /// </summary>
42         public void Unlock()
43         {
44             Console.WriteLine("电话解锁");
45         }
46     }
47     /*
48      * 作者:Jonins
49      * 出处:http://www.cnblogs.com/jonins/
50      */

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //定义发行者
 6             Publisher publisher = new Publisher();
 7             //定义订阅者
 8             Subscriber subscriber = new Subscriber();
 9             //发行者订阅 当来电需要电话解锁
10             publisher.AfterPublication += new Publisher.Publication(subscriber.Unlock);
11             //发行者订阅 当来电则接通电话
12             publisher.AfterPublication += new Publisher.Publication(subscriber.Connect);
13             //来电话了
14             publisher.Call();
15             Console.ReadKey();
16         }
17     }

实行结果:

图片 2

注意:

1.风云只可以够从评释它们的类中调用, 派生类无法直接调用基类中宣称的轩然大波。

1  publisher.AfterPublication();//这行代码在Publisher类外部调用则编译不通过

2.对此事件在申明类外部只好+=,-=不能够直接调用,而委托在外部不仅能够行使+=,-=等运算符还能直接调用。

下边调用情势与地点执行结果一律,利用了信托多播的天性。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Publisher publisher = new Publisher();
 6             Subscriber subscriber = new Subscriber();
 7             //------利用多播委托-------
 8             var publication = new Publisher.Publication(subscriber.Unlock);
 9             publication += new Publisher.Publication(subscriber.Connect);
10             publisher.AfterPublication += publication;
11             //---------End-----------
12             publisher.Call();
13             Console.ReadKey();
14         }
15     }

 

 自定义事件(伊夫ntArgs&伊芙ntHandler&事件监听器)

有过Windwos Form开发经历对上边包车型大巴代码会熟知:

1 private void Form1_Load(object sender, EventArgs e)
2 {
3      ...      
4 }

在设计器Form1.Designer.cs中有事件的叠加。那种措施属于Visual Studio
IDE事件订阅。

1  this.Load += new System.EventHandler(this.Form1_Load);

在 .NET Framework 类库中,事件基于 EventHandler 委托和 EventArgs 基类。

基于伊夫ntHandler方式的轩然大波

 1     /// <summary>
 2     /// 事件监听器
 3     /// </summary>
 4     public class Consumer
 5     {
 6         private string _name;
 7 
 8         public Consumer(string name)
 9         {
10             _name = name;
11         }
12         public void Monitor(object sender, CustomEventArgs e)
13         {
14             Console.WriteLine($"Name:{_name}; 信息:{e.Message};到底要不要接呢?");
15         }
16     }
17     /// <summary>
18     /// 定义保存自定义事件信息的对象
19     /// </summary>
20     public class CustomEventArgs : EventArgs//作为事件的参数,必须派生自EventArgs基类
21     {
22         public CustomEventArgs(string message)
23         {
24             this.Message = message;
25         }
26         public string Message { get; set; }
27     }
28     /// <summary>
29     /// 发布者
30     /// </summary>
31     public class Publisher
32     {
33         public event EventHandler<CustomEventArgs> Publication;//定义事件
34         public void Call(string w)
35         {
36             Console.WriteLine("显示来电." + w);
37             OnRaiseCustomEvent(new CustomEventArgs(w));
38         }
39         //在一个受保护的虚拟方法中包装事件调用。
40         //允许派生类覆盖事件调用行为
41         protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
42         {
43             //在空校验之后和事件引发之前。制作临时副本,以避免可能发生的事件。
44             EventHandler<CustomEventArgs> publication = Publication;
45             //如果没有订阅者,事件将是空的。
46             if (publication != null)
47             {
48                 publication(this, e);
49             }
50         }
51     }
52     /// <summary>
53     /// 订阅者
54     /// </summary>
55     public class Subscriber
56     {
57         private string Name;
58         public Subscriber(string name, Publisher pub)
59         {
60             Name = name;
61             //使用c# 2.0语法订阅事件
62             pub.Publication += UnlockEvent;
63             pub.Publication += ConnectEvent;
64         }
65         //定义当事件被提起时该采取什么行动。
66         void ConnectEvent(object sender, CustomEventArgs e)
67         {
68             Console.WriteLine("通话接通.{0}.{1}", e.Message, Name);
69         }
70         void UnlockEvent(object sender, CustomEventArgs e)
71         {
72             Console.WriteLine("电话解锁.{0}.{1}", e.Message, Name);
73         }
74     }
75     /*
76      * 作者:Jonins
77      * 出处:http://www.cnblogs.com/jonins/
78      */

调用方式:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Publisher pub = new Publisher();
 6             //加入一个事件监听
 7             Consumer jack = new Consumer("Jack");
 8             pub.Publication += jack.Monitor;
 9             Subscriber user1 = new Subscriber("中国移动", pub);
10             pub.Call("号码10086");
11             Console.WriteLine("--------------------------------------------------");
12             Publisher pub2 = new Publisher();
13             Subscriber user2 = new Subscriber("中国联通", pub2);
14             pub2.Call("号码10010");
15             Console.ReadKey();
16         }
17     }

结果如下:

图片 3

1.EventHandler<T>在.NET Framework
2.0中引入,定义了贰个处理程序,它回到void,接受八个参数。

1 public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

率先个参数(sender)是3个指标,包涵事件的发送者。
第二个参数(e)提供了风云的相关音讯,参数随不相同的事件类型而变更(继承伊芙ntArgs)。
.NET1.0为富有分化数据类型的风云定义了几百个委托,有了泛型委托伊芙ntHandler<T>后,不再需求委托了。

2.EventArgs,标识表示包括事件数量的类的基类,并提供用于不分包事件数量的事件的值。

1 [System.Runtime.InteropServices.ComVisible(true)]
2 public class EventArgs

3.还要能够依照编制程序格局订阅事件

1     Publisher pub = new Publisher();
2     pub.Publication += Close;
3     ...
4     //添加一个方法
5     static void Close(object sender, CustomEventArgs a)
6     {
7             // 关闭电话
8     }

4.Consumer类为事件监听器当接触事件时可获得当前发布者对应自定义音信指标,能够依照要求做逻辑编码,再进行事件所订阅的相关处理。增添事件订阅/公布机制的健壮性。

5.以线程安全的章程触发事件    

1 EventHandler<CustomEventArgs> publication = Publication;

接触事件是只含有一行代码的先后。那是C#6.0的作用。在事先版本,触发事件从前要做为空判断。同时在实行null检查和测试和接触之间,大概另二个线程把事件设置为null。所以须要三个有的变量。在C#6.0中,全体触发都得以动用null传播运算符和一个代码行取代。

1 Publication?.Invoke(this, e);

在意:就算定义的类中的事件可依照其余有效委托项目,甚至是重临值的嘱托,但一般照旧提议使用
伊夫ntHandler
使事件基于 .NET Framework 方式。

 

线程安全方式触发事件

在上边的事例中,过去科学普及的触及事件有两种方法:

 1             //版本1
 2             if (Publication != null)
 3             {
 4                 Publication();//触发事件
 5             }
 6 
 7             //版本2
 8             var temp = Publication;
 9             if (temp != null)
10             {
11                 temp();//触发事件
12             }
13 
14             //版本3
15             var temp = Volatile.Read(ref Publication);
16             if (temp != null)
17             {
18                 temp();//触发事件
19             }

版本1会发生NullReferenceException异常。

版本2的化解思路是,将引用赋值到一时变量temp中,后者引用赋值发生时的委托链。所以temp复制后尽管另2个线程更改了AfterPublication对象也不曾涉嫌。委托是不行变得,所以理论上有效。不过编写翻译器恐怕由此一点一滴移除变量temp的章程对上述代码举办优化所以仍恐怕抛出NullReferenceException.

版本3Volatile.Read()的调用,强迫Publication在那些调用发生时读取,引用真的必须赋值到temp中,编写翻译器优化代码。然后temp唯有再部位null时才被调用。

本子3最周详技术科学,版本2也是足以行使的,因为JIT编写翻译机制上知道不应该优化掉变量temp,所以在局地变量中缓存叁个引用,可保险堆应用只被访问三次。但他日是不是变动不佳说,所以建议选用版本3。

 

 

事件揭示

大家再一次审视基础事件里的一段代码:

1     public delegate void NoReturnWithParameters();
2     static event NoReturnWithParameters NoReturnWithParametersEvent;

通过反编写翻译大家能够看来:

图片 4

编写翻译器也正是做了三回如下封装:

 1 NoReturnWithParameters parameters;
 2 private event NoReturnWithParameters NoReturnWithParametersEvent
 3 {
 4      add {  NoReturnWithParametersEvent+=parameters; }
 5      remove {  NoReturnWithParametersEvent-=parameters; }
 6 }
 7 /*
 8  * 作者:Jonins
 9  * 出处:http://www.cnblogs.com/jonins/
10  */

宣示了1个民用的寄托变量,开放七个章程add和remove作为事件访问器用于(+=、-=),NoReturnWithParameters伊芙nt被编写翻译为Private从而达成封装外部不或许触及事件。

1.委托类型字段是对信托列表尾部的引用,事件时有发生时会通告这么些列表中的委托。字段早先化为null,注明无侦听者等级对该事件的关怀。

2.正是原始代码将事件定义为Public,委托字段也一直是Private.指标是提防外部的代码不正确的操作它。

3.方法add_xxxremove**_xxxC#编译器还自行为艺术生成代码调用(System.Delegate的静态方法CombineRemove**)。

4.意欲删除从未添加过的法门,Delegate的Remove方法内部不做其它交事务经,不会抛出10分或任何警告,事件的措施集体保持不变。

5.**addremove方法以线程安全**的一种形式更新值(Interlocked
Anything情势)。

 

结语

类或对象足以经过事件向其余类或对象布告发出的相关工作。事件选用的是揭露/订阅机制,注明事件的类为公布类,而对这一个事件举行拍卖的类则为订阅类。而订阅类怎么着晓得那么些事件时有爆发并拍卖,那时候须求用到委托。事件的选取离不开委托。不过事件并不是信托的一种(事件是新鲜的信托的布道并不科学),委托属于类型(type)它指的是汇集(类,接口,结构,枚举,委托),事件是概念在类里的1个成员。

 

参考文献

 

CLR via C#(第4版) Jeffrey Richter

C#尖端编程(第捌版) Christian Nagel  (版九 、10对事件部分从没多大差别)

果壳中的C# C#5.0权威指南 Joseph Albahari

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/events/index


 

相关文章