427 lines
7.7 KiB
Markdown
427 lines
7.7 KiB
Markdown
# 委托
|
||
概念:
|
||
委托是==可以引用方法的类型==
|
||
可以理解为“函数的容器”或“存方法的变量”
|
||
|
||
作用:
|
||
1. 把方法赋值给变量
|
||
2. 把方法作为参数传递
|
||
3. 让一个地方可以调用多个方法
|
||
4. 为事件、匿名函数、lambda 提供基础
|
||
|
||
简单理解:
|
||
普通变量存数据
|
||
委托变量存“符合条件的方法”
|
||
|
||
----
|
||
# 通常用法
|
||
1. 作为类的成员,保存回调方法
|
||
2. 作为方法参数,让调用者决定具体执行什么逻辑
|
||
3. 同时绑定多个方法,实现多播调用
|
||
4. 配合 `Action`、`Func`、匿名函数和 lambda 使用
|
||
|
||
实际开发中:
|
||
自定义委托仍然会用到
|
||
但很多简单场景更常使用系统自带的 `Action` 和 `Func`
|
||
|
||
----
|
||
# 语法
|
||
关键字:
|
||
```csharp
|
||
delegate
|
||
```
|
||
|
||
定义委托:
|
||
```csharp
|
||
访问修饰符 delegate 返回值类型 委托名(参数列表);
|
||
```
|
||
|
||
示例:
|
||
```csharp
|
||
public delegate void MyDelegate(string message);
|
||
```
|
||
|
||
说明:
|
||
`MyDelegate` 只能保存:
|
||
参数是 `string`
|
||
返回值是 `void`
|
||
的方法
|
||
|
||
----
|
||
# 基础案例
|
||
```csharp
|
||
using System;
|
||
|
||
public delegate void MyDelegate(string message);
|
||
|
||
class Program
|
||
{
|
||
static void PrintMessage(string message)
|
||
{
|
||
Console.WriteLine(message);
|
||
}
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
MyDelegate del = PrintMessage;
|
||
del("你好,委托");
|
||
}
|
||
}
|
||
```
|
||
|
||
输出:
|
||
```text
|
||
你好,委托
|
||
```
|
||
|
||
理解:
|
||
1. `MyDelegate` 规定了方法的格式
|
||
2. `PrintMessage` 的参数和返回值符合这个格式
|
||
3. 所以可以赋值给 `del`
|
||
4. 调用 `del(...)`,本质上就是调用 `PrintMessage(...)`
|
||
|
||
----
|
||
# 方法必须匹配委托格式
|
||
```csharp
|
||
public delegate int Calculate(int a, int b);
|
||
|
||
static int Add(int a, int b)
|
||
{
|
||
return a + b;
|
||
}
|
||
|
||
Calculate calculate = Add;
|
||
Console.WriteLine(calculate(10, 20));
|
||
```
|
||
|
||
正确匹配:
|
||
参数数量一致
|
||
参数类型一致
|
||
返回值类型一致
|
||
|
||
错误示例:
|
||
```csharp
|
||
static void Print(int a, int b)
|
||
{
|
||
}
|
||
|
||
// Calculate calculate = Print;
|
||
```
|
||
|
||
原因:
|
||
`Calculate` 要求返回 `int`
|
||
`Print` 的返回值是 `void`
|
||
两者不匹配
|
||
|
||
----
|
||
# 委托的赋值和调用
|
||
赋值:
|
||
```csharp
|
||
MyDelegate del = PrintMessage;
|
||
```
|
||
|
||
调用:
|
||
```csharp
|
||
del("hello");
|
||
```
|
||
|
||
也可以使用 `Invoke`:
|
||
```csharp
|
||
del.Invoke("hello");
|
||
```
|
||
|
||
通常写法:
|
||
直接写 `del(...)` 更常见
|
||
`Invoke(...)` 的语义更明确
|
||
|
||
----
|
||
# 委托可以作为参数
|
||
作用:
|
||
把“具体要做什么”交给调用者决定
|
||
|
||
```csharp
|
||
using System;
|
||
|
||
public delegate void HandleNumber(int number);
|
||
|
||
class Program
|
||
{
|
||
static void Process(int number, HandleNumber handler)
|
||
{
|
||
handler(number);
|
||
}
|
||
|
||
static void PrintNumber(int number)
|
||
{
|
||
Console.WriteLine("数字:" + number);
|
||
}
|
||
|
||
static void PrintDouble(int number)
|
||
{
|
||
Console.WriteLine("两倍:" + number * 2);
|
||
}
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
Process(10, PrintNumber);
|
||
Process(10, PrintDouble);
|
||
}
|
||
}
|
||
```
|
||
|
||
输出:
|
||
```text
|
||
数字:10
|
||
两倍:20
|
||
```
|
||
|
||
理解:
|
||
`Process` 不关心具体处理方式
|
||
只要求传进来的方法符合 `HandleNumber` 的格式
|
||
|
||
----
|
||
# 多播委托
|
||
一个委托变量可以同时绑定多个方法:
|
||
|
||
```csharp
|
||
using System;
|
||
|
||
public delegate void MyDelegate();
|
||
|
||
class Program
|
||
{
|
||
static void Method1()
|
||
{
|
||
Console.WriteLine("执行方法1");
|
||
}
|
||
|
||
static void Method2()
|
||
{
|
||
Console.WriteLine("执行方法2");
|
||
}
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
MyDelegate del = Method1;
|
||
del += Method2;
|
||
|
||
del();
|
||
}
|
||
}
|
||
```
|
||
|
||
输出:
|
||
```text
|
||
执行方法1
|
||
执行方法2
|
||
```
|
||
|
||
取消绑定:
|
||
```csharp
|
||
del -= Method2;
|
||
```
|
||
|
||
说明:
|
||
1. `+=` 用来添加方法
|
||
2. `-=` 用来移除方法
|
||
3. 调用时会按添加顺序依次执行
|
||
4. 多播委托最适合返回值为 `void` 的情况
|
||
|
||
----
|
||
# 有返回值的多播委托
|
||
```csharp
|
||
public delegate int Calculate();
|
||
|
||
static int Method1()
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
static int Method2()
|
||
{
|
||
return 2;
|
||
}
|
||
|
||
Calculate calculate = Method1;
|
||
calculate += Method2;
|
||
|
||
int result = calculate();
|
||
Console.WriteLine(result);
|
||
```
|
||
|
||
输出:
|
||
```text
|
||
2
|
||
```
|
||
|
||
说明:
|
||
两个方法都会执行
|
||
但委托调用的最终返回值,只会保留最后一个方法的返回值
|
||
|
||
所以:
|
||
多播委托通常更适合 `void` 返回值
|
||
|
||
----
|
||
# 空委托要先判断
|
||
错误示例:
|
||
```csharp
|
||
MyDelegate del = null;
|
||
|
||
// del();
|
||
```
|
||
|
||
原因:
|
||
委托变量是引用类型
|
||
没有绑定方法时可能为 `null`
|
||
|
||
常见写法:
|
||
```csharp
|
||
if (del != null)
|
||
{
|
||
del();
|
||
}
|
||
```
|
||
|
||
更简洁的写法:
|
||
```csharp
|
||
del?.Invoke();
|
||
```
|
||
|
||
说明:
|
||
`?.` 会先判断 `del` 是否为 `null`
|
||
不为 `null` 才调用 `Invoke()`
|
||
|
||
----
|
||
# Action
|
||
`Action` 是系统自带的委托类型:
|
||
==无返回值==
|
||
|
||
无参:
|
||
```csharp
|
||
Action action = PrintHello;
|
||
action();
|
||
|
||
static void PrintHello()
|
||
{
|
||
Console.WriteLine("Hello");
|
||
}
|
||
```
|
||
|
||
有参数:
|
||
```csharp
|
||
Action<string> print = PrintMessage;
|
||
print("你好");
|
||
|
||
static void PrintMessage(string message)
|
||
{
|
||
Console.WriteLine(message);
|
||
}
|
||
```
|
||
|
||
说明:
|
||
`Action` 后面的泛型参数表示方法参数类型
|
||
它的返回值始终是 `void`
|
||
|
||
----
|
||
# Func
|
||
`Func` 是系统自带的泛型委托类型:
|
||
==有返回值==
|
||
|
||
```csharp
|
||
Func<int, int, int> add = Add;
|
||
|
||
int result = add(10, 20);
|
||
Console.WriteLine(result);
|
||
|
||
static int Add(int a, int b)
|
||
{
|
||
return a + b;
|
||
}
|
||
```
|
||
|
||
说明:
|
||
```csharp
|
||
Func<参数1类型, 参数2类型, 返回值类型>
|
||
```
|
||
|
||
上面的:
|
||
```csharp
|
||
Func<int, int, int>
|
||
```
|
||
|
||
表示:
|
||
前两个 `int` 是参数类型
|
||
最后一个 `int` 是返回值类型
|
||
|
||
----
|
||
# 自定义委托、Action、Func 的区别
|
||
| 对比点 | 自定义委托 | `Action` | `Func` |
|
||
|---|---|---|---|
|
||
| 是否需要自己声明 | 需要 | 不需要 | 不需要 |
|
||
| 返回值 | 自己定义 | 必须是 `void` | 必须有返回值 |
|
||
| 参数 | 自己定义 | 可有多个 | 可有多个 |
|
||
| 常见用途 | 语义明确的回调 | 简单无返回值操作 | 简单有返回值操作 |
|
||
|
||
实际使用建议:
|
||
1. 简单无返回值回调,优先考虑 `Action`
|
||
2. 简单有返回值回调,优先考虑 `Func`
|
||
3. 需要清晰业务语义时,可以定义自己的委托类型
|
||
|
||
----
|
||
# 和事件的关系
|
||
委托:
|
||
可以在类外部赋值
|
||
也可以在类外部直接调用
|
||
|
||
事件:
|
||
是对委托的进一步封装
|
||
类外部只能订阅和取消订阅
|
||
不能直接调用事件
|
||
|
||
可以和[[事件]]一起理解:
|
||
委托负责“存方法和调用方法”
|
||
事件负责“更安全地对外发布通知”
|
||
|
||
----
|
||
# 注意
|
||
1. 委托是一种引用类型
|
||
2. 委托变量只能保存签名匹配的方法
|
||
3. 签名主要看参数和返回值
|
||
4. 委托可以绑定一个或多个方法
|
||
5. `+=` 添加方法,`-=` 移除方法
|
||
6. 调用可能为 `null` 的委托前要先判断
|
||
7. `Action` 无返回值,`Func` 有返回值
|
||
8. 自定义委托不能只靠返回值不同来重名
|
||
|
||
----
|
||
# 易错点
|
||
1. 委托不是方法本身,而是“引用方法的变量”
|
||
2. 委托类型和方法签名不匹配时,不能赋值
|
||
3. `Add` 和 `+=` 不一样,委托绑定方法用 `+=`
|
||
4. 多播委托有返回值时,只能拿到最后一个方法的返回值
|
||
5. `del?.Invoke()` 可以避免空引用异常
|
||
|
||
----
|
||
# 一句话记忆
|
||
==委托就是一种可以存方法、传方法、调方法的类型。==
|
||
|
||
----
|
||
# 面试/复习时怎么说
|
||
可以这样答:
|
||
|
||
“委托是一种引用方法的类型,可以把符合指定签名的方法赋值给委托变量,再通过委托统一调用。
|
||
它可以作为参数传递,也可以绑定多个方法,常用于回调和事件机制。
|
||
实际开发中,简单场景通常使用系统自带的 `Action` 和 `Func`。”
|
||
|
||
----
|
||
引用:
|
||
1. Microsoft Learn: [Delegates](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/)
|
||
2. Microsoft Learn: [Action Delegate](https://learn.microsoft.com/en-us/dotnet/api/system.action)
|
||
3. Microsoft Learn: [Func Delegate](https://learn.microsoft.com/en-us/dotnet/api/system.func-2)
|
||
|
||
#委托
|
||
#delegate
|
||
#Action
|
||
#Func
|
||
#进阶
|