Featured image of post Csharp中的委托,事件和Unity中的特殊事件类型

Csharp中的委托,事件和Unity中的特殊事件类型

之前经常搞混委托,事件和unity事件,记录一下。

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
ca -= multi;

委托可以作为函数参数。

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。
alt text
想要带有参数需要重载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部分

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计