知识全景思维导图

一、 委托到底是什么?

通俗理解是把方法(函数)当作包裹交给这个代理人,当你喊“执行”的时候,代理人就会去调用这个方法。

委托的“三步走”战略

1. 声明(定义契约)
// 定义一个契约:我只能代理 [返回值为int,接收两个int参数] 的方法
public delegate int MathOperation(int x, int y);
2. 实例化(绑定方法)
// 传统写法
MathOperation op1 = new MathOperation(Add);
// C# 2.0 简化写法 (语法糖)
MathOperation op2 = Add; 
// 绑定 Lambda 表达式
MathOperation op3 = (x, y) => x * y;
3. 调用(触发执行)
int result1 = op1(3, 4);          // 直接像方法一样调用 (常用)
int result2 = op1.Invoke(3, 4);   // 使用 Invoke 方法调用

注意:委托在编译时会严格校验方法签名,确保类型匹配和调用安全

public delegate void ProcessData(string input);

// ✅ 合法绑定
ProcessData handler = s => Console.WriteLine(s);

// ❌ 非法绑定 参数类型不匹配 (编译错误) 
// ProcessData errorHandler = (int i) => Console.WriteLine(i); 

// ❌ 非法绑定 参数个数不匹配 (编译错误) 
// ProcessData errorHandler1 = (i,j) => Console.WriteLine(i,j); 

// ❌ 非法绑定 返回值类型不匹配 (编译错误) 
// ProcessData errorHandler2  = s => s;

二、 为什么需要委托?

假设我们在开发一个游戏主界面,有 6 个按钮(开始游戏、排行榜、分享好友等)。
如果我们为每个按钮写一个单独的 StartButton、FriendButton 类,代码会极度冗余。

委托的解决方案:只写一个 Button 类,将“点击后干什么”交给外界决定!

例如

class Button {
    public delegate void OnClickDelegate(); // 1. 定义委托类型
    public OnClickDelegate? onClick = null; // 2. 声明委托变量

    public void Click() {
        Console.WriteLine("按钮被按下了...");
        onClick?.Invoke(); // 3. 如果有人绑定了方法,就执行它!
    }
}

// 外界使用:
Button startBtn = new Button();
startBtn.Click();

三、多播委托

多播委托允许一个委托变量内部维护一个方法链表。当你调用这个委托时,它会按照顺序把链表里的方法全执行一遍。

  • +=:将方法加入链表。

  • -=:将方法移出链表。

金典案例: 小明让小张帮忙买水

(1) 先定义一个买东西的类

public class Zhang
{
    public delegate void BuySomethingDelegate();

    public void Buywater()
    {
        Console.WriteLine("买水!");
    }

    public void BuyKFC()
    {
        Console.WriteLine("买肯德基");
    }

    public void BuyHotDog()
    {
        Console.WriteLine("买热狗");
    }
}

(2) 小张帮小明完成了买水的操作

Zhang z = new Zhang();
BuySomethingDelegate bsd = new BuySomethingDelegate(z.Buywater);
bsd();
//bsd.Invoke();

(3) 小明突然想吃东西,又让小张顺路带个热狗和肯德基

Zhang z = new Zhang();
BuySomethingDelegate bsd = new BuySomethingDelegate(z.Buywater);
bsd += z.BuyHotDog;
bsd += z.BuyKFC;
bsd();

(4)还没付钱,小明怕吃不下又让小明把热狗退了

Zhang z = new Zhang();
BuySomethingDelegate bsd = new BuySomethingDelegate(z.Buywater);
bsd += z.BuyHotDog;
bsd += z.BuyKFC;
bsd -= z.BuyHotDog;
bsd();

多播委托的特点

1. 调用顺序:方法按照它们被添加的顺序调用
2. 返回值:如果委托有返回值,只有最后一个方法的返回值会被保留
3. 异常处理:如果某个方法抛出异常,后续方法不会被调用

 四、 微软的三大委托 

为了避免我们每次都要 public delegate void XXX(),微软在 .NET 中直接帮我们提前定义好了三种最常用的泛型委托。

委托名称 特点 适用场景 签名示例
Action<T...> 无返回值 (最多支持16个参数) 只需要执行动作,不需要汇报结果 Action<string> print = s => Console.WriteLine(s);
Func<T..., TResult> 有返回值 (最后一个泛型是返回值类型) 需要计算并返回结果 Func<int, int, int> add = (a, b) => a + b;
Predicate<T> 仅1个参数,返回 bool 条件判断、集合筛选 (Find, FindAll) Predicate<int> isEven = n => n % 2 == 0;

五、 事件 (Event)

1. 什么是事件?

事件本质上是一种特殊的多播委托。 它是基于“发布-订阅(Pub/Sub)”模型的。
发布者 (Publisher):拥有事件的类,决定何时触发事件。
订阅者 (Subscriber):监听事件的类,决定发生事件后做什么。

案例:

// 发布者类
public class PublisherClass
{
    // 和事件搭配的委托
    public delegate void PubDelegate();

    // 定义事件
    public event PubDelegate? PubEvent;

    // 编写处理事件的具体逻辑
    public void EventHandling()
    {
        if (PubEvent == null)
        {
            Console.WriteLine("需要注册事件的啊");
        }
        else
        {
            // 执行注册的事件
            PubEvent();
        }
    }
}

// 订阅者类
public class SubscriberClass
{
    public void PrintOut()
    {
        Console.WriteLine("执行了订阅者类中的事件。");
        Console.ReadLine();
    }
}

 internal class Class1
 {
     static void Main(string[] args)
     {
         // 实例化对象
         PublisherClass p = new PublisherClass();
         SubscriberClass s = new SubscriberClass();
         // 执行事件
         p.EventHandling();
         // 注册事件
         p.PubEvent += new PublisherClass.PubDelegate(s.PrintOut);
         //可以简写:
 		//p.PubEvent += s.PrintOut;
         // 执行事件
         p.EventHandling();

     }
 }

2. 使用EventHandler 声明事件

EventHandler 是 .NET 中最基本的事件委托,其定义如下:

public delegate void EventHandler(object sender, EventArgs e);
//sender:触发事件的对象(通常是 this)。
//e:事件参数,如果不需要传递额外数据,可以使用 EventArgs.Empty。

案例:

//事件发布者
public class Button
{
    // 1. 定义事件
    public event EventHandler Click;

    // 2. 触发事件的方法(遵循 .NET 规范,通常命名为 OnXXX)编写处理事件的具体逻辑
    public virtual void OnClick(EventArgs e)
    {
        Click?.Invoke(this, e); // 安全调用,避免 NullReferenceException
    }
}

EventHandler类型的事件可以通过 += 运算符 来订阅事件处理程序.有下面这几种写法

(1) 使用 Lambda 表达式(推荐简洁写法)

适用场景:简单逻辑,无需复用的事件处理。

btn.Click += (sender, e) =>
{
    Console.WriteLine($"事件触发者:{sender}"); //事件触发者
    Console.WriteLine($"事件参数:{e}");
    Console.WriteLine("Click event handled!");
};

(2) 使用方法(符合传统 .NET 风格)

适用场景:需要复用的逻辑或复杂处理。

 btn.Click += OnButtonClick;

// 事件处理方法
private static  void OnButtonClick(object sender, EventArgs e)
{
    Console.WriteLine($"事件触发者: {sender}, 参数: {e}");
}

六、 事件 vs 委托

对比维度 委托 (Delegate) 事件 (Event)
本质 是一种类型(Type),像类一样 是委托变量的一个包装器(类似于属性包装了字段)
访问控制 外部可以随意赋值 (=) 或清空 外部只能订阅 (+=) 或退订 (-=)
触发权限 任何人拿到委托变量都可以 Invoke() 只有声明事件的类自己才能触发 (Invoke)
接口定义 不能直接定义在接口中 可以定义在接口中 (event EventHandler XXX;)
主要用途 灵活的方法传递、回调函数、LINQ查询 严格的发布-订阅模式,状态通知(如 UI 交互)

总结: 委托是底层基础,事件是基于委托做的高级封装,为的是代码的安全解耦

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐