您的位置:首页 > 编程语言 > C#

C#中的委托和事件

2016-02-25 17:09 537 查看

C#中的委托和事件

 

前言

 

委托和事件在.NET框架中应用的很广泛,但是楼主因为知识尚浅,用到的不多,但是听前辈们说,这个委托和事件就像两座大山,只要翻过了这两座大山,.NET的知识可能就会一通百通了.最近几天咱们就一起来说说什么是委托,为啥要用委托,事件的由来,.NET框架中的委托和事件委托中方法异常和超时的处理,委托,与异步编程,委托和事件对Observer设计模式的意义.貌似很高大上呢!如果你对一个小白说出这些名词,估计小白会把你当天人来对待了!

 

 

首先理解委托

 

将方法作为方法的参数

 

先不管多么绕口,也别管委托杀个啥玩意,先看下面这两个最简单的方法,他们不过是在屏幕上输出一下:

 

public void GreetPeople(string name)
{
//做某些额外的事情,比如初始化之类的
EnglishGreetPeople(name);
}
public void EnglishGreetPeople(string name)
{
Console.WriteLine("hello : "+name);
}


暂且不管这两个方法有没有实际意义,GreetPeople用于向某人问好,当传递代表某人姓名的name参数,比如”Jimmy”进去的时候,在这个方法中,将会调用EnglishGreetProple方法,再次传递name参数,EnglishGreetPeople则用于想屏幕输出”hello : Jimmy”.

 

现在如果这个程序需要进行全球化,比如中国人不明白”hello”是啥意思,怎么办呢?我们需要改程序:

public void ChineseGreeting(string name)
{
Console.WriteLine("你好 : "+name);
}

这个时候,GreetPeople也需要修改,不然如何判断使用哪个版本的问候方法合适呢?在进行这个之前,只好再定义一个枚举作为判断的依据:

public enum Language
{
English,
Chinese
}
public void GreetPeople(string name,Language lang)
{
//做某些额外的事情,比如初始化之类的
switch (lang)
{
case Language.English:
EnglishGreetPeople(name);
break;
case Language.Chinese:
ChineseGreeting(name);
break;
}
}

好了,尽管这样解决了问题,但不用说大家也应该回箱单,这个方法的扩展性很差,世界上有这么多国家,如果这样的话,我们就必须一一的添加各个国家的GreetPeople()方法.

 

在考虑新的解决方案之前,先来看一下GreetPeople的方法签名:

public void GreetPeople(string name,Language lang)
我们仅仅看string name,在这里,string是参数类型,name是参数变量,当赋给name字符串”jimmy”的时候,它就代表”jimmy”这个值;当赋给它”张三”的时候,它又代表”张三”这个值.然后,就可以在方法体内对这个name进行其他操作.

 

再仔细想想,假如GreetPeople()方法可以接受一个参数变量,这个变量可以代表另一个方法,当这个变量被赋值为EnglishGreetPeople的时候,它代表EnglishGreetPeople这个方法;当赋给ChineseGreet的时候,它又代表ChineseGreet这个方法了,我们将这个参数变量命名为MakeGreeting,那么不是可以和给name赋值时一样,在调用GreetPeople()方法的时候,给这个MakGreeting参数也赋上值.然后在方法体内部,也可以像使用别的参数一样使用MakGreeting.但是,由于MakGreeting代表着一个方法,它的使用方式应该和它被赋值的方法(比如ChineseGreet)一样.说了这么多,是不是有点晕?没关系,咱们先看个案例:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 委托和泛型2
{
public delegate void GreetingDelegate(string name);
class Program
{
static void Main(string[] args)
{
GreetPeople("Jimmy",EnglishGreeting);
GreetPeople("张三",ChineseGreeting);
}
public static void ChineseGreeting(string name)
{
Console.WriteLine("你好 "+name);
}
public static void EnglishGreeting(string name)
{
Console.WriteLine("hello "+name);
}
private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
MakeGreeting(name);
}
}
}


 

这个案例可能不是很清楚说明我的意思,咱们慢慢道来.这里不再需要枚举了,因为在向MakeGreeting赋值的时候动态的决定使用哪个方法,而在这个两个方法的内部,已经对使用”hello”还是”你好”.

 

本例中的委托的定义:

public delegate void GreetDelegate(string name)
咱们完全可以先和上面EnglishGreeting方法的签名对比一下,除了加入了delegate关键字以外,其余的是不是完全一样?

 

有了委托的定义,就好办了,定义一个委托其实和定义一个int类型的变量一样.

现在对委托简单的说一点,委托其实是一个类,使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态的赋给参数的做法,可以避免在程序中大量使用判断语句,同时是程序具有很好的扩展性.

 

将方法绑定到委托

 

看到这里,是不是还有点迷糊,没关系,慢慢的就明白了,上面的案例你肯定会觉得不就是输出一行语句吗,没啥实际意义,而且这样的代码更不容易让人理解.

 

上面的的例子中,不一定要直接在GreetPeople()方法中name参数赋值,可以像下面这样使用变量:

static void Main(string[] args)
{
string name1, name2;
name1 = "Jimmy";
name2 = "张三";
GreetPeople(name1,EnglishGreeting);
GreetPeople(name2,ChineseGreeting);
}

而既然委托GreetingDelegate和类型string的地位一样,都是定义了一种参数类型,那么是不是也可以像下面这样使用委托:

  static void Main(string[] args)
{
GreetingDelegate delegate1, delegate2;
delegate1 = EnglishGreeting;
delegate2 = ChineseGreeting;
string name1, name2;
name1 = "Jimmy";
name2 = "张三";
GreetPeople(name1,delegate1);
GreetPeople(name2,delegate2);
}
这样也是没问题的,这里要说明的是委托不同于string的一个特性:可以将多个方法赋给同一个委托,或者理解为将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法.想下面这样:

static void Main(string[] args)
{
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
GreetPeople("张三",delegate1);
}


看一下输出:

hello 张三
你好 张三

 

实际上,完全可以绕过GreetPeople方法,通过委托来直接调用EnglishGreeting和ChineseGreeting:

GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
//将先后调用EnglishGreeting与ChineseGreeting方法
delegate1("张三");

说明一下,这在本例中是没问题的,但回头看下上面GreetPeople()的定义,在其中可以在一些对于EnglishGreeting和ChineseGreeting来说都需要进行的工作,为了简便这里省了.

 

注意,这里第一次用的”=”,是赋值的语法;第二次用的是”+=”,是绑定的语法.如果第一次就是用”+=”,将出现”使用了为赋值的局部变量”的编译错误.

 

可以使用下面的代码赖建华这一过程:

static void Main(string[] args)
{
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;
delegate1("张三");
}

这样的话,应该能看出这样一条语句和实例化一个类是何其的相似.不禁可以想到:上面第一次绑定委托时不可以使用”+=”的编译错误,或许可以用下面的方法来避免:

static void Main(string[] args)
{
GreetingDelegate delegate1 = new GreetingDelegate();
delegate1 += EnglishGreeting;
delegate1 += ChineseGreeting;
delegate1("张三");
}

但是实际上,这样会出现编译错误:”GreetingDelegate”方法没有采用”0”个参数的重载.尽管这样的结果会让人觉得有点沮丧,但是编译时的提示”没有0个参数的重载”再次让我们联想到了类的构造函数.

 

既然委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,这个语法是”-=”;

static void Main(string[] args)
{
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;//给此委托变量再绑定一个方法
//将先后调用EnglishGreeting和ChineseGreeting方法
GreetPeople("张三", delegate1);
Console.WriteLine();
delegate1 -= EnglishGreeting;//取消对EnglishGreeting方法 的绑定
GreetPeople("张三",delegate1);
}

 

小小的总结:

使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用”调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法.

 

 

委托与接口

 

在上面的例子中,使用委托定义了方法的前面,从而隔离了变化,此处所指的变化纪伟方法体内的具体实现.注意这里使用了”隔离变化”这个术语,他是一个面向对象设计的原则.实际上,对于上面说的案例,委托所起到的作用和定义一个只含有一个方法的接口完全一样.下面的代码使用接口完成了与使用委托完全相同的功能:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
public interface IGreeting
{
void GreetingPeople(string name);
}
public class EnglishGreeting : IGreeting
{
public void GreetingPeople(string name)
{
Console.WriteLine("hello :"+name);
}
}
public class ChineseGreeting : IGreeting
{
public void GreetingPeople(string name)
{
Console.WriteLine("你好 :"+name);
}
}
class Program
{
private static void GreetPeople(string name, IGreeting makeGreeting)
{
makeGreeting.GreetingPeople(name);
}
static void Main(string[] args)
{
GreetPeople("Jimmy",new EnglishGreeting());
GreetPeople("张三",new ChineseGreeting());
}
}
}


可以看到,在使用接口时,代码量大了很多,而且还需要定义实现了接口的类.需要注意的是,在采用接口实现时,利用的是C#的多态能力.尽管他们的结构是一样的,但是过程是不一样的.

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C# .net