C#编程(四十二)----------委托和事件

委托和事件

委托是C#总比较重要的概念,学习C#爱这里最容易产生迷惑.

有些时候,犹豫我们在开发程序时对后续可能出现的要求及变化考虑不足而导致麻烦,这些新变化可能导致程序的重新编写,那能不能改变这种情况?后面的需要变化了,后续对应功能的编写对前面的程序不造成影响?

可以的,在C#中可以使用委托来解决这个问题.

delegate

怎么理解委托呢,形象一点就是你的名字叫张三,别人一叫张三,你就答应.就像程序调用一样,一个叫(调用)一个回答(执行).但是不久你因为给老板舔的好,给你升职了,你成了经理了.于是别人也得给你添,不能叫你名字了,改口叫你张经理.并且你也有了名片,可以到处分发(比如县城里通过委托安排方法的执行顺序).人们通过名片就能知道张经理这个人.

以上过程总结如下:


现实


程序


你本人


执行方法的代码


你的名字张三


方法名


(名片上的信息)张经理


你的委托delegate

为什么需要有张经理这个委托名?你和客户谈生意的时候从不可能张三的喊你吧.

实例:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace 委托

{

//定义委托

delegate int delegate_method(int i, int j);

public class DelegateTest

{

public int Add(int i, int j)

{

return i + j;

}

public void test_delegate()

{

delegate_method dm = new delegate_method(this.Add);

Console.WriteLine(dm(3,4));

}

}

//测试代码

class Program

{

static void Main(string[] args)

{

DelegateTest dm = new DelegateTest();

dm.test_delegate();

Console.ReadKey();

}

}

}

完全可以把delegate理解成C中的函数指针,它允许你传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m,说白了就是可以把方法当做参数传递.不过delegate和函数指针还是有点区别的,delegate有许多函数指针不具有的优点.首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数.在引用.其次,与函数指针相比,delegate是面向对象,类型安全,可靠的受控(managed)对象.也就是说,runtime能保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或越界地址.

案例:

//声明delegate对象

public delegate void CompareDelegate(int a, int b);

class Program

{

public static void Compare(int a, int b)

{

Console.WriteLine((a > b).ToString());

}

static void Main(string[] args)

{

//创建delegate对象

CompareDelegate cd = new CompareDelegate(Program.Compare);

//调用delegate

cd(1, 2);//返回false

Console.ReadKey();

}

}

案例2:

//声明delegate对象

public delegate void MyTestDelegate(int a);

class Program

{

static void Main(string[] args)

{

//创建delegate

Fun1(new MyTestDelegate(Fun2));

Console.ReadKey();

}

//这个方法接受一个delegate类型的参数,也就是接受一个函数作为参数

public static void Fun1(MyTestDelegate mydelegate)

{

mydelegate(21);

}

//欲传递的方法

public static void Fun2(int i)

{

Console.WriteLine("传递过来的参数: {0} ",i);

}

}

总结:

1.委托实际上就是函数指针,就是方法的地址,程序中你让它只想那个方法它就指向那个方法.

2.委托是同一的方法的模型,参数必须一致

3.委托实际上是把方法当做参数来传递,可以是静态的也可以是非静态的.

从上面的程序中,你应该明白,类中定义了委托给程序带来了很大的灵活性,有一个类放在那里,里面藏了一个指针,你让它指向哪里它就指向哪里(当然有约定).这让我们想到了事件,比如一个按钮放在窗体上,如果里面也藏了一个这样的玩意,是不是就可以处理相应的单击事件?或者说,你单击了按钮,我让它指向一个处理程序,那么这个是不是就有了所谓的按钮响应,其实,这就是事件,叫按钮的单击事件.

事件

让你明白傻瓜式的OnClick是怎么来的?

说起OnClick,就不得不说.net中的Event事件了.

C#中的事件处理实际上是一种具有特殊签名的delegate,像下面这个样子:

public delegate void MyEventHander(object sender,MyEventArgs e);

其中两个参数,sender代表时间发送者,e是事件参数类.MyEventArgs类用来包含与事件 相关的数据,所有的事件参数类都必须从System.EventArgs类派生.当然,如果你的事件不含参数,那么可以直接用System.EventArgs类作为参数.

什么是事件?EVENT?点击事件?加载事件?一连串的模糊的概念冲击着我们弱小的脑袋...

事件是类在发生其关注的事情时用来提供通知的一种方式.

事件的发生一般都牵扯两个角色:

事件发行者:一个事件的发行者也称为发送者,其实就是对象,这个对象会自行维护本身的状态信息,当本身状态信息变动时,使触发一个事件,并通知所有的事件订阅者.

事件订阅者:对事件感兴趣的对象,也称接受者,可以注册按兴趣的事件,在事件发行者触发一个事件后,会自动执行这段代码.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace 委托

{

public class Publisher

{

//声明一个出版的委托

public delegate void PublicEventHander();

//在委托的机制下我们建立一个出版事件

public event PublicEventHander OnPublicsh;

//事件必须要在方法里去触发,出版社发布新书方法

public void Issue()

{

//如果有人注册了这个事件,也就是这个事件不为空

if (OnPublicsh != null)

{

Console.WriteLine("我们今天有书出版");

OnPublicsh();

}

}

}

//事件订阅者张三

public class Zhangsan

{

public static void Receive()

{

Console.WriteLine("什么烂书,不看");

}

}

//事件订阅者李四

public class Lisi

{

public static void Receive()

{

Console.WriteLine("好书一本,值得推荐");

}

}

class Program

{

static void Main(string[] args)

{

//实例化一个出版社

Publisher publisher = new Publisher();

//给这个出版新书的事件注册感兴趣的订阅者,此例中是李四

publisher.OnPublicsh += new Publisher.PublicEventHander(Lisi.Receive);

//另一种事件注册方式

//publisher.OnPublicsh += Lisi.Receive;

//发布者在这里触发出版新书的事件

publisher.Issue();

Console.ReadKey();

}

}

}

大家应该了解什么是发布者什么时候订阅者了吧,哪至于事件呢.先看这句:

publisher.OnPublish += new Publisher.PublishEventHander(MrMing.Receive);

这是李四想出版社订阅挺喜欢看的书,张三没有订阅,所以没有收到书.

我们再来看看这赋值语句,是不是觉得很相似?是的,在讲委托的时候,简直就是一样.

委托赋值

BugTicketEventHander myDelegate= new BugTicketEventHander (zhangsan.BuyTicket);

所以,大家不要对事件有什么好怕的,其实事件的本质就是一个委托链.

我们来看一下事件的声明:

//声明一个事件

public delegate void PublishEventHander();

//在委托的机制下我们建立一个出版事件

在我们使用事件的时候,必须要声明对应的 委托,而触发事件,其实就是在使用委托链.

先看下面的代码来研究object sender,EventArgs e参数:

protected void Page_Load(object sender, EventArgs e) {

}

protected void btnSearch_Click(object sender, ImageClickEventArgs e) {

}

protected void grdBill_RowDataBound(object sender, GridViewRowEventArgs e) {            }

他们表示什么?

先看.net的编码规范:

1.委托类型的名称都应该以EventHander结束’

2.委托的原型定义:有一个void返回值,并接受两个输入参数:一个object类型一个是EventArgs类型(或继承自EventArgs)

3.事件的命名为委托去掉EventHander之后剩余的部分

4.继承自EventArgs的类型应该以EventArgs结尾

这就是微软编码的规范,当然这不仅仅是规范,而是在这种规则下使程序有更大的灵活性.

案例:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace ConsoleApplication17

{

//所有订阅者[Subscriber]感兴趣的对象,也就是e,都要继承EventArgs

//本例中订阅者[也称为观察者]MrMing,MrZhang他们感兴趣的e对象,就是杂志[magazine]

public class PubEventArgs : EventArgs

{

public readonly string magazineName;

public PubEventArgs()

{ }

public PubEventArgs(string magazineName)

{

this.magazineName = magazineName;

}

}

//发布者(Publisher)

public class Publisher

{

//声明一个委托

//这流动了个参数sender,它代表的就是Subject,也就是监听对象,本例中就是Publisher

public delegate void PublishEventHander(object sender, PubEventArgs e);

//在委托的机制下我们建立一个出版事件

public event PublishEventHander Publish;

//声明一个可重写的OnPublish的保护函数

protected virtual void OnPublish(PubEventArgs e)

{

if (Publish != null)

{

this.Publish(this, e);

}

}

//事件必须要在方法里去触发

public void Issue(string magazineName)

{

OnPublish(new PubEventArgs(magazineName));

}

}

//订阅者

public class MrMing

{

//对事件感兴趣的事情

public static void Receive(object sender, PubEventArgs e)

{

Console.WriteLine("我已经收到最新一期的{}啦,嘎嘎", e.magazineName);

}

}

public class MrZhang

{

//对事情感兴趣的事情

public static void Receive(object sender, PubEventArgs e)

{

Console.WriteLine("什么J8书");

Console.WriteLine("这是我订的书:{0},我觉得这个好", e.magazineName);

}

}

class Program

{

static void Main(string[] args)

{

//实例化一个出版社

Publisher publisher = new Publisher();

Console.WriteLine("请输入要发行的杂志");

string name = Console.ReadLine();

if (name == "火影忍者")

{

//给这个出火影忍者的事件注册感兴趣的订阅者,此例中是小明

publisher.Publish += new Publisher.PublishEventHander(MrMing.Receive);

publisher.Issue("火影忍者");

}

else

{

//给这个出火影忍者的事件注册感兴趣的订阅者,此例中是小明[另一种事件注册方式]

publisher.Publish += MrZhang.Receive;

publisher.Issue("苍井空");

}

Console.ReadKey();

}

}

}

分析:

1.委托声明原型中的object类型的参数代表了Subject,也就是监视对象,在本例中是Publisher(出版社).

2.EventArgs对象包含了Observer所感兴趣的数据,在本例中是杂志

如果大家对设计模式精通的话,其实他们关联的是观察者(Observer)模式,简单的说一下他们的关联:

在C#的event中,委托充当了抽象的Observer接口,而提供时间的对象充当了目标对象.委托是比对象Observer接口更为松耦合的设计.

当我们的信用卡刷完钱的时候,我们就会受到手机短信.其实这就是Observer pattern(观察者模式)

案例二:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

/*

*本例中场景为当用户从银行账号里取出钱后,马上通知电子邮件和发手机短信

*本例中的订阅者,就是观察者是电子邮件和手机

*发布者这就是银行账号

*/

namespace ConsoleApplication1

{

//Observer电子邮件,手机关心的对象e,分别是邮件地址,手机号码,取款金额

public class UserEventArgs : EventArgs

{

public readonly string emailAddress;

public readonly string mobilePhone;

public readonly string amount;

public UserEventArgs(string emailAddress, string mobilePhone, string amount)

{

this.amount = emailAddress;

this.mobilePhone = mobilePhone;

this.amount = amount;

}

}

//发布者,也就是被监视的对象----银行账号

public class BankAccount

{

//声明一个处理银行交易的委托

public delegate void ProcessTranEventHandler(object sender, UserEventArgs e);

//声明一个事件

public event ProcessTranEventHandler ProcessTran;

protected virtual void OnProcessTran(UserEventArgs e)

{

if (ProcessTran!=null)

{

ProcessTran(this, e);

}

}

public void Prcess(UserEventArgs e)

{

OnProcessTran(e);

}

}

//观察者Email

public class Email

{

public static void SendEmail(object sender, UserEventArgs e)

{

Console.WriteLine("向用户邮箱" + e.emailAddress + "发送邮件:您在" + System.DateTime.Now.ToString() + "取款金额为" + e.amount);

}

}

//观察者手机

public class Moblie

{

public static void SendNotification(object sender, UserEventArgs e)

{

Console.WriteLine("向用户手机" + e.mobilePhone + "发送短信:您在" + System.DateTime.Now.ToString() + "取款金额为" + e.amount);

}

}

//订阅系统 ,实现银行系统订阅几个Observer,事件与客户端的松耦合

public class SubscribSystem

{

public SubscribSystem() { }

public SubscribSystem(BankAccount bankAccount,UserEventArgs e)

{

//现在我们在银行账户订阅两个,分别是电子邮件和手机

bankAccount.ProcessTran+=new BankAccount.ProcessTranEventHandler(Email.SendEmail);

bankAccount.ProcessTran+=new BankAccount.ProcessTranEventHandler(Moblie.SendNotification);

bankAccount.Prcess(e);

}

}

class Program

{

static void Main(string[] args)

{

Console.Write("请输入您要取款的金额:");

string amount = Console.ReadLine();

Console.WriteLine("交易成功,请取磁卡。");

//初始化e

UserEventArgs user = new UserEventArgs("[email protected]", "18868789776", amount);

//初始化订阅系统

SubscribSystem subject = new SubscribSystem(new BankAccount(), user);

Console.ReadKey();

}

}

}

时间: 2024-08-11 09:56:04

C#编程(四十二)----------委托和事件的相关文章

《Inside C#》笔记(十二) 委托与事件

C#的委托与C++的函数指针类似,但委托是类型安全的,意味着指针始终会指向有效的函数.委托的使用主要有两种:回调和事件. 一 将委托作为回调函数 在需要给一个函数传递一个函数指针,随后通过函数指针调用函数时,就可以使用回调函数的方式.回调函数经常用于异步编程,如果被调用的方法执行起来比较费时,就可以把这个方法放在单独在线程执行,同时将函数指针交给回调函数,线程结束时再调用回调函数.这样调用者就不必因等待执行起来费时的方法而被阻塞了. a) 举例,有一个数据库管理类DBManager,这个类追踪所

ActionScript3游戏中的图像编程(连载四十二)

2.3.4 Photoshop高度==Flash距离? 剩下高度一项了,跟距离相对应吗? 但是,高度以角度为单位,但距离却是像素,似乎拉不上关系.不过我们照样做下试验:先试一下Photoshop的高度.在调整的过程里发现,浮雕的厚度并没随着高度的增加而变大.只感觉到光影往某个方向微妙地移动着. 图 2.69~图 2.75展示了不同高度下的效果. 图 2.69 高度=0° 图 2.70 高度=15° 图 2.71 高度=30° 图 2.72 高度=45° 图 2.73 高度=60° 图 2.74

NeHe OpenGL教程 第四十二课:多重视口

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第四十二课:多重视口 多重视口 画中画效果,很酷吧.使用视口它变得很简单,但渲染四次可会大大降低你的显示速度哦:) 欢迎来到充满趣味的另一课.这次我将向你展示怎样在单个窗口内显示多个视口.这些视口在窗口模式下能正确的调整大小.其中有

【Unity 3D】学习笔记四十二:粒子特效

粒子特效 粒子特效的原理是将若干粒子无规则的组合在一起,来模拟火焰,爆炸,水滴,雾气等效果.要使用粒子特效首先要创建,在hierarchy视图中点击create--particle system即可 粒子发射器 粒子发射器是用于设定粒子的发射属性,比如说粒子的大小,数量和速度等.在创建完粒子对象后,在右侧inspector视图中便可以看到所有的粒子属性: emit:是否是使用粒子发射器. min size:粒子最小尺寸. max size:粒子最大尺寸. min energy:粒子的最小生命周期

shell编程(十二)--- 添加用户示例

[[email protected] Learn]# cat useradd-final.sh  #!/bin/bash # DEBUG=0 ADD=0 DEL=0 help() { echo "Usage: $(basename $0) -v | --verbose | --add user1,user2,... | --del user1,user2,... | -h | --help" } while [ $# -ne 0 ] do case $1 in -h | --help 

Linux系统裁剪之二(Bash脚本编程之十二)

Linux系统裁剪之二(Bash脚本编程之十二) 系统函数库 ·Linux系统的启动流程     1,POST(加电自检) 计算机本身并不会执行程序,它只是一堆破铜烂铁,但是它可以在开机的时候先去载入一段程序,系统在刚刚启动的时候能够实现将某个ROM芯片中的程序映射到CPU能够寻址的地址空间中去,并且让CPU能够执行其中的指令,这些指令大部分都是用来做系统检测的,当检测完成后,如果系统中所有的基本硬件和核心硬件都没有问题的话,接下来就会根据BIOS中设定的系统启动次序(Boot Sequence

QT开发(四十二)——DOM方式解析XML

QT开发(四十二)--DOM方式解析XML 一.DOM简介 1.DOM简介 DOM是Document Object Model的简写,即XML文档对象模型,是由W3C提出的一种处理XML文档的标准接口. DOM 一次性读入整个XML文档,在内存中构造为一棵树(DOM树)将XML文件表示成一棵树,便于随机访问其中的节点,但消耗内存相对多一些.能够在这棵树上进行导航,比如移动到下一节点或者返回上一节点,也可以对这棵树进行修改,或者是直接将这颗树保存为硬盘上的一个 XML 文件. 2.XML DOM节

程序员的奋斗史(四十二)——大学断代史(六)——我与图书馆

文/温国兵 作为一个爱读书之人,图书馆简直是人间天堂.反之,不过地狱. 读书的好处在于,可以穿越古今中外,超越时间和空间的界限,到达你想到达的地方.你可以回到唐朝和诗仙酌酒言欢,可以回到战国和庄子高谈庄周梦蝶.鲲鹏之硕,可以回到18世纪的法国聆听哲人卢梭的教导,可以回到19世纪的德国瞻仰尼采的智慧,可以回到20世纪的中国感受王小波的特立独行,可以回到春秋时期领略老子的道,可以回到20世纪感受徐志摩的唯美诗歌--书中自有黄金屋,书中自有颜如玉,从书中可以获取到广阔的精神食粮,指引着我们前进,教导我

Android项目实战(四十二):启动页优化,去除短暂白屏或黑屏

原文:Android项目实战(四十二):启动页优化,去除短暂白屏或黑屏 大家会发现一个空项目,从手机桌面打开app是秒启动.但是对于自己开发的项目,有时会发现打开app的时候,会有短暂的1秒--2秒的白屏或者黑屏,然后才进入到程序界面. 个人理解为我们自己实现的Application文件里面做了较多的初始化操作,当这些初始化操作完成后才进入到第一个Activity,这段初始化的时间因为没有界面,应用便会因为主题的类别而显示白屏或者黑屏. 构成白屏/黑屏的原因代码如下: /*** @author

Windows界面编程第十二篇 位图显示特效 飞入效果与伸展效果

分享一下我老师大神的人工智能教程吧.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net 转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8696726 欢迎关注微博:http://weibo.com/MoreWindows Windows界面编程之位图显示特效系列目录: 1. <Windows界面编程第九篇位图显示特效交错效果> http:/