言简意赅的解释
先说总结:事件就是委托链(也就是继承自MulticastDelegate类的委托,用delegate声明的委托就是继承自MulticastDelegate类的)的属性,事件有一个Add方法、一个Remove方法以及一个私有委托链。也就是说,声明一个事件,类似于声明一个进行封装了的委托链。
很多人说:“总是感觉委托和事件没什么区别,调用事件不就是相当于调用多个委托么?”
不全对,事件之于委托,就相当于,集合的属性之于集合的元素,注意:事件封装了私有委托链,事件其实就是一个特殊的多播委托(也叫委托链)。
举个例子:
class Program
{
public delegate void SendHandler(string str); // 用delegate声明的委托就是继承自MulticastDelegate类的
public event SendHandler SendEvent; // 用刚声明的委托类型来声明事件
static void Main(string[] args)
{ }
}
编译后,用ILDASM.EXE打开那个.exe,就会发现SendHandler委托被编译为了一个叫做SendHandler的类,声明的委托就是一个类嘛。
SendEvent事件则是被编译成了包含一个add_前缀的方法(通过调用Delegate.Combine()方法来实现)和一个remove_前缀的方法(通过调用Delegate.Remove()方法来实现)。这是默认实现,事件是可以自行重写add和remove的。
好了,前面说的都不是废话,你还会看到一个私有字段SendEvent(蔚蓝色菱形),其存储了对事件处理方法的引用。而add_前缀方法和remove_前缀方法起到的就是类似C#属性访问器中get_和set_方法的封装私有字段的作用,使用事件能访问私有委托。简而言之,事件就是用来访问私有的委托字段,让应用程序的代码更加的安全。
浅显易懂的解释
委托
这个可以简单的理解为:把方法当成方法的参数,看起来有点绕口,咱们先写个“你好,世界!”
public void HelloWorld(string name)
{
ByEnglish(name);
}
public void ByEnglish(string name)
{
Console.WriteLine("Hello World By" + name);
}
先把这两个方法写出来,先别问为啥这么写好了,现在产品经理打算整个中文版的,好,咱改!
public void ByChinese(string name)
{
Console.WriteLine("你好,世界!作者:" + name);
}
很明显,上面的HelloWorld()得改了
public enum Language
{
English,CHinese
}
public void HelloWorld(string name,Language lan)
{
switch(lan)
{
case Language.English:ByEnglish(string name);break;
case Language.Chinese:ByChinese(string name);break;
}
}
差不多这样的话可以满足目前的需求了现在突然又蹦出来个意大利人想弄出个意大利版(我不会意大利语,不贴了),按照上面的思路,大概也能整出来。然后,产品经理说:这个方法咱要全球化,每个语言都要照顾到。(NMB) 按照上面的思路来,我觉得可以抽刀砍人了。但是法律这玩意儿可怕哟,想到这个我的心儿就碎了。所以咱们还是把“委托”掏出来吧!委托上面的说的是:把方法当做方法的参数,大概就是这个样子public void HelloWorld(string name, 方法 方法名)
上委托,把上面代码改了:
private static delegate void HelloWorldDelete(string name)
public void HelloWorld(string name, HelloWorldDelete chooseLanguage)
{
chooseLanguage(name);
}
private static void ByEnglish(string name)
{
Console.WriteLine("Hello World By" + name);
}
private static void ByChinese(string name)
{
Console.WriteLine("你好,世界!作者:" + name);
}
static void Main(string[] args)
{
HelloWorld("张三",ByChinese);
HelloWorld("ZhangThree",ByEnglish);
}
输出:
你好,世界!作者:张三
Hello World By ZhangThree
再写个绑定版的(这回只写Main中的,因为其他的没变):
static void Main(string[] args)
{
HelloWorldDelete delegateEnglish,delegateChinese;
delegateEnglish = ByEnglish;
delegateChinese = ByChinese;
HelloWorld("张三",delegateChinese);
HelloWorld("ZhangThree",delegateEnglish);
}
输出不变。
换个姿势绑定(只写Main中的,因为其他的没变):
static void Main(string[] args)
{
HelloWorldDelete delegateAll;
delegateAll = ByEnglish;
delegateAll += ByChinese;
HelloWorld("张三", delegateAll );
}
输出:
Hello World By 张三
你好,世界!作者:张三
再换个体位(只写Main中的,因为其他的没变):
static void Main(string[] args)
{
HelloWorldDelete delegateAll;
delegateAll = ByEnglish;
//“=”表示赋值
delegateAll += ByChinese; //“+=”表示绑定,委托不能一开始直接+=,会出现“未赋值
//什么的错误”;相应的“-=”表示解除绑定
delegateAll ("张三");
}
事件
写代码讲究个“说学逗唱”,说错了,讲究个“面向对象”。面向对象:封装、继承、多态。咱们就先从封装开始:
把上面的封装一下
public delegate void HelloWorldDelegate(string name);
public class HelloWorldClass
{
HelloWorldDelegate del;
public void HelloWorld(string name)
{
if(del != null) //有方法注册了
del(name); //委托调用所有注册的方法
}
}
class Program
{
private static void ByEnglish(string name)
{
Console.WriteLine("Hello World By" + name);
}
private static void ByChinese(string name)
{
Console.WriteLine("你好,世界!作者:" + name);
}
static void Main(string[] args)
{
HelloWorldClass hw = new HelloWorldClass();
hw.del = ByEnglish;
hw.del += ByChinese;
hw.HelloWorld("张三");
}
}
输出:
Hello World By 张三
你好,世界!作者:张三
可是我们回头一看,HelloWorldClass里面的东西根本没封装住,很明显del暴露了,不想暴露就得改成private,改了以后几乎就没用了。然后,我们很自然想到private字段、public属性
,例如
public class Number
{
private int num;
public int Num
{
get { return; }
set { num = value; }
}
}
所以,把“事件”掏出来:事件:事件之于委托,类似于属性之于字段。不管事件声明成public还是protected,委托都已经被事件封装成了private的。修改上面代码
public delegate void HelloWorldDelegate(string name);
public class HelloWorldClass
{
public event HelloWorldDelegate del;
public void HelloWorld(string name)
{
del(name); //委托调用所有注册的方法
}
}
class Program
{
private static void ByEnglish(string name)
{
Console.WriteLine("Hello World By" + name);
}
private static void ByChinese(string name)
{
Console.WriteLine("你好,世界!作者:" + name);
}
static void Main(string[] args)
{
HelloWorldClass hw = new HelloWorldClass();
hw.del = ByEnglish; //会出现编译错误
hw.del += ByChinese; //直接+=就好
hw.HelloWorld("张三");
}
}
输出:
你好,世界!作者:张三
其他学习途径
NGUI源码的EventDelegate类:
Delegate callback that Unity can serialize and set via Inspector.
可序列化、通过反射调用委托
[SerializeField]可序列化字段:mTarget、mMethodName、mParameters
UICamera事件响应后通过gameObject.SendMessage(funcName, …)调用相应组件(如:UIButton)中的相应函数(如:OnClick,然后OnClick会调用EventDelegate.Execute(onClick); onClick为之前设置的List<EventDelegate>
)