委托

笔者将其理解为一个特定类型的函数指针集合,增添或者删除就对应 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
// namespace/文件 : DelegateTest
// delegate 的简单用法演示
using System;

namespace DelegateTest
{
class Program
{
// 定义一个无返回值,两个 int 类型的形参的委托
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
{
// 定义一个无返回值,两个 int 类型的形参的委托
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)
{
// TestDelegate event dele = new TestDelegate(Test1);// 错的,因为事件不能作为局部变量
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); // 将被观察者的引用传给构造函数,并在其中注册委托
// chu.dieDelegate += director.Regret; // 这些都不需要了
// chu.findDelegate += director.Celebrate;

Audience doctor = new Audience("doctor", "bbb",chu);
// chu.dieDelegate += doctor.Regret;
// chu.findDelegate += doctor.Celebrate;

// chu.dieDelegate(true); //事件不能在类的外部调用,当这个是委托的时候可以,但最好也不要这么用

chu.FindTheTruth(10);
chu.FindTheTruth(30);
chu.Die(true);
chu.Die(false);
}
}
}

运行结果同上。