【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 ...
随机推荐
- 25_java之Properities集合|对象序列化和反序列化
01Properties集合的特点 * A: Properties集合的特点 * a: Properties类介绍 * Properties 类表示了一个持久的属性集.Properties 可保存在流 ...
- 手把手教你使用node-inspector调试nodejs
最近再看nodejs,这个东西是运行在服务端的,也就是说我们在客户端看不到相应的js代码,那么怎么调试了?目前主流的方法有三种.第一是采用node-inspector.第二种采用nodejs内置的调试 ...
- Microsoft.Office.Core 引用以及 Microsoft.Office.Core.MsoTriState 的问题
转自原文 xiaoanian, Microsoft.Office.Core 引用以及 Microsoft.Office.Core.MsoTriState 的问题 因为要做一个提取ppt文字的工程,第一 ...
- C_FD_PhysRDBMSKinds
C_FD_PhysRDBMSKinds function DateValueToFDSQLStringProc(ADataSet: TDataSet; AValue: Variant): String ...
- AlphaTesting
[Alpha Testing] The alpha test is a last chance to reject a pixel from being written to the screen. ...
- 【UVA11613 训练指南】生产销售规划 【费用流】
题意: Acme公司生产一种X元素,给出该元素在未来M个月中每个月的单位售价.最大产量.最大销售量,以及最大储存时间(过期报废不过可以储存任意多的量).你的任务是计算出公司能够赚到的最大利润. 分析: ...
- 80. Remove Duplicates from Sorted Array II (Array)
Follow up for "Remove Duplicates": What if duplicates are allowed at most twice? For examp ...
- App审核被拒(后台定位被拒,ipv6被拒,广告标示被拒的解决方案)
ipv6被拒问题描述: 解决方案支持ipv6 1)搭建ipv6 环境,搭建好的ipv6 ,环境会有一个共享wifi, 具体如何搭建ipv6测试环境参考本地如何搭建IPv6环境测试你的APP2)app连 ...
- 136. Single Number唯一的数字
[抄题]: Given an array of integers, every element appears twice except for one. Find that single one. ...
- eval 是执行一段完整的js字符串代码,并将结果返回
var strArray="[{"message1":{ "id": "-1","content": &quo ...