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

 更新时间:2012年12月24日 16:34:18   作者:  
事件是用于通知其他对象发生了本对象发生了特定的事情的类型成员;事件是.NET类型成员中相对较为难以理解和实践的一个成员,因为事件的定义不是继承自基础的数据类型,而是对委托(delegate)的封装。所以,在了解事件之前,你需要先了解一点委托
相关概念 

定义:事件是用于通知其他对象发生了本对象发生了特定的事情的类型成员。
说明:事件是.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;
}
}

相关文章

  • ASP.NET MVC为用户创建专属文件夹

    ASP.NET MVC为用户创建专属文件夹

    这篇文章介绍了ASP.NET MVC为用户创建专属文件夹的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • .Net的GC垃圾回收原理及实现

    .Net的GC垃圾回收原理及实现

    在.Net应用程序中很多问题都是没有正确的理解垃圾回收的工作原理而导致的,本文就介绍一下.Net的GC垃圾回收原理及实现,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • asp.net网站的404错误页面的正确设置方法

    asp.net网站的404错误页面的正确设置方法

    asp.net网站的404错误页面的正确设置方法,需要的朋友可以参考下。
    2010-05-05
  • asp.net 动态创建TextBox控件及状态数据如何加载

    asp.net 动态创建TextBox控件及状态数据如何加载

    接着上文Asp.net TextBox的TextChanged事件你真的清楚吗?这里我们来说说状态数据时如何加载的,需要的朋友可以参考下
    2012-12-12
  • 基于ABP架构开发的.Net Core项目部署到IIS问题汇总

    基于ABP架构开发的.Net Core项目部署到IIS问题汇总

    这篇文章介绍了基于ABP架构开发的.Net Core项目部署到IIS问题汇总,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • asp.net core集成JWT的步骤记录

    asp.net core集成JWT的步骤记录

    这篇文章主要给大家介绍了关于asp.net core集成JWT的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用asp.net core具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • asp.net服务器上几种常见异常的解决方案.

    asp.net服务器上几种常见异常的解决方案.

    由于以前就业时算是公司里对服务器上各种硬件和配置原理较为了解. 一直负责公司服务器日常管理.也算是半路出家. 当然日常工作中前前后后也遇到不少大大小小的问题(硬件/服务器日常配置 数据中心合并方案等等). 有1些常见的异常. 总结一些基本快速的处理方法.
    2009-11-11
  • ASP.NET MVC使用母版页视图

    ASP.NET MVC使用母版页视图

    这篇文章介绍了ASP.NET MVC使用母版页视图的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • .NET 日志系统设计思路及实现代码

    .NET 日志系统设计思路及实现代码

    这篇文章主要介绍了.NET 日志系统设计思路及实现代码,有需要的朋友可以参考一下
    2013-12-12
  • 浅谈ASP.NET Core 2.0 带初始参数的中间件(译)

    浅谈ASP.NET Core 2.0 带初始参数的中间件(译)

    这篇文章主要介绍了浅谈ASP.NET Core 2.0 带初始参数的中间件(译),非常具有实用价值,需要的朋友可以参考下
    2017-10-10

最新评论