news 2026/7/27 3:04:57

04-C#.Net-委托和事件-学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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);

优势:

  1. 代码稳定:核心方法不需要修改
  2. 逻辑解耦:不同业务逻辑分离
  3. 逻辑重用:去除重复代码

二、框架内置委托

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 观察者模式(发布-订阅模式)

核心要素:

  1. 发布者(Publisher):定义事件并触发
  2. 订阅者(Subscriber):订阅事件并响应
  3. 订阅关系:通过+=建立

示例:猫叫引发的连锁反应

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 推荐使用EventHandlerEventHandler<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 标准事件模式的特点

  1. 事件处理器签名void Handler(object sender, EventArgs e)

    • sender:事件发布者(谁触发的)
    • e:事件参数(携带什么数据)
  2. 事件参数继承:自定义参数类继承EventArgs

  3. 命名约定

    • 事件名:动词或动词短语(如PriceChangedClick)
    • 事件参数:XxxEventArgs

七、核心概念总结

7.1 委托的使用场景

  1. 方法内部业务逻辑耦合严重→ 使用委托解耦
  2. 多个方法有大量重复代码→ 使用委托实现逻辑重用
  3. 需要回调机制→ 使用委托传递回调方法
  4. 需要实现策略模式→ 使用委托传递不同策略

7.2 事件的使用场景

  1. 需要通知多个对象某个动作发生→ 使用事件
  2. 需要降低类之间的耦合→ 使用事件实现观察者模式
  3. UI 编程(如 WinForms、WPF)→ 使用事件处理用户交互
  4. 需要保护委托不被外部随意调用→ 使用事件增强安全性

7.3 记忆口诀

  • 委托是盒子:可以装方法,可以传递
  • 多播是链条:一个委托串多个方法
  • 事件是保险:只能订阅,不能乱调
  • 观察者模式:发布订阅,解耦神器

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/27 3:04:44

Python3.9镜像指南:快速创建独立环境,避免版本冲突

Python3.9镜像指南&#xff1a;快速创建独立环境&#xff0c;避免版本冲突 1. 为什么需要Python3.9独立环境 在Python开发中&#xff0c;版本冲突是最常见的问题之一。不同项目可能依赖不同版本的Python解释器或第三方库&#xff0c;直接安装到系统环境会导致&#xff1a; 项…

作者头像 李华
网站建设 2026/7/14 14:36:41

Janus-Pro-7B 自动化测试用例生成:基于需求描述的测试脚本创作

Janus-Pro-7B 自动化测试用例生成&#xff1a;基于需求描述的测试脚本创作 最近跟几个测试团队的朋友聊天&#xff0c;他们都在抱怨同一个问题&#xff1a;需求文档写得挺详细&#xff0c;但要把这些需求一条条转化成可执行的测试用例&#xff0c;工作量实在太大了。尤其是敏捷…

作者头像 李华
网站建设 2026/7/14 14:36:44

刷题练习心法之双指针

Q1题目链接&#xff1a;https://leetcode.com/problems/valid-palindrome-ii/ leetcode 680 看到题目的第一思路&#xff1a; 和之前做的125题类似 都是用双指针方法判断字符串是否回文 但是解题思路略有差异 代码随想录之后的想法和总结&#xff1a; 正确的解题思路&#…

作者头像 李华
网站建设 2026/7/14 14:36:41

短剧广告防作弊系统:有效过滤假量、保障广告主与创作者收益

短剧广告变现规模越来越大&#xff0c;刷量、机刷、模拟器、群控、虚假点击等作弊行为也日益猖獗。假量不仅拉低 ECPM、浪费广告预算&#xff0c;更让真实流量主、内容创作者被误伤、收益受损&#xff0c;甚至导致广告主撤量、平台风控关停。一套专业、强硬的短剧广告防作弊系统…

作者头像 李华
网站建设 2026/7/23 16:41:57

通义千问1.5-1.8B-Chat-GPTQ-Int4对话流畅度与逻辑性深度评测报告

通义千问1.5-1.8B-Chat-GPTQ-Int4对话流畅度与逻辑性深度评测报告 最近&#xff0c;一个只有18亿参数的小模型——通义千问1.5-1.8B-Chat-GPTQ-Int4&#xff0c;在社区里引起了不少讨论。大家好奇的是&#xff0c;一个经过量化压缩后的小模型&#xff0c;在真实的对话场景下&a…

作者头像 李华