C# 事件的设计与使用深入理解_实用技巧

相关概念 

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

时间: 2024-10-14 20:35:41

C# 事件的设计与使用深入理解_实用技巧的相关文章

在RowCommand事件中获取索引值示例代码_实用技巧

在RowCommand事件中获取索引值 1.利用e.CommandSource 复制代码 代码如下: protected void lpg_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "ItemCollect") { GridViewRow gvr = (GridViewRow)(((LinkButton)(e.CommandSource)).NamingContainer);

asp.net Application_AcquireRequestState事件,导致Ajax客户端不能加载_实用技巧

开发环境IIS7下一切正常,但是部署到IIS6服务器以后, 每次刷新页面总出现Ajax客户端不能加载的问题. 复制代码 代码如下: protected void Application_AcquireRequestState(object sender, EventArgs e) { string oldToken = Session["token"].ToString(); } MSDN上说: Application_AcquireRequestState, 当ASP.NET获取当前

C# Lambda表达式用途深入理解_实用技巧

假如我们想要从一个整型数组中取出其中是奇数的选项,其实现方式有很多,我们通过下面三种实现方式来对对比理解Lambda表达式的用途 方法一:命名方法 复制代码 代码如下: public class Common { public delegate bool IntFilter(int i); public static List<int> FilterArrayOfInt(int[] ints, IntFilter filter) { var lstOddInt = new List<in

实现基于事件通知的.Net套接字_实用技巧

  以前学过MFC,我用过的它里面的包装Winsock的两个类CSocket和CAsyncSocket,我一直觉得它哪个事件通知的功能比较不错,特别是在连接的两方收发数据没有一定的规律的时候比较有用,虽然不用它的事件通知也可以实现功能,但是你需要循环检测套接字的状态或者阻塞等待,如果每次都要做这些琐碎的工作岂不是很麻烦,于是我就想对这些功能作一次封装.当然在.net里面采用delegate和event是最好的选择了.下面就是我实现的一些细节:   先讲一下思路:其实这个还是很简单了,估计高手们会

ASP.NET的事件模型(很适合学习的文章)_实用技巧

在Default.aspx的页面中第一行是一条页面指令: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AspxEventsModel._Default" %> 其中CodeBehind属性指定代码隐藏页面的名称,Inherits指定所属的命名空间和类,AutoEventWireup属性是可以

ASP.NET2.0缓存(Cache)技术深入理解_实用技巧

ASP.NET2.0提供了一些新的用于提升程序性能的技术特性,其中,缓存技术是非常重要的一个特性,它提供了一种非常好的本地数据缓存机制,从而有效的提高数据访问的性能. 数据缓存(DataCaching)就是将数据暂存于内存缓存区中(有时也暂存于硬盘缓存区中)的一种技术.当数据本身改变得不怎么频繁,而被访问的频率又比较高时,采用这种技术将大大提高警惕数据访问的效率. 1.网页输出缓存 (1)加显缓存 <%@OutputCacheDuration="60"VaryByParam=no

c# indexof 用法深入理解_实用技巧

IndexOf() 查找字串中指定字符或字串首次出现的位置,返首索引值,如: 复制代码 代码如下: str1.IndexOf("字"): //查找"字"在str1中的索引值(位置) str1.IndexOf("字串")://查找"字串"的第一个字符在str1中的索引值(位置) str1.IndexOf("字",start,end)://从str1第start+1个字符起,查找end个字符,查找"字

已有打开的与此命令相关联的DataReader,必须首先将它关闭。对于此异常的理解_实用技巧

首先声明以下几点: 1.也许讲解有点初级,希望高手不要"喷"我,因为我知道并不是每一个人都是高手,我也怕高手们说我装13: 2.如有什么不对的地方,还希望大家指出,一定虚心学习: 3.本文属于作者原创,尊重他人劳动成果,转载请注明作者,谢谢. 下面开讲: 开始在遇到此问题的时候我也在网上查了很多资料,貌似没找到什么好的解决方法,然后我就开始调试程序,我发现有的时候运行不报错,有的时候运行报错,这就很纠结了,但是后来我发现我把ASP.NET Development Server - 端口

asp.net获得数据控件事件索引并获取其中值总结_实用技巧

复制代码 代码如下: 1.RowCommad //获得索引 int index = ((GridViewRow)(((Button)(e.CommandSource)).Parent.Parent)).RowIndex; 2.RowEditing等 //获得索引 int index = e.NewEditIndex; //获取当前GridViewRow对象 GridViewRow editGridViewRow = autoGridView.Rows[girdviewEditIndex]; //