由于C#托管代码中是没有指针的,所以当要把一个函数做为一个参数传给另外一个函数时,用正常方法就没办法了,由于就弄出了个代理(Delegate),当然C#中的代理也只是类似函数指针,它除了能实现函数指针的功能外还有其他一些特性.
先来说下C++中的函数指针:
void PrintNum(int num) { cout<<num;} //一个打印数字的简单函数
void (*FunPtr)(int); //声明一个函数指针
FunPtr = PrintNum; //函数指针指向函数PrintNum
//下面几个语句都是等价的
PrintNum(8);
(*PrintNum)(8);
FunPtr(8);
(*FunPtr)(8);
void Fun(int num,void (*FunPtr)(int)) { FunPtr(num);} //函数指针做为参数传给另外一个函数
Fun(8,FunPtr); //调用上面的函数
C# 代理(Delegate)
说完了指针就来说与它类似的Delegate:
void PrintNum(int num) { Console.Write(num);} //一个打印数字的简单函数
delegate void DePrintNum(int num); //声明一个代理,有点类似声明一个指针,在C#中的话有点类似自定义的一个类型
DePrintNum de; //定义一个定理变量
de= PrintNum; //实例化变量de,相当于指针变量指向一个函数,它和语句de = new DePrintNum(PrintNum);是等价的
de(8); //打印数字8,和PrintNum(8)结果相同
void PrintSomething(int num,DePrintNum de) { de(num);} //把代理变量当参数传递,相当于把函数当参数传递
PrintSomething(8,de); //调用上面的函数
上面是代理和函数指针类似的地方,但它还是些另外的特性,比如可以把多个函数绑定到一个代理变量上
假如还有函数void PrintEno(int num) { Console.Write(num + 1);}
de += PrintEno; //de -= PrintNum就是去掉和函数PrintNum的绑定
de(8); //此时相当于同时调用了PrintNum(8)和PrintEno(8);
C# 事件(Event)
我们都知道C#很喜欢按照面向对象的思想来,本来类中随便声明个变量(标准叫法是字段),但还要再弄个属性出来把它再封装下.
那有个代理之后自然也想个法子把它封装下,于是就有个事件(Event):
比如上面有声明了代理
delegate void DePrintNum(int num);
public event DePrintNumePrintNum ; //其实和声明一个代理变量类似DePrintNum de;相当于把de封装了.不过这里没有用到de,类似于直接用属性没有了字段
ePrintNum += PrintNum; //事件里只能用+=,-=了,不能直接来个等号绑定函数了啊.这里自然也可以用+=连续绑定多个函数
ePrintNum(8); //调用事件,这跟代理一样,都相当于是一个函数指针,去间接的调用函数
//补充:事件和代理用起来区别不太大,只是声明的时候不太一样,不过另外还有个比较大的区别:假如ePrintNum是在类Class1中声明的,则像ePrintNum(8)这样的调用只能
在Class1中才是对的,如果在其他类中都不能实例化一个Class1再调用,只能通过+=,-=去给事件绑定或去掉函数,而如果代理的话就可以通过实例化Class1,然后直接调用代理.这或许就是封装性的一个好处了吧.假如不想让另外的类调用你的代理,而只想让它绑定个函数过来,就可以把代理封装成事件了.
补充
在C#中可能碰到事件最多的地方是.net里面自动生成的很多事件,比如点击下鼠标之类的都对就一个事件,不过我们写代码时只要写事件最终绑定的那个函数里的代码就行了.其他的就不用管了.
另外我们知道C#里面没有了全局函数,全局变量,所有的东西都整到了一个个的class中(或者struct中),但是其实我们发现枚举(enum),代理的声明除了放在class中外也可以放在 class外.其实我们把enum看成类似struct,把代理看成类似class就比较好理解了.