C#委托
介绍
在C#里委托是一种类型安全的函数指针,类似于C/C++中的函数指针,相当于C#的引用之于C/C++的指针一样,提供类似指针的操作,但是更加安全。
且委托允许指向多个函数,即多播。
语法
委托的声明。
1
2
3
4
|
//委托声明
public delegate 返回类型 委托名(参数列表);
//如下
public delegate int MathOperation(int x, int y);
|
在声明委托之后,通过new来实例化一个委托。之后就可以通过委托来调用其引用的函数。
1
2
3
4
5
6
7
8
9
10
|
//委托声明
public delegate int MathOperation(int x, int y);
public int add(int x, int y)
{
return x + y;
}
//实例化时,构造参数不能为空。
MathOperation mo = new MathOperation(add);
int res = mo(3, 5);
//res = 8
|
相同类型的委托可以进行合并。让所有委托时调用多个函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//续之前代码
int a = 1;
public delegate void ChangeA(int b);
public void add(int b)
{
a += b;
}
public void multi(int b)
{
a *= b
}
ChangeA ca;
ChangeA ca1 = new ChangeA(add);
ChangeA ca2 = new ChangeA(multi);
ca = ca1;
ca += ca2;
ca(5);
//a = 30;
|
委托还可以通过-=来移除引用的函数。
委托可以作为函数参数。
1
2
3
4
5
|
//如
public int testFunc(int a, MathOperation mo)
{
return mo(a, a);
}
|
其他
C#提供了几种常见的泛型委托类型,可以使用这些直接实例化常见的委托。
1
2
3
4
5
6
7
8
|
//Action表示不返回值的函数,最多接收16个参数。
public Action<string, int> action = func;
//Action表示有返回值的函数,最多接收16个参数。
//只有一个泛型参数时,代表返回值类型。
//多个时最后一个为返回类型,其他为输入类型。
public Func<int, int, int> addFunc = func;
//Predicate表示返回bool值的函数,泛型参数为输入类型。
public Predicate<int, int> isTrue = func;
|
另外C#匿名函数的使用也会使用delegate关键字。或者类似C++11Lambda表达式那样更简洁的写法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//在需要函数(即委托)作为参数时,可以直接使用匿名函数。
public void test(Func<int, int> func)
{
console.writeLine(func(2));
}
//delegate关键字创建匿名函数。
test(delegate(int a){return a * a;});
test(delegate(int a){return a + 5;});
//lambda表达式创建匿名函数
test((int a) => {return a * a;});
test((int a) => a * a);
//输出:
//4
//7
|
但是因为匿名函数是没有名字的函数,所以如果在委托中添加一个匿名函数,是没办法减掉这个匿名函数的。
如果匿名函数中使用了外部函数的变量,可能导致该变量的生命周期延长,产生异常结果。可以使用static声明不捕获外部变量的匿名函数。
C#事件
介绍
知道了委托的用处,第一反应就是可以用于游戏中的事件处理。
但是同时会发现使用委托实现观察者模式(又名发布-订阅模式,我个人更喜欢这个名字),特别是一个发布,多个订阅时,会发现委托不能直接添加函数,添加订阅时需要创建多个委托后进行合并,比较麻烦。其实C#已经提供了实现发布-订阅模式,且使用方便的事件类型。
C#事件就是对委托进行了封装,来实现发布-订阅模式的一个类型。
语法
1
2
3
4
5
6
7
8
9
10
|
//声明事件之前,需要声明该事件的委托类型(即该事件发生时调用什么样的函数)。
public delegate void testHandler(string args);
//再基于声明的委托定义事件。
public event testHandler Ontest;
//事件的触发,事件只能在定义事件类里面触发即事件由发布者发布。
//?用于检查事件中委托引用的函数是否为空,保证只要有订阅者才触发。
Ontest?.Invoke(s);
//事件的订阅和取消订阅。提供-=,+=
Ontest += funcName;
Ontest -= funcName;
|
完整的发布-订阅模式实现
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
|
//事件的委托类型经常命名为XXHandler
public delegate void testHandler(string args);
//发布者
public class Pulisher
{
public event testHandler Ontest;
public void OntestInvoke(string a)
{
Ontest?.Invoke(s);
}
//模拟事件的触发
public void StartTest()
{
OntestInvoke("sss");
}
}
//订阅者
public class subscriber
{
public void subscribe(Pulisher pulisher)
{
pulisher.Ontest += react;
}
//订阅者对事件发生做出反应
private void react(string s)
{
Console.WriteLine(s);
}
}
static void main(sting[] args)
{
Pulisher p = new Pulisher();
subscriber s = new subscriber();
s.subscribe(p);
subscriber s1 = new subscriber();
s1.subscribe(p);
//多个订阅者
//事件触发
p.StartTest();
//输出:
//sss
//sss
//做出了反应。
}
|
unityEvent
发布-订阅模式是游戏中常用的设计模式,robloxstudio和godot都提供了在编辑器配置事件的方法,unity也有相关功能。
unityEvent是unity提供的对事件操作的api,unityevent允许在inspector直接添加或者删除订阅者函数,简化了代码的书写。
1
2
3
4
5
6
7
8
9
10
11
|
using UnityEngine.Events;
public class tester : MonoBehavior
{
public UnityEvent unityEvent;
//模拟事件触发
public void Invoke()
{
unityEvent?.Invoke();
}
}
|
这样就可以在inspector看见类似button的onclick的事件ui。

想要带有参数需要重载unityEvent<T0>,unityEvent<T0, T1> 抽象类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
using UnityEngine.Events;
[Serializable]
public class FloatEvent : UnityEvent<float>{};
public class tester : MonoBehavior
{
public UnityEvent unityEvent;
public FloatEvent floatEvent;
//模拟事件触发
public void Invoke()
{
unityEvent?.Invoke();
}
public void FloatInvoke()
{
FloatEvent?.Invoke(0.5f);
}
}
|
也可以在代码中添加和删除监听
1
2
|
unityEvent.AddListener(funcName);
unityEvent.RemoveListener(funcName);
|
相关链接:
委托的菜鸟教程相关页面。
事件的菜鸟教程相关页面。
匿名函数和lambda菜鸟教程相关页面。
CSDN,匿名函数和lambda。
CSDNunity事件。
Unity官方,UnityEvent相关文档手册部分和脚本API部分。