当我们为一个委托/事件写 -=
的时候,ReSharper 会提示“Delegate Subtraction Has Unpredictable Result”,即“委托的减法可能出现非预期的结果”。
不用担心! ReSharper警告的第一部分只适用于删除委托的列表。在你的代码中,你总是在删除一个单独的委托。第二部分是关于删除重复委托后的委托顺序。一个事件并不保证其订阅者的执行顺序,所以它也不会真正影响你。
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
的调用列表子序列。
也就是说,委托的加减其实就是委托调用列表中序列的拼接和子序列的移除。
后面用于代表输出结果的注释我依然没改,因为输出结果真的没变!!!也就是说,理论上使用事件并不能帮助减少委托减法带来的结果不确定性。
但是——事件是观察者模式的一种实现,从设计上说,事件只作通知之用,不确保顺序,也不保证结果。在这个角度上说,如果依然用事件写出上面 demo 那样的“不可预期”代码,那简直不把事件当事件用。
不再用委托减法了吗?至少从设计模式上说,事件里委托减法的的那些非预期就忽略吧,那么没有定义成事件的那些委托呢?我们需要如何处理减法?
其实,大可不必太担心,因为大多数场合下我们进行委托加法和减法时,都是用一个包含调用列表的委托与其它只有一个调用节点的委托进行加减,通常结果都是符合预期的,也通常不会对顺序敏感。但是,如果委托的减法是库 API 的一部分,那就需要小心,因为库的使用者可能写出任何一种诡异的代码!这种情况下,换成事件是一个不错的选择。
Code Inspection: Delegate subtractions - Help | ReSharper
events - "Delegate subtraction has unpredictable result" in ReSharper/C#? - Stack Overflow