C# 事件的设计与使用深入理解

所属分类: 网络编程 / ASP.NET 阅读数: 972
收藏 0 赞 0 分享
相关概念 

定义:事件是用于通知其他对象发生了本对象发生了特定的事情的类型成员。
说明:事件是.NET类型成员中相对较为难以理解和实践的一个成员,因为事件的定义不是继承自基础的数据类型,而是对委托(delegate)的封装。所以,在了解事件之前,你需要先了解一点委托。
应用场景:事件的应用场景非常广泛,其中最常见的场景是在各个前端控件中的大量触发事件设计。原因是因为
意义:事件成员的使用有利于在程序中对面向对象原则的实现。例如类型的单一职责原则,控制反转原则。设想如果前端控件不能抽象出大量丰富的事件,那几乎不能将前端的UI元素与业务逻辑脱钩。程序必然高度耦合。
设计模式的应用:经典设计模式中的观察者模式就非常依赖于对事件成员的设计而实现。
本章将通过设计一个电子邮件到达时,触发事件的场景来解析对事件提供者和订阅者类型的设计。案例来源于《CLR Via C#》一书。

事件提供者类型的设计

一. 定义类型来容纳所有需要发送给事件订阅者的附加信息

目标:定义一个类型用于向事件的订阅者传递信息
方法:继承默认的System.EventArgs类型,实现简单的需要传递信息的字段,属性以及实例构造器成员。示例如下:
复制代码 代码如下:

using System;
using System.Linq;

namespace ConsoleTest
{
public class NewMailEventArgs : EventArgs
{
private readonly string from, to, subject;

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

public string Subject
{
get
{
return this.subject;
}
}

public string To
{
get
{
return this.to;
}
}

public string From
{
get
{
return this.from;
}
}
}
}

二. 定义事件成员

目标:在事件提供者类型中定义一个事件成员,用于事件订阅者对象的注册。
方法:封装一个自定义委托,来提供事件处理方法的模板;或者实现一个System.EventHandler的泛型类型来达到一样的效果。(EventHandler是一个默认提供的已封装的委托)。两种方法的示例分别如下:
方法一:

复制代码 代码如下:

public delegate void NewMailHandler(object e, NewMailEventArgs args);

public class MailManager
{
public event NewMailHandler NewMail;
}

方法二:
复制代码 代码如下:

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

为什么这两种方法能够达到同样的效果,查看一下System.EventHandler的定义就能知晓:
复制代码 代码如下:

namespace System
{
// 摘要:
// 表示将处理事件的方法。
//
// 参数:
// sender:
// 事件源。
//
// e:
// 一个包含事件数据的 System.EventArgs。
//
// 类型参数:
// TEventArgs:
// 由该事件生成的事件数据的类型。
[Serializable]
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
}

三. 定义一个统一触发事件的方法入口来通知事件的订阅对象

目标:在事件提供者类型中定义一个方法成员,用来统一的引发目标事件。
说明:为了保证这个方法只能在本类型及派生类型中调用,我们需要将方法修饰为protected, 为了让派生类型可以重写这个方法,我们需要将该方法修饰为virtual
意义:这个统一入口方法的意义在于,能够统一维护触发事件的方式,并且能够确保事件调用的线程安全性。(避免在不同的线程触发时,事件订阅者的状态不同步)
示例如下:

复制代码 代码如下:

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

protected virtual void OnNewMail(NewMailEventArgs e)
{
//处于线程安全的考虑,现在将对委托字段的引用复制到一个临时字段中
EventHandler<NewMailEventArgs> temp = System.Threading.Interlocked.CompareExchange
(ref NewMail, null, null);

//如果有事件订阅者对象的存在,则通知他们,事件已触发
if (temp != null)
temp(this, e);
}
}

四. 在所有需要触发事件的业务方法中,调用第三步中定义的方法

目标:在类型中还需要有一个业务方法,来将业务中的场景转化为事件触发。。
方法:在任意需要的业务方法中,直接调用第三步的方法就可以了,不过需要实现封装一个传递信息的类型。
示例如下:

复制代码 代码如下:

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

protected virtual void OnNewMail(NewMailEventArgs e)
{
//处于线程安全的考虑,现在将对委托字段的引用复制到一个临时字段中
EventHandler<NewMailEventArgs> temp = System.Threading.Interlocked.CompareExchange
(ref NewMail, null, null);

//如果有事件订阅者对象的存在,则通知他们,事件已触发
if (temp != null)
temp(this, e);
}

public void SimulateNewMail(string from, string to, string subject)
{
//构造一个对象来封装向传给事件订阅者的信息
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);

//触发事件引发的入口方法
OnNewMail(e);
}
}

事件订阅者类型的设计

一. 定义类型来订阅和侦听事件

目标:设计一个传真类型Fax类来侦听NewMail事件。
说明:Fax类型中需要具备对NewMail事件的订阅和取消订阅的方法。示例如下:
复制代码 代码如下:

internal sealed class Fax
{
private MailManager mailManager;

public Fax(MailManager mm)
{
this.mailManager = mm;
}

public void Register()
{
mailManager.NewMail += new EventHandler<NewMailEventArgs>(FaxMsg);
}

void FaxMsg(object sender, NewMailEventArgs e)
{
Console.WriteLine("Fax mail message");
Console.WriteLine("From = {0}, To = {1}, Subject = {2}", e.From, e.To, e.Subject);
}

public void Unregister()
{
mailManager.NewMail -= FaxMsg;
}
}
更多精彩内容其他人还在看

开源跨平台运行服务插件TaskCore.MainForm

这篇文章主要为大家详细介绍了开源跨平台运行服务插件TaskCore.MainForm的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

CKEditor自定义按钮插入服务端图片

这篇文章主要为大家详细介绍了CKEditor自定义按钮插入服务端图片的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Asp.net Web Api实现图片点击式图片验证码功能

现在验证码的形式越来越丰富,今天要实现的是在点击图片中的文字来进行校验的验证码。下面通过本文给大家分享Asp.net Web Api实现图片点击式图片验证码功能,需要的的朋友参考下吧
收藏 0 赞 0 分享

WPF实现ScrollViewer滚动到指定控件处

这篇文章主要为大家详细介绍了WPF实现ScrollViewer滚动到指定控件处,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

WPF实现带全选复选框的列表控件

这篇文章主要为大家详细介绍了WPF实现带全选复选框的列表控件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Asp.net MVC 中利用jquery datatables 实现数据分页显示功能

这篇文章主要介绍了Asp.net MVC 中利用jquery datatables 实现数据分页显示功能,非常不错,具有参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

asp.net 利用NPOI导出Excel通用类的方法

本篇文章主要介绍了asp.net 利用NPOI导出Excel通用类的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

VS2015自带LocalDB数据库用法详解

这篇文章主要为大家详细介绍了VS2015自带LocalDB数据库的用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

SignalR Self Host+MVC等多端消息推送服务(一)

这篇文章主要为大家详细介绍了SignalR Self Host+MVC等多端消息推送服务,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

SignalR Self Host+MVC等多端消息推送服务(二)

这篇文章主要为大家详细介绍了SignalR Self Host+MVC等多端消息推送服务的第二篇,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享
查看更多