委托

  • 可以看做函数指针,存放函数的容器的变量
  • 这个指针存放的函数参数返回值一致,函数模板
  • 委托类型在作为参数传递时,相当于直接将参数返回值一样的函数的返回值传进去
  • 因此可以用在多个函数功能类似的情况下
  • 先写一个公用的功能,调用时直接传入委托
  • 不同的地方拎出来一个一个写
  • 符合函数模板可以通过+=添加给委托,委托执行时注册的函数都会执行
  • 由于委托可以被其他委托赋值,存在被修改的风险
  • 将函数作为参数传递时,在另外一个类触发该委托返回值时,可以直接触发这个函数,只关心该函数,而不需要知道传来的类

delegate创建

1
2
public delegate void TestDelegate();
public delegate bool TestBoolDelegate(int i);

Action创建

  • delegate 被封装好的一种简写
Action 无参无构造函数的委托
Action, Action 有参数委托
1
2
public Action testAction;
public Action<int ,float> testInFloatAction;

Func创建

  • 具有返回值的委托
Func 返回值为T类型的委托
Func, Func 第一个类型返回值,后面都是参数
1
2
public Func<bool> testFunc;
public Func<int, bool> testIntBoolFunc;

创建和初始化

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
public class Testing : MonoBehaviour
{
// delegate创建
public delegate void TestDelegate();
public delegate bool TestBoolDelegate(int i);

private TestDelegate testDelegateFunction;
private TestBoolDelegate testBoolDelegateFunction;

// Action创建
public Action testAction;
public Action<int ,float> testInFloatAction;

// Func创建
public Func<bool> testFunc;
public Func<int, bool> testIntBoolFunc;

private void Start()
{
// function直接实名赋值
testDelegateFunction = MyTestDelegateFunction;
testDelegateFunction += MySecondTestDelegateFunction;
testDelegateFunction();

// 其他创建delegate的方法
testDelegateFunction = delegate(){ Debig.log(" ");};

// lambda表达式
testDelegateFunction = () => { Debig.log(" "); };
testDelegateFunction = (int i) => { return i < 5; };

testDelegateFunction += () => { Debig.log(" "); };

// Action 赋值
testInFloatAction = (int i, float f) => { Debig.log(" "); };

// Func
testFunc = () => false;
testIntBoolFunc = (int i) => { return i < 5; };

testBoolDelegateFunction = MyThirdTestDelegateFunction;
MyThirdTestDelegateFunction();
}

private void MyTestDelegateFunction()
{
}
private void MySecondTestDelegateFunction()
{
}
private bool MyThirdTestDelegateFunction(int i)
{
return i;
}
}

事件

  • 一种特殊委托,不同于委托可以被赋值,事件赋值权限为 private,安全性提升
  • 用于类之间的通信
  • 一个事件发生后,通知其他类进行的同类操作
    • 通过 OnEnable += 将方法订阅到事件,在发生事件时可以调用方法
    • 通过 OnDisable -= 将方法退订,在事件销毁时停用方法
  • 否则要在每个类中 Find 关联的其他类
  • 这个同类操作一般用接口实现

  • 注册方法:event<参数>.addlinster(发生的,要被触发的)后面参数就是前面

  • 扩展可以为事件们创建枚举,event<参数>.addlinster(事件枚举,触发函数)后面参数就是前面

EventHandler

delegate

Action

UnityEvent

创建和初始化

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
# Scripts01 

using UnityEngine.Events;

public class TestingEvents:Monobehaviour
{
// EventHandler
public event EventHandler<OnSpacePressedEventArgs> OnSpacePressed;
public class OnSpacePressedEventArgs : EventArgs
{
public int count;
}

// delegate
public event TestEventDelegate OnFloatEvent;
public delegate void TestEventDelegate(float f);

// Action
public event Action<bool, int> OnActionEvent;

// UnityEvent:直接在Inspector窗口拖
public UnityEvent OnUnityEvent;

private int Count;

void Start()
{
}

void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
/*
if (OnSpacePressed != null)
{
OnSpacePressed();
}
*/
// Or
// 实际上事件并不知道这个函数存不存在
Count++;
OnSpacePressed?.Invoke(this, new OnSpacePressedEventArgs {count = Count};
OnFloatEvent?.(0.5f);
OnActionEvent?.(true, 56);
OnUnityEvent?.();
}
}
}

#Scripts02
public class TestEventSub: MonoBehaviour
{
void Start()
{
TestEvent testEvents = GetComponent<TestEvents>();
testEvents.OnSpacePressed += Test_OnSpacePressed;
testEvents.OnFloatEvent += Test_OnFloatEvent;
testEvents.OnActionEvent += Test_OnActionEvent;
}
void Test_OnSpacePressed(object sender, testEvents.OnSpacePressedEventArgs e)
{
Debug.log("Space" + e.count);
}
void Test_OnFloatEvent(float f)
{
Debug.log("Float:" + f);
}
void Test_OnActionEvent(bool args1, int args2)
{
Debug.log(args1 + args2);
}
}

闭包

  • 一个匿名函数引用到外部变量,就会造成闭包。C#为了实现这一点会生成一个匿名类来保存用到的外部变量,因此当调用这个闭包时,首先会实例化一个副本,同时会采用外部变量实际值来初始化这个副本,最终致使会在上分配内存。也就是说闭包就一定会产生内存分配(产生GC)。
  • 实现:内函数作为外函数的返回值,再将外函数赋给一个变量,再使用这个变量调用,这样就直接调用内函数而没有经过外函数
    • 有函数嵌套
    • 内部函数引用外部作用域的变量参数(C#的内部函数就是lamda表达式)
    • 外部函数的返回值是函数
    • 创建一个对象函数,让其一直在,而不是外部全局变量

使用匿名方法,最好将需要的外部变量作为参数传递,避免直接使用产生闭包

踩坑

  • 这里我写出的问题是在包含匿名函数的for循环外定义了一个变量,每次for循环后该变量自增,每次执行匿名函数都会调用该变量,结果每个匿名函数调用的变量都一样了,因为for的循环过程中每次执行的匿名函数共享了这个变量

  • 解决方法是,在每次for循环定义临时变量(或者在for循环内处理该变量),使每个匿名函数都调用的自己的变量

From:https://blog.csdn.net/yinfourever/article/details/122583812,https://blog.csdn.net/deatharthas/article/details/106606754?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-8-106606754-blog-105412263.235