c# 事件

        CLR事件模型以委托为基础。委托是调用回调方法的一种类型安全的方式。对象凭借回调方法接受他们订阅的通知。

        定义了事件成员的类型能提供以下功能:

                      1.  方法能登记它对事件的关注

                      2.  方法能注销它对事件的关注

                      3.  事件发生时,登记了的方法将收到通知

 

第一步: 定义类型来容纳所有需要发送给事件通知接收者的附加信息

第二步: 定义事件成员

第三步: 定义负责引发事件的方法来通知事件的登记对象

第四步: 定义方法将输入转化为期望事件

    //第一步: 定义类型来容纳所有需要发送给事件通知接收者的附加信息
    internal class NewMailEventArgs: EventArgs
    {
        private readonly string m_from, m_to, m_subject;

        public NewMailEventArgs(string from, string to, string subject)
        {
            this.m_from = from;
            this.m_to = to;
            this.m_subject = subject;
        }

        public string From { get { return m_from; } }
        public string To { get { return m_to; } }
        public string Subject { get { return m_subject; } }
    }

    internal class MailManager
    {
        //第二步: 定义事件成员
        public event EventHandler<NewMailEventArgs> NewMail;

        //第三步,定义负责引发事件的方法来通知已登记的对象
        //如果类是密封的,该方法要声明为私有和非虚
        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            //出于线程安全的考录,现在将对象委托字段的引用复制到一个临时变量中
            EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
            //任何方法登记了对事件的关注,就通知它们
            if (temp != null) temp(this, e);
        }
        //第四步: 定义方法将输入转化为期望事件
        public void SimulateNewMail(string from, string to, string subject)
        {
            NewMailEventArgs e = new NewMailEventArgs(from, to, subject);

            OnNewMail(e);
        }
    }

注意: 

                                  第三步中采用了以线程安全的方式引发事件

       .NET Framework 刚发布时建议开发者用以下方式引发事件:

      //版本1

         protected virtual void OnNewMail(NewMailEventArgs e)

         {

                 if (NewMail != null) NewMail(this, e);

         }

       OnNewMail方法的问题在于,虽然线程检查出NewMail不为null, 但就在调用NewMail之前,另一个线程可能从委托链中移除一个委托,使NewMail成了null。这会抛出NullReferenceException异常。为了修正这个竟态问题,许多开发者都像下面这样写OnNewMail方法

        //版本2
        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            EventHandler<NewMailEventArgs> temp = NewMail;
            if (temp != null) temp(this, e);
        }

       它的思路是,将对NewMail的引用复制到临时变量temp中,后者引用赋值发生时的委托链。然后,方法比较temp和null,并调用(invoke)temp;所以,向temp赋值后,即使另一个线程更改了NewMail也没有关系;委托是不可变的(immutable),所以这个技术理论上行得通。但许多开发者没有意思到的是,编译器可能“擅作主张”,通过完全移除局部变量temp的方式对上述代码进行优化。

       想要修正这个问题,应该像下面这样重写OnNewMail

        //版本三
        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            //出于线程安全的考录,现在将对象委托字段的引用复制到一个临时变量中
            EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
            //任何方法登记了对事件的关注,就通知它们
            if (temp != null) temp(this, e);
        }

 

显示实现事件

        为了高效率存储事件委托,公开了事件的每个对象都要维护一个集合(通常是字典)。集合将某种形式的事件标识符作为键(Key),将委托列表作为值。

    //多一点的类型安全和代码可维护性

    public sealed class EventKey { }

    public sealed class EventSet
    {
        //该私有字段用于维护EventKey -> Delegate 映射
        private readonly Dictionary<EventKey, Delegate> m_events = new Dictionary<EventKey, Delegate>();

        //添加EventKey -> Delegate映射(如果EventKey不存在)
        //或者将委托和现有的EventKey合并
        public void Add(EventKey eventKey, Delegate handler)
        {
            Monitor.Enter(m_events);
            Delegate d;
            m_events.TryGetValue(eventKey, out d);
            m_events[eventKey] = Delegate.Combine(d, handler);
            Monitor.Exit(m_events);
        }

        //从EventKey(如果它存在)删除委托, 并且
        //在删除最后一个委托时删除EventKey -> Delegate映射
        public void Remove(EventKey eventKey, Delegate handler)
        {
            Monitor.Enter(m_events);
            //调用TryGetValue,确保在尝试从集合中删除不存在的EventKey时不会抛出异常
            Delegate d;
            if (m_events.TryGetValue(eventKey, out d))
            {
                d = Delegate.Remove(d, handler);
                //如果还有委托,就设置新的头部(地址), 否则删除EventKey
                if (d != null)
                {
                    m_events[eventKey] = d;
                }
                else
                {
                    m_events.Remove(eventKey);
                }
            }
            Monitor.Exit(m_events);
        }

        //为指定的EventKey引发事件
        public void Raise(EventKey eventKey, Object sender, EventArgs e)
        {
            Delegate d;
            Monitor.Enter(m_events);
            m_events.TryGetValue(eventKey, out d);
            Monitor.Exit(m_events);

            if (d != null)
            {
                d.DynamicInvoke(new Object[] { sender, e });
            }
        }
    }

    接着定义一个类来使用EventSet类。在这个类中,一个字段引用了一个EventSet对象,而且这个类的每个事件都是现实实现的,使每个事件的add方法都将指定的回调委托存储到EventSet对象中,而且每个事件的remove方法都删除指定的回调委托(如果找得到的话)。

    public class FooEventArgs: EventArgs { }

    public class TypeWithLotsOfEvents
    {
        //定义私有实例字段来引用集合
        //集合用于管理一组“事件/委托”对
        //注意: EventSet类型不是FCL的一部分,他是我自己的类型
        private readonly EventSet m_eventSet = new EventSet();

        //受保护的属性使派生类能访问集合
        protected EventSet EventSet { get { return m_eventSet; } }

        #region 用于支持Foo事件的代码(为附加的事件重复这个模式) 
        //定义Foo事件必要的成员 
        //2a. 构造一个静态只读对象来标识这个事件
        //每个对象都有自己的哈希码, 以便在对象的集合中查找这个事件的委托链表
        protected static readonly EventKey s_fooEventKey = new EventKey();

        //2b 定义事件的访问器方法,用于在集合中增删委托
        public event EventHandler<FooEventArgs> Foo {
            add { m_eventSet.Add(s_fooEventKey, value); }
            remove { m_eventSet.Remove(s_fooEventKey, value); }
        }

        //2c. 为这个事件定义受保护的虚方法OnFoo
        protected virtual void OnFoo(FooEventArgs e)
        {
            m_eventSet.Raise(s_fooEventKey, this, e);
        }

        //2d. 定义将输入转换成这个事件的方法
        public void SimulateFoo()
        {
            OnFoo(new FooEventArgs());
        }
        #endregion

    }

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值