前言

因为接下来想要整理一下游戏里面的事件模块,而当前项目的业务开发都是通过 Lua 进行的,有一阵子没有写 C#了,回想 C#的委托和事件,有些细节一时模糊了起来,所以打算干脆把相关的知识点整理一下,就当做复习了。

委托

什么是委托

无论是委托还是事件官网都没有一个简单直白的定义,只能说理解万岁。

委托:用于封装方法的一种引用类型,类似于 C++的指针,但其类型安全可靠。通过封装多个方法到一个变量中,调用此变量就可以调用所有绑定的方法。你也可以将它理解性地看做“方法的容器”。

委托的写法

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
/// ---- 普通写法------
// 1.定义委托类型,这一步定义的是委托的类型,并不是委托的实例
public delegate void MyEventHandler();
//2.定义委托变量
public MyEventHandler MyEvent;

//泛式委托
public delegate void MyEventHandler<in T1, in T2>(T1 arg1, T2 arg2);
public MyEventHandler<T1, T2> MyEvent;

//带返回值的委托,若多个返回值发生,保留最后注册的那个
public delegate TResult MyEventHandler<in T1, out TResult>(T1 arg);
public MyEventHandler<T1, TResult> MyEvent;



// ---- 匿名方法写法,省去了定义类型的步骤------
// 无返回值
public Action MyEventHandler;
public Action<in T> MyEventHandler;

//有返回值
public Func<int> MyEventHandler;
public Func<in T, out TResult> MyEventHandler;

委托的作用

熟练使用委托可以提高代码的拓展性,优化代码结构。

这种拓展性的应用场景主要体现在于两个方面:

  1. 代码结构不变的情况,执行处需要在不同的环境下给委托赋值不同的方法,就可以执行不同的功能。
  2. 一个功能的触发,涉及到多个方法的执行。

先说第一种应用,这里以 GameFramework 框架中的写法举例,在 GameFramework 框架中(后面成为 GF)可以分为 GF 层和 UGF 层,GF 层的逻辑是功能的核心逻辑,而 UGF 层则是 GF 在 Unity 的应用。在 GF 的 UI 模块,一个页面的打开/关闭的完成正如其他 UI 框架很可能执行的逻辑一样,通过事件系统派发出了一个 UI 页面打开/关闭的事件。而为了保证 GF 层的结构和功能不变动,提供框架使用者可以根据情况自由拓展或者改变这样的操作。GF 层打开/关闭页面的 Internal 逻辑执行完成后,并没有直接调用事件模块的接口,而是执行了一个委托。 在 UGF 层,使用者就可以通过绑定自己的方法到这个委托上,来实现自己的页面打开/关闭后的处理逻辑,而不会完全不需要接触和改动 GF 层的代码。让我们直接看一下代码写法,

GF层的UIManager:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

//GF层,UIManager.cs

//定义委托,这里你可能觉得诡异,委托的定义居然是使用Private关键字,这个我们在后面事件的时候进行说明
private EventHandler<OpenUIFormSuccessEventArgs> m_OpenUIFormSuccessEventHandler;
private EventHandler<OpenUIFormFailureEventArgs> m_OpenUIFormFailureEventHandler;

//提供绑定接口,这里通过事件进行绑定,之所以不直接用委托,这个后面说明。
/// <summary>
/// 打开界面成功事件。
/// </summary>
public event EventHandler<OpenUIFormSuccessEventArgs> OpenUIFormSuccess
{
add
{
m_OpenUIFormSuccessEventHandler += value;
}
remove
{
m_OpenUIFormSuccessEventHandler -= value;
}
}

/// <summary>
/// 打开界面失败事件。
/// </summary>
public event EventHandler<OpenUIFormFailureEventArgs> OpenUIFormFailure
{
add
{
m_OpenUIFormFailureEventHandler += value;
}
remove
{
m_OpenUIFormFailureEventHandler -= value;
}
}




//页面打开或关闭执行对应的委托

/// <summary>
/// 打开页面
/// </summary>
private void InternalOpenUIForm()
{
//.................省略页面打开成功逻辑......................

//页面打开逻辑后执行完成回调
if (m_OpenUIFormSuccessEventHandler != null)
{
OpenUIFormSuccessEventArgs openUIFormSuccessEventArgs = OpenUIFormSuccessEventArgs.Create(uiForm, duration, userData);
m_OpenUIFormSuccessEventHandler(this, openUIFormSuccessEventArgs);
ReferencePool.Release(openUIFormSuccessEventArgs);
}
}

/// <summary>
/// 关闭界面。
/// </summary>
public void CloseUIForm()
{
//.................省略页面关闭成功逻辑......................

//页面关闭逻辑后执行完成回调
if (m_CloseUIFormCompleteEventHandler != null)
{
CloseUIFormCompleteEventArgs closeUIFormCompleteEventArgs = CloseUIFormCompleteEventArgs.Create(uiForm.SerialId, uiForm.UIFormAssetName, uiGroup, userData);
m_CloseUIFormCompleteEventHandler(this, closeUIFormCompleteEventArgs);
ReferencePool.Release(closeUIFormCompleteEventArgs);
}

}

UGF层,UIComponent中的使用形式:

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

// --------绑定方法--------

/// <summary>
/// 游戏框架组件初始化。
/// </summary>
protected override void Awake()
{
base.Awake();
//获取GF层的UIManager
m_UIManager = GameFrameworkEntry.GetModule<IUIManager>();
if (m_UIManager == null)
{
Log.Fatal("UI manager is invalid.");
return;
}
// 判断绑定需求,如果需要,则绑定事件,这一步使用者就可以根据自己的需求进行自定义功能添加了
if (m_EnableOpenUIFormSuccessEvent)
{
m_UIManager.OpenUIFormSuccess += OnOpenUIFormSuccess;
}

m_UIManager.OpenUIFormFailure += OnOpenUIFormFailure;
}


// --------添加的方法--------

//通过事件模块,派发UI打开/关闭的事件
private void OnOpenUIFormSuccess(object sender, GameFramework.UI.OpenUIFormSuccessEventArgs e)
{
m_EventComponent.Fire(this, OpenUIFormSuccessEventArgs.Create(e));
}

private void OnOpenUIFormFailure(object sender, GameFramework.UI.OpenUIFormFailureEventArgs e)
{
Log.Warning("Open UI form failure, asset name '{0}', UI group name '{1}', pause covered UI form '{2}', error message '{3}'.", e.UIFormAssetName, e.UIGroupName, e.PauseCoveredUIForm.ToString(), e.ErrorMessage);
if (m_EnableOpenUIFormFailureEvent)
{
m_EventComponent.Fire(this, OpenUIFormFailureEventArgs.Create(e));
}
}



再说第二个应用,通过向委托里面添加多个方法,可以实现一个功能的触发,涉及到多个方法的执行。这个从应用上来说其实很好理解,比如游戏中,我使用了恢复血量的药瓶这个道具,那么我的生命值就会进行增加,以及我的敌人会发现我在喝药,并立马进行攻击(bushi)。这种情况其实就是利用一个功能的触发,涉及到多个方法的执行这样的一个原理做到的,这样的功能实现也是建立在委托的支持上的。

委托的缺点

封装的矛盾。面向对象设计,讲究的是对象的封装。委托可以很好把方法进行封装,这是毋庸置疑的,但是委托同时还可以被外部直接进行赋值,一旦赋值就会失去原来绑定好的内容,这严重破坏了委托的封装性。
而如果把委托设置为Private的话,又会让其完全失去原来的作用。这也是上面GF里面之所以声明委托为Private,并通过事件进行封装对外赋值接口的设计原因。
这种情况,就要引入事件这个概念了。

事件

事件是建立在对委托的语言支持之上的。通过给委托变量加上限制关键词 “event”,就可以声明一个事件。可以理解为声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。

1
2
3
public delegate void MyEventHandler(object sender, MyEventArgs e);

public event MyEventHandler MyEvent;

这样声明的事件,相较于普通的委托变量,有如下几个限制:

  1. 其赋值只能在事件发布者内部进行,外部不能直接赋值。
  2. 其执行只能在事件发布者内部进行,外部不能直接调用。

通过这种事件的形式,我们可以获取比委托更好的封装性。
利用委托的第二个应用,配合上事件,达成的类似喝血瓶的例子这种模式,进行抽象后就是所谓的 “观察者模式”,它是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新,是一种 松耦合 的设计模式。

.NET框架中的委托与事件

.NET Framework中有着固定的规范:

  • 命名空间System下存在委托原型定义
    1
    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
  • 委托类型和变量的名称和都应该以EventHandler结束
  • 事件的命名为委托去掉EventHandler之后剩余的部分
  • 继承自 EventArgs 的类型应该以EventArgs 结尾

看到这个原型定义,可以注意到传参的两个点,一个是传参包含了object sender,另一个是 EventArgs 的类型。sender传递的是事件的发布者,EventArgs 则是事件的附加信息,包含监视者需要用到的信息。
EventArgs是存在System命名空间下的一个类,通过继承EventArgs类,可以定义并写入任何自己想要的数据传递给监视者,这一点从上面GF的实例中体现出来的同时,还可以看到对于传递的EventArgs类型,它还拓展进了引用池来保证回收利用。

参考文档