【Unity】【CSharp】事件和委托

委托

委托可以把一个方法代入另一个方法,相当于指向函数的指针,换句话说,委托相当于一个函数指针

总的来说,委托是一个类,该类内部维护着一个字段,指向一个方法。

委托的声明:public delegate void DoDelegate();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 测试方法
void Test(object sender, EventArgs e)
{
Console.Write($"valueStr={sender}");
}
// 定义委托
public delegate void DoDelegate(object sender, EventArgs e);

// 实例化委托——只是实例化,并不会执行
DoDelegate doDelegate = new DoDelegate(Test);
object sender = 888;
EventArgs e = new EventArgs(); // EventArgs是自定义事件,这里想要表达的意思是委托的参数不单只能是数值,还能是事件
// 执行委托的两种方法
// 方法一:
doDelegate?.Invoke(sender, e);
// 方法二:
doDelegate(sender, e);

泛型委托

我们每次要使用一个委托时,都需要先声明这个委托类,规定参数和返回值类型,然后才能实例化、调用。为了简化这个过程, .NET 框架为我们封装了三个泛型委托类,因此大部分情况下我们不必再声明委托,可以拿来直接实例化使用,方便了我们的日常写代码。
这三种泛型委托包括:Func委托、Action委托和Predicate委托。

Func委托

Func委托代表着拥有返回值的泛型委托。Func有一系列的重载,形式如 Func<T1,T2, … TResult>,其中TResult代表委托的返回值类型,其余均是参数类型。只有一个T时,即Func,代表该委托是无参数的。.NET封装了最多16个输入参数的Funct<>委托。
需要特别注意的是,若方法没有返回值,即返回 void ,由于 void 不是数据类型,因此不能定义Func委托。返回 void 的泛型委托见下文的Action。
Func的使用方法与一般的委托相同。例如上面的案例可改写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace delegateTest
{
class Program
{
static void Main(string[] args)
{
int calNum = Calculate(1, 2, Sub);
Console.WriteLine("calNum:{0}", calNum);// -1
}
static int Calculate(int num1, int num2, Func<int, int, int> calDel)
{
return calDel(num1,num2);
}
static int Sub(int num1, int num2)
{
Console.WriteLine("num1 - num2={0}", num1 - num2);
return num1 - num2;
}
}
}
Action委托

Action委托代表返回值为空 void 的委托,它也有一些列重载,最多拥有16个输入参数。用法与Func相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace delegateTest
{
class Program
{
static void Main(string[] args)
{
DoSome("hello",Say);// hello
}
static void DoSome(string str,Action<string> doAction)
{
doAction(str);
}
static void Say(string str)
{
Console.WriteLine(str);
}
}
}
Predicate委托

这个一般用的比较少,它封装返回值为bool类型的委托,可被Func代替。

匿名委托

采用匿名方法实例化的委托称为匿名委托。
每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。
使用的格式是:

委托类名 委托实例名= delegate(args) { 方法体代码 };

这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。

使用匿名方法可以将最初的代码改写为:

1
2
3
4
5
6
7
8
9
10
11
public delegate void DoDelegate(object sender, EventArgs e);

DoDelegate doDelegate = delegate(sender, e)
{
Console.Write($"valueStr={sender}");
};

object sender = 888;
EventArgs e = new EventArgs(); // EventArgs是自定义事件,这里想要表达的意思是委托的参数不单只能是数值,还能是事件

doDelegate(sender, e); // 执行

Lambda表达式

纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。

表达式Lambda

当匿名函数只有一行代码时,可以采用这种形式:

1
2
3
4
5
6
7
8
public delegate void DoDelegate(object sender, EventArgs e);

DoDelegate doDelegate = (sender, e) => Console.Write($"valueStr={sender}");

object sender = 888;
EventArgs e = new EventArgs(); // EventArgs是自定义事件,这里想要表达的意思是委托的参数不单只能是数值,还能是事件

doDelegate(sender, e); // 执行
语句Lambda

当匿名函数有多行代码时,只能采用语句Lambda

1
2
3
4
5
6
7
8
9
10
11
12
public delegate void DoDelegate(object sender, EventArgs e);

DoDelegate doDelegate = (sender, e) => {
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); // 打印当前方法的名称
Console.Write($"valueStr={sender}");
return;
};

object sender = 888;
EventArgs e = new EventArgs(); // EventArgs是自定义事件,这里想要表达的意思是委托的参数不单只能是数值,还能是事件

doDelegate(sender, e); // 执行

语句Lambda不能省略return。


事件

事件相当于保存委托的数组

事件是基于委托的,为委托提供一个订阅或发布的机制。事件是一种特殊的委托,调用事件和委托是一样的。

事件可以被看作是委托类型的一个变量,通过事件注册、取消多个委托和方法。

public event 委托类型 事件名称;
如:public event DoDelegate DoEvent;

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
// 测试方法
void Test(object sender, EventArgs e)
{
Console.Write($"valueStr={sender}");
}
// 定义委托
public delegate void DoDelegate(object sender, EventArgs e);
// 定义事件
public event DODelegate DoEvent;

object sender = 888;
EventArgs e = new EventArgs();
// 两种用法
// 1.给事件注册委托
// 2.给事件直接注册方法
// 用法一(给事件注册委托):
// 给事件注册委托,可注册多个委托
DoEvent += new DoDelegate(doDelegate);
// 执行事件内所有的委托
DoEvent(sender, e);

// 用法二(给事件注册方法):
// 给事件注册方法,可注册多个方法
DoEvent += Test;
// 执行事件内所有的方法
DoEvent(sender, e);