【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#】事件的更多相关文章
- JNI详解---从不懂到理解
转载:https://blog.csdn.net/hui12581/article/details/44832651 Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 C ...
- Jquery的点击事件,三句代码完成全选事件
先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...
- 关于 Chrome 浏览器中 onresize 事件的 Bug
我在写插件时用到了 onresize 事件,在反复地测试后发现该事件在 Chrome 及 Opera(内核基本与 Chrome 相同,以下统称 Chrome)浏览器打开时就会执行,这种情况也许不能算作 ...
- MVVM设计模式和WPF中的实现(四)事件绑定
MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- C++中的事件分发
本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件.对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数.这样在游戏客户端的逻辑处理中,可以非 ...
- 移动端IOS点击事件失效解决方案
解决方案 解决办法有 4 种可供选择: 1 将 click 事件直接绑定到目标元素(即 .target)上 2 将目标元素换成 <a> 或者 button 等可点击的元素 3 将 clic ...
- Android笔记——Button点击事件几种写法
Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...
- HTML 事件(一) 事件的介绍
本篇主要介绍HTML中的事件知识:事件相关术语.DOM事件规范.事件对象. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三 ...
- HTML 事件(二) 事件的注册与注销
本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...
- HTML 事件(三) 事件流与事件委托
本篇主要介绍HTML DOM中的事件流和事件委托. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流与事件委托 4 ...
随机推荐
- CentOS7|Redhat7挂载NTFS格式磁盘
//下载安装ntfs-3g_ntfsprogs.tgz软件包进行编译安装 tar -zxf ntfs-3g_ntfsprogs.tgz cd ntfs-3g_ntfsprogs ./configure ...
- Rector模式
讲到高性能IO绕不开Reactor模式,它是大多数IO相关组件如Netty.Redis在使用的IO模式,为什么需要这种模式,它是如何设计来解决高性能并发的呢? 最最原始的网络编程思路就是服务器用一个w ...
- RHCE7 学习里程-2.telnet 服务配置
一.安装telnet 服务 1.yum install -y telnet-server 2.yum install -y xinetd 安装两个包 将两个服务做成开机启动 1.systemctl e ...
- C# 调用动态代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- 高性能Web服务器Nginx的配置与部署研究(3)Nginx请求处理机制
1. 处理什么样的请求 处理访问到 Nginx 所在 IP 地址的请求,并且这些请求的 HTTP 头信息中的 Host 为所要处理的域名(如下以80端口为例),如下几个 server 就对应响应的请求 ...
- 混合开发之iOS快速集成DSBridge
DSBridge-IOS github:https://github.com/wendux/DSBridge-IOS 使用 Native 实现API 代理类 //JsApiTest.m @implem ...
- Library not found for -lAPOpenSdk
多人开发合作的时候 总是会遇见各种各样的问题 今天就来讲一个关于友盟的问题 在我的小伙伴 用cocoapods 中添加了这样一句话 pod ‘UMengSocialCOM’, 并且pod updat ...
- require.js的用法(转)
一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...
- 虚拟化技术:Xen与KVM的对比
作为开源的虚拟化技术,对比Xen和KVM可以看到,Xen以6个无与伦比的优势领先:更好的可用资源.平台支持.可管理性.实施.动态迁移和性能基准. 可用资源:Xen的问世要比KVM早4年之久(两者分别是 ...
- Openssl smime命令
一.简介 S/MIME工具,用于处理S/MIME邮件,它能加密.解密.签名和验证S/MIME消息 二.语法 openssl smime [-encrypt] [-decrypt] [-sign] [- ...