• <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 显式实现事件

    作者的代码挺好的,只是我想我可能不会遇到在一个类中定义几十个事件的情况,所以就不写了,需要的时候去翻书吧.

    返回目录

  • 相关阅读:
    vue--路径前面的@的意思
    格式化规则修改prettierrc
    windows快捷键
    使用npm 添加less之后报错
    IOS中如何提高UITableView的性能?
    ffmpeg解码h264 Increasing reorder buffer
    Chrome浏览器关联文件图标空白问题解决方案
    mysql 问题sql 截图
    1、HR需要哪些自我提升
    1、文案职业大揭秘
  • 原文地址:https://www.cnblogs.com/harry-wang/p/7205941.html
Copyright © 2020-2023  润新知