十七、委托(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 图:委托机制流程图

flowchart TD A[方法定义] --> B[委托类型声明] B --> C[创建委托对象] C --> D[调用 Invoke 方法] D --> E[实际调用目标方法]

🧠 面试题精选


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 使用大量类似模式
posted @ 2025-08-26 10:06  世纪末の魔术师  阅读(33)  评论(0)    收藏  举报