目录

 事件概述                                                           

[.NET] C# 知识回想,

 

壹常量与字段

   
 在爆发任何类或对象关切的政工作时间,类或对象可通过事件通报它们。发送(或吸引)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。

C# 知识回看 – 伊芙nt 事件

事件概述

委托是一种档次可以被实例化,而事件能够用作将多播委托开始展览打包的多个目的成员(简化委托调用列表增删方法)但不用新鲜的嘱托,珍重订阅互不影响。

 


事件

  • 特点
    • 发行者鲜明什么日期引发风浪,订户鲜明实施何种操作来响应该事件。
    • 贰个事件能够有五个订户。三个订户可处理来自两个发行者的八个事件。
    • 未有订户的事件永远不会被调用。
    • 事件无独有偶用于文告用户操作
    • 假如3个风浪有五个订户,当引发该事件时,会联手调用四个事件处理程序,也得以设置异步调用事件。
    • 能够动用事件联合线程。
    • 事件是依照 伊芙ntHandler 委托和
      伊芙ntArgs 基类的。
【博主】反骨仔    【原文】  

基础事件(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     }

上述代码执行结果:

金沙注册送58 1

 

壹 常量与字段

 事件的订阅和注销订阅                                       

  昨天,通过《C# 知识回看 –
事件入门》介绍了事件的定义及简单用法,后日大家由此控制台来看下“宣布 –
订阅”的主干用法。

 

事件公布&订阅

事件基于委托,为委托提供了1种公布/订阅机制。当使用事件时相似会现身三种剧中人物:发行者订阅者。

发行者(Publisher)也称为发送者(sender):是包蕴委托字段的类,它决定什么时候调用委托广播。

订阅者(Subscriber)也称之为接受者(recevier):是措施指标的收信人,通过在发行者的寄托上调用+=和-=,决定哪一天先河和终结监听。叁个订阅者不亮堂也不干涉别的的订阅者。

来电->开拓手提式有线电话机->接电话,如此那般贰个必要,模拟订阅发布机制:

 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     }

实践结果:

金沙注册送58 2

注意:

一.风云只好够从注脚它们的类中调用, 派生类不可能直接调用基类中声称的风浪。

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

二.对此事件在证明类外部只可以+=,-=不可能直接调用,而委托在外部不仅能够应用+=,-=等运算符还足以直接调用。

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

 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     }

 

(一) 常量

     借使你想编写引发事件时调用的自定义代码,则足以订阅由其他类发布的风浪。例如,能够订阅某些按钮的“单击”事件,以使应用程序在用户单击该按钮时实施一些实惠的操作。

目录

  • 公布符合 .NET 准则的风云
  • 利用
    伊夫ntHandler 格局发表事件
  • 2个简单的发表订阅 德姆o
  • 贯彻自定义事件访问器

 

 自定义事件(伊芙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     }

结果如下:

金沙注册送58 3

1.EventHandler<T>在.NET Framework
二.0中引进,定义了2个处理程序,它回到void,接受七个参数。

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

率先个参数(sender)是3个对象,包罗事件的发送者。
其次个参数(e)提供了事件的连锁新闻,参数随分歧的风浪类型而变更(继承伊夫ntArgs)。
.NET一.0为持有分化数据类型的轩然大波定义了几百个委托,有了泛型委托EventHandler<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;

接触事件是只包蕴1行代码的程序。那是C#陆.0的成效。在事先版本,触发事件以前要做为空判断。同时在展开null检查评定和接触之间,只怕另一个线程把事件设置为null。所以必要2个部分变量。在C#陆.0中,全数触发都能够应用null传播运算符和三个代码行取代。

1 Publication?.Invoke(this, e);

留神:就算定义的类中的事件可依照其余有效委托项目,甚至是重临值的信托,但貌似还是建议使用
伊夫ntHandler
字段以及事件,知识回想。 使事件基于 .NET Framework 形式。

 

  常量总是被视为静态成员,而不是实例成员。定义常量将造成创设元数据。代码引用一个常量时,编写翻译器会在概念常量的顺序集的元数据中查找该符号,提取常量的值,并将值嵌入IL中。由于常量的值直接嵌入IL,所以在运作时不要求为常量分配任何内部存款和储蓄器。其它,无法取得常量的地址,也无法以传递引用的秘诀传递常量。那几个限制意味着,未有很好的跨程序集版本控制本性。由此,只有在鲜明八个标记的值从不变化时,才应该利用。借使指望在运作时从1个顺序集中提取二个先后集中的值,那么不应该选取常量,而应当选用readonly 字段。

  • 订阅事件
    • VS IDE 订阅事件
      • 一旦“属性”窗口不可知,请在“设计”视图中,右击要创立事件处理程序的窗体或控件,然后采纳“属性”。
      • 在“属性”窗口的顶部,单击“事件”图标。
      • 双击要创制的风浪,Visual C#
        会成立二个空事件处理程序方法,并将其添加到您的代码中。或然,您也能够在“代码”视图中手动添加代码。
    • 编制程序情势订阅事件

      • 概念二个事件处理程序方法,其签名与该事件的嘱托签名匹配。例如,假诺事件基于
        伊夫ntHandler 委托类型,则上面包车型客车代码表示方法存根

一、公布符合 .NET 准则的轩然大波

  上边包车型客车历程演示了怎么样将符合标准
.NET 情势的事件添加到您的类和结构中。.NET类库中的全部事件均基于 伊夫ntHandler 委托,定义如下:  

public delegate void EventHandler(object sender, EventArgs e);

  你能够尝尝手动输入 伊芙ntHandler ,然后按下“F12”跳转到定义:

.NET Framework 2.0
引进了此委托的叁个泛型版本,即 伊夫ntHandler<T伊芙ntArgs>。

  【备注】尽管您定义的类中的事件可依据别的有效委托项目(甚至是可再次来到值的嘱托),不过,平时提出你使用 伊芙ntHandler 让事件基于
.NET 情势,如上边包车型地铁演示所示。

 

线程安全情势触发事件

在上头的例子中,过去科学普及的触及事件有二种办法:

 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复制后哪怕另3个线程更改了AfterPublication对象也未曾提到。委托是不行变得,所以理论上有效性。但是编写翻译器可能通过一点1滴移除变量temp的法子对上述代码实行优化所以仍或许抛出NullReferenceException.

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

本子三最全面技术科学,版本二也是足以应用的,因为JIT编写翻译机制上精通不应当优化掉变量temp,所以在有个别变量中缓存多少个引用,可确定保证堆应用只被访问二次。但他日是或不是变动不佳说,所以提出利用版本3。

 

 

金沙注册送58 4

void HandleCustomEvent(object sender, CustomEventArgs a){  }

2、采取 伊芙ntHandler 形式宣布事件

  一.(要是不须要与事件联合发送自定义数据,请跳过此步骤,进入步骤
三a。)在发行者类和订阅方类均可望见的限制中声称自定义数据的类。然后添加入保障留您的自定义事件数量所需的成员。

1     class MyEventArgs : EventArgs
2     {
3         public string Message { get; private set; }
4 
5         public MyEventArgs(string message)
6         {
7             Message = message;
8         }
9     }

 

  二.(倘使你使用的是 伊夫ntHandler<T伊夫ntArgs> 的泛型版本,请跳过此步骤。)在公布类中扬言叁个信托。  为它钦定以
伊夫ntHandler 结尾的称呼。  第三个参数内定自定义
伊芙ntArgs 类型。 

    delegate void MyEventHandler(object sender, MyEventArgs args);

 

  叁.运用以下任一步骤,在昭示类中注脚事件。

    (壹)假若未有自定义
伊夫ntArgs 类,事件类型便是非泛型 伊芙ntHandler 委托。无需声明委托,因为它已在开立
C# 项目时饱含的 System 命名空间中展开了声称。将以下代码添加到发行者类中。 

public event EventHandler MyEvent;

 

    (2)如若运用的是 伊夫ntHandler 的非泛型版本,并且您有贰个由 伊芙ntArgs 派生的自定义类,请在公布类中评释您的事件,并且今后自步骤
二 的嘱托用作类型。

public event MyEventHandler MyEvent;

 

    (三)假设使用的是泛型版本,则不须求自定义委托。相反,在发行者类中,您应将事件类型内定为 伊夫ntHandler<My伊芙ntArgs>,将尖括号中的内容替换为自身的类的称号。  

public event EventHandler<MyEventArgs> MyEvent;

 

事件揭破

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

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

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

金沙注册送58 5

编写翻译器也正是做了一次如下封装:

 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  */

宣称了一个私有的信托变量,开放多少个格局add和remove作为事件访问器用于(+=、-=),NoReturnWithParameters伊夫nt被编写翻译为Private从而完成封装外部不可能触及事件。

一.委托类型字段是对信托列表底部的引用,事件时有发生时会文告这一个列表中的委托。字段最先化为null,证明无侦听者等级对该事件的关切。

二.哪怕原始代码将事件定义为Public,委托字段也一贯是Private.目标是预防外部的代码不科学的操作它。

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

4.计较删除从未添加过的章程,Delegate的Remove方法内部不做任何事经,不会抛出12分或任何警示,事件的艺术集体保持不变。

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

 

(二) 字段

      • 运用加法赋值运算符 (+=)
        来为事件附加事件处理程序。在底下的言传身教中,假如名称叫 publisher
        的指标拥有三个名称为 RaiseCustom伊夫nt
        的轩然大波。请留心,订户类要求引用发行者类才能订阅其事件。

3、四个大概的公布订阅 德姆o

  下边包车型客车言传身教通过将自定义的 My伊夫ntArgs
类和 伊夫ntHandler<T伊夫ntArgs> 进行出现说法:

This is MyEventArgs.cs  //事件参数

 1     /// <summary>
 2     /// 事件参数
 3     /// </summary>
 4     /// <remarks>一个自定义的类:自定义事件的参数</remarks>
 5     class MyEventArgs : EventArgs
 6     {
 7         public string Message { get; }
 8 
 9         public MyEventArgs(string message)
10         {
11             Message = message;
12         }
13     }

 

This is Publisher.cs  //发布者

 1     /// <summary>
 2     /// 事件发布者
 3     /// </summary>
 4     class Publisher
 5     {
 6         //声明一个泛型事件
 7         public event EventHandler<MyEventArgs> MyEvent;
 8 
 9         public void Publish()
10         {
11             Console.WriteLine("Publis is starting");
12 
13             //你可以在事件触发前写些代码
14 
15             OnMyEvent(new MyEventArgs(DateTime.Now.ToString()));
16         }
17 
18         /// <summary>
19         /// 触发事件
20         /// </summary>
21         /// <param name="args"></param>
22         /// <remarks>虚方法,允许子类重写调用行为</remarks>
23         protected virtual void OnMyEvent(MyEventArgs args)
24         {
25             //只有在事件订阅时(!= null),才触发事件
26             MyEvent?.Invoke(this, args);
27         }
28     }

 

This is Subscriber.cs  //订阅者

 1     /// <summary>
 2     /// 订阅者
 3     /// </summary>
 4     class Subscriber
 5     {
 6         public Guid Guid { get; }
 7 
 8         public Subscriber(Publisher publisher)
 9         {
10             Guid = Guid.NewGuid();
11             //使用 C# 2 的语法进行订阅
12             publisher.MyEvent += Publisher_MyEvent;
13         }
14 
15         /// <summary>
16         /// 事件处理程序
17         /// </summary>
18         /// <param name="sender"></param>
19         /// <param name="args"></param>
20         private void Publisher_MyEvent(object sender, MyEventArgs args)
21         {
22             Console.WriteLine($"    Message is {args.Message}, Guid is {Guid}.");
23         }
24     }

 

This is Program.cs   //控制台,用于启动

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var publisher = new Publisher();
 6             var subscriber1 = new Subscriber(publisher);
 7             var subscriber2 = new Subscriber(publisher);
 8 
 9             //触发事件
10             publisher.Publish();
11 
12             Console.WriteLine("OK!");
13             Console.Read();
14         }
15     }

 四、完毕自定义事件访问器

  事件是出格类别的多路广播委托,只可以从声明它的类中调用。客户端代码通过提供对应在吸引风云时调用的点子的引用来订阅事件。那些方法通过事件访问器添加到委托的调用列表中,事件访问器类似于属性访问器,差别之处在于事件访问器被取名字为 add 和 remove。在多数动静下都不要求提供自定义的事件访问器。要是您在代码中从不提供自定义的轩然大波访问器,编写翻译器会自行抬高事件访问器。但在某个景况下,您大概供给提供自定义行为。示例如下:

 1     class MyClass
 2     {
 3         /// <summary>
 4         /// 锁
 5         /// </summary>
 6         private static object Locker = new object();
 7 
 8         /// <summary>
 9         /// 接口
10         /// </summary>
11         public interface IMyEvent
12         {
13             event EventHandler OnCall;
14         }
15 
16         public class MyEvent : IMyEvent
17         {
18             /// <summary>
19             /// 触发前事件
20             /// </summary>
21             event EventHandler PreEvent;
22 
23             public event EventHandler OnCall
24             {
25                 add
26                 {
27                     lock (Locker)
28                     {
29                         PreEvent += value;
30                     }
31                 }
32                 remove
33                 {
34                     lock (Locker)
35                     {
36                         PreEvent += value;
37                     }
38                 }
39             }
40         }
41     }

 

结语

类或对象能够透过事件向其余类或对象布告发出的连锁事情。事件选用的是公布/订阅机制,评释事件的类为宣布类,而对那几个事件进展处理的类则为订阅类。而订阅类怎样知道这么些事件产生并处理,这时候供给用到委托。事件的使用离不开委托。不过事件并不是信托的1种(事件是特种的信托的传道并不科学),委托属于类型(type)它指的是晤面(类,接口,结构,枚举,委托),事件是概念在类里的多个成员。

 

  CL奥迪Q7扶助项目字段和实例字段。对于项目字段,用于容纳字段数据的动态内部存款和储蓄器是在档次对象中分配的,而项目对象是在类型加载到贰个AppDomain时创设的;对于实例字段,用于容纳字段数据的动态内部存款和储蓄器则是在构造类型的1个实例时分配的。字段化解了版本控制难点,其值存款和储蓄在内部存款和储蓄器中,唯有在运行时才能取得。

publisher.RaiseCustomEvent += HandleCustomEvent;
publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);

传送门

  《C# 知识回看 – 系列化》

  《C# 知识回看 – 表达式树 Expression Trees》

  《C# 知识回想 – 天性 Attribute》、《剖析 AssemblyInfo.cs –
掌握常用的性状 Attribute》

  《C# 知识回想 – 委托 delegate》、《C# 知识回想 – 委托 delegate
(续)》

  《C# 知识回看 – 事件入门》

 


【参考】微软官方文书档案

] C#金沙注册送58 , 知识回看, C# 知识回想 – 伊夫nt 事件
【博主】反骨仔 【原著】
序 昨天,通过《C# 知识回看 – 事…

参考文献

 

CLR via C#(第4版) Jeffrey Richter

C#高档编制程序(第8版) Christian Nagel  (版9、10对事件部分从没多大差异)

果壳中的C# C#5.0胜过指南 Joseph Albahari


 

  要是字段是引用类型,且被标记为readonly,那么不可改变的是援引,而非字段引用的指标。

    • 匿名格局订阅事件
      • 应用加法赋值运算符 (+=)
        来为事件附加匿名格局。在底下的演示中,要是名叫 publisher
        的对象拥有三个名称叫 RaiseCustom伊芙nt 的事件,并且还定义了四个Custom伊夫ntArgs
        类以承载有些类型的专用事件音讯。请留心,订户类须要引用
        publisher 才能订阅其事件。

(三) 常量与只读字段的分别

publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
{
    string s = o.ToString() + " " + e.ToString();
    Console.WriteLine(s);
};

  readonly和const本质上都以常量,readonly是运作时常量而const是编写翻译期常量。两种常量具有以下分别:

  • 撤除订阅
  • 编写翻译期常量的值在编写翻译时收获,而运作时常量的值在运维时取得。
  • 双方访问情势分化。编写翻译期常量的值是在指标代码中举办轮换的,而运作时常量将在运维时求值,引用运营时常量生成的IL将引用到readonly的变量,而不是变量的值。由此,编写翻译期常量的习性更加好,而运转时常量更为灵活。
  • 编写翻译期常量仅协理整型、浮点型、枚举和字符串,此外值类型如Date提姆e是力不从心开始化编写翻译期常量的。可是,运营时常量则扶助别的项目。
  • 编写翻译期常量是静态常量,而运作时常量是实例常量,能够为项指标各种实例存放不一致的值。

   
 要积谷防饥在掀起风浪时调用事件处理程序,您只需撤废订阅该事件。要幸免能源泄露,请在自由订户对象在此之前撤消订阅事件,这点很重点。在撤除订阅事件在此以前,在发表对象中作为该事件的基本功的多路广播委托会引用封装了订户的事件处理程序的委托。只要发表对象涵盖该引用,就不会对订户对象实施垃圾回收。

  综上所述,除非须求在编写翻译时期取得适当的数值以外,别的情状,都应当尽恐怕使用运维时常量。

     使用减法赋值运算符 (-=)
裁撤订阅事件。全部订户都撤除订阅某事件后,发行者类中的事件实例会设置为
null。

(四) 常量与字段的筹划

publisher.RaiseCustomEvent -= HandleCustomEvent;
  • 毫不提供公有的或受保险的实例字段,应该一味把字段定义为private。
  • 要用常量字段来表示永远不会变动的常量。
  • 要用公有的静态只读字段定义预订义的目的实例。
  • 毫不把可变类型的实例赋值给只读字段。

 文告标准事件                                           


事件

     上边包车型大巴长河演示了哪些将符合标准 .NET
Framework 情势的轩然大波添加到您自个儿的类和结构中。.NET Framework
类库中的全部事件均基于 伊夫ntHandler 委托,定义如下。

  假诺类型定义了轩然大波,那么类型(或项目实例)就能够公告其余对象发送了一定的工作。假设定义了轩然大波成员,那样类型要提供以下能力:

public delegate void EventHandler(object sender, EventArgs e);
  • 格局能够挂号对事件的青眼。
  • 办法可以撤除对事件的关注。
  • 事件发送时,关切该事件的方法会收到布告。
  • 利用 伊芙ntHandler
    格局公布事件
    • (假如不须要发送含事件的自定义数据,请跳过此步骤,直接进入步骤
      三。)在发行者类和订户类均可望见的限量中注明类,并累加保留自定义事件数量所需的成员。在此示例中,会回到一个简易字符串。

  类型之所以能提供事件通报功用,是因为项目维护了1个已登记方法的列表,事件发送后,类型会布告列表中负有办法。

public class CustomEventArgs : EventArgs
{
    public CustomEventArgs(string s)
    {
        msg = s;
    }
    private string msg;
    public string Message
    {
        get { return msg; }
    } 
}

(一) 如何运用事件

    • (假若你使用的是 伊芙ntHandler
      的泛型版本,请跳过此步骤。)在揭露类中宣示一个委托。为它钦赐以
      伊芙ntHandler 结尾的名称。首个参数钦点自定义 伊夫ntArgs
      类型。

  下例展现了怎么样运用事件:

public delegate void CustomEventHandler(object sender, CustomEventArgs a);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            CostomEventPublisher cep = new CostomEventPublisher();
            CostomEventListener cel = new CostomEventListener(cep);
            cep.FireEvent("Hello");
            cep.FireEvent("Word");
            Console.ReadLine();
        }
    }

    //自定义事件参数
    internal sealed class CostomEventArgs : EventArgs
    {
        private readonly string message;

        public string Message
        {
            get { return message; }
        }

        public CostomEventArgs(string message)
        {
            this.message = message;
        }
    }

    //定义事件发布者
    internal class CostomEventPublisher
    {
        //定义事件
        public event EventHandler<CostomEventArgs> CostomEvent;

        //引发事件
        protected virtual void OnCostomEvent(CostomEventArgs e)
        {
            e.Raise(this, ref CostomEvent, false);
        }

        //构造参数实例,并引发事件
        public void FireEvent(string message)
        {
            CostomEventArgs e = new CostomEventArgs(message);
            OnCostomEvent(e);
        }
    }

    //扩展方法封装线程安全逻辑
    public static class EventArgExtensions
    {
        public static void Raise<T>(this T e, Object sender, ref EventHandler<T> eventDelegate, bool ifIgnoreException) where T : EventArgs
        {
            EventHandler<T> temp = Interlocked.CompareExchange(ref eventDelegate, null, null);
            if (temp != null)
            {
                if (!ifIgnoreException)
                {
                    try
                    {
                        temp(sender, e);
                    }
                    catch
                    {
                        //TODO:处理异常
                    }
                }
                else
                {
                    Delegate[] delegates = temp.GetInvocationList();
                    foreach (Delegate del in delegates)
                    {
                        try
                        {
                            temp(sender, e);
                        }
                        catch
                        { }
                    }
                }
            }
        }
    }

    //定义监听者
    internal sealed class CostomEventListener
    {
        //添加事件监听
        public CostomEventListener(CostomEventPublisher costomEventManager)
        {
            costomEventManager.CostomEvent += showMessage;
        }

        //响应方法
        private void showMessage(object sender, CostomEventArgs e)
        {
            Console.WriteLine(e.Message);
        }

        //移除事件监听
        public void Unregister(CostomEventPublisher costomEventManager)
        {
            costomEventManager.CostomEvent -= showMessage;
        }
    }
}
    • 使用以下任一步骤,在昭示类中声称事件。
      • 假诺未有自定义 伊芙ntArgs
        类,事件类型就是非泛型 伊芙ntHandler
        委托。它无需表明,因为它已在 C# 项目默许包罗的 System
        命名空间中展开了声称

第壹步 自定义事件参数

public event EventHandler RaiseCustomEvent;

  应该在伊芙ntArgs派生类中为事件处理程序提供参数,并将那个参数作为类成员。委托类中遍历他的订阅者列表,将参数对象在订阅者中逐条传递。但无能为力防护有些订阅者修改参数值,进而影响其后具备的处管事人件的订阅者。日常状态下,当这么些成员在订阅者中传递时,应预防订阅者对其展开改动,可将参数的拜会权限设置为只读,或公开这一个参数为公共成员,并应用readonly访问修饰符,在那二种情景下,都应有在构造器中初步化那个参数。

      • 假使应用的是 伊夫ntHandler
        的非泛型版本,并且您有一个由 伊夫ntArgs
        派生的自定义类,请在公布类中宣示您的事件,并且将您的嘱托用作类型

第一步 定义委托签名

class Publisher
{
    public event CustomEventHandler RaiseCustomEvent;
}

  固然委托注解可以定义任何方法签名,但在实践中事件委托应该符合1些特定的引导方针,首要不外乎:

      • 若果选取的是泛型版本,则不供给自定义委托。相反,应将事件类型钦命为
        伊芙ntHandler<Custom伊芙ntArgs>,在尖括号内放置您自身的类的名称。
  • 第二,指标措施的归来类型应为void。使用void的来由是,向事件发表者重回3个值毫无意义,发表者不理解事件订阅者为何要订阅,别的,委托类向发表者隐藏了实际上发表操作。该信托对其内部接收器列表举办遍历(订阅对象),调用各样对应的法子,由此回到的值不会流传到宣布者的代码。使用void再次回到类型还建议我们制止选择含有ref或out参数修饰符的出口参数,因为各种订阅者的输出参数不会传播给公布者。
  • 其次,一些订阅者或者想要从多个事件公布源接收相同的风云。为了让订阅者区分出不相同的公布者触发的事件,签名应涵盖宣布者的标识。在不借助于泛型的景观下,最简易的章程便是增加3个object类型的参数,称为发送者(sender)参数。之所以要求sender参数是object类型,首假如由于几次三番。另一个缘故是看人下菜。它同意委托由多个体系应用,唯有这么些品种提供了二个会传送相应的事件参数的事件。
  • 最后,定义实际事件参数将订阅者与公布者耦合起来,因为订阅者须求壹组特定的参数。.NET提供了伊芙ntArgs类,作为正式是事件参数容器。
public event EventHandler<CustomEventArgs> RaiseCustomEvent;

其三步
定义负责吸引轩然大波的方法来文告事件的挂号

 掀起派生类中的基类事件                                      

  类应定义2个受保险的虚方法。要引发事件时,当前类及其派生类中的代码会调用该方法。

   
 以下简单示例演示了在基类中扬言可从派生类引发的事件的正经措施。此方式广泛应用于
.NET Framework 基类库中的 Windows 窗体类。

第陆步 防御式发表事件

     在开立可用作其它类的基类的类时,必须怀想如下事实:事件是杰出类型的嘱托,只好够从申明它们的类中调用。派生类不能直接调用基类中宣示的轩然大波。固然有时你恐怕希望某些事件只好通过基类引发,但在多数景况下,您应该允许派生类调用基类事件。为此,您能够在含有该事件的基类中创立三个受保障的调用方法。通过调用或重写此调用方法,派生类便得以直接调用该事件。

  在.NET中,假若委托在其里面列表中并未有对象,它的值将设置为null。C#公布者在尝试调用委托在此以前,应该检查该信托是或不是为null,以咬定是还是不是有订阅者订阅事件。

namespace BaseClassEvents
{
    using System;
    using System.Collections.Generic;
    public class ShapeEventArgs : EventArgs
    {
        private double newArea;

        public ShapeEventArgs(double d)
        {
            newArea = d;
        }
        public double NewArea
        {
            get { return newArea; }
        }
    }
    public abstract class Shape
    {
        protected double area;

        public double Area
        {
            get { return area; }
            set { area = value; }
        }
        public event EventHandler<ShapeEventArgs> ShapeChanged;
        public abstract void Draw();
        protected virtual void OnShapeChanged(ShapeEventArgs e)
        {
            EventHandler<ShapeEventArgs> handler = ShapeChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }
    public class Circle : Shape
    {
        private double radius;
        public Circle(double d)
        {
            radius = d;
            area = 3.14 * radius;
        }
        public void Update(double d)
        {
            radius = d;
            area = 3.14 * radius;
            OnShapeChanged(new ShapeEventArgs(area));
        }
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
            base.OnShapeChanged(e);
        }
        public override void Draw()
        {
            Console.WriteLine("Drawing a circle");
        }
    }
    public class Rectangle : Shape
    {
        private double length;
        private double width;
        public Rectangle(double length, double width)
        {
            this.length = length;
            this.width = width;
            area = length * width;
        }
        public void Update(double length, double width)
        {
            this.length = length;
            this.width = width;
            area = length * width;
            OnShapeChanged(new ShapeEventArgs(area));
        }
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
            base.OnShapeChanged(e);
        }
        public override void Draw()
        {
            Console.WriteLine("Drawing a rectangle");
        }

    }
    public class ShapeContainer
    {
        List<Shape> _list;

        public ShapeContainer()
        {
            _list = new List<Shape>();
        }

        public void AddShape(Shape s)
        {
            _list.Add(s);
            s.ShapeChanged += HandleShapeChanged;
        }
        private void HandleShapeChanged(object sender, ShapeEventArgs e)
        {
            Shape s = (Shape)sender;
            Console.WriteLine("Received event. Shape area is now {0}", e.NewArea);
            s.Draw();
        }
    }
    class Test
    {

        static void Main(string[] args)
        {
            Circle c1 = new Circle(54);
            Rectangle r1 = new Rectangle(12, 9);
            ShapeContainer sc = new ShapeContainer();
            sc.AddShape(c1);
            sc.AddShape(r1);
            c1.Update(57);
            r1.Update(7, 7);
            Console.WriteLine();
            Console.WriteLine("Press Enter to exit");
            Console.ReadLine();
        }
    }
}

  另三个索要专注的标题是十二分。全体未处理的订阅者引发的百般都会流传给发表者,导致发表者崩溃。所以,使用时最棒在try/catch块内部发布事件。

 落到实处接口事件                                            

  还需求小心的是线程安全,上例给出了一种线程安全的风浪引发代码。考虑线程竟态条件应该发现到的三个要害是,3个措施只怕在事变的嘱托列表中移除之后获得调用。

   
 接口可证明事件。下边包车型地铁言传身教演示如何在类中达成接口事件。接口事件的达成规则与任何接口方法或性质的兑现规则基本相同。

(二) 管理多量轩然大波

  • 在类中实现接口事件

  处理大批量事变的难点在于,为各类事件都分配3个类成员是不现实的。为缓解此难题,.NET提供了伊芙ntHandlerList类。伊芙ntHandlerList是存款和储蓄键/值对的线性列表。键是标识事件的靶子,值是Delegate的实例。因为索引是2个目的,所以它能够是整数、字符串、特定的按钮实例等等。使用AddHandler和RemoveHandler方法能够分级增加和删除各种事件处理方法。还可以够运用AddHandlers()方法添加现有伊芙ntHandlerList的情节。要接触事件,用带键值对象的索引器来拜访事件列表,获得三个Delegate对象。将该为他转移为实际事件委托,然后触发事件。实例代码如下:

   
 在类中扬言事件,然后在适合的岗位调用该事件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            CostomEventPublisher cep = new CostomEventPublisher();
            CostomEventListener cel = new CostomEventListener(cep);
            cep.FireClick("Hello");
            cep.FireDoubleClick("Word");
            Console.ReadLine();
        }
    }

    //自定义事件参数
    internal sealed class CostomEventArgs : EventArgs
    {
        private readonly string message;

        public string Message
        {
            get { return message; }
        }

        public CostomEventArgs(string message)
        {
            this.message = message;
        }
    }

    //定义事件发布者
    internal class CostomEventPublisher
    {
        EventHandlerList eventList;
        static object eventClickKey = new object();//使用预分配静态变量作为键,以减少托管堆压力。
        static object eventDoubleClickKey = new object();

        public CostomEventPublisher()
        {
            eventList = new EventHandlerList();
        }

        //定义事件
        public event EventHandler<CostomEventArgs> Click
        {
            add
            {
                eventList.AddHandler(eventClickKey, value);
            }
            remove
            {
                eventList.RemoveHandler(eventClickKey, value);
            }
        }
        public event EventHandler<CostomEventArgs> DoubleClick
        {
            add
            {
                eventList.AddHandler(eventDoubleClickKey, value);
            }
            remove
            {
                eventList.RemoveHandler(eventDoubleClickKey, value);
            }
        }

        //引发事件
        protected virtual void OnEvent<T>(T e, EventHandler<T> eventDelegate) where T : EventArgs
        {
            e.Raise(this, ref eventDelegate, false);
        }

        //构造参数实例,并引发事件
        public void FireClick(string message)
        {
            CostomEventArgs e = new CostomEventArgs("Click:" + message);
            OnEvent(e, eventList[eventClickKey] as EventHandler<CostomEventArgs>);
        }

        public void FireDoubleClick(string message)
        {
            CostomEventArgs e = new CostomEventArgs("Double:" + message);
            OnEvent(e, eventList[eventDoubleClickKey] as EventHandler<CostomEventArgs>);
        }
    }

    //扩展方法封装线程安全逻辑
    public static class EventArgExtensions
    {
        public static void Raise<T>(this T e, Object sender, ref EventHandler<T> eventDelegate, bool ifIgnoreException) where T : EventArgs
        {
            EventHandler<T> temp = Interlocked.CompareExchange(ref eventDelegate, null, null);
            if (temp != null)
            {
                if (!ifIgnoreException)
                {
                    try
                    {
                        temp(sender, e);
                    }
                    catch
                    {
                        //TODO:处理异常
                    }
                }
                else
                {
                    Delegate[] delegates = temp.GetInvocationList();
                    foreach (Delegate del in delegates)
                    {
                        try
                        {
                            temp(sender, e);
                        }
                        catch
                        { }
                    }
                }
            }
        }
    }

    //定义监听者
    internal sealed class CostomEventListener
    {
        //添加事件监听
        public CostomEventListener(CostomEventPublisher costomEventManager)
        {
            costomEventManager.Click += showMessage;
            costomEventManager.DoubleClick += showMessage;
        }

        //响应方法
        private void showMessage(object sender, CostomEventArgs e)
        {
            Console.WriteLine(e.Message);
        }

        //移除事件监听
        public void Unregister(CostomEventPublisher costomEventManager)
        {
            costomEventManager.Click -= showMessage;
            costomEventManager.DoubleClick -= showMessage;
        }
    }
}
public interface IDrawingObject
{
    event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs {…}
public class Shape : IDrawingObject
{
    event EventHandler ShapeChanged;
    void ChangeShape()
    {
        // Do something before the event…
        OnShapeChanged(new MyEventsArgs(…));
        // or do something after the event. 
    }
    protected virtual void OnShapeChanged(MyEventArgs e)
    {
        if(ShapeChanged != null)
        {
           ShapeChanged(this, e);
        }
    }
}

(三)  封装事件访问器及成员

     上面包车型大巴言传身教演示如何处理以下的不常见事态:您的类是从四个以上的接口继承的,每一种接口都包含同名事件)。在那种情形下,您至少要为个中三个风云提供显式接口实现。为事件编写显式接口落成时,必须编制add 和 remove
事件访问器。这多少个事件访问器常常由编写翻译器提供,但在那种情景下编写翻译器不能够提供。

  通过逃匿实际事件成员,事件访问器提供了迟早水平的包裹。这还不够,通过编写制定订阅者接口进一步封装。实例代码如下:

     您能够提供温馨的访问器,以便钦赐那多少个事件是由你的类中的同一事件代表,依旧由不一致事件表示。例如,依据接口规范,若是事件应在差别时间引发,则足以将各种事件与类中的四个单独达成关系。在底下的演示中,订户将造型引用强制转换为
IShape 或 IDrawingObject,从而鲜明自身将会收取哪个 OnDraw 事件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            CostomEventPublisher cep = new CostomEventPublisher();
            CostomEventListener cel = new CostomEventListener();
            cep.Subscribe(cel, EventType.OnAllEvents);
            cep.FireEvent(EventType.OnClick, "Click:Hello");
            cep.FireEvent(EventType.OnDoubleClick, "Double:Word");
            Console.ReadLine();
        }
    }

    //自定义事件参数
    public sealed class CostomEventArgs : EventArgs
    {
        private readonly string message;

        public string Message
        {
            get { return message; }
        }

        public CostomEventArgs(string message)
        {
            this.message = message;
        }
    }

    //定义监听者接口
    public interface ICostomEventListener
    {
        void OnClick(object sender, CostomEventArgs eventArgs);
        void OnDoubleClick(object sender, CostomEventArgs eventArgs);
    }

    //定义事件类型枚举
    [Flags]
    public enum EventType
    {
        OnClick = 0x01,
        OnDoubleClick = 0x02,
        OnAllEvents = OnClick | OnDoubleClick
    }

    //定义事件发布者
    internal class CostomEventPublisher
    {
        EventHandlerList eventList;
        static object eventClickKey = new object();
        static object eventDoubleClickKey = new object();

        public CostomEventPublisher()
        {
            eventList = new EventHandlerList();
        }

        //定义事件
        public event EventHandler<CostomEventArgs> Click
        {
            add
            {
                eventList.AddHandler(eventClickKey, value);
            }
            remove
            {
                eventList.RemoveHandler(eventClickKey, value);
            }
        }
        public event EventHandler<CostomEventArgs> DoubleClick
        {
            add
            {
                eventList.AddHandler(eventDoubleClickKey, value);
            }
            remove
            {
                eventList.RemoveHandler(eventDoubleClickKey, value);
            }
        }

        //引发事件
        protected virtual void OnEvent<T>(T e, EventHandler<T> eventDelegate) where T : EventArgs
        {
            e.Raise(this, ref eventDelegate, false);
        }

        //添加事件监听
        public void Subscribe(ICostomEventListener listener, EventType type)
        {
            if ((type & EventType.OnClick) == EventType.OnClick)//判断是否包含某个枚举值
            {
                this.Click += listener.OnClick;
            }
            if ((type & EventType.OnDoubleClick) == EventType.OnDoubleClick)
            {
                this.DoubleClick += listener.OnDoubleClick;
            }
        }

        //移除事件监听
        public void Unsubscribe(ICostomEventListener listener, EventType type)
        {
            if ((type & EventType.OnClick) == EventType.OnClick)
            {
                this.Click -= listener.OnClick;
            }
            if ((type & EventType.OnDoubleClick) == EventType.OnDoubleClick)
            {
                this.DoubleClick -= listener.OnDoubleClick;
            }
        }

        //构造参数实例,并引发事件
        public void FireEvent(EventType type, string message)
        {
            CostomEventArgs e = new CostomEventArgs(message);
            if ((type & EventType.OnClick) == EventType.OnClick)
            {
                OnEvent(e, eventList[eventClickKey] as EventHandler<CostomEventArgs>);
            }
            if ((type & EventType.OnDoubleClick) == EventType.OnDoubleClick)
            {
                OnEvent(e, eventList[eventDoubleClickKey] as EventHandler<CostomEventArgs>);
            }
        }
    }

    //扩展方法封装线程安全逻辑
    public static class EventArgExtensions
    {
        public static void Raise<T>(this T e, Object sender, ref EventHandler<T> eventDelegate, bool ifIgnoreException) where T : EventArgs
        {
            EventHandler<T> temp = Interlocked.CompareExchange(ref eventDelegate, null, null);
            if (temp != null)
            {
                if (!ifIgnoreException)
                {
                    try
                    {
                        temp(sender, e);
                    }
                    catch
                    {
                        //TODO:处理异常
                    }
                }
                else
                {
                    Delegate[] delegates = temp.GetInvocationList();
                    foreach (Delegate del in delegates)
                    {
                        try
                        {
                            temp(sender, e);
                        }
                        catch
                        { }
                    }
                }
            }
        }
    }

    //定义监听者
    internal sealed class CostomEventListener : ICostomEventListener
    {
        //响应方法
        private void showMessage(object sender, CostomEventArgs e)
        {
            Console.WriteLine(e.Message);
        }

        public void OnClick(object sender, CostomEventArgs eventArgs)
        {
            showMessage(sender, eventArgs);
        }
        public void OnDoubleClick(object sender, CostomEventArgs eventArgs)
        {
            showMessage(sender, eventArgs);
        }
    }
}
namespace WrapTwoInterfaceEvents
{
    using System;
    public interface IDrawingObject
    {
        event EventHandler OnDraw;
    }
    public interface IShape
    {
        event EventHandler OnDraw;
    }
    public class Shape : IDrawingObject, IShape
    {
        event EventHandler PreDrawEvent;
        event EventHandler PostDrawEvent;
        event EventHandler IDrawingObject.OnDraw
        {
            add { PreDrawEvent += value; }
            remove { PreDrawEvent -= value; }
        }
        event EventHandler IShape.OnDraw
        {
            add { PostDrawEvent += value; }
            remove { PostDrawEvent -= value; }
        }
        public void Draw()
        {
            EventHandler handler = PreDrawEvent;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
            Console.WriteLine("Drawing a shape.");
            handler = PostDrawEvent;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
        }
    }
    public class Subscriber1
    {
        public Subscriber1(Shape shape)
        {
            IDrawingObject d = (IDrawingObject)shape;
            d.OnDraw += new EventHandler(d_OnDraw);
        }
        void d_OnDraw(object sender, EventArgs e)
        {
            Console.WriteLine("Sub1 receives the IDrawingObject event.");
        }
    }
    public class Subscriber2
    {
        public Subscriber2(Shape shape)
        {
            IShape d = (IShape)shape;
            d.OnDraw += new EventHandler(d_OnDraw);
        }

        void d_OnDraw(object sender, EventArgs e)
        {
            Console.WriteLine("Sub2 receives the IShape event.");
        }
    }
    public class Program
    {
        static void Main(string[] args)
        {
            Shape shape = new Shape();
            Subscriber1 sub = new Subscriber1(shape);
            Subscriber2 sub2 = new Subscriber2(shape);
            shape.Draw();

            Console.WriteLine("Press Enter to close this window.");
            Console.ReadLine();
        }
    }
}

  上边代码显示了这种格局的助益,仅透过一回调用就足以通报任何接口,并展现了对事件类成员的完全封装。

 接纳字典存款和储蓄事件实例                                       

(肆) 事件的本质

     accessor-declarations
的一种用法是公共场合大气的事件但不为每一个事件分配字段,而是使用字典来囤积那个事件实例。那唯有在颇具13分多的轩然大波、但你推测大部分风浪都不会落到实处时才有用。

  事件是2个类为委托的字段再加上多少个对字段实行操作的措施。事件是寄托列表底部的引用,是为着简化和创新使用委托来作为应用程序的回调机制时的编码。.NET事件支持信赖于委托,以下代码突显了在未选择事件时的编码:

public delegate void EventHandler1(int i);
public delegate void EventHandler2(string s);
public class PropertyEventsSample
{
    private System.Collections.Generic.Dictionary<string, System.Delegate> eventTable;
    public PropertyEventsSample()
    {
        eventTable = new System.Collections.Generic.Dictionary<string, System.Delegate>();
        eventTable.Add("Event1", null);
        eventTable.Add("Event2", null);
    }
    public event EventHandler1 Event1
    {
        add
        {
            eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
        }
        remove
        {
            eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
        }
    }
    public event EventHandler2 Event2
    {
        add
        {
            eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
        }
        remove
        {
            eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
        }
    }
    internal void RaiseEvent1(int i)
    {
        EventHandler1 handler1;
        if (null != (handler1 = (EventHandler1)eventTable["Event1"]))
        {
            handler1(i);
        }
    }
    internal void RaiseEvent2(string s)
    {
        EventHandler2 handler2;
        if (null != (handler2 = (EventHandler2)eventTable["Event2"]))
        {
            handler2(s);
        }
    }
}
public class TestClass
{
    public static void Delegate1Method(int i)
    {
        System.Console.WriteLine(i);
    }
    public static void Delegate2Method(string s)
    {
        System.Console.WriteLine(s);
    }
    static void Main()
    {
        PropertyEventsSample p = new PropertyEventsSample();

        p.Event1 += new EventHandler1(TestClass.Delegate1Method);
        p.Event1 += new EventHandler1(TestClass.Delegate1Method);
        p.Event1 -= new EventHandler1(TestClass.Delegate1Method);
        p.RaiseEvent1(2);

        p.Event2 += new EventHandler2(TestClass.Delegate2Method);
        p.Event2 += new EventHandler2(TestClass.Delegate2Method);
        p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
        p.RaiseEvent2("TestString");
    }
}
    class Program
    {
        static void Main(string[] args)
        {
            Bell bell = new Bell();
            bell.RingList = new Bell.Ring(CallWhenRingA);
            bell.Rock(1);
            //赋值新对象
            bell.RingList = new Bell.Ring(CallWhenRingB);
            bell.Rock(2);
            //直接调用委托
            bell.RingList.Invoke(3);

            Console.ReadLine();
        }

        static void CallWhenRingA(int times)
        {
            Console.WriteLine("A"+times);
        }

        static void CallWhenRingB(int times)
        {
            Console.WriteLine("B" + times);
        }
    }

    public sealed class Bell
    {
        public delegate void Ring(int times);

        public Ring RingList;

        public void Rock(int times)
        {
            if (RingList != null)
            {
                RingList(times);
            }
        }
    }

 事件的异步格局                            

  以上代码存在下列难题——公布类需求将委托成员公开为国有成员变量,那样具有参预方都能够向该信托列表添加订阅者,公共的嘱托成员打破了打包,导致代码应用程序安全风险。因而为了化解该难点,并简化编码微软交付了event来细化作为事件订阅和公告使用的委托项目。将委托成员变量定义为事件后,就算该成员为公家成员,也仅有发表类(不蕴含子类)能够出发此事件(纵然任哪个人都得以向该信托列表添加目的措施)。由宣布类的开发者来控制是还是不是提供2个公共措施来触发该事件。使用事件代表本来委托还会回落发表者与订阅者间的松耦合,因为发表者触发事件的事务逻辑对订阅者是藏身的。

     有各样艺术可向客户端代码公开异步作用。基于事件的异步情势为类规定了用于浮现异步行为的提出措施。对于相对简便易行的四线程应用程序,BackgroundWorker
组件提供了三个不难的缓解方案。对于更复杂的异步应用程序,请考虑实现贰个合乎基于事件的异步情势的类。

一 事件访问器

    • “在后台”执行耗费时间职务(例如下载和数据库操作),但不会暂停您的应用程序。
    • 与此同时履行多个操作,每一种操作达成时都会收到通报。
    • 等待财富变得可用,但不会停止(“挂起”)您的应用程序。
    • 接纳深谙的事件和委托模型与挂起的异步操作通讯。

  事件访问器类似于属性,在简单使用的还要隐藏了实在类成员。

  Costom伊夫ntReleaser中利用以下代码定义事件:

    public event
EventHandler<CostomEventArgs> CostomEvent;

  那段代码是概念事件的一种缩写情势,它会隐式定义添加和删除处理程序的主意并声称委托的3个变量。当编写翻译器编写翻译那段代码时,会把它转换为以下三个布局:

金沙注册送58 6

2 隐式达成事件

  我们在上例中看到的风云的欧洲经济共同体定义会转换为以下三个布局:

   style=”font-size: 1二px;”>//1 二个被开端化为null的个人民委员会托字段

style=”font-size: 12px;”>  private EventHandler<CostomEventArgs>
CostomEvent = null;

 

  //22个公共add_xxx方法(xxx代表事件名),用于添加事件订阅

  public void
add_CostomEvent(EventHandler<CostomEventArgs> value)

  {

style=”font-size: 12px;”>    EventHandler<CostomEventArgs>
prevHandler;

style=”font-size: 12px;”>    EventHandler<CostomEventArgs> costomEvent
=this.CostomEvent ;

    do

    {

      prevHandler=costomEvent
;

style=”font-size: 12px;”>      EventHandler<CostomEventArgs>
newHandler=(EventHandler<CostomEventArgs>)Delegate.Combine(prevHandler,value);

      costom伊芙nt
=Interlocked.CompareExchange<伊夫ntHandler<Costom伊夫ntArgs>>(ref
this.Costom伊芙nt,newHandler,prevHandler);//通过轮回和对CompareExchange的调用,能够以一种线程安全的主意向事件添加2个寄托。

    }

    while(costomEvent
!= prevHandler);

  }

  

  //三16个公共remove_xxx方法,用于撤销事件订阅

  public void
remove_CostomEvent(EventHandler<CostomEventArgs> value)

  {

style=”font-size: 12px;”>    EventHandler<CostomEventArgs>
prevHandler;

style=”font-size: 12px;”>    EventHandler<CostomEventArgs> costomEvent
=this.CostomEvent ;

    do

    {

      prevHandler=costomEvent
;

style=”font-size: 12px;”>      EventHandler<CostomEventArgs>
newHandler=(EventHandler<CostomEventArgs>)Delegate.Remove(prevHandler,value);

      costom伊夫nt
=Interlocked.CompareExchange<伊夫ntHandler<Costom伊夫ntArgs>>(ref
this.Costom伊夫nt,newHandler,prevHandler);//通过巡回和对CompareExchange的调用,能够以一种线程安全的主意向事件移除三个委托。

    }

    while(costomEvent
!= prevHandler);

  }

  除了生成上述一个布局,编写翻译器还会在托管程序集的元数据中生成贰个事变定义纪录项。那么些记录项包含部分标明和基本功委托项目,还引述了add和remove访问器方法。那些信息的职能是建立“事件”的抽象概念和它的访问器方法之间的关联。编写翻译器和别的工具得以动用那一个元数据新闻,并可因此System.Reflection.伊夫ntInfo类获取这几个消息。可是,CLRAV四本人并不行使这个音讯,它在运转时只供给访问器方法。

(5)
事件与自定义处理函数的规划

1 事件的设计

  • 要用System.伊芙ntHandler<T>来定义事件处理函数,而不是手工业创制新的寄托来定义事件处理函数。
  • 设想用伊芙ntArgs的子类来做事件的参数,除非整套确信该事件不供给给事件处理方法传递任何数据,在这种意况下得以向来运用伊夫ntArgs。
  • 要用受保证的虚方法来触发事件,一般方法以名字“On”起头,随后是事件名字。该规则只适用于非密封类中的非静态事件,不适用于结构、密封类及静态事件。派生类在覆盖虚方法时能够不调用基类的落实,要预备好应对那种情景,不要在该方法中做任何对基类来说缺壹不可的处理。
  • 倘若类中有二个风云,那么在调用委托以前须要加1个非空测试,其代码形如:“ClickHandler
    handler=Click;if(handler !=null) handler(this,e);”。
  • 编写翻译器生成的用来添加和删除事件处理方法的代码在三十二线程中不安全,所以借使急需支持让多线程同时加上或删除事件处理方法,那么须求自个儿编排代码来添加和去除事件处理方法,并在内部进行锁操作。
  • 要让触发事件的受保障方法带一个参数,该参数的档次为事件参数类,该参数的名字应该为“e”。
  • 并非在触发非静态事件时把null作为sender参数字传送入。
  • 要在触发静态事件时把null作为sender参数字传送入。
  • 不要在接触事件时把null作为数据参数字传送入,要是那个传任何数据应该使用伊芙ntArgs.Empty。
  • 设想使用Cancel伊芙ntArgs或它的子类作为参数,来触发能够被最终用户撤销的事件,这只适应于前置事件。代码形如:“void
    ColeingHandler(object
    sender,Cancel伊芙ntArgse){ e.Cancel=true;}”。

二 自定义处理函数的设计

  • 把事件处理函数的回来值类型定义为void。
  • 要用object作为事件处理函数的率先个参数的花色,并将其取名称为sender。
  • 要用伊夫ntArgs或其子类作为事件处理函数的第三个参数的品类,并将其取名叫e。
  • 毫无在事件处理函数中利用七个以上的参数。

(6).NET松耦合事件

  .NET事件简化了风云管理,它使大家不用去写管理订阅者列表的繁琐的代码。但是依照委托的事件照旧存在以下缺陷:

  • 对于每种要从里头接收事件的发表者对象,订阅者都不可能不再一次添加订阅的代码,未有1种方法能够订阅有些项目标轩然大波并使该事件传递给订阅者,而无论是公布者是哪个人。
  • 订阅者不恐怕筛选已接触是事件(例如,提醒“仅在知足某种条件是才文告小编该事件”)。
  • 订阅者必须有某种方式获得宣布者对象才能对其展开订阅,那样就导致了订阅者和发布者之间以及各样订阅者之间的耦合。
  • 公布者和订阅者具有耦合的生命周期,两者必须同时运转。订阅者不能够通告.NET“如若其余对象触发此事件,请创建一个作者的实例,并由小编来处理”。
  • 并未有走后门执行撤废订阅操作,宣布者对象在脱机是电脑上接触事件,一旦总计机处于联机状态,该事件便会传给订阅者。反之,订阅者运转在脱机的微处理器上,一旦处于联机状态,接收到在断开连接时接触是事件也是大概的。
  • 订阅的确立和撤回必须通过编制程序格局实现。

  .Net和此外第一方框架支持松耦合事件,在.NET中定义松耦合事件可以参照MSDN中有关System.EnterpriseServices.ServicedComponent的连带内容。

相关文章

网站地图xml地图