zoukankan      html  css  js  c++  java
  • CLR笔记:10.事件

    事件也是方法。

    定义一个事件成员意味着类型具有三种能力:
        *类型的静态方法/实例方法可以订阅类型事件
        *类型的静态方法/实例方法可以注销类型事件
        *事件发生时通知已订阅事件的方法

    .NET2.0的事件仍然是基于Win32的,只不过使用了Observer模式来实现,同时建立在Delegate机制之上。
    事件的设计步骤如下(基本上是Observer的实现步骤):

    10.1    设计一个对外提供事件的类型

    1.定义EventArgs或子类,用于存放附加信息:
        定义一个类,继承于EventArgs,以EventArgs结束,包含一组私有字段以及相应的只读公共属性。
        public class NewMailEventArgs : EventArgs
        {
            
    private string from;

            
    public string From
            {
                
    get { return from; }
            }
        }
       
        这里,EventArgs基类在FCL中是这个样子的:
        [Serializable]
        [ComVisible(
    true)]
        
    public class EventArgs
        {
            
    // Summary:
            
    //     表示没有事件数据的事件。
            public static readonly EventArgs Empty;

            
    public EventArgs();
        }

        大多数事件没有附加数据,那么就不用定义任何私有字段和属性,直接使用EventArgs基类作为参数。

    2.定义事件成员:
        class MailManager
        
    {
            
    public event EventHandler<NewMailEventArgs> NewMail;
        }

        这条语句等价于:
            public delegate void EventHandler<TVEventArgs>(Object sender, TVEventArgs e) where TVEventArgs: NewMailEventArgs;

        所以方法原型相应为 void MethodName(Object sender, NewMailEventArgs e)

        这里,第一个参数sender类型是Object,因为要兼容所有类型,所以提供一个最广泛的基类型。
                第二个参数名始终是e,而且派生于EventArgs,保持了对Observer模式的一致性,所有人(包括VS2005)都会调用这个e
                事件方法要求都为void,即不允许有回调值,从而事件链易于操作。

    3.定义引发事件的方法——负责通知订阅事件的对象:
        这是一个protected的虚方法,并接受EventArgs或其子类的参数。
        这个虚方法可以由派生类重写,以添加新的功能;不重写也可以,因为基本上已经可以使用了
        class MailManager
        
    {
            
    protected virtual void OnNewMail(NewMailEventArgs e)
            
    {
                EventHandler
    <NewMailEventArgs> temp = NewMail;

                
    if (temp != null)
                    temp(
    this, e);
            }

        }

        这里,使用临时变量temp,是为了防止可能存在的线程同步问题。

    4.定义一个激发事件的方法

    将输入转换成EventArgs或其子类的对象,然后激发事件
        internal class MailManager
        
    {
            
    public void SimulateNewMail(String from, String to, String subject)
            

                NewMailEventArgs e 
    = new NewMailEventArgs(from, to, subject);
                OnNewMail(e);
            }

        }


    10.3    设计订阅者的类,使用事件
        在ctor中订阅事件,绑定FaxMsg回调方法,在Unregister方法中注销事件
        提供回调方法FaxMsg,当事件激发时自动调用
        internal sealed class Fax
        
    {
            
    public Fax(MailManager mm)
            
    {
                mm.NewMail 
    += FaxMsg;
            }


            
    private void FaxMsg(Object sender, NewMailEventArgs e)
            
    {
                Console.WriteLine(
    "Fax: {0}, {1}, {2}", e.From, e.To, e.Subject);
            }


            
    public void Unregister(MailManager mm)
            
    {
                mm.NewMail 
    -= FaxMsg;
            }

        }
        
    注意:使用+=和-=操作符,而不能显示使用add/remove方法
            事件注销的意义:只要有一个对象还有一个方法仍然订阅事件,该对象就不会被垃圾收集
                IDispose接口的Dispose方法,注销所有事件。
            FaxMsg方法的sender参数为MailMessager对象,可以使用sender访问MailMessager的对象成员,

    补充:在Main函数中实现:
       public static void Main() {

          MailManager mm 
    = new MailManager();

          
    //注册pager和fax
          Fax fax = new Fax(mm);
          Pager pager 
    = new Pager(mm);

          
    //通知pager和fax
          mm.SimulateNewMail("Jeffrey""Kristin""I Love You!");

          
    //注销fax,只剩下pager
          fax.Unregister(mm);
        
          
    //只通知pager
          mm.SimulateNewMail("Jeffrey""Mom & Dad""Happy Birthday.");
       }


    10.2    事件机制
    对于public event EventHandler<NewMailEventArgs> NewMail;
        C#编译时,相应为
                //一个初始化为null的私有委托字段:    
                private EventHandler<NewMailEventArgs> NewMail = null;

                
    //一个订阅事件的公共方法:
                [MethodImpl(MethodImplOptions.Synchronized)]
                
    public void add_NewMail(EventHandler<NewMailEventArgs> value)
                
    {
                    NewMail 
    = (EventHandler<NewMailEventArgs>)Delegate.Combine(NewMail, value);
                }


                
    //一个注销事件的公共方法:
                [MethodImpl(MethodImplOptions.Synchronized)]
                
    public void remove_NewMail(EventHandler<NewMailEventArgs> value)
                
    {
                    NewMail 
    = (EventHandler<NewMailEventArgs>)Delegate.Remove(NewMail, value);
                }


    注:    在IL中也是3个成员:一个私有字段,两个公有方法
              如果将event声明为protected,则两个方法也相应为protected
              event也可以是static或virtual,则两个方法也相应为static或virtual


    10.4    事件与线程安全
    在上面的实例中,System.Runtime.CompilerServices命名空间下,自定义属性[MethodImpl(MethodImplOptions.Synchronized)]保证了事件的线程同步。
    但是这样的同步会有问题。
        对于实例事件,CLR使用自身对象作为线程同步锁;
        对于静态事件,CLR使用类型对象作为线程同步锁。
    但是线程同步指导方针指出,方法永远不要在对象本身或类型对象上加锁,否则这个锁对外公开,会导致其它线程死锁

    没有好的办法保证值类型的实例事件成员是线程安全的,因为C#不会为其add/remove生成[MethodImpl(MethodImplOptions.Synchronized)]
    值类型的静态事件成员肯定是线程安全的。

    10.5    显示控制事件的订阅与注销
    即显示的实现add和remove访问器方法:
        建立一个临时委托变量m_NewMail与相应的属性,代替原先的事件成员NewMail,
        新建一个作为线程同步锁的私有实例字段m_eventLock
    主要改动如下:
        class MailManager
        
    {
            
    private EventHandler<NewMailEventArgs> m_NewMail;

            
    public event EventHandler<NewMailEventArgs> NewMail
            
    {
                add 
                
    {
                    
    lock (m_eventLock)
                    
    {
                        m_NewMail 
    += value;
                    }

                }


                remove
                
    {
                    
    lock (m_eventLock)
                    
    {
                        m_NewMail 
    -= value;
                    }

                }

            }

        }

    注意,C#不能分辨add/remove方法是由编译器自动创建的,还是程序员显示实现的,所以仍可以使用+=和-=这两个操作符处理事件。

    10.6    多事件模型
    System.Windows.Forms.Control类型有70多个事件,不可能用上述方法实现,会造成未使用事件对内存的浪费。
    解决办法:使用注册工厂,建立事件池。具体见设计模式。




  • 相关阅读:
    Qt之界面数据存储与获取(使用setUserData()和userData())
    UML中关联(Association)、聚合(Aggregation)和合成(Composition)之间的区别
    Entity Framework Model First下改变数据库脚本的生成方式
    keepalive学习
    函数、极限、连续
    C#集合基础与运用
    面向查询服务的参数化查询
    WinDbg 命令手册
    知识管理方法论
    项目管理Project
  • 原文地址:https://www.cnblogs.com/Jax/p/883610.html
Copyright © 2011-2022 走看看