前言

写这么多年 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. 全网最适合入门的面向对象编程教程:09 类和对象的Python实现-类之间的关系,你知道多少?

    全网最适合入门的面向对象编程教程:09 类和对象的 Python 实现-类之间的关系,你知道多少? 摘要: 本文主要对类之间的关系进行了基本介绍,包括继承.组合.依赖关系,并辅以现实中的例子加以讲解, ...

  2. oeasy教您玩转vim - 3 - # 打开文件

    打开文件 回忆上节课内容 什么不会,就 :help 什么 命令有完整和简写两种模式 :help 和 :h :quit 和 :q 注意:是左右手小拇指配合输入的 vim 有 6 种基本模式 但是我们还没 ...

  3. Doris failed to initialize storage reader. tablet=106408, res=[NOT_IMPLEMENTED_ERROR]to be implemented

    Apache Doris 2.3 以下的版本会存在一个 bug,导致数据在合并时存在异常,在后续查询该字段数据时会提示 [1105] [HY000]: errCode = 2, detailMessa ...

  4. JavaScript实现防抖函数

    什么是防抖?防抖就是避免快速多次点击后执行过多的函数调用,就是本来你点击支付宝支付后不小心在点击一次,导致支付函数被调用了两次,还都执行了,付了两次钱. 防抖函数的思想就是将函数延迟调用,延迟时间内不 ...

  5. 基于.NET开源、强大易用的短链生成及监控系统

    前言 今天大姚给大家分享一个基于.NET开源(MIT License).免费.强大易用的短链生成及监控系统:SuperShortLink. 项目介绍 SuperShortLink是一个基于.NET开源 ...

  6. 【Hibernate】03 配置文件 & API

    映射器文件: - 字段的Column属性可以不写缺省,这将表示和实体类的属性标识一样 - type 属性用于声明表字段在Java中的类型,这个属性可不写缺省,自动匹配 Hibernate 4个核心AP ...

  7. 【Vue】Re16 Router 第三部分(懒加载、子路由)

    一.配置路由懒加载 懒加载的原因: 因为组件不断的增加,项目的路由会越来越多 打包后的文件越来越大,当超过IO读写的瓶颈时,项目加载就很慢了 所以需要将路由文件分离,在被调用时进行加载 分析路由ind ...

  8. 如何使用深度学习技术探测代码逻辑死循环 —— 浪潮集团的“公开号CN117271314A”专利

    专利公开号: CN117271314A 新闻链接: https://mbd.baidu.com/newspage/data/landingsuper?context={"nid"% ...

  9. 洛谷P1226 【模板】快速幂

    1.快速幂模板 前置知识 一个数字n,它的二进制位数一定是log2n向下取整+1: 快速幂模板代码 这段代码实现了快速幂算法(Exponentiation by squaring),用来计算 ( an ...

  10. 5分钟教你使用idea调试SeaTunnel自定义插件

    在用Apache SeaTunnel研发SM2加密组件过程中,发现社区关于本地调试SeaTunnel文章过于简单,很多情况没有说明,于是根据自己遇到问题总结这篇文档.SeaTunnel本地调试官方文档 ...