目录
提示:本篇中的图片是网上一些学习资料中的截图,以及我对这些知识点的理解与代码编写。对这部分内容来说,我也是初学者,如果读者有什么要补充的,或者有哪些理解有误的,欢迎评论,谢谢!
1.委托
using UnityEngine;
/// <summary>
///
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:委托例子
/// *
/// * Remarks:
///
/// </summary>
//声明委托,可以在类的外面,也可以在类的内部
public delegate X ADelegate<in T, out X>(T t);//有返回值的委托 对应Func委托 in为输入 out输出
public delegate void BDelegate<in T>(T t);//无返回值的委托 对应Action委托
public class DelegateTest : MonoBehaviour
{
public ADelegate<int, string> MyTest;
[ContextMenu("Exe")]
private void Start()
{
MyTest = IntNumber;
TestDelegate(MyTest);//输出 返回值:100
//目标实例(当目方法为静态时,目标实例为null)
Debug.Log(MyTest.Target); //输出 DelegateTest
//实例方法
Debug.Log(MyTest.Method); //输出 System.String IntNumber(Int32)
//(返回值类型)(目标方法)(方法参数)
}
public string IntNumber(int a) => (a * a).ToString();
public void TestDelegate(ADelegate<int, string> aDelegate) => Debug.LogFormat("返回值:{0}", aDelegate?.Invoke(10));
}
using System;
using UnityEngine;
/// <summary>
///
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:委托例子
/// *
/// * Remarks:
///
/// </summary>
public class DelegateTest : MonoBehaviour
{
[ContextMenu("Exe")]
private void Start()
{
TestDelegate(Add);
TestDelegate(Sub);
//-----------委托与接口类似,委托能实现的,接口也能实现,但又有所不同
TestInterface(new Add());
TestInterface(new Sub());
/*
* 对于这个例子这种情况,更适合使用委托,因为接口而言,要多次实现接口,过于麻烦。
*
*/
}
public int Add(int num) => num + num;
public int Sub(int num) => num - num;
public void TestDelegate(Func<int ,int> _func) => Debug.LogFormat("返回值:{0}", _func?.Invoke(10));
public void TestInterface(ICalcNumber calc) => Debug.LogFormat("返回值:{0}", calc.Calc(10));
}
public interface ICalcNumber
{
public int Calc(int num);
}
class Add : ICalcNumber
{
public int Calc(int num) => num + num;
}
class Sub : ICalcNumber
{
public int Calc(int num) => num - num;
}
引用的是同一个方法的入口地址,所以为True
using System;
using UnityEngine;
/// <summary>
///
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:委托例子
/// *
/// * Remarks:
///
/// </summary>
public class DelegateTest : MonoBehaviour
{
/* 委托类型定义的参数 可以比 委托方法定义的参数 更具体,
* 或
* 委托方法定义的参数 可以比 委托类型定义的参数 更抽象,
* string是object的子类,string更具体,object更抽象
* 反之则不行
* 这就叫ContraVariance(逆变)
* 这种转换并不是类型转换!!!
* 比如方法参数是long 委托定义是int 会异常
* 并且只能是同引用类型,或者同值类型之间才可以
* 比如 int->object
*/
public delegate void MyDelegate(string s);
public delegate void MyDelegate1(int i);
[ContextMenu("Exe")]
private void Start()
{
MyDelegate myDelegate = TestDelegate;
myDelegate("XXX");
MyDelegate1 myDelegate1 = TestDelegate; //报错,异常 int(值类型) -> object(引用类型) 属于装箱拆箱操作
myDelegate1(1);
}
public void TestDelegate(object o) => Debug.LogFormat("输出值:{0}", o);
}
委托的协变性和逆变性
协变性是指方法的返回类型可以是从委托的返回类型派生的,也就是说协变性描述的是委托返回类型。逆变性则是指方法获取的参数的类型可以是委托的参数的类型的基类,换言之逆变性描述的是委托的参数类型。
object->string 逆变
string->object :协变
委托的作用
一般调用方法,我们称之为直接调用方法,而委托可以间接调用方法,也就是委托封装了一个或多个方法,方法的类型必须与委托的类型兼容。委托还可以当做方法的参数,传递到某个方法里面去,使用了这个传进来的委托,就会间接的调用委托所封装的方法,形成动态调用方法的代码,并且降低代码的耦合性。
缺点
多播委托容易人为失误,导致将所有封装的方法重置掉。提高了维护成本,且安全性下降。过度使用委托,还会造成内存泄漏。滥用的话,可读性会比较差。
2.事件
事件的五个组成部分
事件的定义:
事件有能力使一个类或对象去通知其他类、对象,做出相应的反应。
事件是基于委托的
两层意思:1.类型兼容。2.存储方法的引用。
1=>事件需要委托类型来做一个约束,这个约束既规定了事件能发送什么样的消息,同时也规定了事件的响应者能收到什么样的消息。这就决定了,事件响应者的事件处理器,必须和这个约束匹配,才能订阅这个事件。
2=>当事件的响应者,向事件的拥有者提供了一个与之匹配的事件处理器之后,这些记录、存储、保存,就只有委托类型的实例能办到。
总结:委托是事件的“底层基础”,事件是委托的“上层建筑”。
语法糖
也可以说是糖衣语法,一种便捷的写法,编译器会帮我们做转换,提高开发效率,性能上也不会有损失。
using System;
using UnityEngine;
/// <summary>
///
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:事件例子=====>顾客点单
/// *
/// * Remarks:
///
///
/// 事件模型的五个组成部分
/// 事件的拥有者: 类
/// 事件: event关键字修饰
/// 事件的响应者: 类
/// 事件处理器: 方法-受到约束的方法
/// 事件的订阅关系: +=
///
/// </summary>
/* 用于事件约束的委托类型,签名应该遵循:事件名+EventHandler
* (当别人看到这个后缀之后,就知道,这个委托是用来约束事件处理器的,不会用于做别的事!!!)
*/
public delegate void OrderEventHandler(Customer _customer, OrderEventArgs _oe);
public class EventEx : MonoBehaviour
{
//顾客类实例化
Customer customer = new Customer();
//服务员类实例化
Waiter waiter = new Waiter();
private void Start()
{
customer.OnOrder += waiter.TakeAction;
customer.Order();
customer.PayTheBill();
}
}
public class Customer
{
public float Bill { get; set; }
public void PayTheBill() => Debug.Log("我应该支付:" + Bill);
#region 事件的完整声明格式
//private OrderEventHandler orderEventHandler;
//public event OrderEventHandler OnOrder
//{
// add { orderEventHandler += value; }//事件添加器
// remove { orderEventHandler -= value; }//事件移除器
//}
//public void Order()
//{
// if (orderEventHandler != null)
// {
// OrderEventArgs orderEventArgs = new OrderEventArgs
// {
// CoffeeName = "摩卡",
// CoffeeSize = "小",
// CoffeePrice = 28
// };
// orderEventHandler(this, orderEventArgs);
// OrderEventArgs orderEventArgs1 = new OrderEventArgs
// {
// CoffeeName = "摩卡",
// CoffeeSize = "小",
// CoffeePrice = 28
// };
// orderEventHandler(this, orderEventArgs1);
// }
//}
#endregion
#region 事件的简略声明格式
public event OrderEventHandler OnOrder;
public void Order()
{
//语法糖衣
if (OnOrder != null)
{
OrderEventArgs orderEventArgs = new OrderEventArgs
{
CoffeeName = "摩卡",
CoffeeSize = "小",
CoffeePrice = 28
};
OnOrder(this, orderEventArgs);
OrderEventArgs orderEventArgs1 = new OrderEventArgs
{
CoffeeName = "摩卡",
CoffeeSize = "小",
CoffeePrice = 28
};
OnOrder(this, orderEventArgs1);
}
}
#endregion
}
public class OrderEventArgs : EventArgs
{
public string CoffeeName { get; set; }
public string CoffeeSize { get; set; }
public float CoffeePrice { get; set; }
}
public class Waiter
{
//事件处理器
internal void TakeAction(Customer _customer, OrderEventArgs _oe)
{
float finaPrice = 0;
switch (_oe.CoffeeSize)
{
case "小":
finaPrice = _oe.CoffeePrice;
break;
case "中":
finaPrice = _oe.CoffeePrice + 3;
break;
case "大":
finaPrice = _oe.CoffeePrice + 6;
break;
default:
break;
}
_customer.Bill += finaPrice;
}
}
事件与委托关系的总结
委托是事件的基础,事件是委托的包装器,类似于字段与属性之间的关系,属性包装着字段,通过一系列的逻辑来保护字段。事件也是如此,起到保护委托类型,以免被外界滥用的作用。只能通过+=和-=来添加、移除事件处理器,不能直接外部访问或调用。
注意:事件并不是委托类型的字段!!!
使用System类库下的通用委托
using System;
using UnityEngine;
/// <summary>
///
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:事件例子=====>顾客点单
/// *
/// * Remarks:
///
///
/// 事件模型的五个组成部分
/// 事件的拥有者: 类
/// 事件: event关键字修饰
/// 事件的响应者: 类
/// 事件处理器: 方法-受到约束的方法
/// 事件的订阅关系: +=
///
/// </summary>
public class EventEx : MonoBehaviour
{
//顾客类实例化
Customer customer = new Customer();
//服务员类实例化
Waiter waiter = new Waiter();
private void Start()
{
customer.OnOrder += waiter.TakeAction;
customer.Order("摩卡", "中", 30);
customer.PayTheBill();
}
}
public class Customer
{
public float Bill { get; set; }
public void PayTheBill() => Debug.Log("我应该支付:" + Bill);
//EventHandler是System类库下提供的通用委托
public event EventHandler OnOrder;
public void Order(string _name, string _size, float _price)
{
//语法糖衣
if (OnOrder != null)
{
OrderEventArgs orderEventArgs = new OrderEventArgs
{
CoffeeName = _name,
CoffeeSize = _size,
CoffeePrice = _price
};
OnOrder(this, orderEventArgs);//语法糖衣
}
}
}
public class OrderEventArgs : EventArgs
{
public string CoffeeName { get; set; }
public string CoffeeSize { get; set; }
public float CoffeePrice { get; set; }
}
public class Waiter
{
//事件处理器
internal void TakeAction(object o, EventArgs e)
{
float finaPrice = 0;
Customer customer = o as Customer;
OrderEventArgs oe = e as OrderEventArgs;
switch (oe.CoffeeSize)
{
case "小":
finaPrice = oe.CoffeePrice;
break;
case "中":
finaPrice = oe.CoffeePrice + 3;
break;
case "大":
finaPrice = oe.CoffeePrice + 6;
break;
default:
break;
}
customer.Bill += finaPrice;
}
}
3.Lambda表达式
public class LambdaTest : MonoBehaviour
{
public delegate int MyDelegate(int t);
[ContextMenu("Exe")]
private void Start()
{
/** 实际上,编译器会通过编写一个私有方法来解析这个lambda表达式
* 然后把表达式的代码移动到这个方法里
**/
MyDelegate my = (x) => { return x * x; };//小括号可以省略,因为类型可以推断出来
MyDelegate my1 = (x) => x * x;//小括号可以省略,因为类型可以推断出来
int num = my(2);
Debug.Log(num);
}
}
这个闭包,之前有遇到过,比如需要给多个按钮添加监听器:
using UnityEngine;
using UnityEngine.UI;
/// <summary>
///
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:Lambda表达式例子
/// *
/// * Remarks:
///
/// </summary>
public class LambdaTest : MonoBehaviour
{
public Button[] buttons;
[ContextMenu("Exe")]
private void Start()
{
for (int i = 0; i < buttons.Length; i++)
{
int x = i;//去掉这句的话,每次按下按钮,只会输出最后一个
buttons[i].onClick.AddListener(() => { Debug.Log("输出:" + x); });
}
}
}
public class LambdaTest : MonoBehaviour
{
public delegate int MyDelegate(int num);
MyDelegate myDelegate;
[ContextMenu("Exe")]
private void Start()
{
myDelegate = delegate (int x) { return x * x; };//匿名函数
myDelegate = x => { return x * x; };//lambda表达式
myDelegate = x => x * x;//lambda表达式
myDelegate(2);
}
}