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("jinjiangbo2008@163.com", "18868789776", amount);
//初始化订阅系统
SubscribSystem subject = new SubscribSystem(new BankAccount(), user);
Console.ReadKey();
}
}
}
C#编程(四十二)----------委托和事件的更多相关文章
- 《Inside C#》笔记(十二) 委托与事件
C#的委托与C++的函数指针类似,但委托是类型安全的,意味着指针始终会指向有效的函数.委托的使用主要有两种:回调和事件. 一 将委托作为回调函数 在需要给一个函数传递一个函数指针,随后通过函数指针调用 ...
- NeHe OpenGL教程 第四十二课:多重视口
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- 网站开发进阶(四十二)巧用clear:both
网站开发进阶(四十二)巧用clear:both 前言 我们在制作网页中用div+css或者称xhtml+css都会遇到一些很诡异的情况,明明布局正确,但是整个画面却混乱起来了,有时候在IE6下看的很正 ...
- Gradle 1.12用户指南翻译——第四十二章. Announce插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- SQL注入之Sqli-labs系列第四十一关(基于堆叠注入的盲注)和四十二关四十三关四十四关四十五关
0x1普通测试方式 (1)输入and1=1和and1=2测试,返回错误,证明存在注入 (2)union select联合查询 (3)查询表名 (4)其他 payload: ,( ,( 0x2 堆叠注入 ...
- “全栈2019”Java第四十二章:静态代码块与初始化顺序
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 第四十二个知识点:看看你的C代码为蒙哥马利乘法,你能确定它可能在哪里泄漏侧信道路吗?
第四十二个知识点:看看你的C代码为蒙哥马利乘法,你能确定它可能在哪里泄漏侧信道路吗? 几个月前(回到3月份),您可能还记得我在这个系列的52件东西中发布了第23件(可以在这里找到).这篇文章的标题是& ...
- 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- .NET面试题系列(十)委托与事件
委托 有了委托的存在,使得方法可以作为参数传递给另一个方法. int Max(int x,int y) { return x>y?x:y; } int Min(int x,int y) { re ...
随机推荐
- 错误/异常:java.net.SocketException: Unrecognized Windows Sockets error: 0: JVM_Bind;的解决方法
1.异常视图 2.解决方法 SocketException(JVM_Bind):套接口异常 说明:服务器端口号被占用 了 解决方法:点击 Window-->Preferences- ...
- java 通过异常处理错误
java的基本理念是"结构不佳的代码不能够运行" 一.概念 发现错误的理想时机是编译阶段,然而,编译期间并不能找出所有的错误,余下的问题必须在运行时期解决. 二.基本异常 异常情形 ...
- Linux Centos安装Jenkins
Jenkins 是一个开源项目,提供了一种易于使用的持续集成系统,使开发者从繁杂的集成中解脱出来,专注于更为重要的业务逻辑实现上.同时 Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和 ...
- **linux实用命令之如何移动文件夹及文件下所有文件
http://www.linuxde.net/2013/02/12448.html 格式: mv [选项(option)] 源文件或目录 目标文件或目录 使用命令: mv webdata /bin/u ...
- Oracle数据库创建表空间
//创建表空间create tablespace ACQUISITION_DATA datafile 'F:\app\kelly\oradata\acquisition\acquisition_dat ...
- fis3-postpackager-loader
静态资源前端加载器,用来分析页面中使用的和依赖的资源(js或css), 并将这些资源做一定的优化后插入页面中.如把零散的文件合并. 注意 此插件做前端硬加载,适用于纯前端项目,不适用有后端 loade ...
- 【CodeChef】QTREE- Queries on tree again!
题解 给你一棵基环树,环长为奇数(两点间最短路径只有一条) 维护两点间路径最大子段和,支持把一条路径上的值取反 显然只要断开一条边维护树上的值,然后对于那条边分类讨论就好了 维护树上的值可以通过树链剖 ...
- 2018年东北农业大学春季校赛 C-wyh的商机
一天,你们wyh学长和你们zhl学长玩一个游戏,这个游戏规则是这样的 给你n个城市,保证这n个城市之间都只有一条道路可以到达. 有一件物品,在所有城市中都是一样的,但是由于各个城市的经济发展不同,导致 ...
- Educational Codeforces Round 41 (Rated for Div. 2)
这场没打又亏疯了!!! A - Tetris : 类似俄罗斯方块,模拟一下就好啦. #include<bits/stdc++.h> #define fi first #define se ...
- 常见Java库漏洞汇总
1.ActiveMQ 反序列化漏洞(CVE-2015-5254) ref:https://www.nanoxika.com/?p=408 Apache ActiveMQ是美国阿帕奇(Apache)软件 ...