十七、委托(Delegates)
✅ 第17章:委托(Delegates)
📘 一、什么是委托(Delegate)
委托是对方法的安全封装,它本质上是一个对象,可以指向一个或多个方法并调用它们。
你可以理解为:类型安全的函数指针 + 多播功能 + 支持对象上下文
🧠 二、CLR 中委托的本质
✅ 委托是类的一个实例
public delegate int Transformer(int x);
编译器生成的实际上是一个类,内部包含:
- 方法指针(用于调用)
- 目标对象引用(用于绑定实例方法)
- 支持链式调用
🧪 三、基本使用示例
🔹 调用静态方法:
public static int Square(int x) => x * x;
Transformer t = Square;
Console.WriteLine(t(5)); // 25
🔹 调用实例方法:
class MathOps {
public int Cube(int x) => x * x * x;
}
MathOps m = new MathOps();
Transformer t = m.Cube;
Console.WriteLine(t(3)); // 27
🔁 四、委托链(Multicast Delegate)
委托支持组合,自动顺序调用多个方法:
Action a = Method1;
a += Method2;
a += Method3;
a(); // 调用顺序:1 → 2 → 3
⚠️ 仅最后一个方法的返回值有效(非 void 情况下会覆盖)
🧱 五、委托的构造方式(IL层结构)
在 IL 中,委托是这样构造的:
ldftn (method pointer)
ldarg.0 (target instance)
newobj instance of delegate type
➡️ ldftn 指向方法,newobj 构建委托对象。
✅ 六、泛型委托推荐使用(减少定义数量)
.NET 提供了多个内置泛型委托,不必每次都手写 delegate 定义
| 委托类型 | 参数 | 返回值 |
|---|---|---|
Action<T> |
有参数 | 无返回值 |
Func<T, TResult> |
有参数 | 有返回值 |
Predicate<T> |
一个 T | 返回 bool |
🔍 七、简化语法(Lambda & 方法组)
Func<int, int> square = x => x * x;
Action print = Console.WriteLine;
Lambda 与方法组转换让委托定义变得更现代、简洁。
🔄 八、委托 + 反射
你可以动态绑定方法:
MethodInfo mi = typeof(string).GetMethod("ToUpper", Type.EmptyTypes);
Delegate del = Delegate.CreateDelegate(typeof(Func<string, string>), mi);
Console.WriteLine(del.DynamicInvoke("abc")); // ABC
➡️ 用于插件机制、表达式树、IoC 框架等场景。
📊 Mermaid 图:委托机制流程图
🧠 面试题精选
1️⃣ 什么是委托?它和函数指针有什么区别?
✅ 委托是 CLR 中的对象,是类型安全的多播函数指针;函数指针在 C 中不支持类型安全、目标对象绑定、链式调用等。
2️⃣ 委托可以绑定几个方法?
✅ 委托支持多播(multicast),通过 += 绑定多个方法;调用顺序为注册顺序。
3️⃣ Func<int, int> 和 delegate int MyFunc(int x) 有何区别?
✅ 本质相同,但 Func<> 是通用定义,简洁,推荐使用;后者用于需要更具语义的命名。
4️⃣ 委托如何与事件机制配合?
✅ 事件本质上是一个受保护的委托字段,提供 +=/-= 订阅机制,是委托的封装。
5️⃣ 什么是闭包捕获(Closure)?它在委托中有何表现?
✅ 闭包允许 Lambda 捕获外部变量,生成一个对象来持有上下文 → 常用于异步、延迟调用。
✅ 总结速查表
| 概念 | 描述 |
|---|---|
| 委托定义 | 类型安全方法封装类型 |
| 委托是引用类型 | 本质是 class,内部存储方法和目标对象 |
| 委托支持链式调用 | += 添加方法,多播 |
| 泛型委托 | 使用 Action<T>、Func<T> 替代自定义 |
| 委托支持反射调用 | 动态注册、插件场景常用 |
🎮 Unity 案例:反射 + 委托构建灵活事件调用器
🎯 场景模拟:我们要创建一个通用的“按钮调用器”
目标:在按钮点击时,从字符串中动态加载组件方法并调用。
✅ Step 1:组件定义(包含目标方法)
public class Player : MonoBehaviour
{
public void Jump() => Debug.Log("Player jumps!");
public void Attack(string target) => Debug.Log($"Attacks {target}");
}
✅ Step 2:反射 + 委托注册器(核心)
public class MethodInvoker
{
public static Action BindAction(object target, string methodName)
{
MethodInfo mi = target.GetType().GetMethod(methodName, Type.EmptyTypes);
if (mi == null) throw new Exception("Method not found");
return (Action)Delegate.CreateDelegate(typeof(Action), target, mi);
}
public static Action<T> BindAction<T>(object target, string methodName)
{
MethodInfo mi = target.GetType().GetMethod(methodName, new Type[] { typeof(T) });
if (mi == null) throw new Exception("Method not found");
return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), target, mi);
}
}
✅ Step 3:使用示例(比如在按钮点击中调用)
public class GameUI : MonoBehaviour
{
public Player player;
void Start()
{
Action jumpAction = MethodInvoker.BindAction(player, "Jump");
jumpAction(); // 输出:Player jumps!
Action<string> attackAction = MethodInvoker.BindAction<string>(player, "Attack");
attackAction("Goblin"); // 输出:Attacks Goblin
}
}
📦 优势
| 特性 | 效果 |
|---|---|
| 动态方法调用 | 方法名可配置(如 JSON 表、UI 配置) |
| 与委托配合类型安全 | 利用委托避免直接用 Invoke() |
| 适合动态系统/热更框架 | 如 XLua、ILRuntime 使用大量类似模式 |
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号