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多个事件,不可能用上述方法实现,会造成未使用事件对内存的浪费。
    解决办法:使用注册工厂,建立事件池。具体见设计模式。




  • 相关阅读:
    yii2 gii 命令行自动生成控制器和模型
    控制器中的方法命名规范
    Vue Property or method "" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based
    IDEA插件:GsonFormat
    Spring Boot : Access denied for user ''@'localhost' (using password: NO)
    Typora添加主题
    Git基础命令图解
    Java Joda-Time 处理时间工具类(JDK1.7以上)
    Java日期工具类(基于JDK1.7版本)
    Oracle SQL Developer 连接Oracle出现【 状态: 失败 -测试失败: ORA-01017: invalid username/password; logon denied】
  • 原文地址:https://www.cnblogs.com/Jax/p/883610.html
Copyright © 2011-2022 走看看