威尼斯人线上娱乐

【威尼斯人线上娱乐】上学笔记,知识回想

7 4月 , 2019  


目录

 事件概述                                                           

[.NET] C# 知识回看,

 

一常量与字段

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

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

事件概述

寄托是一种档次能够被实例化,而事件能够作为将多播委托开始展览打包的三个指标成员(简化委托调用列表增删方法)但毫无新鲜的嘱托,保养订阅互不影响。

 


事件

  • 特点
    • 发行者鲜明什么时候引发风云,订户分明实施何种操作来响应该事件。
    • 一个事件能够有四个订户。多个订户可处理来自多少个发行者的三个事件。
    • 从未有过订户的轩然大波永远不会被调用。
    • 事件司空眼惯用于通告用户操作
    • 假定一个风浪有四个订户,当引发该事件时,会一起调用八个事件处理程序,也得以安装异步调用事件。
    • 可以采纳事件联合线程。
    • 事件是依据 伊芙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):是办法目的的收信人,通过在发行者的委托上调用+=和-=,决定几时起头和了结监听。1个订阅者不知晓也不干涉其它的订阅者。

来电->开拓手提式有线电话机->接电话,那样两个急需,模拟订阅发布机制:

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

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

实践结果:

威尼斯人线上娱乐 2

注意:

一.轩然大波只可以够从注明它们的类中调用, 派生类不恐怕直接调用基类中申明的风云。

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

2.对于事件在注明类外部只好+=,-=不能一直调用,而委托在表面不仅能够动用+=,-=等运算符还足以平素调用。

上面调用方式与地点执行结果壹致,利用了寄托多播的性状。

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

 

(一) 常量

     假诺您想编写引发风浪时调用的自定义代码,则能够订阅由其它类发表的轩然大波。例如,可以订阅某些按钮的“单击”事件,以使应用程序在用户单击该按钮时实施1些管用的操作。

目录

  • 颁发符合 .NET 准则的风云
  • 选拔伊夫ntHandler 格局公布事件
  • 2个简便的颁发订阅 德姆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);

第四个参数(sender)是八个目的,包罗事件的发送者。
第3个参数(e)提供了风浪的连带音讯,参数随区别的轩然大波类型而更改(继承伊芙ntArgs)。
.NET一.0为具有差别数据类型的事件定义了几百个委托,有了泛型委托伊夫ntHandler<T>后,不再须求委托了。

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

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

三.并且能够依照编制程序形式订阅事件

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。所以要求三个片段变量。在C#六.0中,全数触发都得以动用null传播运算符和3个代码行取代。

1 Publication?.Invoke(this, e);

留意:固然定义的类中的事件可依据别的有效委托项目,甚至是重回值的寄托,但壹般还是建议利用
伊芙ntHandler
使事件基于 .NET Framework 格局。

 

  常量总是被视为静态成员,而不是实例成员。定义常量将导致创立元数据。代码引用四个常量时,编写翻译器会在概念常量的次序集的元数据中查找该符号,提取常量的值,并将值嵌入IL中。由于常量的值直接嵌入IL,所以在运作时不须求为常量分配任何内部存款和储蓄器。其余,无法博取常量的地址,也不能够以传递引用的法子传送常量。那些限制意味着,未有很好的跨程序集版本控制性情。由此,唯有在规定2个标记的值从不变化时,才应该运用。假如希望在运作时从2个主次集中提取3个主次集中的值,那么不应该使用常量,而应当使用
readonly 字段。

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

      • 概念1个事件处理程序方法,其签名与该事件的信托签名相配。例如,借使事件基于
        伊芙ntHandler 委托类型,则下边包车型的士代码表示方法存根

1、发布符合 .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复制后哪怕另叁个线程更改了AfterPublication对象也未尝关联。委托是不足变得,所以理论上有效性。可是编写翻译器或者通过一点一滴移除变量temp的章程对上述代码进行优化所以仍恐怕抛出NullReferenceException.

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

本子3最健全技术不错,版本贰也是足以行使的,因为JIT编写翻译机制上驾驭不应当优化掉变量temp,所以在有的变量中缓存1个引用,可确定保障堆应用只被访问一遍。但他日是否改变不佳说,所以提出利用版本三。

 

 

威尼斯人线上娱乐 4

void HandleCustomEvent(object sender, CustomEventArgs a){  }

贰、选择 伊夫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 结尾的名称。  第2个参数钦定自定义
伊芙ntArgs 类型。 

    delegate void MyEventHandler(object sender, MyEventArgs args);

 

  三.使用以下任一步骤,在公布类中声称事件。

    (一)固然未有自定义
伊芙ntArgs 类,事件类型就是非泛型 伊芙ntHandler 委托。无需表明委托,因为它已在创造C# 项目时带有的 System 命名空间中开始展览了申明。将以下代码添加到发行者类中。 

public event EventHandler MyEvent;

 

    (二)借使使用的是 伊芙ntHandler 的非泛型版本,并且您有叁个由 伊芙ntArgs 派生的自定义类,请在颁发类中宣称您的轩然大波,并且未来自步骤
2 的嘱托用作类型。

public event MyEventHandler MyEvent;

 

    (叁)如果应用的是泛型版本,则不必要自定义委托。相反,在发行者类中,您应将事件类型内定为 伊夫ntHandler<My伊夫ntArgs>,将尖括号中的内容替换为协调的类的称谓。  

public event EventHandler<MyEventArgs> MyEvent;

 

事件揭秘

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

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

宣示了1个民用的信托变量,开放几个点子add和remove作为事件访问器用于(+=、-=),NoReturnWithParameters伊夫nt被编写翻译为Private从而完成封装外部不能接触事件。

1.委托类型字段是对信托列表底部的引用,事件发生时会公告这一个列表中的委托。字段初叶化为null,评释无侦听者等级对该事件的关注。

贰.便是原始代码将事件定义为Public,委托字段也始终是Private.目标是制止外部的代码不得法的操作它。

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

4.打算删除从未添加过的形式,Delegate的Remove方法内部不做别的事经,不会抛出越发或别的警示,事件的法子集体保持不变。

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     }

 

结语

类或对象能够通过事件向其余类或对象公告发出的连锁事情。事件选拔的是公布/订阅机制,注脚事件的类为发表类,而对这些事件展开处理的类则为订阅类。而订阅类怎样知道那个事件发生并处理,那时候需求用到委托。事件的利用离不开委托。不过事件并不是信托的一种(事件是特种的委托的传教并不得法),委托属于类型(type)它指的是聚众(类,接口,结构,枚举,委托),事件是概念在类里的贰个分子。

 

  CLRAV4帮衬项目字段和实例字段。对于项目字段,用于容纳字段数据的动态内部存款和储蓄器是在类型对象中分红的,而项目对象是在项目加载到一个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# 知识回顾, C# 知识回想 – 伊芙nt 事件
【博主】反骨仔 【最初的小说】
序 昨天,通过《C# 知识回想 – 事…

参考文献

 

CLR via C#(第4版) Jeffrey Richter

C#尖端编制程序(第柒版) Christian Nagel  (版九、拾对事件部分未有多大距离)

果壳中的C# C#伍.0高雅指南 何塞普h 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的变量,而不是变量的值。因而,编写翻译期常量的质量更加好,而运营时常量更为灵活。
  • 编写翻译期常量仅补助整型、浮点型、枚举和字符串,其余值类型如DateTime是无力回天开始化编写翻译期常量的。可是,运营时常量则辅助任何项目。
  • 编写翻译期常量是静态常量,而运行时常量是实例常量,能够为品种的每一个实例存放分裂的值。

   
 要严防在诱惑事件时调用事件处理程序,您只需撤废订阅该事件。要谨防能源败露,请在刑释订户对象在此之前撤消订阅事件,那点很关键。在废除订阅事件从前,在发表对象中作为该事件的基本功的多路广播委托会引用封装了订户的事件处理程序的寄托。只要发表对象涵盖该引用,就不会对订户对象执行垃圾回收。

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

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

(4) 常量与字段的布置

publisher.RaiseCustomEvent -= HandleCustomEvent;
  • 并非提供公有的或受有限支撑的实例字段,应该1味把字段定义为private。
  • 要用常量字段来表示永远不会变动的常量。
  • 要用公有的静态只读字段定义预约义的对象实例。
  • 并非把可变类型的实例赋值给只读字段。

 布告正式事件                                           


事件

     下边包车型客车进程演示了哪些将符合标准 .NET
Framework 格局的风浪添加到您本人的类和布局中。.NET Framework
类库中的全体事件均基于 伊夫ntHandler 委托,定义如下。

  假诺类型定义了风浪,那么类型(或项目实例)就能够通告别的对象发送了一定的政工。假若定义了风云成员,这样类型要提供以下能力:

public delegate void EventHandler(object sender, EventArgs e);
  • 办法能够注册对事件的关切。
  • 措施可以撤除对事件的关爱。
  • 事件发送时,关心该事件的方法会收到布告。
  • 使用 伊芙ntHandler
    形式发表事件
    • (如若不须求发送含事件的自定义数据,请跳过此步骤,直接进入步骤
      3。)在发行者类和订户类均可看见的限定中评释类,并丰裕保留自定义事件数量所需的分子。在此示例中,会回来一个不难易行字符串。

  类型之所以能提供事件通报作用,是因为项目维护了二个已登记方法的列表,事件发送后,类型会文告列表中颇具办法。

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

(1) 怎样运用事件

    • (假设您使用的是 伊夫ntHandler
      的泛型版本,请跳过此步骤。)在昭示类中扬言3个信托。为它钦点以
      伊芙ntHandler 结尾的名号。第2个参数钦定自定义 伊夫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
        的非泛型版本,并且您有3个由 伊夫ntArgs
        派生的自定义类,请在宣布类中宣示您的风云,并且将你的信托用作类型

其次步 定义委托签名

class Publisher
{
    public event CustomEventHandler RaiseCustomEvent;
}

  即便委托注脚能够定义任何措施签名,但在实践中事件委托应该符合一些一定的指引方针,首要不外乎:

      • 如若采取的是泛型版本,则不必要自定义委托。相反,应将事件类型内定为
        伊芙ntHandler<Custom伊芙ntArgs>,在尖括号内放置您本人的类的名称。
  • 第二,目的措施的回来类型应为void。使用void的原由是,向事件发布者重临贰个值毫无意义,公布者不知情事件订阅者为何要订阅,其它,委托类向宣布者隐藏了实际上发表操作。该信托对其内部接收器列表进行遍历(订阅对象),调用每个对应的不二秘籍,由此回到的值不会流传到公布者的代码。使用void重临类型还提出大家幸免选拔带有ref或out参数修饰符的出口参数,因为种种订阅者的出口参数不会传播给宣布者。
  • 说不上,一些订阅者或许想要从八个事件发表源接收相同的事件。为了让订阅者区分出分裂的公布者触发的风浪,签名应包罗发表者的标识。在不借助泛型的意况下,最简单易行的办法便是添加三个object类型的参数,称为发送者(sender)参数。之所以须要sender参数是object类型,重如果出于后续。另2个原因是人云亦云。它同意委托由多个品类应用,唯有那一个项目提供了1个会传送相应的轩然大波参数的轩然大波。
  • 最后,定义实际事件参数将订阅者与公布者耦合起来,因为订阅者须要1组特定的参数。.NET提供了伊芙ntArgs类,作为专业是事件参数容器。
public event EventHandler<CustomEventArgs> RaiseCustomEvent;

其三步
定义负责吸引事件的章程来打招呼事件的注册

 抓住派生类中的基类事件                                      

  类应定义3个受保证的虚方法。要抓住轩然大波时,当前类及其派生类中的代码会调用该格局。

   
 以下不难示例演示了在基类中声称可从派生类引发的风浪的正儿8经措施。此方式广泛应用于
.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();
        }
    }
}

  另2个内需留意的难点是丰裕。全数未处理的订阅者引发的老大都会传出给公布者,导致公布者崩溃。所以,使用时最佳在try/catch块内部揭橥事件。

 达成接口事件                                            

  还需求专注的是线程安全,上例给出了一种线程安全的轩然大波引发代码。思虑线程竟态条件应该发现到的一个重点是,三个方法只怕在事变的寄托列表中移除之后获得调用。

   
 接口可申明事件。上面包车型地铁示范演示怎么样在类中贯彻接口事件。接口事件的兑现规则与其余接口方法或性质的贯彻规则基本相同。

(二) 管理多量事变

  • 在类中贯彻接口事件

  处理大批量风浪的难点在于,为每一种事件都分配1个类成员是不现实的。为化解此题材,.NET提供了EventHandlerList类。伊芙ntHandlerList是存款和储蓄键/值对的线性列表。键是标识事件的对象,值是Delegate的实例。因为索引是多个目的,所以它能够是整数、字符串、特定的按钮实例等等。使用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);
        }
    }
}

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

     下面包车型大巴演示演示怎样处理以下的不常见事态:您的类是从五个以上的接口继承的,各类接口都包括同名事件)。在那种处境下,您至少要为当中1个轩然大波提供显式接口完结。为事件编写显式接口达成时,必须编写制定add 和 remove
事件访问器。那八个事件访问器日常由编写翻译器提供,但在那种气象下编译器不可能提供。

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

     您能够提供温馨的访问器,以便钦命那多个事件是由你的类中的同一事件表示,照旧由区别事件代表。例如,根据接口规范,假如事件应在分歧时间引发,则足以将各类事件与类中的3个单身达成关系。在底下的以身作则中,订户将造型引用强制转换为
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();
        }
    }
}

  上面代码展现了这种办法的长处,仅通过1遍调用就足以通报任何接口,并体现了对事件类成员的一点1滴封装。

 运用字典存款和储蓄事件实例                                       

(四) 事件的真相

     accessor-declarations
的一种用法是公开大气的事件但不为每一种事件分配字段,而是利用字典来囤积那一个事件实例。那只有在具有极度多的事件、但您推测大多数事件都不会完成时才有用。

  事件是3个类为委托的字段再加上三个对字段实行操作的办法。事件是寄托列表尾部的引用,是为着简化和创新使用委托来作为应用程序的回调机制时的编码。.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来细化作为事件订阅和通报使用的嘱托项目。将委托成员变量定义为事件后,尽管该成员为公家成员,也仅有公布类(不包蕴子类)能够出发此事件(尽管任何人都能够向该信托列表添加目的措施)。由揭橥类的开发者来控制是还是不是提供二个集体措施来触发该事件。使用事件代表本来委托还会下落公布者与订阅者间的松耦合,因为宣布者触发事件的事情逻辑对订阅者是隐形的。

     有各样方法可向客户端代码公开异步效率。基于事件的异步格局为类规定了用来体现异步行为的提出措施。对于绝对不难的四线程应用程序,BackgroundWorker
组件提供了三个不难易行的消除方案。对于更复杂的异步应用程序,请酌量达成一个顺应基于事件的异步格局的类。

壹 事件访问器

    • “在后台”执行耗费时间任务(例如下载和数据库操作),但不会一噎止餐您的应用程序。
    • 并且执行几个操作,各种操作完毕时都会吸收布告。
    • 伺机财富变得可用,但不会告一段落(“挂起”)您的应用程序。
    • 利用深谙的风云和信托模型与挂起的异步操作通信。

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

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

    public event
EventHandler<CostomEventArgs> CostomEvent;

  那段代码是概念事件的1种缩写方式,它会隐式定义添加和删除处理程序的措施并申明委托的一个变量。当编写翻译器编写翻译那段代码时,会把它转换为以下三个结构:

威尼斯人线上娱乐 6

二 隐式完结事件

  大家在上例中看看的轩然大波的总体定义会转换为以下二个结构:

   style=”font-size: 1贰px;”>//① 1个被初叶化为null的私有委托字段

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

 

  //二一个公共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的调用,能够以壹种线程安全的措施向事件添加一个寄托。

    }

    while(costomEvent
!= prevHandler);

  }

  

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

    }

    while(costomEvent
!= prevHandler);

  }

  除了生成上述一个布局,编写翻译器还会在托管程序集的元数据中生成贰个轩然大波定义纪录项。这么些记录项包涵部分标明和基础委托项目,还引述了add和remove访问器方法。那一个音信的功用是建立“事件”的抽象概念和它的访问器方法之间的联络。编写翻译器和别的工具得以运用那个元数据新闻,并可经过System.Reflection.伊芙ntInfo类获取那个音信。不过,CL帕杰罗本身并不行使这个新闻,它在运营时只要求访问器方法。

(伍)
事件与自定义处理函数的安插

一 事件的规划

  • 要用System.伊芙ntHandler<T>来定义事件处理函数,而不是手工业创制新的委托来定义事件处理函数。
  • 设想用伊芙ntArgs的子类来做事件的参数,除非整套确信该事件不要求给事件处理方法传递任何数据,在那种气象下能够直接运用伊芙ntArgs。
  • 要用受保证的虚方法来触发事件,一般方法以名字“On”初阶,随后是事件名字。该规则只适用于非密封类中的非静态事件,不适用于组织、密封类及静态事件。派生类在覆盖虚方法时能够不调用基类的达成,要准备好应对那种景况,不要在该办法中做别的对基类来说不可或缺的拍卖。
  • 假使类中有2个风浪,那么在调用委托之前供给加四个非空测试,其代码形如:“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;}”。

2 自定义处理函数的计划性

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

(陆).NET松耦合事件

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

  • 对于种种要从内部接收事件的发布者对象,订阅者都必须再次添加订阅的代码,未有1种情势能够订阅某些项目标风云并使该事件传递给订阅者,而随便揭橥者是哪个人。
  • 订阅者不可能筛选已接触是事件(例如,提醒“仅在满意某种条件是才文告自身该事件”)。
  • 订阅者必须有某种方式获得发表者对象才能对其进展订阅,这样就导致了订阅者和发表者之间以及各类订阅者之间的耦合。
  • 发表者和订阅者具有耦合的生命周期,两者必须同时运转。订阅者不能够文告.NET“若是其他对象触发此事件,请成立八个自身的实例,并由本身来处理”。
  • 尚未近便的小路执行撤消订阅操作,发表者对象在脱机是电脑上接触事件,壹旦总结机处于联机状态,该事件便会传给订阅者。反之,订阅者运转在脱机的微型计算机上,一旦处于联机状态,接收到在断开连接时接触是事件也是唯恐的。
  • 订阅的创设和注销必须透过编制程序形式成就。

  .Net和别的第②方框架帮衬松耦合事件,在.NET中定义松耦合事件能够参见MSDN中有关System.EnterpriseServices.ServicedComponent的有关内容。


相关文章

发表评论

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

网站地图xml地图