【C#进阶系列】11 事件
事件,定义了事件成员的类型允许类型或类型的实例通知其它对象发生了特定的事情。
按照我自己的理解而言,事件可以被(方法)关注,也可以被(方法)取消关注,事件发生后关注了事件的一方会了解到,并对事件做出相应的应对(执行方法)。(我每次都是这么理解的,这样从字面意义上更好理解一点)
众所周知,事件实际上就是基于委托的。而委托是调用回调函数的一种类型安全的方式。
今天写一个关于分手的事件Demo,算是生动形象吧。
定义事件参数类(可忽略这步)
一个事件发生后若要传递附加的参数信息,就需要定义事件参数类,需要继承EventArgs,否则就直接使用EventArgs.Empty即可。(EventArgs.Empty实际上就是new EventArgs())
public class 分手EventArgs : EventArgs { public 分手EventArgs(string name, string title) { this._分手的人 = name; this._分手原因 = title; } public string 分手的人 { get { return _分手的人; } } public string 分手原因 { get { return _分手原因; } } private readonly string _分手的人; private readonly string _分手原因; }
定义事件成员
public class Troy { //委托类型EventHandler的声明public delegate void EventHandler //第一个对象顾名思义是事件发出者,这里肯定就是Troy发生了事件 //第二个参数传递的就是我们之前定义的事件附加信息 public event EventHandler<分手EventArgs> 宣称要分手; }(object sender, TEventArgs e);
现在有了一个Troy的宣称分手事件。
既然事件有了,那么接下来就是去让本人去引发这个事件
引发事件
于是就变成了这样
public class Troy { //委托类型EventHandler的声明public delegate void EventHandler //第一个对象顾名思义是事件发出者,这里肯定就是Troy发生了事件 //第二个参数传递的就是我们之前定义的事件附加信息 public event EventHandler<分手EventArgs> 宣称要分手; //定义负责引发事件的方法来通知已关注事件的人,一般是要定义为protected和virtual protected virtual void On宣称要分手(分手EventArgs e) { //如果有人听我说这个事,那么就说,没人听我就肯定不说了,你在程序里玩自言自语也不是不行,不过显得很傻而已(完美贴切好形象) if(宣称要分手!=null)this.宣称要分手(this,e); } }(object sender, TEventArgs e);
本书中其实还介绍了一个出于线程安全考虑的引发事件的写法,
因为如果在我宣称要分手前,本来听我讲这个事的人A有另一个人B叫他,A他突然跑掉了(在另一个线程中 宣称要分手 的委托链就被移除了委托),那么此时 宣称要分手 这个事件就没人听了(宣称要分手为null),然后我酝酿了半天的话吐不出来就报了个Null异常。
所以有了下面这种写法
protected virtual void On宣称要分手(分手EventArgs e) { //下面代码的意思就是:我要说分手的时候将关注了 宣称要分手 这个事件的人都拉到讨论组中 //然后就算被另一个线程的人将在我旁边的人都叫走了,实际上因为我把他们丢拉到讨论组中了 //此时讨论组中都有成员,所以关注的人还是获悉了这个悲伤的故事 var 讨论组 = System.Threading.Volatile.Read(ref 宣称要分手);//Volatile.Read仅仅起到赋值宣称要分手的引用给讨论组,之所以不用等于,是因为可能会被编译器优化时去掉临时变量 讨论组。 if (讨论组 != null)this.宣称要分手(this,e); }
然而JIT编译器表示,实际上这种用等于也可以,只是为了防范于未然。
然而此处我只想安静地分手,所以让我们沿用更上面的说法
真实的引发事件
我每天都可以说很多分手事件,有的是你,有的还是你。
为了更准确的把这个事给说清楚了,我也许还要传递个信息,就是这次可能要分手的是我。
public class Troy { //委托类型EventHandler的声明public delegate void EventHandler //第一个对象顾名思义是事件发出者,这里肯定就是Troy发生了事件 //第二个参数传递的就是我们之前定义的事件附加信息 public event EventHandler<分手EventArgs> 宣称要分手; //定义负责引发事件的方法来通知已关注事件的人,一般是要定义为protected和virtual,前者是要,后者是为了派生类的必要 protected virtual void On宣称要分手(分手EventArgs e) { if(宣称要分手!=null)this.宣称要分手(this,e); } public void 宣称自己要分手() { //这一步我们把事说清楚 var e = new 分手EventArgs("Troy", "心累"); On宣称要分手(e);//如果子类没有重写这个事件引发函数,那么就告诉所有关注的人这个事 } }(object sender, TEventArgs e);
事件类型被编译后会出现什么?
C#编译器将事件编译后会转换成3个东西:一个私有的委托字段 宣称要分手,一个公共的关注事件的方法 add_宣称要分手,一个公共的取消关注的方法 remove_宣称要分手。
除了这三个东西,编译器还会在托管程序集的元数据中生成一个事件定义记录项,它的作用只是为了建立“事件”的抽象概念和它的访问器方法之间的联系。(高深吧?然而你并不需要懂)
关于关注事件的那些人
事件以及事件的引发都弄好了,接下来就是定义关注事件的那些人了。以下人物由真实人物改编:
//下面是将婚同事李 public class Lee { public Lee(Troy troy) { //初始化就关注事件 troy.宣称要分手 += this.AfterListen; } private void AfterListen(Object sender, 分手EventArgs e) { Console.WriteLine("让troy去玩游戏"); } } //然后是单身同事肖 public class Xiao { public Xiao(Troy troy) { troy.宣称要分手 += this.AfterListen; } private void AfterListen(Object sender, 分手EventArgs e) { Console.WriteLine("表示Troy打击单身狗"); } //由于Xiao并不是每天都和Lee一样,与Troy同行,所以Xiao也许开始听得到,后来跑远了,就取消关注事件了 private void UnListen(Troy troy) { troy.宣称要分手 -= this.AfterListen; } }
一个对象只要某个方法关注了事件,那么它就不能被回收了。所以如果你想让垃圾处理器回收某对象,就让他不要再关注事件了。
好吧,就这些了。
PS:
这确实是一个真实的故事,就在昨天。
我也不知道我为什么还有心情写博客,反正除了这个我也已经不知道该干嘛好了。
大概是因为就算失恋了也不可能说不去吃饭睡觉之类的感觉吧。
更加令人惊奇的是,效率不知道为什么出奇得高,以至于十一点前就完成了学习和博客。
每个人都需要去成长,成长的故事大多都是不舒服的,一如熬夜学习写博客,一如分手这件事。
怀着感恩的心去面对已经失去的人,反而比什么样的疗伤都来得有效d(╯﹏╰)b