首页 > 代码库 > 【C#】事件

【C#】事件

前言:CLR事件模式建立在委托的基础上,委托说调用回调方法的一种类型安全的方式。

我个人觉得事件本质就是委托,所以把委托弄清楚,只要知道事件基本语法就会使用了(如果说到线程安全,我个人觉得这个应该和线程一起去讨论),所以这篇只做一个简单的时间介绍和写一些我看到的或我用到的一些代码。

EventHandler

  

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

  上面是C#的源码,超简单有木有(还是委托)。

  有两个类型:

    1.Object sender :事件源

    2.TEventArgs e :泛型,这里可以自定义事件所需要的额外参数。

  

  既然知道基本的语法,我们来看看怎么写。

internal sealed 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; } }}
View Code

 这里我们定义另一个类NewMailEventArgs,注意了 这里继承了EventArgs,所有的事件参数的类都需要继承这个父类。这里都是我们回调函数所要用的参数。

 internal class MailManager    {        public event EventHandler<NewMailEventArgs> NewMail;        protected virtual void OnNewMail(NewMailEventArgs e)        {            if (NewMail!=null)            NewMail(this, e);        }    }
View Code

这里定义了一个事件NewMail,EventHandler的参数类型是NewMailEventArgs(就是我们自定义的那个参数类),我们惯用的方式在同一个类里面写调用的方法,以On开头+事件名结尾,但是这里有一个很危险地方,就是多线程的时候,如果在执行的时候,其他地方正好取消了订阅呢,又会变成NULL,所以这里可以变成

 

  internal class MailManager    {        public event EventHandler<NewMailEventArgs> NewMail;        protected virtual void OnNewMail(NewMailEventArgs e)        {            e.Raise(this, ref NewMail);        }        public void SimulateNewMail(string from, string to, string subject)        {            var e = new NewMailEventArgs(from, to, subject);            OnNewMail(e);        }    }    public static class EventArgExtensions    {        public static void Raise<TEventArgs>(this TEventArgs e, Object sender,            ref EventHandler<TEventArgs> eventDelegate) where TEventArgs : EventArgs        {            EventHandler<TEventArgs> temp = Interlocked.CompareExchange(ref eventDelegate, null, null);            if (temp != null) temp(sender, e);        }    }
View Code

这里是我习惯用法,也是CLR书上推荐的用法,做一个通用Args的扩展类EventArgExtensions:主要说用于做线程安全和执行回调函数所用(我觉得这样比较单一,封装起来,要修改也只要修改一处就好,记住这里是通用的事件调用,所以如果有特殊的需求,请不要加进来,可以在特殊的地方处理),有关Interlocked.CompareExchange可以看下官方的文档,这个就是用来做轻量级线程同步比较的。

 

        static void Main(string[] args)        {            var mail = new MailManager();            mail.NewMail += mail_NewMail;            mail.SimulateNewMail("a", "b", "c");            mail.NewMail -= mail_NewMail;            Console.ReadKey();        }        static void mail_NewMail(object sender, NewMailEventArgs e)        {            Console.WriteLine(e.From);            Console.WriteLine(e.To);            Console.WriteLine(e.Subject);        }
View Code

最后我们在Main方法中订阅事件(mail.NewMail+=mail_NewMail;这里的话直接+=然后按两个Tab键,自己就出来了),取消订阅用-=。是不是很简单?到这里基本的事件就已经说完了。


接下来分析一下CLR最后提供一份EventSet代码(这份代码也是我的大爱,可以集中管理起来事件,不会让事件到处乱飞,喜欢的朋友可以研究下,这里就不多做介绍了,提供的代码还是有不少错误,比如空指针,没有判断是否存在key之类的情况,不过里面的想法的确值得好好学习)

public sealed class EventKey : Object {}///////////////////////////////////////////////////////////////////////////////public sealed class EventSet {   // The private dictionary used to maintain EventKey -> Delegate mappings   private readonly Dictionary<EventKey, Delegate> m_events =       new Dictionary<EventKey, Delegate>();   // Adds an EventKey -> Delegate mapping if it doesn‘t exist or    // combines a delegate to an existing 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);   }   // Removes a delegate from an EventKey (if it exists) and    // removes the EventKey -> Delegate mapping the last delegate is removed   public void Remove(EventKey eventKey, Delegate handler) {      Monitor.Enter(m_events);      // Call TryGetValue to ensure that an exception is not thrown if      // attempting to remove a delegate from an EventKey not in the set      Delegate d;      if (m_events.TryGetValue(eventKey, out d)) {         d = Delegate.Remove(d, handler);         // If a delegate remains, set the new head else remove the EventKey         if (d != null) m_events[eventKey] = d;         else m_events.Remove(eventKey);      }      Monitor.Exit(m_events);   }   // Raises the event for the indicated EventKey   public void Raise(EventKey eventKey, Object sender, EventArgs e) {      // Don‘t throw an exception if the EventKey is not in the set      Delegate d;      Monitor.Enter(m_events);      m_events.TryGetValue(eventKey, out d);      Monitor.Exit(m_events);      if (d != null) {         // Because the dictionary can contain several different delegate types,         // it is impossible to construct a type-safe call to the delegate at          // compile time. So, I call the System.Delegate type’s DynamicInvoke          // method, passing it the callback method’s parameters as an array of          // objects. Internally, DynamicInvoke will check the type safety of the          // parameters with the callback method being called and call the method.         // If there is a type mismatch, then DynamicInvoke will throw an exception.         d.DynamicInvoke(new Object[] { sender, e });      }   }}
View Code
public class FooEventArgs : EventArgs { }// Define the EventArgs-derived type for this event.public class BarEventArgs : EventArgs { }///////////////////////////////////////////////////////////////////////////////internal class TypeWithLotsOfEvents {    // Define a private instance field that references a collection.    // The collection manages a set of Event/Delegate pairs.    // NOTE: The EventSet type is not part of the FCL, it is my own type.    private readonly EventSet m_eventSet = new EventSet();    // The protected property allows derived types access to the collection.    protected EventSet EventSet { get { return m_eventSet; } }    #region Code to support the Foo event (repeat this pattern for additional events)    // Define the members necessary for the Foo event.    // 2a. Construct a static, read-only object to identify this event.    // Each object has its own hash code for looking up this    // event’s delegate linked list in the object’s collection.    protected static readonly EventKey s_fooEventKey = new EventKey();    // 2d. Define the event’s accessor methods that add/remove the    // delegate from the collection.    public event EventHandler<FooEventArgs> Foo {        add { m_eventSet.Add(s_fooEventKey, value); }        remove { m_eventSet.Remove(s_fooEventKey, value); }    }    // 2e. Define the protected, virtual On method for this event.    protected virtual void OnFoo(FooEventArgs e) {        m_eventSet.Raise(s_fooEventKey, this, e);    }    // 2f. Define the method that translates input to this event.    public void SimulateFoo() {        OnFoo(new FooEventArgs());    }    #endregion    #region Code to support the Bar event    // 3. Define the members necessary for the Bar event.    // 3a. Construct a static, read-only object to identify this event.    // Each object has its own hash code for looking up this    // event’s delegate linked list in the object’s collection.    protected static readonly EventKey s_barEventKey = new EventKey();    // 3d. Define the event’s accessor methods that add/remove the    // delegate from the collection.    public event EventHandler<BarEventArgs> Bar {        add { m_eventSet.Add(s_barEventKey, value); }        remove { m_eventSet.Remove(s_barEventKey, value); }    }    // 3e. Define the protected, virtual On method for this event.    protected virtual void OnBar(BarEventArgs e) {        m_eventSet.Raise(s_barEventKey, this, e);    }    // 3f. Define the method that translates input to this event.    public void SimulateBar() {        OnBar(new BarEventArgs());    }    #endregion}
View Code


关于Action和Func本质还是一样的只是带上了参数。

  Action只有输入参数,有好多重载的版本

  Func有输入参数,也有一个输出参数,同样有很多重载的版本

  用一下就知道怎么玩了,本质完全一样的。

若有不对,不足之处请指出,请不要只写一个:漏洞百出此类评价,谢谢大家的指点和帮助!