委托 笔者将其理解为一个特定类型的函数指针集合,增添或者删除就对应 add 和 remove。
可以使用 +=
或者 -=
来增加或者减少委托指向的函数
同一个委托内必须都是同一个类型的函数(相同的参数和相同的返回值)
调用时是按照函数被添加的顺序来执行的
如果是有返回的值的函数,那么调用结束之后只会获得最后一个函数的返回值,所以多播委托最好使用 void
类型的返回值函数
当委托内一个函数都没有时,调用委托会得到空指针异常
在逐一调用委托内的函数时,有任何一个函数抛出异常,那么整个过程都会停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using System;namespace DelegateTest { class Program { delegate void TestDelegate (int a, int b ) ; static void Test1 (int a, int b ) { Console.WriteLine("Test1 function a={0},b={1}" , a, b); } static void Test2 (int a, int b ) { Console.WriteLine("Test2 function a={0},b={1}" , a, b); } static void Main (string [] args ) { TestDelegate dele = new TestDelegate(Test1); dele += Test2; Delegate[] delegates = dele.GetInvocationList(); foreach (Delegate d in delegates) { d.DynamicInvoke(1 , 2 ); } } } }
运行结果:
Test1 function a=1,b=2 Test2 function a=1,b=2
事件 事件是基于委托的,下面是一个小例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using System;namespace DelegateTest { class Program { public delegate void TestDelegate (int a, int b ) ; public TestDelegate dele1; public event TestDelegate dele2; static void Test1 (int a,int b ) { Console.WriteLine("Test1 function a={0},b={1}" ,a,b); } static void Test2 (int a, int b ) { Console.WriteLine("Test2 function a={0},b={1}" ,a,b); } static void Main (string [] args ) { Program p = new Program(); p.dele1 = new TestDelegate(Test1); p.dele1 += Test2; p.dele1(1 , 2 ); p.dele2 = new TestDelegate(Test1); p.dele2 += Test2; p.dele2(2 , 3 ); } } }
运行结果:
Test1 function a=1,b=2 Test2 function a=1,b=2 Test1 function a=2,b=3 Test2 function a=2,b=3
委托与事件的区别
委托可以声明一个成员变量,或者是一个局部变量,但是事件只能作为成员变量,不能作为局部变量。
事件只能在类的内部触发,不能在类的外部触发,可以在类的外部注册。委托可以在外部触发,但最好不要这么用。
事件是一种特殊的委托,或者说是受限制的委托,只能使用 += 或者 -= 操作符,但二者本质上是同一个东西。
event ActionHandler Tick
编译成一个私有的委托实例
使用的时候,委托常用来表示回调,事件用来表示外发的接口。
观察者设计模式 被观察者只有一个,假设我们叫他“楚门”,观察者有很多,当楚门的一些状态发生变化时,观察者也会做出相应的动作。 在游戏中的体现可能是,比如被观察者是“开始按钮”,观察者是资源管理器,场景管理,音乐播放器等,当开始按钮被点击,也就是状态发生了改变,那么这些管理器也需要做出相应的动作。
下面的例子是如果楚门发现了真相,那么所有人都欢呼,如果楚门在这个世界一直到死去,那么所有人都说遗憾,可惜。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using System;using System.Collections.Generic;using System.Text;namespace ChuMenEvent { class ChuMen { public delegate void findTheTruth (float time ) ; public findTheTruth findDelegate; public delegate void die (bool hasFindTheTruth ) ; public die dieDelegate; public void FindTheTruth (float time ) { Console.WriteLine("楚门用时 {0}years 发现了真相" , time); if (findDelegate != null ) { findDelegate(time); } } public void Die (bool hasFindTheTruth ) { Console.WriteLine("ChuMen Died" ); if (dieDelegate != null ) { dieDelegate(hasFindTheTruth); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 using System;using System.Collections.Generic;using System.Text;namespace ChuMenEvent { class Audience { private string identity; private string name; public Audience (string identity,string name ) { this .identity = identity; this .name = name; } public void Celebrate (float time ) { if (time < 20 ) { Console.WriteLine("身为{0}的{1}说:不会吧不会吧,楚门不到20岁就发现了" , identity, name); } else { Console.WriteLine("身为{0}的{1}说:哎呀,被他发现了呢" , identity, name); } } public void Regret (bool hasFindTheTruth ) { if (hasFindTheTruth) { Console.WriteLine("身为{0}的{1}说:这是他的选择" , identity, name); } else { Console.WriteLine("身为{0}的{1}说:噶比(可惜)" , identity, name); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System;namespace ChuMenEvent { class Program { static void Main (string [] args ) { ChuMen chu = new ChuMen(); Audience director = new Audience("director" , "aaa" ); chu.dieDelegate += director.Regret; chu.findDelegate += director.Celebrate; Audience doctor = new Audience("doctor" , "bbb" ); chu.dieDelegate += doctor.Regret; chu.findDelegate += doctor.Celebrate; chu.FindTheTruth(10 ); chu.FindTheTruth(30 ); chu.Die(true ); chu.Die(false ); } } }
这样子的话,后面再加观察者是不需要改动被观察者(楚门)的代码的,只需要新建一个观察者,然后注册委托,就可以了。 但上面的例子,每次都需要手动注册,也非常的麻烦,因为观察者只有一个,所以可以给被观察者的构造函数传递一个观察者的引用,并在构造函数中注册委托。 下面将代码进行优化,并且将委托改为事件的写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using System;using System.Collections.Generic;using System.Text;namespace ChuMenEvent { class ChuMen { public delegate void findTheTruth (float time ) ; public event findTheTruth findDelegate; public delegate void die (bool hasFindTheTruth ) ; public event die dieDelegate; public void FindTheTruth (float time ) { Console.WriteLine("楚门用时 {0}years 发现了真相" , time); if (findDelegate != null ) { findDelegate(time); } } public void Die (bool hasFindTheTruth ) { Console.WriteLine("ChuMen Died" ); if (dieDelegate != null ) { dieDelegate(hasFindTheTruth); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 using System;using System.Collections.Generic;using System.Text;namespace ChuMenEvent { class Audience { private string identity; private string name; public Audience (string identity,string name,ChuMen chu ) { this .identity = identity; this .name = name; chu.dieDelegate += Regret; chu.findDelegate += Celebrate; } public void Celebrate (float time ) { if (time < 20 ) { Console.WriteLine("身为{0}的{1}说:不会吧不会吧,楚门不到20岁就发现了" , identity, name); } else { Console.WriteLine("身为{0}的{1}说:哎呀,被他发现了呢" , identity, name); } } public void Regret (bool hasFindTheTruth ) { if (hasFindTheTruth) { Console.WriteLine("身为{0}的{1}说:这是他的选择" , identity, name); } else { Console.WriteLine("身为{0}的{1}说:噶比(可惜)" , identity, name); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using System;namespace ChuMenEvent { class Program { static void Main (string [] args ) { ChuMen chu = new ChuMen(); Audience director = new Audience("director" , "aaa" ,chu); Audience doctor = new Audience("doctor" , "bbb" ,chu); chu.FindTheTruth(10 ); chu.FindTheTruth(30 ); chu.Die(true ); chu.Die(false ); } } }
运行结果同上。