当我们为一个委托写 -= 的时候,ReSharper 会提示“Delegate Subtraction Has Unpredictable Result”,即“委托的减法可能出现非预期的结果”。然而在写为事件写 -= 的时候却并没有这样的提示。然而这个提示是什么意思呢?为什么会“非预期”?为什么委托会提示而事件不会提示?

阅读本文将了解委托的减法。



▲ 委托的减法可能出现非预期的结果

 

ReSharper 的官方帮助文档

例子和现象

从 ReSharper 的提示中,我们可以跳转到官方帮助文档 Code Inspection: Delegate subtractions - Help - ReSharper

官方文档中给出了一个非常典型的 Demo 程序:

static void Main()
{
Action a = () => Console.Write("A");
Action b = () => Console.Write("B");
Action c = () => Console.Write("C");
Action s = a + b + c + Console.WriteLine;
s(); //ABC
(s - a)(); //BC
(s - b)(); //AC
(s - c)(); //AB
(s - (a + b))(); //C
(s - (b + c))(); //A
(s - (a + c))(); //ABC
}

关键就在最后一行的输出结果。由于 s 等于 a + b + cs - (a + c) 却依然输出 ABC,而不是前面例子中就像数学加减法一样的输出。

ReSharper 同时还给出另一个例子,说明委托的减法顺序也可能非预期:

s = a + b + a;
(s - a)(); // AB

它会从尾部减起,而这一点也容易被大家忽视。

原理

究其原因,ReSharper 官方文档也已说明。因为委托保存了一个调用列表,委托的 a + b,是将 b 的调用列表追加到 a 的调用列表之后;而委托的 a - b 是从 a 的调用列表中移除 b 的调用列表子序列。

也就是说,委托的加减其实就是委托调用列表中序列的拼接和子序列的移除

用图来表示这个调用列表的加减过程,可以画成这样。其中 a, b 是委托,x, y, z, w 是调用列表中的每一项。


▲ 调用列表的加减其实就是序列的拼接和子序列的移除

将委托和事件比较

既然 ReSharper 对委托做出了这样的提示,而事件几乎就是委托的封装,那为何事件不给出提示呢?!

带着疑问,我将 ReSharper 官方例子中的 s 改成了事件,其他代码完全一样。

private static event Action s;

static void Main()
{
Action a = () => Console.Write("A");
Action b = () => Console.Write("B");
Action c = () => Console.Write("C");
// 这一句注释掉,因为 s 换成了事件,而事件必须定义在类中。
// Action s = a + b + c + Console.WriteLine;
s(); //ABC
(s - a)(); //BC
(s - b)(); //AC
(s - c)(); //AB
(s - (a + b))(); //C
(s - (b + c))(); //A
(s - (a + c))(); //ABC
}

后面用于代表输出结果的注释我依然没改,因为输出结果真的没变!!!也就是说,理论上使用事件并不能帮助减少委托减法带来的结果不确定性。

但是——事件是观察者模式的一种实现,从设计上说,事件只作通知之用,不确保顺序,也不保证结果。在这个角度上说,如果依然用事件写出上面 demo 那样的“不可预期”代码,那简直不把事件当事件用。

不再用委托减法了吗?

至少从设计模式上说,事件里委托减法的的那些非预期就忽略吧,那么没有定义成事件的那些委托呢?我们需要如何处理减法?

其实,大可不必太担心,因为大多数场合下我们进行委托加法和减法时,都是用一个包含调用列表的委托与其它只有一个调用节点的委托进行加减,通常结果都是符合预期的,也通常不会对顺序敏感。但是,如果委托的减法是库 API 的一部分,那就需要小心,因为库的使用者可能写出任何一种诡异的代码!这种情况下,换成事件是一个不错的选择。


参考资料

为什么委托的减法(- 或 -=)可能出现非预期的结果?(Delegate Subtraction Has Unpredictable Result)的更多相关文章

  1. [改善Java代码] 避免instanceof非预期结果

    建议18: 避免instanceof非预期结果 instanceof是一个简单的二元操作符,它是用来判断一个对象是否是一个类实例的,其操作类似于>=.==,非常简单,我们来看段程序,代码如下: ...

  2. int.TryParse非预期执行引发的思考 ASP.NET -- WebForm -- 给图片添加水印标记 Windows -- 使用批处理文件.bat删除旧文件

    int.TryParse非预期执行引发的思考   问题出现 这天在写一个页面,想谨慎些就用了int.TryParse,结果出问题了. 代码如下: Copy int id = 1000; //Reque ...

  3. int.TryParse非预期执行引发的思考

    问题出现 这天在写一个页面,想谨慎些就用了int.TryParse,结果出问题了. 代码如下: int id = 1000; //Request.QueryString["id"] ...

  4. MySQL时间盲注五种延时方法 (PWNHUB 非预期解)

    转自cdxy师傅:https://www.cdxy.me/?p=789 PWNHUB 一道盲注题过滤了常规的sleep和benchmark函数,引发对时间盲注中延时方法的思考. 延时函数 SLEEP ...

  5. C语言的可变参数在Linux(Ubuntu)与Windows下注意点

    基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...

  6. Hibernate的查询语言之HQL(二)——Hibernate查询的from字句

    from 是最简单的HQL语句,也是最基本的HQL语句.from 关键字后紧跟持久化类的类名.例如: from Person 表明从Person持久化类中取出全部的实例. 大部分时候,推荐位该Pers ...

  7. [Object Tracking] Overview of algorithms for Object Tracking

    From: https://www.zhihu.com/question/26493945 可以载入史册的知乎贴 目标跟踪之NIUBILITY的相关滤波 - 专注于分享目标跟踪中非常高效快速的相关滤波 ...

  8. 【算法基础】卡尔曼滤波KF

    kalman filter KCF 尺度变化是跟踪中比较基本和常见的问题,前面介绍的三个算法都没有尺度更新,如果目标缩小,滤波器就会学习到大量背景信息,如果目标扩大,滤波器就跟着目标局部纹理走了,这两 ...

  9. CTF_show平台 web题解 part3

    web13 题目显示文件上传,各类型上传均提示错误,在使用ctf-scan扫描的时候,发现upload.php.bak. 查看源码: <?php header("content-typ ...

随机推荐

  1. @configurationProperties的写法

  2. 设计模式——迭代器(Iterator)模式

    概述 迭代器模式简单的说(按我目前的理解)就是一个类提供一个对外迭代的接口,方面调用者迭代.这个迭代接口至少包括两个方法:hasNext()--用于判断是否还有下一个,next()--用于取出下一个对 ...

  3. spring的静态代理和动态代理

    Java静态代理 Jdk动态代理 java代理模式 即Proxy Pattern,23种java常用设计模式之一.代理模式的定义:对其他对象提供一种代理以控制对这个对象的访问. 原理: 代理模式的主要 ...

  4. Spring Boot的自动配置的原理

    Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器. 1.1.1.   ...

  5. JavaScript权威指南--词法结构

    使用广泛,所有的浏览器(桌面.手机.屏蔽等等)都配有相应的JavaScript解析器. JavaScript解析器如何工作? 浏览器在读取HTML文件的时候,只有当遇到<script>标签 ...

  6. TileMode(平铺模式) 枚举的成员:

    TileMode(平铺模式) 枚举的成员:   成员名称 说明   FlipX 与 Tile 相同,只不过图块的交替列被水平翻转. 基本图块本身不翻转.   FlipXY FlipX 和 FlipY ...

  7. 通用Mapper相关

    1.通用Mapper中,用@Table来映数据表与实体,其中 name:指定表的名称,例如@Table(name="ls_post") catalog: 指定数据库名称,默认为当前 ...

  8. Linux命令详解-hwclock/chock

    hwclock命令可以用来显示/设置硬件时钟命令. 在Linux中有硬件时钟与系统时钟等两种时钟.硬件时钟是指主机板上的时钟设备,也就是通常可在BIOS画面设定的时钟.系统时钟则是指kernel中 的 ...

  9. idea Controller层编译Mapper层报错

    解决方案:ctrl+alt+s  搜索insp  设置spring的编译Error改为Warning就可以了

  10. CF911D

    题解: 简单的奇偶判断 代码: #include<bits/stdc++.h> using namespace std; ; int n,a[N],ans,m,p,q; int main( ...