<NET CLR via c# 第4版>笔记 第11章 事件
11.1 设计要公开事件的类型
11.1.1 第一步: 定义类型来容纳所有需要发送给事件通知接收者的附加信息
    //第一步:定义一个类型来容纳所有应该发送给事件通知接收者的附加信息
    internal class NewMailEventArgs : EventArgs
    {
        private readonly string m_from, m_to, m_subject;
        public NewMailEventArgs(string from, string to, string subject)
        {
            m_from = from; m_to = to; m_subject = subject;
        }
        public string From { get { return m_from; } }
        public string To { get { return m_to; } }
        public string Subject { get { return m_subject; } }
    }
11.1.2 第二步: 定义事件成员
    internal class MailManager
    {
        //第二步:定义事件成员
        public event EventHandler<NewMailEventArgs> NewMail;
        ...
    }
方法原型必须具有以下形式:
    void MethodName(object sender,NewMailEventArgs e);
- 要求所有事件处理程序的返回类型都是 void. 因为引发事件后可能要调用好几个回调方法,但没办法获得所有方法的返回值.
 
11.1.3 第三步: 定义负责引发事件的方法来通知事件的登记对象
    internal class MailManager
    {
        ...
        //第三步:定义负责引发事件的方法来通知已登录的对象.
        //如果类是密封的,该方法要声明为私有和非虚
        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            Volatile.Read(ref NewMail)?.Invoke(this, e);
        }
        ...
    }
以前可能会这样写:
protected virtual void OnNewMail(NewMailEventArgs e){
    //出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中
    EventHandler<NewMailEventArgs> temp = NewMail;
    if (temp != null) temp(this,e);
}
这样写不好的地方是: temp有可能被编译器优化掉(目前MS所有JIT编译器都未这样做,只是理论上存在这种可能),这样就不能防止调用NewMail前,其它线程可能会移除委托,导致NewMail变为null的问题.所以可以像下面这样写:
    EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
    if (temp != null) temp(this, e);
对 Volatile.Read 的调用强迫 NewMail 在这个调用发生时读取,引用真的必须复制到temp变量中(编译器别想走捷径).
Volatile.Read(ref NewMail)?.Invoke(this, e)是c#6.0的写法.
11.1.4 第四步: 定义方法将输入转化为期望事件
    internal class MailManager
    {
        ...
        //第四步:定义方法将输入转化为期望事件
        public void SimulateNewMail(string from, string to, string subject) {
            //构造一个对象来容纳想传给通知接收者的信息
            NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
            //调用虚方法通知对象事件已发生,
            //如果没有类型重写该方法,我们的对象将通知事件的所有登记对象
            OnNewMail(e);
        }
        ...
    }
11.2 编译器如何实现事件
c#编译器编译时把它转换为以下3个构造:
        // 1. 一个被初始化为null的私有委托字段
        private EventHandler<NewMailEventArgs> NewMail = null;
        // 2. 一个公共 add_Xxx 方法(其中Xxx是事件名)
        // 允许方法登录对事件的关注
        public void add_NewMail(EventHandler<NewMailEventArgs> value)
        {
            // 通过循环和对CompareExchange的调用,可以
            // 以一种线程安全的方式向事件添加委托
            EventHandler<NewMailEventArgs> prevHandler;
            EventHandler<NewMailEventArgs> newMail = this.NewMail;
            do
            {
                prevHandler = newMail;
                //合并原委托和新添加的委托
                EventHandler<NewMailEventArgs> newHandler =
                    (EventHandler<NewMailEventArgs>)Delegate.Combine(prevHandler, value);
                //如果 prevHandler 等于 this.NewMail (也就是说当前线程添加委托过程中,
                //没有其它线程向 this.NewMail 添加委托)
                //,则更新 this.NewMail 为合并后的新委托 newHandler
                //newMail为this.NewMail的原始值
                newMail = Interlocked.CompareExchange(ref this.NewMail, newHandler, prevHandler);
                //否则执行下一次循环,重新进行合并操作
            } while (newMail != prevHandler);
        }
        // 3. 一个公共remove_Xxx 方法(其中 Xxx 是事件名)
        // 允许方法注销对事件的关注
        public void remove_NewMail(EventHandler<NewMailEventArgs> value)
        {
            // 通过循环和对CompareExchange的调用,可以
            // 以一种线程安全的方式从事件中移除一个委托
            EventHandler<NewMailEventArgs> prevHandler;
            EventHandler<NewMailEventArgs> newMail = this.NewMail;
            do
            {
                prevHandler = newMail;
                EventHandler<NewMailEventArgs> newHandler =
                    (EventHandler<NewMailEventArgs>)Delegate.Remove(prevHandler, value);
                newMail = Interlocked.CompareExchange(ref this.NewMail, newHandler, prevHandler);
            } while (newMail != prevHandler);
        }
- 试图删除从未添加过的方法,Delegate的Remove方法在内部不做任何事情.也就是说,不会抛出任何异常,也不会显示任何警告.
 
11.3 设计侦听事件的类型
    internal sealed class Fax
    {
        //将MailManager对象传给构造器
        public Fax(MailManager mm)
        {
            //构造EventHandler<NewMailEventArgs>委托的一个实例,
            //使它引用我们的FaxMsg回调方法
            //向MailManager的NewMail事件登记我们的回调方法
            mm.NewMail += FaxMsg;
        }
        //新电子邮件到达时,MailManager将调用这个方法
        private void FaxMsg(object sender, NewMailEventArgs e)
        {
            // 'sender' 表示MailManager对象,便于将信息传回给它
            // 'e' 表示MailManager对象想传给我们的附加事件信息
            Console.WriteLine("Faxing mail message:");
            Console.WriteLine(" From={0},To={1},Subject={2}",
                e.From, e.To, e.Subject);
        }
        //执行这个方法,Fax 对象将向NewMail事件注销自己对它的关注.
        //以后不再接收通知
        public void Unregister(MailManager mm)
        {
            //向MailManager的NewMail事件注销自己对这个事件的关注
            mm.NewMail -= FaxMsg;
        }
    }
- +=操作符向事件添加委托; -=操作向事件注销委托(扫描委托列表,找到一个恰当的委托——基中包装的方法和传递的方法相同,然后remove).
 - 对象不再希望接收事件通知时,应注销对事件的关注. 对象只要向事件登记了它的一个方法,便不能被垃圾回收.所以,如果你的类型要实现IDisposable的Dispose方法,就应该在实现中注销对所有事件的关注.
 
11.4 显式实现事件
作者的代码挺好的,只是我想我可能不会遇到在一个类中定义几十个事件的情况,所以就不写了,需要的时候去翻书吧.
<NET CLR via c# 第4版>笔记 第11章 事件的更多相关文章
- <NET CLR via c# 第4版>笔记 第19章 可空值类型
		
System.Nullable<T> 是结构. 19.1 C# 对可空值类型的支持 C# 允许用问号表示法来声明可空值类型,如: Int32? x = 5; Int32? y = null ...
 - <NET CLR via c# 第4版>笔记 第18章 定制特性
		
18.1 使用定制特性 FCL 中的几个常用定制特性. DllImport 特性应用于方法,告诉 CLR 该方法的实现位于指定 DLL 的非托管代码中. Serializable 特性应用于类型,告诉 ...
 - <NET CLR via c# 第4版>笔记 第17章 委托
		
17.1 初识委托 .net 通过委托来提供回调函数机制. 委托确保回调方法是类型安全的. 委托允许顺序调用多个方法. 17.2 用委托回调静态方法 将方法绑定到委托时,C# 和 CLR 都允许引用类 ...
 - <NET CLR via c# 第4版>笔记 第16章 数组
		
//创建一个一维数组 int[] myIntegers; //声明一个数组引用 myIntegers = new int[100]; //创建含有100个int的数组 //创建一个二维数组 doubl ...
 - <NET CLR via c# 第4版>笔记 第13章 接口
		
13.1 类和接口继承 13.2 定义接口 C#用 interface 关键字定义接口.接口中可定义方法,事件,无参属性和有参属性(C#的索引器),但不能定义任何构造器方法,也不能定义任何实例字段. ...
 - <NET CLR via c# 第4版>笔记 第12章 泛型
		
泛型优势: 源代码保护 使用泛型算法的开发人员不需要访问算法的源代码.(使用c++模板的泛型技术,算法的源代码必须提供给使用算法的用户) 类型安全 向List<DateTime>实例添加一 ...
 - <NET CLR via c# 第4版>笔记 第5章 基元类型、引用类型和值类型
		
5.1 编程语言的基元类型 c#不管在什么操作系统上运行,int始终映射到System.Int32; long始终映射到System.Int64 可以通过checked/unchecked操作符/语句 ...
 - <NET CLR via c# 第4版>笔记 第6章 类型和成员基础
		
6.1 类型的各种成员 6.2 类型的可见性 public 全部可见 internal 程序集内可见(如忽略,默认为internal) 可通过设定友元程序集,允许其它程序集访问该程序集中的所有inte ...
 - <NET CLR via c# 第4版>笔记 第7章 常量和字段
		
7.1 常量 常量 是值从不变化的符号.定义常量符号时,它的值必须能够在编译时确定. 只能定义编译器识别的基元类型的常量,如果是非基元类型,需把值设为null. 常量的值直接嵌入代码,所以不能获取常量 ...
 
随机推荐
- hash入门
			
如果你已经确保自己的hash技巧已经入门,那么请左转这篇博文 首先介绍一下hash? 事实上是一种叫做蛤丝的病毒 以下讲到的hash都是OI中最常用到的hash方法:进制哈希 做法: 首先设一个进制数 ...
 - FOJ-1001-Duplicate Pair
			
题目:Duplicate Pair 大意: 有多组测试数据,输入整数n,接着输入n个整数(integers),这些数字几乎都只出现一次,但其中有一个数出现了两次,请输出这个数. 题解: 大数据问题,普 ...
 - stm32 ADC使用方法
			
void Adc_Init(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_AP ...
 - BZOJ 1001: [BeiJing2006]狼抓兔子(s-t平面图+最短路求最小割)
			
http://www.lydsy.com/JudgeOnline/problem.php?id=1001 题意: 思路:这道题目是最小割题目,但是吧你直接套用Dinic是会超时的. 这里有种很奇妙的做 ...
 - DDSM数据处理之PngWithOverlay 框出病灶区域
			
修改代码路径 若overlay是某一侧的标注. 文件夹里某一侧的png 应该有对应 某一侧的overlay 这样一一对应才可以使得代码运行. 否则需要手动删除没有overlay的png图片. 左侧没有 ...
 - 一步一步实现JS拖拽插件
			
js拖拽是常见的网页效果,本文将从零开始实现一个简单的js插件. 一.js拖拽插件的原理 常见的拖拽操作是什么样的呢?整过过程大概有下面几个步骤: 1.用鼠标点击被拖拽的元素 2.按住鼠标不放,移动鼠 ...
 - js 去重
			
function unique(array) { let obj = {}; return array.filter((item, index, array) => { let newI ...
 - Unity 如何在打包的时候执行一些逻辑
			
1.如果想让unity在打包的过程中,执行一些逻辑,那么该如何做呢?代码如下: using UnityEditor; using UnityEditor.Build; using UnityEngin ...
 - 图片保存到数据库以及C#读取图片
			
图片保存到数据库,如果是sqlserver就是Image类型,如果保存到Oracle就是blob类型,在c#中相对应的就是byte[]类型,同时只需要对读出的数据强制转换就行(byte[])objec ...
 - The requested URL /phpmyadmin was not found on this server.
			
这个报错,我弄了好久,第一次我以为我安装有问题,我就卸载重新安装了,但是在结果还是报这样子的错. 查找phpmyadmin的安装位置输入: sudo dpkg -L phpmyadmin 可以看到很多 ...