首先先来说一下匿名方法,通常情况下,在使用委托的时候,往往需要声明相应的方法,这些方法需要符合返回值类型,参数都与委托类型定义的方法原型相同的条件,但是这样做的话,必须要创建与委托方法原型对应的新的方法,这样做起来可能会使代码变得十分臃肿,匿名方法应运而生。
下面先来看一个例子
void Start()
{
Action<string> tellMeYourName = delegate (string name)
{
string info = "My Name Is";
Debug.Log(info + name);
};
Action<int> tellMeYourAge = delegate (int age)
{
string info = "My Age Is";
Debug.Log(info + age);
};
tellMeYourName("李晨");
tellMeYourAge(15);
}
我们暂且先不来解视这段代码,首先我们需要知道的是,在日常使用中,常见的 有两个泛型委托类型,分别是
1.Action
public delegate void Action<in T>(T obj);
...
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
从一个参数到多个参数的表现形式
2.Func
public delegate TResult Func<in T, out TResult>(T arg);
。。。
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
从一个参数到多个参数的表达形式也都具有,但是从两者定义的形式上来看,Action是没有返回值的,因此,它适用于任何没有返回值的方法,而Func 不同,他的定义是相对于Action而言的,Action是没有返回值的方法委托,而Func是存在返回值的委托,返回值的类型由泛型中定义的类型进行约束,如
Func<int, int> func = delegate (int age)
{
return age;
};
在这里返回值的类型就是整型;
对这两个泛型有了了解之后,看一下上面的使用了匿名方法的代码,首先使用了delegate关建字后,如果由参数,则是参数部分,没有的话对应的是代码块定义对委托实例的操作,通过这段代码,匿名函数也可以做到普通函数可以做到的事情,匿名方法的实现同样要归功为编译器,在编译器中,隐藏了很多复杂的操作,首先,在CIL代码中,编译器为源代码中的每一个匿名方法都创建了一个对应的方法,并且蚕蛹了和创建委托实例相同的操作,将创建的方法作为回调函数由委托实例进行包装,然后在使用的时候进行调用;
有人很奇怪了,上面的代码其实看上去依然很臃肿,实际上匿名方法的功能是简化代码,而简化代码的方式是 将匿名方法作为另一个方法的参数使用,这样才可以体现出他的价值,如下
void Start()
{
list.Add(new Vector3(0, 0, 1));
list.Add(new Vector3(1, 0, 0));
list.Add(new Vector3(0, 1, 0));
//ForEach是可获取Action<T>为参数的方法
list.ForEach(delegate (Vector3 obj) {
Debug.Log(obj.normalized.ToString());});
}
这样是不是显得比较简洁了呢?
当然,除了像Action这种返回类型为void的委托类型之外,上面还说了一种带有返回类型的委托类型,所以代码可以修改成这样
void Start()
{
Func<string,string> tellMeYourName = delegate (string name)
{
string info = "My Name Is";
return info + name;
};
Func<int,int> tellMeYourAge = delegate (int age)
{
return age - 1;
};
Debug.Log( tellMeYourName("李晨"));
Debug.Log(tellMeYourAge(15));
}
在匿名方法中,使用了return来返回指定类型的值,并且将匿名方法赋值给了Func委托类型的实例,图片是运行后的结果
当然,除了前面提过的两种委托类型外,还有一些在实际开发中可能会用到的预置的委托类型,例如返回值为bool的委托类型Predicate 签名如下
public delegate bool Predicate<in T>(T obj);
这种委托类型常常在过滤和匹配目标的时候发挥作用
List<int> vs = new List<int>();
vs.Add(2);
vs.Add(3);
vs.Add(4);
Predicate<int> predicate = delegate (int a)
{
return a > 0;
};
foreach(int a in vs)
{
if(predicate(a))
{
Debug.Log(a);
}
}
当然也可以换成枚举值,判断是怪物,NPC、Plater,然后符合的话执行相应的逻辑
现实开发中,还会碰到对列表按照某一种条件进行排序的情况,如按照英雄的熟练度排序,战斗力排序等,按照要求排序也是游戏系统开发的最常见的需求之一,下面我们使用委托和匿名方法来方便的实现排序功能
c#中提供了Comparison委托类型
签名如下
public delegate int Comparison(in T)(T x, T Y)
由于Comparison委托类型是IComparison接口的类型版本分析其参数
参数 | 作用 |
---|---|
x | 要比较的第一个对象 |
y | 要比较的第二个对象 |
Comparison的返回值
返回值 | 含义 |
---|---|
小于0 | x小于y |
等于0 | x等于y |
– | – |
大于0 | x大于y |
使用
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ActionTest : MonoBehaviour
{
public class hero
{
public int id;
public int hp;
public hero(int id,int Hp)
{
this.id = id;
this.hp = Hp;
}
}
// Start is called before the first frame update
void Start()
{
List<hero> vs = new List<hero>();
vs.Add(new hero(1,1));
vs.Add(new hero(0,0));
vs.Add(new hero(-1,-1));
//Predicate<int> predicate = delegate (int a)
//{
// return a > 0;
//};
//foreach(int a in vs)
//{
// if(predicate(a))
// {
// Debug.Log(a);
// }
//}
this.SortHeros(vs, delegate (hero a, hero b)
{
return b.id.CompareTo(a.id);
});
this.SortHeros(vs, delegate (hero a, hero b)
{
return b.hp.CompareTo(a.hp);
});
}
private void SortHeros(List<hero> vs, Comparison<hero> p)
{
hero[] ss = vs.ToArray();
Array.Sort(ss, p);
foreach(hero a in ss)
{
Debug.Log(a.id);
Debug.Log(a.hp);
}
}
// Update is called once per frame
void Update()
{
}
}
这样就可以通过匿名函数来实现按照英雄ID或者HP来排序的功能了而不用为每一种排序都单独写一个独立的方法,通过上面的分析,使用了匿名函数的确是简化了在使用委托的时候单独声明对应回调函数的繁琐,但是再想一想可不可以再简化一点,直接用匿名方法直接订阅事件呢??下面我们来尝试一下
假如正常的订阅事件如下
public delegate void MyDelegate(int num);//声明一个委托类型 返回值是空,参数是一个整性的实参
public event MyDelegate myDelegate;
// Start is called before the first frame update
void Start()
{
myDelegate += sort;
}
直接使用匿名函数进行订阅事件
public delegate void MyDelegate(int num);//声明一个委托类型 返回值是空,参数是一个整性的实参
public event MyDelegate myDelegate;
// Start is called before the first frame update
void Start()
{
myDelegate += sort;
myDelegate += delegate (int num)
{
Debug.Log(num);
};
}
现在使用了匿名方法作为事件的回调方法,而无需再单独一个方法,但是不一定说所有的方法都需要传入参数,在少数情况下,也可以选择不传入参数的匿名函数订阅事件
myDelegate += delegate
{
Debug.Log("无参");
};
需要注意的是,在使用匿名方法的时候,指的我们注意的是闭包的情况,下篇博客会介绍
(参考教材:U3D脚本编程)