委托和事件
委托是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("jinjiangbo2008@163.com", "18868789776", amount);
//初始化订阅系统
SubscribSystem subject = new SubscribSystem(new BankAccount(), user);
Console.ReadKey();
}
}
}