多播委托(一个委托绑定多个方法)
一、概念
多播委托 = 一个委托实例可以绑定多个方法调用这个委托时,所有绑定的方法都会按顺序被调用。
核心特性: 每个方法必须签名匹配委托 返回值类型只能是 void(否则只返回最后一个方法的值)可以通过 +=添加方法,通过-=移除方法
二、最小示例
using System;
class Program
{
// 委托类型,返回 void
delegate void Notify(string message);
static void Hello(string msg)
{
Console.WriteLine("Hello " + msg);
}
static void Goodbye(string msg)
{
Console.WriteLine("Goodbye " + msg);
}
static void Main()
{
Notify n = Hello; // 初始绑定 Hello
n += Goodbye; // 追加 Goodbye
n("World"); // 调用多播委托
// 输出:
// Hello World
// Goodbye World
n -= Hello; // 移除 Hello
n("C#"); // 输出:
// Goodbye C#
}
}
三、拆解说明
n += Goodbye;把 Goodbye方法添加到n的调用列表中n("World");调用时依次执行 Hello→Goodbyen -= Hello;从调用列表中移除 Hello返回值注意: 多播委托中只有最后一个方法的返回值被使用 如果返回值不是 void,通常只使用最后一个的返回值,前面的被丢弃
4 :委托 vs 接口 vs Func / Action
一、一句话总览
委托 Action / Func 接口
👉 核心区别:方法 vs 对象
二、委托解决的到底是什么问题?
委托 = “把方法当数据传递”
void Run(Action action)
{
action();
}
这里关心的只有:
✔ 做什么 ❌ 谁来做 ❌ 有什么状态
👉 这是纯行为
委托的特点
✔ 只关注“怎么执行” ✔ 可以是静态方法 / 实例方法 / lambda ❌ 不能保存状态(除非闭包) ❌ 不能表达“多种能力”
三、接口解决的是什么问题?
接口 = “定义一个角色 / 能力集合”
interface ILogger
{
void Info(string msg);
void Warn(string msg);
}
使用时:
void Process(ILogger logger)
{
logger.Info("start");
}
👉 你关心的是:
这个对象能做什么 它可能有内部状态 它可能有多种实现
接口的特点
✔ 表达“身份 / 职责” ✔ 可以持有状态(通过实现类) ✔ 可扩展 ❌ 使用成本高 ❌ 为简单回调显得笨重
四、Func / Action 在这里的位置
Func / Action = “委托的快捷模板”
Action<T>Func<T, TResult>
示例:
Func<int, int, int> add = (a, b) => a + b;
本质上仍然是委托:
delegate int Func<int,int,int>(int a, int b);
👉 只是省掉了你写 delegate 的步骤
五、直接对比
1️⃣ 委托 vs 接口(最重要)
👉 判断口诀:
只关心“做什么” → 委托关心“是谁 + 能做什么” → 接口
2️⃣ 委托 vs Action / Func
👉 公共 API / 框架层:自定义 delegate 👉 业务内部 / 工具方法:Action / Func
六、用“多播打印”例子做真实对比
方案 1:Action
Action<string> printer;
✔ 简洁 ✔ 行为简单 ✔ 无状态
方案 2:自定义委托(语义更强)
delegate void LogHandler(string message);
适合日志系统 / 框架层
方案 3:接口(反而不合适)
interface IPrinter
{
void Print(string msg);
}
❌ 只有一个方法 ❌ 没有状态 ❌ 显得臃肿
七、关键认知纠偏
❌ 错误理解(很多人会这样想)
“接口更面向对象,所以比委托更高级”
✅ 正确理解
接口和委托解决的是完全不同维度的问题
委托:函数式 接口:对象式
不是替代关系,而是互补关系
八、练习
✅ 练习 1(判断题)
下面场景选 委托 / Action / 接口,并说明理由:
1.排序算法,排序规则由调用者决定
✅ 正确选择:委托 / Func<T, T, int>
list.Sort((a, b) => a.Age.CompareTo(b.Age));
为什么是委托?
排序规则本质是: 👉 “给我两个元素,告诉我谁大谁小” 这是单一行为 大部分情况下不需要长期保存状态
即使“可能有状态”,也可以通过 闭包 携带状态
接口什么时候才适合?
class AgeComparer : IComparer<Person>
{
public int Compare(Person x, Person y) { ... }
}
✔ 需要复用 ✔ 有复杂状态 ✔ 生命周期较长
👉 LINQ / 集合 API:优先委托
2.支付方式(支付宝 / 微信 / 银行卡)
✅ 正确选择:接口
interface IPayment
{
void Pay(decimal amount);
}
原因(非常关键):
支付方式:
✔ 有状态(配置、密钥、账号) ✔ 有多步流程 ✔ 是长期对象 ✔ 有业务语义
👉 这已经不是“一个动作”,而是一个业务角色
3.任务执行完成后的回调
✅ 回调 = “我做完了,通知你”
void DownloadFile(string url, Action onCompleted)
{
// 下载逻辑
onCompleted(); // 调用者提供的回调
}
✔ 临时 ✔ 一次性 ✔ 只关心“做什么”
👉 回调 = 委托的经典使用场景
4.日志系统对外暴露的扩展点
4️⃣ 日志系统对外暴露的扩展点
内部扩展点(对)✅
Action<string> Log;
对外框架 API(错)❌
interface ILogger
{
void Log(string msg);
}
判断标准:
业务内部 → 委托 框架 / SDK / 对外 → 接口(语义 + 稳定性)
✅ 练习 2(改写)
把下面接口写法,改成更合适的委托版本:
interface IValidator
{
bool Validate(string input);
}
// 委托版本
Func<string, bool> validator;
// 或者自定义委托
delegate bool Validator(string input);
✅ 练习 3(思考)
为什么 LINQ 大量使用 Func<T, bool>,而不是接口?
LINQ 使用 Func 的根本原因
1️⃣ 行为是一次性的
.Where(x => x > 10)
2️⃣ 组合性极强
.Where(...).Select(...).OrderBy(...)
3️⃣ lambda + 闭包
int limit = 10;
.Where(x => x > limit)
4️⃣ 接口会极度臃肿
class Filter : IFilter<int> { ... }
👉 LINQ 是函数式风格 API,委托是天然载体