文化回看,学习笔记


目录

 事件概述                                                           

[.NET] C# 知识回想,

 

一常量与字段

   
 在发生任何类或对象关切的事务时,类或对象可通过事件通报它们。发送(或引发)事件的类称为“发行者”,接收(或拍卖)事件的类称为“订户”。

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

文化回看,学习笔记。事件概述

寄托是1种类型能够被实例化,而事件能够看成将多播委托开始展览打包的三个对象成员(简化委托调用列表增删方法)但不要新鲜的嘱托,尊敬订阅互不影响。

 


事件

  • 特点
    • 发行者明确什么日期引发事件,订户明显实施何种操作来响应该事件。
    • 2个事变能够有八个订户。贰个订户可处理来自多个发行者的三个事件。
    • 尚未订户的轩然大波永远不会被调用。
    • 事件司空见惯用于布告用户操作
    • 比方2个风云有七个订户,当引发该事件时,会共同调用四个事件处理程序,也能够安装异步调用事件。
    • 能够接纳事件联合线程。
    • 事件是基于 伊夫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     }

上述代码执行结果:

美高梅开户网址 1

 

1 常量与字段

 事件的订阅和收回订阅                                       

  昨天,通过《C# 知识回想 –
事件入门》介绍了轩然大波的概念及简便用法,前天大家通过决定台来看下“公布 –
订阅”的主导用法。

 

事件揭发&订阅

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

发行者(Publisher)也号称发送者(sender):是带有委托字段的类,它控制曾几何时调用委托广播。

订阅者(Subscriber)也叫做接受者(recevier):是格局目的的接收者,通过在发行者的委托上调用+=和-=,决定何时发轫和告竣监听。3个订阅者不知晓也不干预别的的订阅者。

来电->开辟手提式有线话机->接电话,如此3个须求,模拟订阅发表机制:

 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  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 方式公布事件
  • 3个大致的颁发订阅 德姆o
  • 金镶玉裹福禄双全自定义事件访问器

 

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

有过Windwos Form开发经历对上边包车型地铁代码会熟习:

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

在设计器Form一.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);

第3个参数(sender)是一个对象,包括事件的发送者。
其次个参数(e)提供了轩然大波的相干新闻,参数随不一样的事件类型而改变(继承伊芙ntArgs)。
.NET壹.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#陆.0的机能。在头里版本,触发事件以前要做为空判断。同时在进行null检查实验和接触之间,大概另七个线程把事件设置为null。所以需求一个有的变量。在C#陆.0中,全数触发都足以动用null传播运算符和贰个代码行取代。

1 Publication?.Invoke(this, e);

留意:尽管定义的类中的事件可根据别的有效委托项目,甚至是重回值的寄托,但一般依旧提出利用
伊芙ntHandler
使事件基于 .NET Framework 方式。

 

  常量总是被视为静态成员,而不是实例成员。定义常量将导致成立元数据。代码引用1个常量时,编写翻译器会在概念常量的程序集的元数据中查找该符号,提取常量的值,并将值嵌入IL中。由于常量的值直接嵌入IL,所以在运转时不需求为常量分配任何内部存款和储蓄器。此外,不能够博取常量的地点,也不能以传递引用的办法传送常量。那么些限制意味着,未有很好的跨程序集版本控制个性。因而,唯有在分明三个符号的值从不变化时,才应该运用。假设愿意在运维时从3个主次集中提取叁个主次集中的值,那么不应当使用常量,而应该利用
readonly 字段。

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

      • 概念一个事件处理程序方法,其签名与该事件的寄托签名相称。例如,如若事件基于
        伊夫ntHandler 委托类型,则下面包车型客车代码表示方法存根

一、发表符合 .NET 准则的风浪

  上面包车型大巴进程演示了哪些将符合标准
.NET 格局的轩然大波添加到您的类和结构中。.NET类库中的全数事件均依照 伊夫ntHandler 委托,定义如下:  

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

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

.NET Framework 二.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对象也从不关系。委托是不可变得,所以理论上有效性。可是编写翻译器也许因而一点一滴移除变量temp的不二等秘书籍对上述代码举办优化所以仍大概抛出NullReferenceException.

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

本子3最完善技术不错,版本2也是能够动用的,因为JIT编写翻译机制上知道不应当优化掉变量temp,所以在部分变量中缓存叁个引用,可确定保证堆应用只被访问三遍。但他日是还是不是改变倒霉说,所以提议接纳版本三。

 

 

美高梅开户网址 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     }

 

  2.(若是您使用的是 伊夫ntHandler<T伊芙ntArgs> 的泛型版本,请跳过此步骤。)在公布类中宣称叁个委托。  为它钦命以
伊芙ntHandler 结尾的称呼。  第叁个参数钦点自定义
伊夫ntArgs 类型。 

    delegate void MyEventHandler(object sender, MyEventArgs args);

 

  3.运用以下任一步骤,在文告类中宣称事件。

    (一)如若没有自定义
伊芙ntArgs 类,事件类型正是非泛型 伊夫ntHandler 委托。无需注明委托,因为它已在开创
C# 项目时带有的 System 命名空间中展开了证明。将以下代码添加到发行者类中。 

public event EventHandler MyEvent;

 

    (二)如果运用的是 EventHandler 的非泛型版本,并且您有2个由 伊夫ntArgs 派生的自定义类,请在发表类中宣示您的事件,并且今后自步骤
2 的寄托用作类型。

public event MyEventHandler MyEvent;

 

    (3)借使选拔的是泛型版本,则不要求自定义委托。相反,在发行者类中,您应将事件类型钦定为 伊夫ntHandler<My伊芙ntArgs>,将尖括号中的内容替换为投机的类的称号。  

public event EventHandler<MyEventArgs> MyEvent;

 

事件揭秘

咱俩再一次审视基础事件里的一段代码:

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

透过反编写翻译大家能够阅览:

美高梅开户网址 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从而达成封装外部不能够触及事件。

1.委托类型字段是对信托列表底部的引用,事件时有产生时会文告那么些列表中的委托。字段初阶化为null,注明无侦听者等级对该事件的关注。

2.正是原始代码将事件定义为Public,委托字段也平素是Private.指标是防范外部的代码不科学的操作它。

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

4.总括删除从未添加过的点子,Delegate的Remove方法内部不做其余交事务经,不会抛出很是或任何警示,事件的法门集体保持不变。

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

 

(二) 字段

      • 利用加法赋值运算符 (+=)
        来为事件附加事件处理程序。在底下的演示中,假使名称叫 publisher
        的目标拥有二个名字为 RaiseCustom伊夫nt
        的轩然大波。请留心,订户类须求引用发行者类才能订阅其事件。

三、二个粗略的揭露订阅 Demo

  上边包车型大巴演示通过将自定义的 MyEventArgs
类和 伊芙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     }

 

结语

类或对象足以因此事件向任何类或对象布告发出的相干作业。事件选拔的是揭露/订阅机制,注解事件的类为公布类,而对那些事件开展拍卖的类则为订阅类。而订阅类怎么样驾驭这一个事件时有爆发并拍卖,那时候必要用到委托。事件的运用离不开委托。不过事件并不是委托的一种(事件是例外的寄托的布道并不正确),委托属于类型(type)它指的是汇聚(类,接口,结构,枚举,委托),事件是概念在类里的一个成员。

 

  CL奥迪Q7支持项目字段和实例字段。对于项目字段,用于容纳字段数据的动态内部存款和储蓄器是在档次对象中分配的,而项目对象是在类型加载到1个AppDomain时创立的;对于实例字段,用于容纳字段数据的动态内部存款和储蓄器则是在构造类型的叁个实例时分配的。字段消除了版本控制难题,其值存款和储蓄在内部存款和储蓄器中,唯有在运作时才能赢得。

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

传送门

  《C# 知识回看 – 连串化》

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

  《C# 知识回看 – 天性 Attribute》、《剖析 AssemblyInfo.cs –
领悟常用的个性 Attribute》

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

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

 


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

] C# 知识回想, C# 知识回想 – 伊芙nt 事件
【博主】反骨仔 【原来的书文】
序 昨天,通过《C# 知识回想 – 事…

参考文献

 

CLR via C#(第4版) Jeffrey Richter

C#高档编制程序(第8版) 克赖斯特ian Nagel  (版玖、10对事件部分从没多大差距)

果壳中的C# C#5.0权威指南 Joseph 阿尔巴hari


 

  假设字段是引用类型,且被标记为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的变量,而不是变量的值。因而,编写翻译期常量的性格更加好,而运营时常量更为灵活。
  • 编写翻译期常量仅支持整型、浮点型、枚举和字符串,别的值类型如DateTime是无能为力开首化编写翻译期常量的。但是,运转时常量则帮助其余项目。
  • 编写翻译期常量是静态常量,而运行时常量是实例常量,能够为品种的每种实例存放不相同的值。

   
 要防范在诱惑事件时调用事件处理程序,您只需废除订阅该事件。要防范财富走漏,请在刑释订户对象从前撤消订阅事件,那一点很重大。在裁撤订阅事件在此之前,在颁发对象中作为该事件的基础的多路广播委托会引用封装了订户的事件处理程序的寄托。只要发布对象涵盖该引用,就不会对订户对象实施垃圾回收。

  综上所述,除非须要在编写翻译时期取得确切的数值以外,别的景况,都应当尽量接纳运营时常量。

     使用减法赋值运算符 (-=)
废除订阅事件。全数订户都收回订阅某事件后,发行者类中的事件实例会安装为
null。

(4) 常量与字段的统一筹划

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

 公布正式事件                                           


事件

     上面包车型客车长河演示了怎么着将符合标准 .NET
Framework 模式的轩然大波添加到您自身的类和布局中。.NET Framework
类库中的全部事件均基于 伊芙ntHandler 委托,定义如下。

  若是类型定义了轩然大波,那么类型(或项目实例)就足以通报任何对象发送了特定的业务。假如定义了风浪成员,这样类型要提供以下能力:

public delegate void EventHandler(object sender, EventArgs e);
  • 办法可以挂号对事件的关爱。
  • 艺术能够裁撤对事件的关怀。
  • 事件发送时,关切该事件的方法会收到布告。
  • 行使 伊芙ntHandler
    情势发表事件
    • (借使不需求发送含事件的自定义数据,请跳过此步骤,间接进去步骤
      3。)在发行者类和订户类均可望见的限量中评释类,并加上保留自定义事件数量所需的积极分子。在此示例中,会回去3个简练字符串。

  类型之所以能提供事件通报成效,是因为品种维护了3个已登记方法的列表,事件发送后,类型会文告列表中拥有办法。

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

(1) 怎么着运用事件

    • (假设你使用的是 伊芙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的原故是,向事件宣布者重返一个值毫无意义,发布者不驾驭事件订阅者为啥要订阅,别的,委托类向发布者隐藏了实际发表操作。该信托对其内部接收器列表进行遍历(订阅对象),调用每一种对应的章程,因而回到的值不会传播到公布者的代码。使用void再次回到类型还提出我们防止使用带有ref或out参数修饰符的输出参数,因为种种订阅者的出口参数不会传来给公布者。
  • 说不上,一些订阅者恐怕想要从七个事件公布源接收相同的风云。为了让订阅者区分出分裂的发布者触发的轩然大波,签名应包涵发表者的标识。在不借助泛型的状态下,最简单易行的方法正是添加三个object类型的参数,称为发送者(sender)参数。之所以供给sender参数是object类型,重假设出于延续。另3个缘故是人云亦云。它同意委托由三个类型应用,只有那一个品种提供了三个会传递相应的事件参数的事件。
  • 最后,定义实际事件参数将订阅者与发表者耦合起来,因为订阅者需求一组特定的参数。.NET提供了伊夫ntArgs类,作为正式是事件参数容器。
public event EventHandler<CustomEventArgs> RaiseCustomEvent;

其三步
定义负责吸引轩然大波的秘籍来通告事件的登记

 掀起派生类中的基类事件                                      

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

   
 以下不难示例演示了在基类中表明可从派生类引发的事件的正儿八经措施。此形式广泛应用于
.NET Framework 基类库中的 Windows 窗体类。

第伍步 防御式公布事件

     在开立可用作任何类的基类的类时,必须挂念如下事实:事件是优良类其他委托,只可以够从注解它们的类中调用。派生类不能直接调用基类中扬言的轩然大波。尽管有时你大概希望某些事件只可以通过基类引发,但在多数气象下,您应该允许派生类调用基类事件。为此,您可以在含蓄该事件的基类中创制3个受保证的调用方法。通过调用或重写此调用方法,派生类便足以间接调用该事件。

  在.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();
        }
    }
}

  另3个内需专注的难题是可怜。全部未处理的订阅者引发的特别都会传播给公布者,导致发表者崩溃。所以,使用时最棒在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();
        }
    }
}

  上边代码突显了那种方法的优点,仅通过2遍调用就足以通报任何接口,并展现了对事件类成员的完全封装。

 选择字典存款和储蓄事件实例                                       

(肆) 事件的本来面目

     accessor-declarations
的一种用法是当众大气的风云但不为每一个事件分配字段,而是选用字典来存储这一个事件实例。那唯有在有着十三分多的事件、但你算计大部分风云都不会落到实处时才有用。

  事件是八个类为委托的字段再添加几个对字段实行操作的办法。事件是委托列表尾部的引用,是为着简化和改善使用委托来作为应用程序的回调机制时的编码。.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来细化作为事件订阅和文告使用的嘱托项目。将委托成员变量定义为事件后,尽管该成员为公家成员,也仅有发表类(不包蕴子类)能够出发此事件(就算任哪个人都足以向该信托列表添加目的措施)。由公布类的开发者来支配是或不是提供1个公家措施来触发该事件。使用事件代表本来委托还会降低公布者与订阅者间的松耦合,因为公布者触发事件的工作逻辑对订阅者是隐匿的。

     有三种措施可向客户端代码公开异步作用。基于事件的异步格局为类规定了用来显示异步行为的建议措施。对于相对不难的多线程应用程序,BackgroundWorker
组件提供了2个总结的消除方案。对于更扑朔迷离的异步应用程序,请思量达成3个契合基于事件的异步格局的类。

壹 事件访问器

    • “在后台”执行耗时职责(例如下载和数据库操作),但不会中断您的应用程序。
    • 并且进行四个操作,各样操作完成时都会收取通报。
    • 等候能源变得可用,但不会甘休(“挂起”)您的应用程序。
    • 采纳深谙的风浪和委托模型与挂起的异步操作通讯。

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

  Costom伊芙ntReleaser中动用以下代码定义事件:

    public event
EventHandler<CostomEventArgs> CostomEvent;

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

美高梅开户网址 6

二 隐式落成事件

  大家在上例中来看的事件的完全定义会转换为以下贰个组织:

   style=”font-size: 1贰px;”>//一 四个被开头化为null的民用委托字段

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

 

  //23个公共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.CostomEvent,newHandler,prevHandler);//通过轮回和对CompareExchange的调用,能够以一种线程安全的格局向事件添加1个委托。

    }

    while(costomEvent
!= prevHandler);

  }

  

  //三1个公共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的调用,能够以壹种线程安全的情势向事件移除3个信托。

    }

    while(costomEvent
!= prevHandler);

  }

  除了生成上述三个布局,编写翻译器还会在托管程序集的元数据中生成二个事变定义纪录项。那个记录项包涵部分评释和基本功委托项目,还引用了add和remove访问器方法。那个消息的效能是起家“事件”的抽象概念和它的访问器方法之间的维系。编写翻译器和其余工具得以利用那几个元数据音信,并可由此System.Reflection.EventInfo类获取那些信息。不过,CL奥迪Q伍本人并不利用那个消息,它在运作时只须要访问器方法。

(5)
事件与自定义处理函数的宏图

一 事件的安插

  • 要用System.伊芙ntHandler<T>来定义事件处理函数,而不是手工业成立新的信托来定义事件处理函数。
  • 设想用伊芙ntArgs的子类来做事件的参数,除非整套确信该事件不要求给事件处理方法传递任何数据,在那种情景下得以一贯利用伊夫ntArgs。
  • 要用受保障的虚方法来触发事件,一般方法以名字“On”开端,随后是事件名字。该规则只适用于非密封类中的非静态事件,不适用于协会、密封类及静态事件。派生类在覆盖虚方法时能够不调用基类的贯彻,要未焚徙薪好应对那种景色,不要在该方式中做其余对基类来说不能缺少的拍卖。
  • 要是类中有三个事件,那么在调用委托以前要求加贰个非空测试,其代码形如:“ClickHandler
    handler=Click;if(handler !=null) handler(this,e);”。
  • 编写翻译器生成的用来丰裕和删除事件处理方法的代码在多线程中不安全,所以借使须要协理让三十二线程同时加上或删除事件处理方法,那么须要团结编排代码来添加和去除事件处理方法,并在中间开始展览锁操作。
  • 要让触发事件的受保险格局带一个参数,该参数的项目为事件参数类,该参数的名字应该为“e”。
  • 不要在触发非静态事件时把null作为sender参数字传送入。
  • 要在触发静态事件时把null作为sender参数字传送入。
  • 决不在接触事件时把null作为数据参数字传送入,假设不行传任何数据应该选择伊夫ntArgs.Empty。
  • 思虑采取Cancel伊夫ntArgs或它的子类作为参数,来触发能够被最后用户撤消的风云,那只适应于前置事件。代码形如:“void
    ColeingHandler(object
    sender,CancelEventArgse){ e.Cancel=true;}”。

贰 自定义处理函数的安插性

  • 把事件处理函数的回来值类型定义为void。
  • 要用object作为事件处理函数的率先个参数的门类,并将其命名字为sender。
  • 要用伊芙ntArgs或其子类作为事件处理函数的第二个参数的档次,并将其命名字为e。
  • 无须在事件处理函数中央银行使三个以上的参数。

(6).NET松耦合事件

  .NET事件简化了风云管理,它使咱们不用去写管理订阅者列表的麻烦的代码。可是依照委托的事件可能存在以下缺陷:

  • 对于每一种要从内部接收事件的揭橥者对象,订阅者都必须再度添加订阅的代码,未有一种格局能够订阅有些项目标轩然大波并使该事件传递给订阅者,而随便发表者是何人。
  • 订阅者不可能筛选已接触是事件(例如,提示“仅在满足某种条件是才公告本身该事件”)。
  • 订阅者必须有某种格局获得发表者对象才能对其开始展览订阅,那样就导致了订阅者和发表者之间以及种种订阅者之间的耦合。
  • 公布者和订阅者具有耦合的生命周期,两者必须同时运行。订阅者不可能通告.NET“尽管别的对象触发此事件,请创建八个自家的实例,并由本人来拍卖”。
  • 未曾近便的小路执行打消订阅操作,宣布者对象在脱机是电脑上接触事件,1旦计算机处于联机状态,该事件便会传给订阅者。反之,订阅者运行在脱机的微型总括机上,1旦处于联机状态,接收到在断开连接时接触是事件也是唯恐的。
  • 订阅的树立和注销必须透过编制程序格局形成。

  .Net和别的第1方框架援救松耦合事件,在.NET中定义松耦合事件可以参照MSDN中有关System.EnterpriseServices.ServicedComponent的有关内容。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图