04-C#.Net-委托和事件-学习笔记
一、委托(Delegate)基础
1.1 什么是委托?
委托是一种引用类型,本质上是一个类,继承自 MulticastDelegate。它可以封装方法的引用,类似于 C/C++ 中的函数指针,但更加类型安全。
// 委托的定义
public delegate void NoReturnNoPara();
public delegate void NoReturnWithPara(int x, int y);
public delegate int WithReturnNoPara();
public delegate int WithReturnWithPara(out int x, ref int y);
关键点:
- 委托定义时需要指定参数类型和返回值类型
- 委托本质是类,会被编译器编译成继承自
MulticastDelegate的类 - 委托可以定义在类外部或类内部
1.2 委托的实例化
// 方式1:使用 new 关键字
NoReturnNoPara delegate1 = new NoReturnNoPara(NoReturnNoParaMethod);
// 方式2:直接赋值(语法糖)
NoReturnNoPara delegate2 = NoReturnNoParaMethod;
// 方式3:使用 Lambda 表达式
NoReturnWithPara delegate3 = (x, y) => { Console.WriteLine($"x={x}, y={y}"); };
1.3 委托的执行
// 方式1:使用 Invoke 方法
delegate1.Invoke();
// 方式2:直接调用(语法糖)
delegate1();
// 异步调用(已过时,不推荐)
// delegate1.BeginInvoke(null, null);
1.4 委托的作用和意义
问题场景: 学生问候方式因地域不同而不同(武汉、上海、广东等)
传统方案的问题:
- 方案1(枚举+switch):业务逻辑耦合,方法不稳定
- 方案2(多个独立方法):大量重复代码
委托解决方案:
public delegate void SayHiDelegate();
public void SayHiPerfect(SayHiDelegate sayHiDelegate)
{
Console.WriteLine("问候前,招招手...");
sayHiDelegate.Invoke(); // 执行传入的具体问候逻辑
}
// 使用
student.SayHiPerfect(student.SayHiWuhan);
student.SayHiPerfect(student.SayHiShanghai);
优势:
- 代码稳定:核心方法不需要修改
- 逻辑解耦:不同业务逻辑分离
- 逻辑重用:去除重复代码
二、框架内置委托
2.1 Action 委托
Action 是无返回值的委托,最多支持 16 个参数。
// 无参数
Action action = () => Console.WriteLine("Hello");
// 有参数
Action<int> action1 = (x) => Console.WriteLine(x);
Action<int, string> action2 = (x, y) => Console.WriteLine($"{x}, {y}");
// 最多16个参数
Action<int, int, int, ..., int> action16 = (p1, p2, ..., p16) => { };
2.2 Func 委托
Func 是有返回值的委托,最多支持 16 个输入参数 + 1 个返回值。
// 无参数,有返回值
Func<int> func = () => 100;
// 有参数,有返回值(最后一个泛型参数是返回值类型)
Func<int, int> func1 = (x) => x * 2;
Func<int, string, int> func2 = (x, y) => x + y.Length;
// 最多16个输入参数 + 1个返回值
Func<int, int, ..., int, string> func17 = (p1, p2, ..., p16) => "result";
为什么要有内置委托?
- 统一委托类型,避免定义大量自定义委托
- 减少类型数量(委托本质是类)
- 提高代码可读性和互操作性
三、多播委托(Multicast Delegate)
3.1 概念
所有委托都继承自 MulticastDelegate,可以通过 += 和 -= 操作符注册和移除多个方法。
Action action = DoNothing;
action += DoNothingStatic;
action += new Student().Study;
action += Student.StudyAdvanced;
action += () => Console.WriteLine("Lambda");
// 执行时按注册顺序依次调用
action.Invoke();
3.2 移除方法
action -= DoNothing; // 成功移除
// 注意:以下情况无法移除
action -= new Student().Study; // 失败:不是同一个实例
action -= () => Console.WriteLine("Lambda"); // 失败:Lambda 每次生成不同方法
移除规则:
- 从后往前逐个匹配
- 必须是同一个实例的同一个方法
- 匹配到后移除并停止继续匹配
3.3 异步执行多播委托
// 多播委托不能直接使用 BeginInvoke
// action.BeginInvoke(null, null); // 错误
// 正确做法:遍历委托列表
foreach (Action item in action.GetInvocationList())
{
item.BeginInvoke(null, null); // 每个方法异步执行
}
四、委托的高级应用:嵌套委托(俄罗斯套娃)
4.1 委托嵌套原理
委托可以包装另一个委托,形成多层嵌套,实现 AOP(面向切面编程)。
public delegate void ShowDelegate();
// 核心业务逻辑
ShowDelegate coreMethod = () => Console.WriteLine("核心业务");
// 第一层包装:添加日志
ShowDelegate withLog = () => {
Console.WriteLine("前置日志");
coreMethod.Invoke();
Console.WriteLine("后置日志");
};
// 第二层包装:添加性能监控
ShowDelegate withMonitor = () => {
Console.WriteLine("开始监控");
withLog.Invoke();
Console.WriteLine("结束监控");
};
withMonitor.Invoke();
4.2 结合特性实现自动装配
public abstract class AbstractMethodAttribute : Attribute
{
public abstract ShowDelegate Do(ShowDelegate action);
}
public class BeforeMethodAttribute : AbstractMethodAttribute
{
public override ShowDelegate Do(ShowDelegate action)
{
return new ShowDelegate(() => {
Console.WriteLine("前置处理");
action.Invoke();
Console.WriteLine("后置处理");
});
}
}
// 使用
[BeforeWriteLog]
[BeforeMethod]
public void Method()
{
Console.WriteLine("核心业务");
}
// 自动装配执行
ShowDelegate showMethod = () => methodInfo.Invoke(instance, null);
foreach (var attribute in methodInfo.GetCustomAttributes().Reverse())
{
showMethod = attribute.Do(showMethod);
}
showMethod.Invoke();
应用场景:
- ASP.NET Core 中间件管道
- 日志记录
- 性能监控
- 事务管理
- 权限验证
五、事件(Event)
5.1 事件的定义
事件是特殊的委托,使用 event 关键字修饰。
public class Cat
{
// 委托
public Action MiaoAction = null;
// 事件
public event Action MiaoEvent = null;
public void Miao()
{
Console.WriteLine("喵~");
MiaoEvent?.Invoke(); // 只能在类内部触发
}
}
5.2 委托 vs 事件
| 特性 | 委托 | 事件 |
|---|---|---|
| 本质 | 类 | 特殊的委托 |
| 外部赋值 | 可以 = 赋值 |
只能 += 和 -= |
| 外部调用 | 可以直接调用 | 不能外部调用 |
| 安全性 | 较低 | 较高 |
| 子类访问 | 可以 | 不可以 |
Cat cat = new Cat();
// 委托可以这样操作
cat.MiaoAction = null; // 清空所有订阅
cat.MiaoAction(); // 外部直接触发
// 事件只能这样操作
cat.MiaoEvent += Handler; // 只能添加
cat.MiaoEvent -= Handler; // 只能移除
// cat.MiaoEvent(); // 错误:不能外部触发
// cat.MiaoEvent = null; // 错误:不能赋值
5.3 观察者模式(发布-订阅模式)
核心要素:
- 发布者(Publisher):定义事件并触发
- 订阅者(Subscriber):订阅事件并响应
- 订阅关系:通过
+=建立
示例:猫叫引发的连锁反应
public class Cat
{
public event Action MiaoEvent;
public void Miao()
{
Console.WriteLine("猫:喵~");
MiaoEvent?.Invoke();
}
}
public class Dog
{
public void Wang() => Console.WriteLine("狗:汪汪!");
}
public class Mouse
{
public void Run() => Console.WriteLine("老鼠:快跑!");
}
// 使用
Cat cat = new Cat();
cat.MiaoEvent += new Dog().Wang;
cat.MiaoEvent += new Mouse().Run;
cat.Miao(); // 触发所有订阅者的响应
优势:
- 职责单一:猫只负责叫,不关心后续反应
- 降低耦合:猫不依赖狗、老鼠等类
- 易于扩展:添加新的订阅者不需要修改猫的代码
六、标准事件模式
6.1 标准事件定义
.NET 推荐使用 EventHandler 或 EventHandler<TEventArgs> 定义事件。
// 自定义事件参数
public class PriceChangeEventArgs : EventArgs
{
public int OldPrice { get; set; }
public int NewPrice { get; set; }
}
// 发布者
public class Course
{
private int _price = 5299;
// 标准事件定义
public event EventHandler<PriceChangeEventArgs> PriceChanged;
public int Price
{
get => _price;
set
{
if (value > _price)
{
// 触发事件
PriceChanged?.Invoke(this, new PriceChangeEventArgs
{
OldPrice = _price,
NewPrice = value
});
}
_price = value;
}
}
}
// 订阅者
public class Student
{
public void OnPriceChanged(object sender, EventArgs e)
{
var course = (Course)sender;
var args = (PriceChangeEventArgs)e;
Console.WriteLine($"课程涨价了!从 {args.OldPrice} 涨到 {args.NewPrice}");
Console.WriteLine("赶紧购买!");
}
}
// 使用
Course course = new Course();
course.PriceChanged += new Student().OnPriceChanged;
course.Price = 6299; // 触发事件
6.2 标准事件模式的特点
-
事件处理器签名:
void Handler(object sender, EventArgs e)sender:事件发布者(谁触发的)e:事件参数(携带什么数据)
-
事件参数继承:自定义参数类继承
EventArgs -
命名约定:
- 事件名:动词或动词短语(如
PriceChanged、Click) - 事件参数:
XxxEventArgs
- 事件名:动词或动词短语(如
七、核心概念总结
7.1 委托的使用场景
- 方法内部业务逻辑耦合严重 → 使用委托解耦
- 多个方法有大量重复代码 → 使用委托实现逻辑重用
- 需要回调机制 → 使用委托传递回调方法
- 需要实现策略模式 → 使用委托传递不同策略
7.2 事件的使用场景
- 需要通知多个对象某个动作发生 → 使用事件
- 需要降低类之间的耦合 → 使用事件实现观察者模式
- UI 编程(如 WinForms、WPF)→ 使用事件处理用户交互
- 需要保护委托不被外部随意调用 → 使用事件增强安全性
7.3 记忆口诀
- 委托是盒子:可以装方法,可以传递
- 多播是链条:一个委托串多个方法
- 事件是保险:只能订阅,不能乱调
- 观察者模式:发布订阅,解耦神器
本文来自博客园,作者:龙猫•ᴥ•,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/19738849

浙公网安备 33010602011771号