【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; } }
}

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

 internal class MailManager
    {
        public event EventHandler<NewMailEventArgs> NewMail;

        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            if (NewMail!=null)
            NewMail(this, e);
        }
    }

这里定义了一个事件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);
        }
    }

这里是我习惯用法,也是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);
        }

最后我们在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 });
      }
   }
}

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
}

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

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

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

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

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

  

【C#】事件

时间: 2024-08-28 14:38:32

【C#】事件的相关文章

移动端点击事件全攻略,有你知道与不知道的各种坑

看标题的时候你可能会想,点击事件有什么好说的,还写一篇攻略?哈哈,如果你这么想,只能说明你too young to simple. 接触过移动端开发的同学可能都会面临点击事件的第一个问题:click事件的300ms延迟响应.不能立即响应给体验造成了很大的困扰,因此解决这个问题就成为了必然. 这个问题的解决方案就是: zepto.js的tap事件.tap事件可以理解为在移动端的click事件,而zepto.js因为几乎完全复制jQuery的api,因此常常被用在h5的开发上用来取代jquery.

js监控微信浏览器的自带的返回事件

pushHistory(); window.addEventListener("popstate", function(e) { e.preventDefault(); //alert("我监听到了浏览器的返回按钮事件啦"); //根据自己的需求实现自己的功能 //window.location = 'http://www.baidu.com'; pushHistory(); }, false); function pushHistory() { var state

微信小程序学习总结(2)------- 之for循环,绑定点击事件

最近公司有小程序的项目,本人有幸参与其中,一个项目做下来感觉受益匪浅,与大家做下分享,欢迎沟通交流互相学习. 先说一下此次项目本人体会较深的几个关键点:微信地图.用户静默授权.用户弹窗授权.微信充值等等. 言归正传,今天分享我遇到的关于wx:for循环绑定数据的一个tips:  1. 想必大家的都知道wx:for,如下就不用我啰嗦了: <view class="myNew" wx:for="{{list}}">{{item.title}}<view

touchstart和touchend事件

touchstart和touchend事件 移动互联网是未来的发展趋势,现在国内很多互联网大佬都在争取移动这一块大饼,如微信及支付宝是目前比较成功的例子,当然还有各种APP和web运用. 由于公司的需要,最近也在开发移动web,对于一个没有移动开发经验的人来说,其实也是比较困恼的一件事情.对于移动web开发目前主要是基于webkit内核的浏览器.在webkit内核的环境下开发,你可以充分的运用html5+css3,还有它的一些私有属性.这让我很兴奋.可是,毕竟,对于一个长期习惯pc端做固定像素开

苹果手机输入中文不会触发onkeyup事件

今天同事的项目有这个问题,用我的安卓手机输入中文是ok的,但是苹果手机就不行 使用keyup事件检测文本框内容:  $('#keyup_i').bind('keyup', function(){         $('#keyup_s').text($(this).val());  } 本来是上面这种处理方式,现在改成下面这样就ok了 使用oninput以及onpropertychange事件检测文本框内容:  //先判断浏览器是不是万恶的IE,没办法,写的东西也有IE使用者       var

离散事件模型

0x01 代码框架逻辑 模拟内容: 1.离散事件模拟,模拟银行营业时的排队情况 2.不考虑顾客中途离开,顾客到达事件随机,业务办理时间 3.长度随机,选择最短的队排队,不再换队 代码逻辑: 1.一个事件链表,四个窗口排队队列 2.事件驱动:每有一个新的顾客到达,将产生下一个新顾客到达的新事件按时间顺序从小到大(OccurTime)插入事件链表(EventList) (如果此时窗口队列只有 一个顾客,还将产生此顾客离开事件插入事件链表中)      每有一个顾客从某一队列首离开,将产生他的后一位顾

zepto事件

1.zepto的定义 Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api 2.zepto与jq的区别 ①相同点:Zepto最初是为移动端开发的库,是jQuery的轻量级替代品,因为它的API和jQuery相似,而文件更小:zepto的API大部分都能和jQuery兼容,所以用起来极其容易 ②不同点: (1).针对移动端程序,Zepto有一些基本的触摸事件可以用来做触摸屏交互(tap事件.swipe事件) (2),Dom操作的区别:添加id时jQ

15.1-全栈Java笔记:Java事件模型是什么?事件控制的过程有哪几步??

应用前边两节上一章节的内容,大家可以完成一个简单的界面,但是没有任何的功能,界面完全是静态的,如果要实现具体功能的话,必须要学习事件模型. 事件模型简介及常见事件模型 对于采用了图形用户界面的程序来说,事件控制是非常重要的. 一个源(事件源)产生一个事件并把它(事件对象)送到一个或多个监听器那里,监听器只是简单地等待,直到它收到一个事件,一旦事件被接收,监听器将处理这些事件. 一个事件源必须注册监听器以便监听器可以接收关于一个特定事件的通知. 每种类型的事件都有其自己的注册方法,一般形式为: v

RecyclerView动态添加、删除及点击事件

上一节讲解了RecyclerView的三种显示方式,本节将主要研究一下RecyclerView的动态添加.删除及其单击和长按事件的处理.我们在上一节代码的基础上进行相关操作. 一.修改适配器类MyAdapter,加入添加和删除这两个方法: public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private Context context; private List<String>

事件(1)

事件三要素 事件源:事件发生的来源 事件:行为(点击,触摸...) 监听器:当事件发送时,所要做的事情  onClickListener(单击事件) 组件.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { String str=et.getText().toString(); tv.setText(str); } }); 1 public class Click extends Ac