一、前置基础(MVC学习前提)_核心特性_【C# MVC 前置】委托与事件:从 “小区通知” 看懂 MVC 过滤器的底层逻辑
目录
- 一、先破抽象:用生活例子看懂委托与事件
- 1. 委托:像 “中介” 一样帮你找 “干活的人”
- 2. 事件:像 “小区停水通知” 一样的 “被动响应”
- 二、委托:从基础到多播,代码一步步来
- 1. 基础委托:声明→绑定→调用
- 2. 多播委托:一个中介对接多个工人
- 三、事件:从发布到订阅,模拟 MVC 里的 “通知逻辑”
- 1. 事件完整示例:发布者→订阅者→触发
- 四、委托与事件在 MVC 中的核心应用:过滤器
- 五、新手必踩的 5 个坑,附解决方案
- 坑 1:委托未初始化就调用,报空引用
- 坑 2:多播委托中一个方法抛异常,后续方法不执行
- 坑 3:事件订阅后不取消,导致内存泄漏
- 坑 4:事件和委托的区别搞混,滥用事件
- 坑 5:委托签名不匹配,编译报错
- 六、互动时间
- 关于“委托与事件”,你目前最卡壳的是?
大家好,我是William_cl。今天咱们聊 C# 里最 “抽象但又最核心” 的两个概念 —— 委托与事件。很多学 MVC 的新手会觉得 “过滤器”“事件驱动” 这些词很绕,但其实它们的底层就是委托与事件,搞懂这俩,MVC 里的拦截器、生命周期钩子都能一通百通。
一、先破抽象:用生活例子看懂委托与事件
1. 委托:像 “中介” 一样帮你找 “干活的人”
你家要装修,需要找水电工、木工、油漆工,但你不用一个个去联系 —— 找个装修中介,告诉中介 “我要做什么”,中介帮你对接工人。这里的 “中介” 就是委托,“工人” 是具体的方法,“你告诉中介需求” 就是委托绑定方法,“中介安排工人干活” 就是委托调用方法。
2. 事件:像 “小区停水通知” 一样的 “被动响应”
小区物业要停水,会在业主群发通知(发布事件),业主看到后会做准备:有人囤水,有人提前洗澡,有人无所谓(订阅事件并处理)。这里的 “停水通知” 是事件,“物业” 是事件发布者,“业主” 是事件订阅者,“囤水 / 洗澡” 是事件处理方法。关键区别:委托是 “主动找方法”,事件是 “被动等通知”—— 事件本质是 “受限制的委托”,防止外部乱调用。
二、委托:从基础到多播,代码一步步来
1. 基础委托:声明→绑定→调用
先定义一个 “计算” 委托,能指向 “两个 int 做运算” 的方法:
using System;// 1. 声明委托(定义中介的“业务范围”:只能找“输入两个int,返回int”的工人)
delegate int CalculateDelegate(int a, int b);class Program
{// 2. 定义具体方法(工人:加法、乘法)static int Add(int x, int y) => x + y;static int Multiply(int x, int y) => x * y;static void Main(string[] args){// 3. 绑定委托(中介对接工人)CalculateDelegate calcAdd = Add; // 绑定加法CalculateDelegate calcMult = Multiply; // 绑定乘法// 4. 调用委托(中介安排工人干活)int result1 = calcAdd(3, 5);int result2 = calcMult(3, 5);Console.WriteLine($"3+5={result1}"); // 输出:8Console.WriteLine($"3×5={result2}"); // 输出:15}
}
2. 多播委托:一个中介对接多个工人
委托支持用+=绑定多个方法,调用时会按绑定顺序依次执行(像中介一次安排多个工人干活):
static void Main(string[] args)
{// 多播委托:绑定两个方法CalculateDelegate multiCalc = Add;multiCalc += Multiply; // 再加一个乘法// 调用多播委托:会依次执行Add和MultiplyConsole.WriteLine("多播委托结果:");int result = multiCalc(2, 4); // 注意:只返回最后一个方法的结果(乘法的8)// 输出:// 多播委托结果:// (Add执行了,但返回值被覆盖,只拿到Multiply的8)Console.WriteLine(result); // 输出:8
}
三、事件:从发布到订阅,模拟 MVC 里的 “通知逻辑”
MVC 里的 “Action 执行前拦截”“异常触发通知”,本质就是事件。咱们用 “订单支付成功” 模拟这个过程:支付成功后,要触发 “发短信”“更库存” 两个操作。
1. 事件完整示例:发布者→订阅者→触发
using System;// 1. 先定义委托(事件的“协议”:确定通知的格式)
delegate void OrderPaidDelegate(string orderId);// 2. 事件发布者(订单系统:负责发“支付成功”通知)
class OrderSystem
{// 声明事件(用event关键字修饰委托,限制外部只能订阅/取消,不能直接调用)public event OrderPaidDelegate OnOrderPaid;// 模拟“支付成功”操作,触发事件public void PaySuccess(string orderId){Console.WriteLine($"订单{orderId}支付成功,准备通知...");// 触发事件前先判断:有没有订阅者(避免空引用)OnOrderPaid?.Invoke(orderId); // C#6+语法:等同于if(OnOrderPaid!=null) OnOrderPaid(orderId)}
}// 3. 事件订阅者1(短信服务:收到通知发短信)
class SmsService
{// 事件处理方法(要和委托签名一致)public void SendSms(string orderId){Console.WriteLine($"给订单{orderId}的用户发送支付成功短信");}
}// 4. 事件订阅者2(库存服务:收到通知减库存)
class StockService
{public void ReduceStock(string orderId){Console.WriteLine($"订单{orderId}对应的商品库存减少");}
}// 测试代码
class Program
{static void Main(string[] args){// 创建对象OrderSystem orderSys = new OrderSystem();SmsService sms = new SmsService();StockService stock = new StockService();// 订阅事件(给订单系统的OnOrderPaid绑定处理方法)orderSys.OnOrderPaid += sms.SendSms;orderSys.OnOrderPaid += stock.ReduceStock;// 模拟支付成功:触发事件orderSys.PaySuccess("ORDER123456");// 输出结果:// 订单ORDER123456支付成功,准备通知...// 给订单ORDER123456的用户发送支付成功短信// 订单ORDER123456对应的商品库存减少}
}
四、委托与事件在 MVC 中的核心应用:过滤器
MVC 里的Action 过滤器(比如在 Action 执行前做权限校验,执行后做日志记录),底层就是用委托事件实现的。咱们简化一个 MVC 过滤器的逻辑,让你看明白原理:
简化 MVC 过滤器示例
using System;// 1. 定义过滤器委托(协议:Action执行前后的方法格式)
delegate void ActionExecutingDelegate(string actionName); // Action执行前
delegate void ActionExecutedDelegate(string actionName); // Action执行后// 2. 模拟MVC控制器(包含Action和过滤器事件)
class HomeController
{// 过滤器事件(供外部订阅)public event ActionExecutingDelegate OnActionExecuting;public event ActionExecutedDelegate OnActionExecuted;// 模拟一个Index Actionpublic void Index(){// Step1:Action执行前,触发过滤器事件OnActionExecuting?.Invoke("Index");// Step2:执行Action本身的逻辑Console.WriteLine("执行Home/Index Action的业务逻辑(比如查数据)");// Step3:Action执行后,触发过滤器事件OnActionExecuted?.Invoke("Index");}
}// 3. 自定义过滤器(日志过滤器:订阅事件做日志)
class LogFilter
{// Action执行前的日志public void LogBeforeAction(string actionName){Console.WriteLine($"[日志] Action {actionName} 开始执行,时间:{DateTime.Now:HH:mm:ss}");}// Action执行后的日志public void LogAfterAction(string actionName){Console.WriteLine($"[日志] Action {actionName} 执行完成,时间:{DateTime.Now:HH:mm:ss}");}
}// 测试:模拟MVC请求流程
class Program
{static void Main(string[] args){// 创建控制器和过滤器HomeController controller = new HomeController();LogFilter logFilter = new LogFilter();// 订阅过滤器事件(MVC框架会自动帮你做这一步)controller.OnActionExecuting += logFilter.LogBeforeAction;controller.OnActionExecuted += logFilter.LogAfterAction;// 模拟请求Home/Indexcontroller.Index();// 输出结果(完美模拟MVC过滤器流程):// [日志] Action Index 开始执行,时间:14:30:00// 执行Home/Index Action的业务逻辑(比如查数据)// [日志] Action Index 执行完成,时间:14:30:00}
}
这就是 MVC 过滤器的底层逻辑:控制器在关键节点(Action 执行前 / 后)触发事件,过滤器订阅这些事件,从而实现 “拦截” 和 “扩展” 功能。
五、新手必踩的 5 个坑,附解决方案
坑 1:委托未初始化就调用,报空引用
CalculateDelegate calc = null;
calc(3,5); // 报错:未将对象引用设置到对象的实例
原因: 委托没绑定任何方法,是 null。
解决: 调用前判断 null,或用?.Invoke()(C#6+):
if (calc != null) calc(3,5);
// 或
calc?.Invoke(3,5);
坑 2:多播委托中一个方法抛异常,后续方法不执行
// 故意写一个抛异常的方法
static int Divide(int x, int y) => x / y; CalculateDelegate multiCalc = Add;
multiCalc += Divide; // 绑定除法(如果y=0会抛异常)
multiCalc += Multiply;multiCalc(4, 0); // Divide抛异常,Multiply不会执行
原因: 多播委托按顺序执行,一个方法报错会中断整个调用链。
解决: 拆分成单个委托调用,逐个加 try-catch:
foreach (var method in multiCalc.GetInvocationList())
{try{method.DynamicInvoke(4, 0); // 逐个调用}catch (Exception ex){Console.WriteLine($"方法出错:{ex.Message}");}
}
坑 3:事件订阅后不取消,导致内存泄漏
// WinForm/ASP.NET中常见:页面关闭后,事件还绑定着
public class MyPage : Page
{private OrderSystem _orderSys = new OrderSystem();protected void Page_Load(object sender, EventArgs e){// 订阅事件,但页面关闭时没取消_orderSys.OnOrderPaid += sms.SendSms;}// 忘记写取消订阅的代码// protected void Page_Unload(...) { _orderSys.OnOrderPaid -= sms.SendSms; }
}
原因: 事件发布者(_orderSys)会持有订阅者(MyPage)的引用,即使页面关闭,MyPage也无法被垃圾回收,导致内存泄漏。
解决: 在订阅者销毁时(如页面 Unload、Dispose)取消订阅:
protected void Page_Unload(object sender, EventArgs e)
{_orderSys.OnOrderPaid -= sms.SendSms; // 取消订阅
}
坑 4:事件和委托的区别搞混,滥用事件
// 错误:不需要“通知”的场景用了事件
public event CalculateDelegate MyEvent;
// 实际只是需要一个“执行计算”的委托,用事件反而麻烦(不能直接赋值,只能+=/-=)
区别总结:
场景 | 用委托还是事件? |
---|---|
主动调用多个方法(如计算、聚合) | 委托(多播) |
被动响应通知(如 MVC 过滤器、按钮点击) | 事件 |
坑 5:委托签名不匹配,编译报错
// 委托是int CalculateDelegate(int a, int b)
static string AddString(int x, int y) => $"{x}+{y}={x+y}"; // 返回值是stringCalculateDelegate calc = AddString; // 报错:无法将类型“...AddString”转换为“CalculateDelegate”
原因: 委托的返回值、参数个数 / 类型必须和方法完全一致。
解决: 要么修改方法签名,要么重新定义匹配的委托:
// 重新定义委托(返回值为string)
delegate string CalculateStringDelegate(int a, int b);
CalculateStringDelegate calc = AddString; // 正确
六、互动时间
委托与事件的核心是 “解耦”—— 就像中介帮你对接工人,你不用管工人是谁;MVC 过滤器帮你对接业务逻辑,你不用改 Controller 代码。
你在学 MVC 时,有没有被过滤器的 “执行顺序”“拦截逻辑” 搞晕过?或者在写委托事件时踩过其他坑?欢迎在评论区分享你的经历,我会抽取 2 位读者,送我整理的《MVC 过滤器实战手册》(含 5 个常用过滤器的完整代码)~
下一期咱们聊 “匿名类型与动态类型”,看看它们在 MVC 的 ViewModel 里怎么简化代码,不见不散!
关于“委托与事件”,你目前最卡壳的是?
- 多播委托的异常处理(一个方法报错,后续全中断)
- 事件订阅后的内存泄漏(不清楚何时该取消订阅)
- 委托/事件与MVC过滤器的绑定逻辑(没理清触发顺序)
- 概念区分(分不清委托和事件的使用场景)
- 其他(评论区补充你的具体问题)