​ 这篇文章是我学习 C# 语言过程中对委托(delegate)和事件(event)的一些简单理解和记录。刚开始接触这两个概念时觉得有点抽象,不太明白它们和普通方法调用有什么区别,也搞不清它们之间的关系。

​ 后来通过查资料、写了一些小例子,慢慢有了一些初步的认识。这里整理一下我学到的内容,包括基本语法、调用过程、委托和事件的区别,以及我自己的理解,方便以后复习,也希望对其他正在学习的人有一点点参考价值

委托(Delegate)

基本定义与使用

​ 委托其实就是方法的引用,本质上就是一种类型安全的函数指针。我们可以通过委托来把某个方法赋值给一个变量,然后在合适的时候调用这个变量,相当于间接调用方法。

​ 下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
// 定义一个返回 int、参数为 int 的委托类型
delegate int NumberChanger(int n);

// 定义几个与委托签名一致的方法
int AddNum(int n) { num += n; return num; }
int MultNum(int n) { num *= n; return num; }

// 使用委托
NumberChanger nc;
nc = AddNum;
Console.WriteLine(nc(10)); // 调用 AddNum 方法

​ 这里我们定义了一个NumberChanger委托类型的变量nc并且将一个方法赋值给他,因为我们的签名一致(传入值和返回值的类型都一致),所以我们在传入10的时候会逐步调用委托中的方法,例子中会执行AddNum。

疑惑:为什么要用委托而不直接使用方法?

​ 这里我一开始觉得好像有点多余,因为我明明可以直接使用这个方法对吧,为什么要多此一举用这个委托去添加呢。

​ 简要来说就是委托的作用是“把方法当作参数或变量来使用”,它让程序具有更高的灵活性、扩展性和解耦性。

​ 举个例子:

普通方法调用(写死了):

1
2
3
4
5
6
7
8
9
public class Button
{
public void Click()
{
ShowPanel(); // 必须提前写死逻辑,不能动态改变
}

void ShowPanel() { Console.WriteLine("打开面板"); }
}

这种方式耦合死了,只能点击就执行 ShowPanel,不能变通。

使用委托(可以动态绑定):

1
2
3
4
5
6
7
8
9
public class Button
{
public Action OnClick; // 内部定义委托变量(Action 是系统内置的无参委托)

public void Click()
{
OnClick?.Invoke(); // 运行时才决定执行什么
}
}

外部就可以自由绑定行为:

1
2
3
button.OnClick = ShowPanel;
button.OnClick += PlaySound;
button.Click(); // 同时调用 ShowPanel 和 PlaySound

​ 这里注意到委托是可以通过+=来为其添加方法的,也就是说可以委托多个方法。我可以在外部去订阅这个委托,从而能够灵活的决定我要执行什么方法;

事件(Event)

基本理解

​ 事件可以理解为“更安全、更受控的委托”。它是委托的封装,只能在声明它的类中触发(Invoke),外部只能 += 添加,不能直接调用,这就确保了订阅机制的封闭性。

​ 举个例子:

先定义一个事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声明一个事件用的委托类型
public delegate void NotifyEventHandler();

public class Publisher
{
// 声明一个事件,使用上面的委托类型
public event NotifyEventHandler OnProcessCompleted;

public void DoSomething()
{
Console.WriteLine("开始处理任务...");

// 处理完成后触发事件,通知订阅者
OnProcessCompleted?.Invoke();
}
}

使用事件的类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Subscriber
{
public void Respond()
{
Console.WriteLine("订阅者收到完成通知!");
}

public void Subscribe(Publisher pub)
{
pub.OnProcessCompleted += Respond; // 订阅事件
}
}

具体使用

1
2
3
4
5
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();

subscriber.Subscribe(publisher); // 订阅
publisher.DoSomething(); // 触发事件,输出响应

​ 看起来和委托很像是不是,确实很像,区别就在于事件的封闭性更高,外部只能对其增改不能赋值,而且他是一个private的,也就是说外部不能去触发这个事件。

项目 委托 事件
是否可以被外部触发 ✅ 可以 ❌ 不能(更安全)
外部是否可以覆盖 ✅ = 可重写 ❌ 只能 += / -=
是否适合做通知 可以,但不推荐 ✅ 专门为“通知订阅者”设计
是否解耦 部分解耦 ✅ 更符合发布者/订阅者结构

​ 用一句更加C#的话来说就是:事件是”只能订阅,不能随便调用“的安全委托。——事件的触发权限是受限制的,只有定义它的类内部可以调用 .Invoke(),外部只能订阅,不能主动调用,这就保证了调用逻辑的封闭性和安全性。

用途

​ 在游戏中主要用于模块间的消息通知、游戏逻辑解耦、监听玩家行为或系统状态变化。

​ 我自己目前接触和理解的,比如我写一个gameover的一个方法,我需要在游戏结束的时候让场上发生一些变化,比如怪物播放胜利动画,场景变换等等;

​ 这个时候我可以用事件去管理这个部分,例如:

1
2
3
4
5
6
7
8
9
10
// 发布者(比如 GameManager)
public class GameManager : MonoBehaviour
{
public static event Action OnGameOver;

public void TriggerGameOver()
{
OnGameOver?.Invoke(); // 内部触发事件
}
}

​ 这个时候委托和事件的优势就体现出来了,我场上的物体是在动态变化的,比如怪物,那他死了之后就不能继续播放动画了对吧,生成了之后就加入这个需要播放的列表。这个时候我就可以在OnEnable和DisAble中去写,在启用的时候将播放动画的方法订阅到这个事件,结束的时候取消订阅。

​ 哎,就很好用。