原文地址:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx
按我的思路整理一下,作者大神的一些内容理解比较困难。
委托
一、为什么使用委托
public enum Language{
English, Chinese
}
public void GreetPeople(string name, Language lang){
//做某些额外的事情,比如初始化之类,此处略
swith(lang){
case Language.English:
EnglishGreeting(name);
break;
case Language.Chinese:
ChineseGreeting(name);
break;
}
}
以上这段代码使用了枚举作为判断依据,在方法中运用switch判断,而如果需要增加更多的语言种类时,就会变得很麻烦,于是可以使用委托。
在GreetPeople方法中,枚举Language被用作判断,而如果我们定义一个变量,该变量指向一个方法,在调用GreetPeople方法时,直接调用参数中指向的方法,就可以免去判断,枚举这些步骤。
这种用来指向方法的参数类型,就是委托。
我准备给GreetPeople添加一个MakeGreeting的参数。
首先,声明这个委托所需要指向的两个方法:
public void EnglishGreeting(string name)
public void ChineseGreeting(string name)
于是我们就可以定义一个委托GreeetingDelegate:
public delegate void GreetingDelegate(string name);
这个委托定义了参数MakeGreeting的参数类型。
有了委托后就可以再次修改GreetPeople方法
public void GreetPeople(string name, GreetingDelegate MakeGreeting){
MakeGreeting(name);
}
将原本的枚举Language换成了委托后,就可以将需要的方法作为参数,直接指向GreetPeople方法中所需要的方法。
完整代码范例:
using System;
using System.Collections.Generic;
using System.Text;
namespace Delegate {
//定义委托,它定义了可以代表的方法的类型
public delegate void GreetingDelegate(string name);
class Program {
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//注意此方法,它接受一个GreetingDelegate类型的方法作为参数
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
static void Main(string[] args) {
GreetPeople("Jimmy Zhang", EnglishGreeting);
GreetPeople("张子阳", ChineseGreeting);
Console.ReadKey();
}
}
}
输出如下:
Morning, Jimmy Zhang
早上好, 张子阳
我们现在对委托做一个总结:
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
二、委托的用法,将方法绑定到委托
在调用GreetPeople时,我们可以直接传入"Jimmy"到方法中,当然也可以定义一个变量name="Jimmy"。
string name1= "Jimmy Zhang";
GreetPeople(name1, EnglishGreeting);
或者
GreetPeople("Jimmy Zhang", EnglishGreeting);
同样的,既然string可以这样做,委托作为参数类型也是可以的(虽然委托在编译过程中会被编译成类)。
static void Main(string[] args) {
GreetingDelegate delegate1, delegate2;
delegate1 = EnglishGreeting;
delegate2 = ChineseGreeting;
GreetPeople("Jimmy Zhang", delegate1);
GreetPeople("张子阳", delegate2);
Console.ReadKey();
}
这样做事完全没问题的,而比起string,委托还有一个特性,可以将多个方法绑定到同一个委托,当调用这个委托时,将依次调用其所绑定的方法。
static void Main(string[] args) {
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
GreetPeople("Jimmy Zhang", delegate1);
Console.ReadKey();
}
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
实际上,我们可以也可以绕过GreetPeople方法,通过委托来直接调用EnglishGreeting和ChineseGreeting:
static void Main(string[] args) {
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
delegate1 ("Jimmy Zhang");
Console.ReadKey();
}
注意这里,第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
另外还有这样的简化写法:
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:
static void Main(string[] args) {
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
GreetPeople("Jimmy Zhang", delegate1);
Console.WriteLine();
delegate1 -= EnglishGreeting; //取消对EnglishGreeting方法的绑定
// 将仅调用 ChineseGreeting
GreetPeople("张子阳", delegate1);
Console.ReadKey();
}
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
早上好, 张子阳
事件
一、为什么要用事件
在实际操作中,GreetPeople和ChineseGreeting、EnglishGreeting应该放在不用的类中。现在将GreetingPeople()放在一个叫GreetingManager的类中,如下:
namespace Delegate {
//定义委托,它定义了可以代表的方法的类型
public delegate void GreetingDelegate(string name);
//新建的GreetingManager类
public class GreetingManager{
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
}
class Program {
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
static void Main(string[] args) {
// ... ...
}
}
}
在Main()添加如下代码:
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.GreetPeople("Jimmy Zhang", EnglishGreeting);
gm.GreetPeople("张子阳", ChineseGreeting);
}
成功运行了,接下来用另一种方法,将方法绑定到委托。
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang", delegate1);
}
输出:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
上面在调用时(即Main()方法中)才声明了delegate1这个委托类型的变量,为何不在GreetingManager类中事先声明,这样调用会更加方便。
public class GreetingManager{
//在GreetingManager类的内部声明delegate1变量
public GreetingDelegate delegate1;
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
}
之后就能这样实现这个方法
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.delegate1 = EnglishGreeting;
gm.delegate1 += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang", gm.delegate1);
}
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
之后为了在调用时看上去更简洁
public class GreetingManager{
//在GreetingManager类的内部声明delegate1变量
public GreetingDelegate delegate1;
public void GreetPeople(string name) {
if(delegate1!=null){ //如果有方法注册委托变量
delegate1(name); //通过委托调用方法
}
}
}
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.delegate1 = EnglishGreeting;
gm.delegate1 += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang"); //注意,这次不需要再传递 delegate1变量
}
输出为:
Morning, Jimmy Zhang
早上好, Jimmy Zhang
将类中的变量定义为public是非常不安全的,所以我们要对delegate1进行封装。
如果将delegate1声明为provate会怎么样?delegate1这个变量就完全没有用了,因为无法调用这个委托,当然无法在进行方法的注册,这个委托根本没有意义。
之后作者所说的没法理解,总之这里就需要事件对delegate1进行封装(相当于用属性来封装字段)。
在用事件Event封装委托类型的变量后,在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。(好绕,不懂)
继续改写GreetingManager
public class GreetingManager{
//这一次我们在这里声明一个事件
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name) {
MakeGreet(name);
}
}
事件MakeGreet和之前委托delegate1的声明差别只有一个event关键字,事件相当于进行过封装的委托。
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
gm.MakeGreet = EnglishGreeting; // 编译错误1
gm.MakeGreet += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang");
}
会得到编译错误:事件“Delegate.GreetingManager.MakeGreet”只能出现在 += 或 -= 的左边(从类型“Delegate.GreetingManager”中使用时除外)。
在变成gm.MakeGreet += EnglishGreeting;后正常,。
尽管在GreetingManager中奖MakeGreet声明为public,但他会被编译成私有字段,所以不允许在GreetingManger类外以赋值的方式访问。
以下是MakeGreet所产生的代码
private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托变量
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
}
MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它总是被声明为private。另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符。(就是声明事件时为public ,这两个方法就同样是public)