为什么委托的减法(- 或 -=)可能出现非预期的结果?(Delegate Subtraction Has Unpredictable Result)
当我们为一个委托写 -=
的时候,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 + c
,s - (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 的一部分,那就需要小心,因为库的使用者可能写出任何一种诡异的代码!这种情况下,换成事件是一个不错的选择。
参考资料
- Code Inspection: Delegate subtractions - Help - ReSharper
- events - “Delegate subtraction has unpredictable result” in ReSharper/C#? - Stack Overflow
为什么委托的减法(- 或 -=)可能出现非预期的结果?(Delegate Subtraction Has Unpredictable Result)的更多相关文章
- [改善Java代码] 避免instanceof非预期结果
建议18: 避免instanceof非预期结果 instanceof是一个简单的二元操作符,它是用来判断一个对象是否是一个类实例的,其操作类似于>=.==,非常简单,我们来看段程序,代码如下: ...
- int.TryParse非预期执行引发的思考 ASP.NET -- WebForm -- 给图片添加水印标记 Windows -- 使用批处理文件.bat删除旧文件
int.TryParse非预期执行引发的思考 问题出现 这天在写一个页面,想谨慎些就用了int.TryParse,结果出问题了. 代码如下: Copy int id = 1000; //Reque ...
- int.TryParse非预期执行引发的思考
问题出现 这天在写一个页面,想谨慎些就用了int.TryParse,结果出问题了. 代码如下: int id = 1000; //Request.QueryString["id"] ...
- MySQL时间盲注五种延时方法 (PWNHUB 非预期解)
转自cdxy师傅:https://www.cdxy.me/?p=789 PWNHUB 一道盲注题过滤了常规的sleep和benchmark函数,引发对时间盲注中延时方法的思考. 延时函数 SLEEP ...
- C语言的可变参数在Linux(Ubuntu)与Windows下注意点
基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...
- Hibernate的查询语言之HQL(二)——Hibernate查询的from字句
from 是最简单的HQL语句,也是最基本的HQL语句.from 关键字后紧跟持久化类的类名.例如: from Person 表明从Person持久化类中取出全部的实例. 大部分时候,推荐位该Pers ...
- [Object Tracking] Overview of algorithms for Object Tracking
From: https://www.zhihu.com/question/26493945 可以载入史册的知乎贴 目标跟踪之NIUBILITY的相关滤波 - 专注于分享目标跟踪中非常高效快速的相关滤波 ...
- 【算法基础】卡尔曼滤波KF
kalman filter KCF 尺度变化是跟踪中比较基本和常见的问题,前面介绍的三个算法都没有尺度更新,如果目标缩小,滤波器就会学习到大量背景信息,如果目标扩大,滤波器就跟着目标局部纹理走了,这两 ...
- CTF_show平台 web题解 part3
web13 题目显示文件上传,各类型上传均提示错误,在使用ctf-scan扫描的时候,发现upload.php.bak. 查看源码: <?php header("content-typ ...
随机推荐
- 缓存技术内部交流_05_Cache Through
参考资料: http://www.ehcache.org/documentation/3.2/caching-patterns.html http://www.ehcache.org/document ...
- [spring]<context:property-placeholder/>
问题: 有些参数在某些阶段中是常量,这些参数在不同阶段之间又往往需要改变,如: 在开发阶段我们连接数据库时的url,username,password等信息 分布式应用中client端的server地 ...
- IP地址分类、私有地址、子网、子网掩码
IP地址分类介绍 这里讨论IPv4,IP地址分成了A类.B类.C类.C类.E类,如下图所示: 解释: A类以0开头,网络地址有7位,主机地址有24位,举例:A类地址:0 10000000 000000 ...
- 1-13 RHEL7-硬盘介绍和磁盘管理
熟悉Linux平台下的存储介质,LVM逻辑卷.RAID 磁盘陈列等 大纲: 1-1 硬盘的分类及使用fdisk分区工具 1-1-1 认识硬盘的分类和特性.SCSI.IDE.SAS.SAT ...
- 闲话__stdcall, __cdecl, __fastcall出现的历史背景以及各自解决的问题
可以认为最先由微软搞出来了__stdcall, 其实就是和WINAPI的声明是一样的,入栈顺序是从右到左,函数返回时,会进行出栈操作. PASCAL语言是非常古老的编程语言,在C语言之前,因此在当时的 ...
- Django之model字段操作
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models import ...
- 一款连接SqlServer的数据库工具
由于自己使用的电脑系统是xp,而服务器上的数据库是SqlServer2012,于是用SqlServer2005管理端操作2012,总是不成功.在网上也百度谷歌了很久,也没有解决,也发了很多问没有找到解 ...
- fegin 调用源码分析
http://techblog.ppdai.com/2018/05/28/20180528/ 这篇文章是相当详细
- eureka -1 - 介绍
eureka ,服务发现注册中心 eureka 包含server, client两部分. eureka server,服务发现组件,各个微服务启动的时候会向server注册自己的信息(ip,hostn ...
- PentesterLab-From SQL Injection to Shell: PostgreSQL edition
一.打开页面,随便点了几下,返现和From SQL Injection to Shell差不多,直奔主题开始注入 由于PostgreSQL与MySQL不同,几个关注点需要注意下 二.order by下 ...