图片 5

字段以及事件


目录

 


常量与字段

事件概述

寄托是一种档期的顺序能够被实例化,而事件能够作为将多播委托进行包装的多少个目的成员(简化委托调用列表增删方法)但毫无新鲜的委托,拥戴订阅互不影响。

 


事件

基本功事件(event)

在.Net中声明事件应用首要词event,使用也非常轻松在信托(delegate)后面加上event:

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

上述代码试行结果:

图片 1

 

一 常量与字段

事件公布&订阅

事件基于委托,为委托提供了一种发布/订阅机制。当使用事件时一般会师世三种剧中人物:发行者订阅者。

发行者(Publisher)也堪当发送者(sender):是包含委托字段的类,它调节曾几何时调用委托广播。

订阅者(Subscriber)也叫做接受者(recevier):是方法指标的接收者,通过在发行者的委托上调用+=和-=,决定哪一天早先和得了监听。三个订阅者不领悟也可是问别的的订阅者。

来电->打开手机->接电话,诸有此类一个供给,模制订阅揭橥机制:

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

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

施行结果:

图片 2

注意:

1.平地风波只可以够从申明它们的类中调用, 派生类无法直接调用基类中扬言的轩然大波。

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

2.对于事件在声明类外界只可以+=,-=不可能一向调用,而委托在外表不只好够动用+=,-=等运算符还足以一向调用。

上面调用方式与地点试行结果同样,利用了委托多播的性状。

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

 

(一) 常量

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

有过Windwos Form开垦经历对下边包车型地铁代码会驾驭:

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

在设计器Form1.Designer.cs中有事件的附加。这种艺术属于Visual Studio
IDE事件订阅。

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

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

基于EventHandler形式的平地风波

 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)是贰个对象,满含事件的发送者。
其次个参数(e)提供了平地风波的连带新闻,参数随差异的事件类型而退换(继承伊夫ntArgs)。
.NET1.0为富有分歧数据类型的风浪定义了几百个委托,有了泛型委托EventHandler<T>后,不再需求委托了。

2.EventArgs,标志表示包涵事件数量的类的基类,并提供用于不含有事件数量的风云的值。

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

3.何况能够基于编制程序格局订阅事件

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

4.Consumer类为事件监听器当接触事件时可获取当前发表者对应自定义音信目的,能够依据要求做逻辑编码,再实行事件所订阅的相干管理。扩充事件订阅/发表机制的健壮性。

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

1 EventHandler<CustomEventArgs> publication = Publication;

接触事件是只包蕴一行代码的程序。那是C#6.0的效率。在事先版本,触发事件此前要做为空判别。同不经常间在拓展null检测和接触之间,恐怕另四个线程把事件设置为null。所以要求一个部分变量。在C#6.0中,全数触发都得以应用null传播运算符和贰个代码行替代。

1 Publication?.Invoke(this, e);

留心:就算定义的类中的事件可依附别的有效委托项目,以至是重回值的信托,但貌似依然提出利用
伊芙ntHandler
使事件基于 .NET Framework 形式。

 

  常量总是被视为静态成员,并不是实例成员。定义常量将招致创设元数据。代码援用三个常量时,编写翻译器会在概念常量的先后集的元数据中查找该符号,提取常量的值,并将值嵌入IL中。由于常量的值直接嵌入IL,所以在运营时无需为常量分配任何内部存款和储蓄器。其余,不能够获得常量的地点,也不能够以传递援用的不二等秘书诀传送常量。这几个限制意味着,没有很好的跨程序集版本调整个性。由此,独有在分明八个标识的值从不改变化时,才应该运用。倘使期待在运维时从三个主次集中提取一个主次聚集的值,那么不应有使用常量,而相应使用
readonly 字段。

线程安全格局触发事件

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

 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最健全本领科学,版本2也是足以应用的,因为JIT编写翻译机制上明白不应当优化掉变量temp,所以在有个别变量中缓存三个引用,可保证堆应用只被访问叁遍。但他日是不是变动不好说,所以提出利用版本3。

 

 

图片 4

事件揭示

笔者们重新审视基础事件里的一段代码:

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作为事件访谈器用于(+=、-=),NoReturnWithParametersEvent被编写翻译为Private进而达成封装外界不可能接触事件。

1.委托类型字段是对信托列表尾部的引用,事件产生时会文告这么些列表中的委托。字段初步化为null,申明无侦听者品级对该事件的关切。

2.固然原始代码将事件定义为Public,委托字段也始终是Private.目标是幸免外界的代码不得法的操作它。

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

4.绸缪删除从未增多过的办法,Delegate的Remove方法内部不做其余交事务经,不会抛出万分或其余警示,事件的主意集体保持不改变。

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

 

(二) 字段

结语

类或对象能够通过事件向别的类或对象布告发出的连锁事情。事件选拔的是公布/订阅机制,申明事件的类为公布类,而对那一个事件张开管理的类则为订阅类。而订阅类怎么着知道那几个事件时有发生并管理,这时候须求用到委托。事件的行使离不开委托。可是事件实际不是委托的一种(事件是格外的委托的传教并不得法),委托属于类型(type)它指的是聚众(类,接口,结构,枚举,委托),事件是概念在类里的一个成员。

 

  CL途观帮忙项目字段和实例字段。对于项目字段,用于容纳字段数据的动态内部存款和储蓄器是在项目对象中分配的,而项目对象是在品种加载到二个AppDomain时创建的;对于实例字段,用于容纳字段数据的动态内部存款和储蓄器则是在构造类型的四个实例时分配的。字段化解了版本调控难题,其值存款和储蓄在内部存款和储蓄器中,独有在运作时技术博取。

参照他事他说加以考察文献

 

CLR via C#(第4版) Jeffrey Richter

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

果壳中的C# C#5.0高尚指南 Joseph Albahari


 

  如若字段是援用类型,且被标识为readonly,那么不可改动的是援引,而非字段援用的靶子。

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

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

  • 编写翻译期常量的值在编写翻译时收获,而运作时常量的值在运维时收获。
  • 双面访谈格局不一样。编写翻译期常量的值是在目的代码中开始展览替换的,而运作时常量将要运作时求值,援引运维时常量生成的IL将援引到readonly的变量,实际不是变量的值。由此,编写翻译期常量的性格更加好,而运转时常量更为灵活。
  • 编写翻译期常量仅支持整型、浮点型、枚举和字符串,其余值类型如DateTime是爱莫能助最先化编写翻译期常量的。但是,运维时常量则帮忙任何项目。
  • 编写翻译期常量是静态常量,而运维时常量是实例常量,可以为品种的各类实例存放差异的值。

  综上所述,除非需求在编写翻译时期获得适当的数值以外,别的意况,都应有尽也许利用运维时常量。

(四) 常量与字段的宏图

  • 决不提供公有的或受有限支撑的实例字段,应该一味把字段定义为private。
  • 要用常量字段来表示永久不会更换的常量。
  • 要用公有的静态只读字段定义预约义的对象实例。
  • 决不把可变类型的实例赋值给只读字段。


事件

  假如类型定义了事件,那么类型(或项目实例)就能够文告别的对象发送了一定的业务。如果定义了事件成员,那样类型要提供以下手艺:

  • 措施能够登记对事件的关心。
  • 情势能够裁撤对事件的关怀。
  • 事件发送时,关心该事件的方法会收到通告。

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

(一) 怎么样运用事件

  下例凸显了怎么选取事件:

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;
        }
    }
}

先是步 自定义事件参数

  应该在EventArgs派生类中为事件管理程序提供参数,并将这么些参数作为类成员。委托类中遍历他的订阅者列表,将参数对象在订阅者中逐条传递。但力不可能支防卫某些订阅者修改参数值,进而影响其后具备的处总管件的订阅者。平常状态下,当这个分子在订阅者中传送时,应防止订阅者对其进行修改,可将参数的访谈权限设置为只读,或当面那么些参数为公家成员,并利用readonly访谈修饰符,在那二种景况下,都应当在构造器中初叶化这几个参数。

其次步 定义委托具名

  即使委托注解能够定义任何方法具名,但在实施中事件委托应该符合一些特定的教导方针,首要归纳:

  • 先是,目标措施的回到类型应该为void。使用void的缘由是,向事件宣布者再次回到一个值毫无意义,发表者不晓得事件订阅者为何要订阅,其它,委托类向发表者掩盖了实际上公布操作。该信托对个中直接收器列表举办遍历(订阅对象),调用每一个对应的不二诀要,由此回到的值不会流传到公布者的代码。使用void重回类型还提议我们幸免使用带有ref或out参数修饰符的出口参数,因为各种订阅者的输出参数不会传播给发布者。
  • 其次,一些订阅者只怕想要从四个事件发表源接收同样的平地风波。为了让订阅者区分出区别的发表者触发的事件,签字应涵盖宣布者的标志。在不借助于泛型的图景下,最简易的点子正是增进三个object类型的参数,称为发送者(sender)参数。之所以要求sender参数是object类型,首即便由于持续。另一个缘故是看人下菜。它同意委托由四个类型应用,唯有这个种类提供了三个会传递相应的风浪参数的风云。
  • 最后,定义实际事件参数将订阅者与宣布者耦合起来,因为订阅者须求一组特定的参数。.NET提供了EventArgs类,作为正式是事件参数容器。

其三步
定义担任吸引事件的不二等秘书技来打招呼事件的注册

  类应定义七个受保险的虚方法。要吸引轩然大波时,当前类及其派生类中的代码会调用该办法。

第四步 防卫式发表事件

  在.NET中,假若委托在其内部列表中绝非对象,它的值将安装为null。C#公布者在品尝调用委托在此之前,应该检查该信托是还是不是为null,以咬定是还是不是有订阅者订阅事件。

发表评论

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