C#中的delegate委托

委托(delegate)详解

一、什么是委托?

委托(delegate)是 C# 中"方法的类型"。它像一个"方法指针"或"方法的容器",可以:

  1. 指向一个方法(把方法当作值来保存)
  2. 作为参数传递(把方法传给另一个方法)
  3. 作为返回值(从方法中返回一个方法)
  4. 多播(一个委托同时指向多个方法,调用时全部执行)

一句话:委托让"方法"成为可以像变量一样传递和操作的对象。


二、为什么需要委托?

C# 中方法本身不能直接作为参数传递。比如你想写一个"对数组每个元素执行某个操作"的通用方法,但"操作"是什么由调用方决定------这时就需要把"方法"作为参数传进去,而委托就是干这个的。

csharp 复制代码
// 没有委托:每种操作都要写一个方法,代码重复
void ProcessWithAddOne(int[] arr) { for (int i = 0; i < arr.Length; i++) arr[i] += 1; }
void ProcessWithDouble(int[] arr) { for (int i = 0; i < arr.Length; i++) arr[i] *= 2; }

// 有委托:一个方法搞定所有操作
void Process(int[] arr, Func<int, int> operation)
{
    for (int i = 0; i < arr.Length; i++) arr[i] = operation(arr[i]);
}
// 调用时传入不同方法
Process(arr, x => x + 1);   // 加一
Process(arr, x => x * 2);   // 翻倍

三、委托的三种声明/使用方式

方式 1:自定义委托类型(delegate 关键字)
csharp 复制代码
// 声明一个委托类型:接收 string,返回 void
public delegate void StringHandler(string message);

// 定义一个符合签名的方法
void Show(string msg) => Console.WriteLine(msg);

// 使用
StringHandler handler = Show;   // 指向方法
handler("你好");                 // 调用 → 输出: 你好
方式 2:内置泛型委托 Action(无返回值)
csharp 复制代码
Action<string> handler = msg => Console.WriteLine(msg);
handler("你好");
方式 3:内置泛型委托 Func(有返回值)
csharp 复制代码
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 4);   // 7

记忆:Action 无返回值,Func 有返回值(最后一个泛型参数是返回类型)。


四、委托的三种写法(指向方法的方式)

csharp 复制代码
// 1. 指向具名方法
Action<string> a = Show;

// 2. 匿名方法(C# 2.0)
Action<string> b = delegate (string msg) { Console.WriteLine(msg); };

// 3. Lambda 表达式(C# 3.0,最常用)
Action<string> c = msg => Console.WriteLine(msg);

三者等价,Lambda 最简洁。


五、多播委托(一个委托指向多个方法)

委托用 += 可以挂多个方法,调用时按注册顺序依次执行

csharp 复制代码
Action say = () => Console.WriteLine("方法1");
say += () => Console.WriteLine("方法2");
say += () => Console.WriteLine("方法3");

say();
// 输出:
// 方法1
// 方法2
// 方法3

-= 可以移除某个方法。这正是事件(event)的底层机制。


六、委托的经典应用场景

场景 1:按钮点击事件(UI 事件处理)

这是委托最经典的场景。WPF/WinForms 中按钮的 Click 事件本质就是一个委托,你写的"点击后要执行的方法"就是挂到委托上的回调:

csharp 复制代码
// 按钮的 Click 事件本质是委托:void ClickHandler(object sender, RoutedEventArgs e)
myButton.Click += OnButtonClick;   // 把方法挂到事件上

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    MessageBox.Show("按钮被点击了!");
}

委托在这里的作用:框架不知道你点击后想干什么,它只负责"点击发生时调用挂上去的方法"。你通过委托把"自己的逻辑"注入到框架的流程中。

场景 2:排序比较器(把规则传给通用方法)

List<T>.Sort 接受一个比较委托,让你自定义排序规则,而不用重写排序算法:

csharp 复制代码
var people = new List<Person> { /* ... */ };

// 按年龄升序排序(传入比较规则)
people.Sort((a, b) => a.Age.CompareTo(b.Age));

// 按姓名降序排序(换一个规则即可)
people.Sort((a, b) => b.Name.CompareTo(a.Name));

委托在这里的作用:排序算法是通用的,只有"怎么比较两个元素"是变化的。把比较规则作为委托传入,一套算法复用所有规则。

场景 3:回调通知(异步操作完成后通知)

下载文件是异步的,完成后需要通知调用方。用委托做回调:

csharp 复制代码
// 下载方法:完成后调用回调,把结果传回去
void DownloadFile(string url, Action<bool, string> onCompleted)
{
    Task.Run(() =>
    {
        bool success = /* 模拟下载 */;
        string message = success ? "下载成功" : "下载失败";
        onCompleted(success, message);   // 完成后回调
    });
}

// 调用方传入"完成后要做什么"
DownloadFile("http://example.com/file.zip", (ok, msg) =>
{
    if (ok) Console.WriteLine(msg);
    else Console.WriteLine("错误: " + msg);
});

委托在这里的作用 :异步方法无法用 return 返回结果(方法已经返回了),所以通过回调委托把结果"送"回调用方。

场景 4:发布-订阅(观察者模式)

多个对象想监听同一个事件,用多播委托实现一对多通知:

csharp 复制代码
// 发布者:温度传感器
public class TemperatureSensor
{
    public event Action<int>? TemperatureChanged;   // 事件 = 委托

    public void Update(int newTemp)
    {
        TemperatureChanged?.Invoke(newTemp);   // 通知所有订阅者
    }
}
// 订阅者 1:显示器
var display = new TemperatureSensor();
display.TemperatureChanged += temp => Console.WriteLine($"显示器: 当前温度 {temp}°C");

// 订阅者 2:报警器
display.TemperatureChanged += temp =>
{
    if (temp > 40) Console.WriteLine($"报警器: 温度过高 {temp}°C!");
};

display.Update(42);
// 输出:
// 显示器: 当前温度 42°C
// 报警器: 温度过高 42°C!

委托在这里的作用 :传感器不知道也不关心谁在监听,它只负责"温度变了就调用委托"。所有订阅者通过 += 挂上去,一次广播全部收到。

场景 5:策略模式(运行时切换算法)

用委托实现策略模式,运行时动态替换算法:

csharp 复制代码
// 定义一个计算策略委托
Func<decimal, decimal> taxStrategy;

// 平时用普通税率
taxStrategy = amount => amount * 0.1m;

// 促销期间切换为优惠税率
taxStrategy = amount => amount * 0.05m;

// 计算时无需关心当前是哪种策略
decimal tax = taxStrategy(1000m);

委托在这里的作用:把"算法"作为值存储和替换,运行时动态改变行为,无需修改调用代码。


七、事件(event)与委托的关系

事件是"受保护的委托"------这是理解两者的关键:

对比 委托(delegate) 事件(event)
能否在类外部直接赋值/调用 ✅ 可以 ❌ 只能 +=/-= 订阅
封装性 弱(外部可随意操作) 强(只有声明类能 Invoke
用途 通用回调 发布-订阅通知
csharp 复制代码
public delegate void Handler(string msg);

// 委托:外部可以直接 handler("x") 或 handler = 其他方法
public Handler MyDelegate;

// 事件:外部只能 += / -=,不能直接赋值或调用
public event Handler? MyEvent;

事件 = 委托 + 访问限制 。事件内部就是一个委托字段,+= 就是多播委托的挂载。


八、完整流程图

#mermaid-svg-vUAQitV4pkYeJdIW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vUAQitV4pkYeJdIW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vUAQitV4pkYeJdIW .error-icon{fill:#552222;}#mermaid-svg-vUAQitV4pkYeJdIW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vUAQitV4pkYeJdIW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vUAQitV4pkYeJdIW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vUAQitV4pkYeJdIW .marker.cross{stroke:#333333;}#mermaid-svg-vUAQitV4pkYeJdIW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vUAQitV4pkYeJdIW p{margin:0;}#mermaid-svg-vUAQitV4pkYeJdIW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vUAQitV4pkYeJdIW .cluster-label text{fill:#333;}#mermaid-svg-vUAQitV4pkYeJdIW .cluster-label span{color:#333;}#mermaid-svg-vUAQitV4pkYeJdIW .cluster-label span p{background-color:transparent;}#mermaid-svg-vUAQitV4pkYeJdIW .label text,#mermaid-svg-vUAQitV4pkYeJdIW span{fill:#333;color:#333;}#mermaid-svg-vUAQitV4pkYeJdIW .node rect,#mermaid-svg-vUAQitV4pkYeJdIW .node circle,#mermaid-svg-vUAQitV4pkYeJdIW .node ellipse,#mermaid-svg-vUAQitV4pkYeJdIW .node polygon,#mermaid-svg-vUAQitV4pkYeJdIW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vUAQitV4pkYeJdIW .rough-node .label text,#mermaid-svg-vUAQitV4pkYeJdIW .node .label text,#mermaid-svg-vUAQitV4pkYeJdIW .image-shape .label,#mermaid-svg-vUAQitV4pkYeJdIW .icon-shape .label{text-anchor:middle;}#mermaid-svg-vUAQitV4pkYeJdIW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vUAQitV4pkYeJdIW .rough-node .label,#mermaid-svg-vUAQitV4pkYeJdIW .node .label,#mermaid-svg-vUAQitV4pkYeJdIW .image-shape .label,#mermaid-svg-vUAQitV4pkYeJdIW .icon-shape .label{text-align:center;}#mermaid-svg-vUAQitV4pkYeJdIW .node.clickable{cursor:pointer;}#mermaid-svg-vUAQitV4pkYeJdIW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vUAQitV4pkYeJdIW .arrowheadPath{fill:#333333;}#mermaid-svg-vUAQitV4pkYeJdIW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vUAQitV4pkYeJdIW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vUAQitV4pkYeJdIW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vUAQitV4pkYeJdIW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vUAQitV4pkYeJdIW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vUAQitV4pkYeJdIW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vUAQitV4pkYeJdIW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vUAQitV4pkYeJdIW .cluster text{fill:#333;}#mermaid-svg-vUAQitV4pkYeJdIW .cluster span{color:#333;}#mermaid-svg-vUAQitV4pkYeJdIW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vUAQitV4pkYeJdIW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vUAQitV4pkYeJdIW rect.text{fill:none;stroke-width:0;}#mermaid-svg-vUAQitV4pkYeJdIW .icon-shape,#mermaid-svg-vUAQitV4pkYeJdIW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vUAQitV4pkYeJdIW .icon-shape p,#mermaid-svg-vUAQitV4pkYeJdIW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vUAQitV4pkYeJdIW .icon-shape .label rect,#mermaid-svg-vUAQitV4pkYeJdIW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vUAQitV4pkYeJdIW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vUAQitV4pkYeJdIW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vUAQitV4pkYeJdIW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 声明委托类型

delegate void Handler(string)
定义符合签名的方法
把方法挂到委托/事件

handler += 方法 或 Subscribe
触发/广播

handler.Invoke(...) 或 Publish
所有订阅方法依次执行


九、委托的常见陷阱

  1. 空引用 :委托没挂任何方法时调用会抛 NullReferenceException,所以要用 ?.Invoke(...)(空条件运算符)。
  2. 多播顺序+= 挂载的方法按注册顺序执行,但不保证异常隔离------一个方法抛异常会中断后续方法。
  3. 内存泄漏 :长期存在的对象订阅了短生命周期对象的事件,短对象无法被回收。用完记得 -= 取消订阅。
  4. 线程安全 :委托本身非线程安全,多线程广播时需加锁或用 Interlocked

十、总结

  • 委托 = 方法的类型,让方法可以像变量一样传递、存储、调用。
  • 三种形式:自定义 delegate、内置 Action(无返回值)、Func(有返回值)。
  • 三种写法:具名方法、匿名方法、Lambda(最常用)。
  • 事件 = 受保护的委托,是委托最经典的应用,用于发布-订阅模式。
  • 经典应用场景:UI 事件处理、排序比较器、异步回调、发布-订阅、策略模式。
相关推荐
一直走下去-明1 小时前
简单的http抓包解包完整代码
开发语言·python
caimouse2 小时前
ReactOS 图形系统分析(51):元文件子系统 — metafile.c
c语言·开发语言
学习星球3 小时前
Solid.js 实战:拆解官方 RealWorld 项目
开发语言·javascript·vue.js
OPEN-F3 小时前
Python进阶教程:正则表达式进阶与文本处理
开发语言·python·正则表达式
zhifou1234563 小时前
java 17升级安装
java·开发语言
for_ever_love__3 小时前
python基础语法学习: 面向对象的三大特性
开发语言·python·学习
不会代码的小猴4 小时前
7. JSON
开发语言·c++·笔记·qt·算法·json
沫璃染墨4 小时前
《Qt从零入门系列(六):信号与槽进阶——从多种连接方式到Lambda表达式》
开发语言·c++·qt·代码规范·设计规范·qt5
ZJU_统一阿萨姆5 小时前
【算子开发】环境搭建与第一个CUDA程序
开发语言·人工智能·系统架构