×

C#委托入门:多播委托、委托 vs 接口 vs Func / Action

独孤求败 独孤求败 发表于2026-02-09 14:58:57 浏览16 评论0

抢沙发发表评论

多播委托(一个委托绑定多个方法)

一、概念

多播委托 = 一个委托实例可以绑定多个方法调用这个委托时,所有绑定的方法都会按顺序被调用。

  • 核心特性:
    1. 每个方法必须签名匹配委托
    2. 返回值类型只能是 void(否则只返回最后一个方法的值)
    3. 可以通过 += 添加方法,通过 -= 移除方法

二、最小示例

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#
    }
}

三、拆解说明

  1. n += Goodbye;
    • 把 Goodbye 方法添加到 n 的调用列表中
  2. n("World");
    • 调用时依次执行 Hello → Goodbye
  3. n -= Hello;
    • 从调用列表中移除 Hello
  4. 返回值注意
    • 多播委托中只有最后一个方法的返回值被使用
    • 如果返回值不是 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<intintintadd = (a, b) => a + b;

本质上仍然是委托:

delegate int Func<int,int,int>(int a, int b);

👉 只是省掉了你写 delegate 的步骤


五、直接对比

1️⃣ 委托 vs 接口(最重要)

维度
委托
接口
表达对象
表达单一行为
多个方法
是否有状态
使用成本
灵活度

👉 判断口诀

只关心“做什么” → 委托关心“是谁 + 能做什么” → 接口


2️⃣ 委托 vs Action / Func

对比点
自定义 delegate
Action / Func
语义清晰度
样板代码
复用性
规范性(公共 API)

👉 公共 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<stringbool> 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,委托是天然载体


群贤毕至

访客