前言

写这么多年 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的更多相关文章

  1. Delegate & Event

    Long time without coding,貌似对programming都失去了曾有的一点点sense了,今日有空再细瞄一下.net的委托和事件. Delegate 首先,委托用于引用一类具有相 ...

  2. event & EventHandler

    [event & EventHandler] 在老C#中EventHandler指的是一个需要定义一个delegate,这个delegate是回调的规范.例如: public delegate ...

  3. C# delegate event func action 匿名方法 lambda表达式

    delegate event action func 匿名方法 lambda表达式 delegate类似c++的函数指针,但是是类型安全的,可以指向多个函数, public delegate void ...

  4. [UE4] C++实现Delegate Event实例(例子、example、sample)

    转自:http://aigo.iteye.com/blog/2301010 虽然官方doc上说Event的Binding方式跟Multi-Cast用法完全一样,Multi-Cast论坛上也有很多例子, ...

  5. [C#] Delegate, Multicase delegate, Event

    声明:这篇博客翻译自:https://www.codeproject.com/Articles/1061085/Delegates-Multicast-delegates-and-Events-in- ...

  6. C# delegate & event

    public delegate void MyDelegate(string mydelegate);//声明一个delegate对象 //实现有相同参数和返回值的函数        public v ...

  7. Delegate&Event

    Delegate 1.基本类: public class Student { public int Id { get; set; } public string Name { get; set; } ...

  8. Delegate event 委托事件---两个From窗体使用委托事件

    窗体如下:   public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void b ...

  9. 委托与事件--delegate&&event

    委托 访问修饰符 delegate 返回值 委托名(参数); public delegate void NoReturnNoPara(); public void NoReturnNoParaMeth ...

  10. delegate, event - 里面涉及的参数类型必须完全一致,子类是不行的

    public void TestF() { Test += Fun; } public void Fun(Person p) { }  // 如 Person变成 SubPerson,则报错..pub ...

随机推荐

  1. 写了一个json小工具,希望大家体验(Mac平台)

    用rust写了一个json小工具"JSON PICKER",欢迎大家试用: https://github.com/davelet/json-picker/releases/tag/ ...

  2. EXPLAIN sql优化方法

    select A . id , A . title , B . title from jos_content   A left join jos_categories B on A . catid = ...

  3. Django 通过自定义context_processors实现自定义tag

    通过自定义context_processors实现自定义tag by:授客 QQ:1033553122 测试环境 Win7 Django 1.11 实践 步骤1 应用根目录下,新建自定义context ...

  4. Linux 破解mysql密码

    mysql忘记密码怎么办 [root@master ~]# mysql -uroot -pHuawei123123$ mysql: [Warning] Using a password on the ...

  5. BCLinux 8.2安装配置图解教程--龙蜥社区国产移动云系统

    社区镜像下载地址:https://openanolis.cn/download 安装参考地址:https://www.osyunwei.com/archives/13017.html 1安装系统 界面 ...

  6. Tomcat日志信息有乱码的处理方法

    1.问题描述 1.1.Idea中的tomcat日志有乱码 1.2.直接启动tomcat的日志有乱码 1.3.原因分析 问题是由于tomcat使用的编码和操作系统使用的编码不一致导致: Windows1 ...

  7. 【Eclipse】入门使用

    Eclipse界面简单概述 第一次启动时,工作空间的选择 工作界面的介绍: 选项条 工具栏 工程浏览窗口 工程大纲窗口 控制台输出窗口 在窗口选项中悬浮放在Show View选项中可以查看所有的窗口 ...

  8. NVIDIA具身机器人实验室 —— GEAR —— Generalist Embodied Agent Research —— NVIDIA机器人实验室

    相关: https://www.youtube.com/watch?v=jbJPG2H8hn4

  9. 进程的CPU绑定是否有意义 —— 进程的 CPU 亲和性

    好多年前就学习过 进程的 CPU 亲和性这个概念,说直白些就是CPU的进程绑定,也就是指定某个进程绑定到某个CPU核心上,以此提高进程切换时缓存的命中率,加快进程的运算速度. 虽然在编程的时候中会遇到 ...

  10. 【转载】 拒绝遗忘:高效的动态规划算法 —— “到底什么是动态规划”—— An intro to Algorithms: Dynamic Programming

    原文地址(英文): https://medium.freecodecamp.org/an-intro-to-algorithms-dynamic-programming-dd00873362bb   ...