C# – delegate, event, EventHandler
前言
写这么多年 C#, 我从来没有写过 EventHandler. 我想应该是因为我没有用 C# 开发过前端的关系, 绝对不是我技术不行哦.
这篇就补上一个学习笔记呗.
参考
C#知识点讲解之C#delegate、event、Action、EventHandler的使用和区别
介绍
EventHandler 就是观察者模式的实现, 你可以把它完全当作前端 DOM addEventListener 去理解. (当然前端的还有冒泡概念, 会更加复杂一点)
delegate
在理解 EventHandler 之前, 要先了解什么是 delegate (委托).
C# 不像 JS 那样, 函数一等公民. 天生就可以当变量, 参数使用.
在 C# 函数通常是作为类的方法来使用的, 传递的变量, 参数则是对象, 而不是函数(方法).
而 delegate 则赋予了 C# 直接传递函数的能力. 函数变一等公民了.
定义函数类型
public delegate void MyMethod(string name, int age);
这个定义不需要在 class 内哦
参数是一个函数
public class MyClass
{
// 参数是一个函数
public void Run(MyMethod myMethod)
{
myMethod("name", 11);
}
}
参数是一个变量
public static void Main()
{
var myClass = new MyClass();
// 变量是一个函数
MyMethod myMethod = (name, age) =>
{
Console.Write($"{name}, {age}");
};
myClass.Run(myMethod);
}
上面这些代码完全可以用 TypeScript 来实现.
// 定义函数类型
interface MyMethod {
(name: string, age: number) : void
} class MyClass {
// 参数是一个函数
run(myMethod: MyMethod): void {
myMethod('name', 11);
}
}
const myClass = new MyClass();
// 变量是一个函数
const myMethod: MyMethod = (name, age) => {
console.log(`${name}, {age}`);
}
myClass.run(myMethod);
delegate as event
delegate 有一个很奇葩的能力, 那就是它可以用来实现观察者模式. 这点有点像 es5 的 function 被用来实现 class 一样 (没错, 就是来乱的)
我们换一下 class name
public delegate void MyEventHandler(string name, int age);
public class MyElement
{
public MyEventHandler? MyEventHandlers { get; set; }
}
想象它是 DOM, 我们想 addEventListener
var myElement = new MyElement();
MyEventHandler myEventHandler1 = (name, age) => { };
MyEventHandler myEventHandler2 = (name, age) => { };
MyEventHandler myEventHandler3 = (name, age) => { };
myElement.MyEventHandlers += myEventHandler1;
myElement.MyEventHandlers += myEventHandler2;
myElement.MyEventHandlers += myEventHandler3;
myElement.MyEventHandlers -= myEventHandler3;
myElement.MyEventHandlers?.Invoke("name", 11);
+= 就是 addEventListener
-= 就是 removeEventListener
= 就是覆盖, 相等于 remove all + add new one.
如果函数有返回值, 那么最后一个返回值会是 Invoke 的 return value. 当然这个是不正确的, 观察者发布不应该会收到反馈的.
event
delegate as event 太别扭了. 于是 C# 有了一个新概念叫 event, 它就类似 es6 class 的出现那样, event 专注在观察者模式的实现.
public delegate void MyEventHandler(string name, int age);
public class MyElement
{
public event MyEventHandler? MyEventHandlers;
}
delegate 函数类型定义依然是需要的, 但是 MyElenent 的函数属性多了 event keyword 在前面. 这样就声明了这个属性是用来搞观察者模式的, 而不仅仅是一个函数属性.
var myElement = new MyElement();
MyEventHandler myEventHandler1 = (name, age) => { };
MyEventHandler myEventHandler2 = (name, age) => { };
MyEventHandler myEventHandler3 = (name, age) => { };
myElement.MyEventHandlers += myEventHandler1;
myElement.MyEventHandlers += myEventHandler2;
myElement.MyEventHandlers += myEventHandler3;
myElement.MyEventHandlers -= myEventHandler3;
myElement.MyEventHandlers = myEventHandler3; // 报错! event 只可以 += 和 -= 不可以 =
myElement.MyEventHandlers?.Invoke("name", 11); // 报错! dispatch 只可以让 MyElement 内部实现.
最后两行会报错. "=" 的效果是 override 全部 handlers 这个操作有点过分了. 所以 event 出现后也平昌了这过大的能力.
另一点是, 外部不再直接 Invoke handlers, 取而代之的是应该要通过一个 Dispatch 操作去完成. 这也比较符合大家对观察者模式的实现和使用直觉.
public class MyElement
{
public event MyEventHandler? MyEventHandlers;
public void Dispatch()
{
MyEventHandlers?.Invoke("name", 11);
}
}
EventHandler
event 已经算及格了. 但是 C# 任然搞了一个更上层的语法 EventHandler. 它又更贴近观察者模式的实现了.
public class MyEventArgs : EventArgs
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
首先 delegate 不需要了. 我们只定义传递的 parameters (EventArgs) 就好. 彻底和 delegate 分家 (不可能再混淆了)
public class MyElement
{
public event EventHandler<MyEventArgs>? MyEventHandlers;
public void Dispatch()
{
MyEventHandlers?.Invoke(this, new MyEventArgs { Name = "name", Age = 11 });
}
}
没有了 delegate 就需要有个替代, 那就是 EventHandler 类. 这是是 C# build-in 的.
Invoke 的时候, 需要把 obj 和 args 传入.
使用如下
var myElement = new MyElement();
EventHandler<MyEventArgs> myEventHandler1 = (obj, args) => { };
EventHandler<MyEventArgs> myEventHandler2 = (obj, args) => { };
EventHandler<MyEventArgs> myEventHandler3 = (obj, args) => { };
myElement.MyEventHandlers += myEventHandler1;
myElement.MyEventHandlers += myEventHandler2;
myElement.MyEventHandlers += myEventHandler3;
myElement.MyEventHandlers -= myEventHandler3;
myElement.Dispatch();
这一套下来, 和 DOM – Event Listener 真的很像了. 原来 C# 这么多年就是用这套来搞前端的丫.
Cancelation
观察者模式再发布 event 后, 大家唯一的沟通方式就是传递的 event 对象.
比如 DOM 的 event.preventDefault 用来阻止游览器默认事件处理就是利用了这一点.
那 C# 当然也可以实现这些. 毕竟 event 对象是我们自己定义的嘛.
上面我继承了 C# build-in 的 EventArgs, 它还有一个 CancelEventArgs.
里面有一个 Cancel 属性, 你可以把它当成 preventDefault 看待.
来一个完整版的:
public class MyEventArgs : CancelEventArgs
{
public string Name { get; set; } = "";
public int Age { get; set; }
} public class MyElement
{
public event EventHandler<MyEventArgs>? MyEventHandlers;
public void Dispatch(MyEventArgs args)
{
MyEventHandlers?.Invoke(this, args);
}
}
public static class Program
{
public static void Main()
{
var myElement = new MyElement();
EventHandler<MyEventArgs> myEventHandler1 = (obj, args) => { args.Cancel = true; };
EventHandler<MyEventArgs> myEventHandler2 = (obj, args) => { if (args.Cancel) return; };
myElement.MyEventHandlers += myEventHandler1;
myElement.MyEventHandlers += myEventHandler2;
var args = new MyEventArgs { Name = "Derrick", Age = 11 }
myElement.Dispatch(args);
if (args.Cancel)
{
// skip
}
}
}
结语
本篇简单介绍了 C# 实现的观察者模式. 通常这些都是用来处理前端事件. 当然也可以用在任何你想使用观察者模式的地方.
今天, 我是意外的在 HtmlSanitizer 源码中看见了它使用 EventHandler, 好奇下才认真的研究了一下. 顺便分享一下

如果我们想 whitelist 一下 attributes 不要被 sanitize, 除了直接提供一个 list 之外, 它还提供了一个方式.
那就是 addEventListener + preventDefault. 它在 reomove attribute 前会先 dispatch. 如果发现 cancel 那它就不 remove attribute 了.
这就类似与 click <a> element, 游览器的行为是跳转, 但是如果我们 addEventListener + preventDefault 那它就不跳了.
同样的思路.
C# – delegate, event, EventHandler的更多相关文章
- Delegate & Event
Long time without coding,貌似对programming都失去了曾有的一点点sense了,今日有空再细瞄一下.net的委托和事件. Delegate 首先,委托用于引用一类具有相 ...
- event & EventHandler
[event & EventHandler] 在老C#中EventHandler指的是一个需要定义一个delegate,这个delegate是回调的规范.例如: public delegate ...
- C# delegate event func action 匿名方法 lambda表达式
delegate event action func 匿名方法 lambda表达式 delegate类似c++的函数指针,但是是类型安全的,可以指向多个函数, public delegate void ...
- [UE4] C++实现Delegate Event实例(例子、example、sample)
转自:http://aigo.iteye.com/blog/2301010 虽然官方doc上说Event的Binding方式跟Multi-Cast用法完全一样,Multi-Cast论坛上也有很多例子, ...
- [C#] Delegate, Multicase delegate, Event
声明:这篇博客翻译自:https://www.codeproject.com/Articles/1061085/Delegates-Multicast-delegates-and-Events-in- ...
- C# delegate & event
public delegate void MyDelegate(string mydelegate);//声明一个delegate对象 //实现有相同参数和返回值的函数 public v ...
- Delegate&Event
Delegate 1.基本类: public class Student { public int Id { get; set; } public string Name { get; set; } ...
- Delegate event 委托事件---两个From窗体使用委托事件
窗体如下: public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void b ...
- 委托与事件--delegate&&event
委托 访问修饰符 delegate 返回值 委托名(参数); public delegate void NoReturnNoPara(); public void NoReturnNoParaMeth ...
- delegate, event - 里面涉及的参数类型必须完全一致,子类是不行的
public void TestF() { Test += Fun; } public void Fun(Person p) { } // 如 Person变成 SubPerson,则报错..pub ...
随机推荐
- ASP.NET Core中创建中间件的几种方式
前言 今天我们一起来盘点一下在ASP.NET Core应用程序中添加和创建中间件常见的四种方式. 中间件介绍 ASP.NET Core中间件(Middleware)是用于处理HTTP请求和响应的组件, ...
- [oeasy]python0002_终端_CLI_GUI_编程环境_游戏_真实_元宇宙 🥊
回忆 上次 了解了 python 语言的特点 历史悠久 功能强大 深受好评 已成趋势 3大主流操作系统 mac windows linux 添加图片注释,不超过 140 ...
- [MAUI 项目实战] 笔记App:程序设计
前言 有人说现在记事类app这么多,市场这么卷,为什么还想做一个笔记类App? 一来,去年小孩刚出生,需要一个可以记录喂奶时间的app,发现市面上没有一款app能够在两步内简单记录一个时间,可能iOS ...
- JavaScript 常用 Web APIs
Web APIs Web APIs DOM 页面文档对象模型 DOM 树 获取元素 事件 监听事件 事件解绑 操作元素 样式属性操作 结点操作 结点关系 创建节点 DOM 事件流 事件对象 常用鼠标事 ...
- Pytest Pytest源码分析
Pytest源码分析 By:授客 QQ:1033553122 测试环境 pytest 5.4.3 测试脚本mytest.py #!/usr/bin/env python # -*- coding:ut ...
- pycharm中可以运行脚本(只在控制台运行,Debugger不运行,设置的断点没用)但是不能debug脚本
以前用的时候好好地,但是最近上班突然脚本就不能debug了,debug直接报错,如下所示 上网查过该有的原因: 1.在pycharm中两个地方设置成utf-8,页面右下角和File>settin ...
- c++ 快速复习第一部份
去年有事无事学过一c++ ,,由于工作用不上,学来没有用,所以学得乱七八的,最近需要复习一下,因为最近想学习一下硬 件相关 第一 引用头文件和自定义头 #include <iostream& ...
- Scratch全套Q版三国人物角色素材包免费下载
全新Q版三国人物角色素材包,内含142张细腻可爱的Q版风格图片,涵盖三国名将.士兵.场景等丰富元素,为scratch爱好者提供多样选择,适合各类三国主题创作. 免费下载:www.xiaohujing. ...
- 【Hearts Of Iron IV】钢铁雄心4 安装笔记
一.解决Steam购买游戏和下载问题 我可能是正版受害者 Steam平台这个游戏本体是购买锁国区的 然后在淘宝上面买激活码激活的 都激活过了的Key,所以放出来也无所谓了 钢铁雄心4学员版本体:7B0 ...
- Ubuntu系统下python模块graphviz运行报错:graphviz.backend.execute.ExecutableNotFound: failed to execute PosixPath(‘dot‘)
代码中需要运行python模块graphviz,安装: pip install graphviz 运行后报错: graphviz.backend.execute.ExecutableNotFound: ...