关键词:
本篇文章主要采用理论和代码实例相结合方式来论述委托和事件,涉及到一些边界技术,如软件架构的OCP原则(开-闭原则),
软件架构解耦,设计模式(Sender-Order)和事件驱动模型,有一定难度和深度,不适合初级者。
第一部份 委托
关于委托内容,主要围绕下图来论述。
一 委托是什么(what)
(一)委托产生的背景之一
1.我们先来假设这样一个情景需求:
设计一个系统,使其满足如下条件:
(1)当前,只有中国人和英国人使用该系统;
(2)向系统输入用户名和相应的语言,将产生相应语言的问候语;
(3)后期,可能会有其他国家语言加入该系统(系统变化的部分) ;
2.技术方案实现
关于技术方案实现,我们可以采用下图中的三种方式之一。
为了更好地叙述委托,我们分别实现三种技术方案,并找出它们的关系。
2.1 一般实现
Code(控制台程序)
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { Console.WriteLine(GetGreetingContens("小王", "Chinese")); Console.WriteLine(GetGreetingContens("Alan_beijing", "English")); Console.WriteLine(GetGreetingContens("Linda", "Russian")); Console.Read(); } //根据用户名和语言,获取问候语 public static string GetGreetingContens(string UserName, string Language) { //New 一个GreetToUsers对象 GreetToUsers greetToUsers = new GreetToUsers(); //当然,你也可以使用switch开发语句来代替如下的if......else...... if (Language == "Chinese") { return greetToUsers.ChinesePeople(UserName); } else if (Language == "English") { return greetToUsers.EnglishPeople(UserName); } else { return "抱歉,当前系统只支持汉语与英语(Sorry, the current system only supports Chinese and English.)"; } } } //定义基本问候类和方法 public class GreetToUsers { //Chinese People public string ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; return GreetContents; } //English People public string EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; return GreetContents; } } }
Result
分析
2.2用接口实现
如上,我们分析了方案一中的问题,为了更好地解决方案一存在的问题,我们采用面向接口编程的形式来实现。
2.2.1 什么是面向接口编程?
面向接口编程,主要是解决软件架构设计中“动静问题”,即封装不变(静),剥离变化(抽出变化)。
Code:
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { GreetToChineseUsers greetToChinesUsers = new GreetToChineseUsers(); GreetToEnglishUsers greetToEnglishUsers = new GreetToEnglishUsers(); GreetToOtherUsers greetToOtherUsers = new GreetToOtherUsers(); //Chinse Users IGreetToUsers iGreetToChineseUsers = greetToChinesUsers; Console.WriteLine(iGreetToChineseUsers.CountryPeople("小王", "Chinese")); //English Users IGreetToUsers iGreetToEnglishUsers = greetToEnglishUsers; Console.WriteLine(iGreetToEnglishUsers.CountryPeople("Alan_beijing", "English")); //Other Users IGreetToUsers iGreetToOtherUsers = greetToOtherUsers; Console.WriteLine(iGreetToOtherUsers.CountryPeople("Linda", "Russian")); Console.Read(); } } //系统输出问候语(变化的部分,语言为变化因子) public interface IGreetToUsers { string CountryPeople(string UserName,string Language); } //汉语用户类 public class GreetToChineseUsers:IGreetToUsers { //Chinese People public string CountryPeople(string UserName, string Language) { string GreetContents = "您好!" + UserName; return GreetContents; } } //英语用户类 public class GreetToEnglishUsers : IGreetToUsers { //English People public string CountryPeople(string UserName, string Language) { string GreetContents = "Hello," + UserName + "!"; return GreetContents; } } //其他用户类 public class GreetToOtherUsers : IGreetToUsers { //English People public string CountryPeople(string UserName, string Language) { return "Sorrry,当前系统只支持汉语与英语"; } } }
result
分析:
(1)如上,我们将变化因子"语言"剥离出来,形成接口,以后只要每增加一个语言,只需实现接口即可,满足了OCP原则,基本解决了方案一中存在的问题;
(2)如上代码只是为了演示面向接口编程这个功能,并不完善,感兴趣的读者,可自行完善(如将语言定义为枚举类型等);
方案二中的代码,细心的读者会发现,Main方法中new了三个对象,假若以后系统有300门语言,那岂不New 300个类,这样的话,也不利于代码维护呀,怎么解决这个问题呢?(提示一下,采用设计模式抽象工厂即可解决该问题)
2.3 用委托实现
在这里,没接触过委托的读者,先跳过这部分,往下读,看完(三)怎样使用委托(How to use)后,再来看本部分。
Code
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { //根据语言判断,传递哪个方法的参数 Console.WriteLine("------------请输入用户名------------"); string UserName = Console.ReadLine(); Console.WriteLine("------------请输入语言------------"); string Language = Console.ReadLine(); Console.WriteLine("------------输出结果------------"); GreetToUsers greetToUsers = new GreetToUsers(); if (Language == "Chinese") { Console.WriteLine(GetGreetingContents(UserName, greetToUsers.ChinesePeople)); } else if (Language == "English") { Console.WriteLine(GetGreetingContents(UserName, greetToUsers.EnglishPeople)); } else { Console.WriteLine(GetGreetingContents(UserName, greetToUsers.OtherPeople)); } Console.Read(); } public static string GetGreetingContents(string UserName,DelegateGetGreeting delegateGetGreeting) { return delegateGetGreeting(UserName); } } //定义委托 public delegate string DelegateGetGreeting(string UserName); //定义基本问候类和方法 public class GreetToUsers { //Chinese People public string ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; return GreetContents; } //English People public string EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; return GreetContents; } //非英非汉 public string OtherPeople(string UserName) { return "Sorrry,当前系统只支持汉语与英语"; } } }
Result
2.3 分析上述三种实现方案的关系
通过上诉三种方式的比较,我们容易得出委托的如下结论:
(1)抽象方法,屏蔽方法细节,调用只需传递方法名字即可;
(2)能够实现程序的解耦,松耦合(在方案一中,GetGreetingContens方法体内new了GreetToUsers对象,强耦合)
(3)委托一般扮演中间者的角色,这功能在委托事件中体现非常明显(第二部分 事件 将详细论述)
如我们在租房子时,可以直接找房东(技术实现的一般方法,强耦合,让租房者和房东直接联系),也可找中介(技术实现的委托,松耦合,租房者通过中介来与房东联系)
2.4 委托背景概述
委托的重要产生背景,就是事件驱动模型(关于什么是事件和事件驱动,在本文第二部份 事件 论述)。
(二) 委托定义
用delegate关键字定义委托(注意,委托是没有方法体的,类似接口里面的方法),在定义委托前,必须明确两个问题:
1.委托将要绑定的方法;
2.委托的形参类型,形参个数和委托的返回值必须与将要绑定的方法的形参类型,形参个数和返回值一致;
(三)相关概念
委托涉及的相关概念有函数指针,类型安全性、事件、Lambda表达式等
1.函数指针:在C++中,指针的一个类别,主要指向函数(变量指针,主要指向变量地址),可以把C#中的委托理解为函数指针;
2.类型安全性:在C++中,我们都知道指针是类型不安全的(返回值,返回类型和什么时候返回,这些都是未知的),而委托是类型安全的;
3.事件:可以把事件理解为委托的一种特例(在本文第二部份 事件 论述)
4.Lambda表达式:委托与Lambd表达式相结合,实现高效编程,与Jquery的“较少代码做更多的事”类似,委托与Lambda,Linq相结合,使较短代码就能实现比较复杂的功能(在本篇文章中不讲解Lambda与Lambda树,将在后续文章中讲解)
(四)委托组成
大致分为两部分:声明委托和注册方法(也叫绑定方法)
1.声明委托
用delegate声明;
2.绑定方法
绑定具体方法,传递方法名称;
(五) 委托种类
委托种类,一般分为多播委托和单播委托
1.单播委托:绑定单个方法
2.绑定多个方法
(六) 委托操作
1.绑定方法
2.解绑方法
二 委托能解决什么问题(Can do)
1.避免核心方法中存在大量的if....else....语句(或swich开关语句);
2.满足程序设计的OCP原则;
3.使程序具有扩展性;
4.绑定事件;
5.结合Lambda表达式,简化代码,高效编程;
6.实现程序的松耦合(解耦),这个在事件(event)中体现比较明显;
三 怎么使用委托(How to use)(本篇文章不谈匿名委托,匿名委托具体内容,将在Lambda章节讲解)
(一)委托的基本构成
通常地,使用委托的步骤与使用类的步骤是一样的。大致分为两步:定义委托和绑定方法(传递方法)
1.定义委托
用delegate关键字定义委托(注意,委托是没有方法体的,类似接口里面的方法),在定义委托前,必须明确两个问题:
(1).委托将要绑定的方法;
(2).委托的形参类型,形参个数和委托的返回值必须与将要绑定的方法的形参类型,形参个数和返回值一致;
public delegate 委托返回类型 委托名(形参)
例子:如上我们委托将要表示系统输出的问候语
a.委托将要绑定的方法
public string ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; return GreetContents; } //English People public string EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; return GreetContents; } //非英非汉 public string OtherPeople(string UserName) { return "Sorrry,当前系统只支持汉语与英语"; }
b.由如上方法可看出,方法的返回类型为string,方法有一个string类型的形参,在定义委托时,与其保持一致即可
//定义委托 public delegate string DelegateGetGreeting(string UserName);
2.绑定方法
使用委托时,将方法名字作为参数传递给委托即可
1 GreetToUsers greetToUsers = new GreetToUsers(); 2 GetGreetingContents(UserName, greetToUsers.ChinesePeople)
(二)委托绑定方法
1.绑定单个方法
绑定单个方法,将单个方法名字传给委托即可
static void Main(string[] args) { //根据语言判断,传递哪个方法的参数 Console.WriteLine("------------请输入用户名------------"); string UserName = Console.ReadLine(); Console.WriteLine("------------请输入语言------------"); string Language = Console.ReadLine(); Console.WriteLine("------------输出结果------------"); GreetToUsers greetToUsers = new GreetToUsers(); DelegateGetGreeting DGG; if (Language == "Chinese") { //绑定单个方法 DGG = greetToUsers.ChinesePeople; Console.WriteLine(GetGreetingContents(UserName, DGG)); } else if (Language == "English") { DGG = greetToUsers.EnglishPeople; Console.WriteLine(GetGreetingContents(UserName, DGG)); } else { DGG = greetToUsers.OtherPeople; Console.WriteLine(GetGreetingContents(UserName, DGG)); } Console.Read(); }
另一种不太规范写法:不用GetGreetingContents(string UserName,DelegateGetGreeting delegateGetGreeting)方法
static void Main(string[] args) { //根据语言判断,传递哪个方法的参数 Console.WriteLine("------------请输入用户名------------"); string UserName = Console.ReadLine(); Console.WriteLine("------------请输入语言------------"); string Language = Console.ReadLine(); Console.WriteLine("------------输出结果------------"); GreetToUsers greetToUsers = new GreetToUsers(); DelegateGetGreeting DGG; if (Language == "Chinese") { //绑定单个方法 DGG = greetToUsers.ChinesePeople; Console.WriteLine(DGG(UserName)); } else if (Language == "English") { DGG = greetToUsers.EnglishPeople; Console.WriteLine(DGG(UserName)); } else { DGG = greetToUsers.OtherPeople; Console.WriteLine(DGG(UserName)); } Console.Read(); }
之所以不规范,主要是在项目中,不利于代码的模块化。
2.绑定多个方法(多播委托)
注意:绑定多个方法时,委托范围类型必须为void类型,否则只返回最后一个绑定的值。
绑定多个方法,采用 += 绑定
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { GreetToUsers greetToUsers = new GreetToUsers(); DelegateGetGreeting DGG; //绑定多个方法 DGG = greetToUsers.ChinesePeople; DGG += greetToUsers.EnglishPeople; DGG("小王"); Console.Read(); } } //定义委托 public delegate void DelegateGetGreeting(string UserName); //定义基本问候类和方法 public class GreetToUsers { //Chinese People public void ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; Console.WriteLine(GreetContents); } //English People public void EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; Console.WriteLine(GreetContents); } //非英非汉 public void OtherPeople(string UserName) { Console.WriteLine("Sorrry,当前系统只支持汉语与英语"); } } }
3.解绑方法
解载绑定的方法,采用 -= 解绑
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { GreetToUsers greetToUsers = new GreetToUsers(); DelegateGetGreeting DGG; //绑定多个方法 DGG = greetToUsers.ChinesePeople; DGG += greetToUsers.EnglishPeople; //解绑ChinesePeople方法 DGG-= greetToUsers.ChinesePeople; DGG("小王"); Console.Read(); } } //定义委托 public delegate void DelegateGetGreeting(string UserName); //定义基本问候类和方法 public class GreetToUsers { //Chinese People public void ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; Console.WriteLine(GreetContents); } //English People public void EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; Console.WriteLine(GreetContents); } //非英非汉 public void OtherPeople(string UserName) { Console.WriteLine("Sorrry,当前系统只支持汉语与英语"); } } }
(三)委托机制
将如下代码通过反汇编工具.NET Reflector反汇编
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { GreetToUsers GTU = new GreetToUsers(); DelegateGreet DG = new DelegateGreet(); //DG.delegateGetGreeting = GTU.ChinesePeople;//注册方法 DG.delegateGetGreeting += GTU.ChinesePeople; DG.delegateGetGreeting += GTU.EnglishPeople; DG.GreetUser("小王"); Console.Read(); } } public class DelegateGreet { //声明委托 public delegate void DelegateGetGreeting(string UserName); //委托变量为public,破坏了类的封装性 public DelegateGetGreeting delegateGetGreeting; //虽然Event论定义为public,但其还是私有变量,只能通过+=,或-=访问 //public event DelegateGetGreeting EventGreet; public void GreetUser(string UserName) { //EventGreet?.Invoke(UserName); delegateGetGreeting(UserName); } } //定义基本问候类和方法 public class GreetToUsers { //Chinese People public void ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; Console.WriteLine(GreetContents); } //English People public void EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; Console.WriteLine(GreetContents); } //非英非汉 public void OtherPeople(string UserName) { Console.WriteLine("Sorrry,当前系统只支持汉语与英语"); } } }
反汇编
分析:
1.三个核心方法:BeginInvoke,EndInvoke和Invoke
(1)使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。
(2使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也
可以使用EndInvoke方法或者其它类
再谈c#中的委托和事件
...多篇。但是就我的观察来看,大多数文在讲述这方面概念时,都会用烧开水和狗叫主人的例子来讲述事件怎么工作,这样比喻固然与生活联系紧密,但看多了难免有一些审美疲劳。所以今天,我打算结合自己... 查看详情
事件引入和本质
...,相信你会对事件有更深刻的认识和理解,不信,你看!概念用event 关键字使您可以声明事件。 事件是类在相关事情发生时发出通知的方法。【简述】事件就是类在发生其关注的事情的时候用来提供通知的一种方式。引... 查看详情
对张子扬显示的两篇委托和事件说得很透文章读后的思考
第一篇C#中的委托和事件http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx 第二篇 C#中的委托和事件(续)http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-Advanced.aspx&nbs 查看详情
分分钟用上c#中的委托和事件
每一个初学C#的程序猿,在刚刚碰到委托和事件的概念时,估计都是望而却步,茫然摸不到头脑的。百度一搜,关于概念介绍的文章大把大把的,当然也不乏深入浅出的好文章。可看完这些文章,大多数新手,估计也只是信心满... 查看详情
分分钟用上c#中的委托和事件(代码片段)
每一个初学C#的程序猿,在刚刚碰到委托和事件的概念时,估计都是望而却步,茫然摸不到头脑的。百度一搜,关于概念介绍的文章大把大把的,当然也不乏深入浅出的好文章。可看完这些文章,大多数新手,估计也只是信心满... 查看详情
c#委托和事件:最简单的委托和事件
...事件基于委托,所以先说委托。一切脱离实际场景的抽象概念新手看上去就像是在扯犊子,不错,我就是个新手。所以我需要一个实际的场景。明天刚好考试(商务英语),考试上有两个角色(class):老师(Teacher)和学生(Stu... 查看详情
c#中的委托与事件
委托与事件是C#中的重要概念,这两个概念既有联系又有区别,容易混淆,下面就对C#中的这两个概念进行一下比较。1.委托声明委托的语法和声明函数非常类似,不过需要使用delegate关键字,并没有函数体。例... 查看详情
事件捕获事件冒泡和事件委托
...档才发现是平时忽略的事件捕获,事件委托和事件冒泡的概念出现的问题。遂上网了解了一下相关的内容。以下是我的学习总结。事件捕获:发生事件时首先在document上,然后依次传递到body,最后到目标节点上;事件冒泡:指事... 查看详情
c#编程之委托与事件四
...、事件的概貌。希望本文能有助于大家理解委托、事件的概念,理解委托、事件的用途,理解它的C#实现方法,理解委托与事件为我们带来的好处。C#是一种新的语言,希望大家能通过本文清楚地看到这些,从而可以对委托、事... 查看详情
事件流和事件委托(代码片段)
...顺序是怎样的呢?为了解决这个问题,所以有了事件流的概念。1、事件流当一个HTML元素产生一个事件时,该事件会在元素节点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为DOM事件流。简单... 查看详情
webapisdom-网页特效篇-滚动事件和加载事件(代码片段)
...对象/DOM重绘和回流/ DOM-事件对象/DOM-事件流 / DOM-事件委托+综合案例一、滚动事件和加载事件1.1滚动事件当 查看详情
c#委托与事件
...制台下使用委托和事件我们都知道,C#中有“接口”这个概念,所谓的“接口”就是定义一套标准,然后由实现类来具体实现其中的方法,所以说“接口,是一组类的抽象”。同样道理,我们可以将“委托”理解为“方法的抽象... 查看详情
对张子阳先生对委托和事件的两篇文章的读后思考(说得很透,内附故事一篇)
第一篇C#中的委托和事件第二篇 C#中的委托和事件(续) 首先,张子阳先生的这是两篇关于委托和事件间关系的文章,是目前为止我读过的介绍委托和事件以及异步调用最简明清晰文章,作者通过非常有节奏的“标... 查看详情
委托泛型委托多播委托匿名函数lamda表达式事件
...委托 将一个方法作为参数传递给另一个方法2、委托概念 publicdelegateint委托名(inta,intb); 声明一个委托类型,可以用访问修饰符修饰,delegate关键字,有返回值和参数 委托所指向的函数必须跟委托具有相同的签名,... 查看详情
事件委托原理
...子元素冒泡上来的事件,找到是哪个子元素的事件。基本概念非常简单,但仍有很多人不理解事件委托的工作原理。这里我将要解释事件委托是如何工作的,并提供几个纯JavaScript的基本事件委托的例子。假定我们有一个UL元素,... 查看详情
c#中的委托与事件
委托与事件是C#中的重要概念,这两个概念既有联系又有区别,容易混淆,下面就对C#中的这两个概念进行一下比较。1.委托声明委托的语法和声明函数非常类似,不过需要使用delegate关键字,并没有函数体。例... 查看详情
java专家之路--面向对象的基础概念和原理,事件机制?
什么是回调?什么是事件?委托?事件驱动的编程模型? 查看详情
c#中的委托是啥?事件是否一种委托?
...指针。那么在.NET中,以委托的方式来实现了函数指针的概念。.NET中使用委托的主要原因是它是类型安全的,为什么呢?因为在以前,比如C中,函数指针只不过是一个指向存储单元的指针,我们无法说出这个指针实际指向什么,... 查看详情