.net 委托(委托链)的若干"陷阱"
陷阱1:“委托链在执行期间可以(根据业务需要)动态增减其中委托实例”
委托链是由委托对象(实例)构成的一个集合。利用委托链,可依次调用集合中委托所代表的全部方法。
"+="、"-="操作符常用来简化委托链的构造,它们分别表示向委托链中增加一个委托对象和从委托链中移除一个委托对象。
委托链构造的便捷性给开发者设下了一个优美的陷阱。有不少同学(包括我自己)认为,在委托链依次执行的过程中,我们可以根据某种逻辑规则来决定是否让委托链继续执行。示例代码如下:
public class Class1 { /// <summary> /// 委托类型 /// </summary> /// <param name="ismove">是否向下执行的标识</param> public delegate void Print(ref string ismove); /// <summary> /// 委托实例 /// </summary> Print p; /// <summary> ///默认构造函数 /// </summary> public Class1() { //添加四个方法 p = new Print(method1);//第一个方法用来实例化委托第一个实例 p += method2; p += method3; p += method4; } /// <summary> /// 开始执行委托链 /// </summary> /// <returns></returns> public string run() { string ismove = "yes"; p(ref ismove); return ismove; } public void method1(ref string ismove) { Console.WriteLine("method1"); if (ismove == "yes")//判断是否要继续向下执行 { } } public void method2(ref string ismove) { Console.WriteLine("method2"); if (ismove == "yes")//判断是否要继续向下执行 { //这里已经处理完所有的逻辑,不需要在调用后面的调用 p -= method3; //移除方法3 p -= method4;//移除方法4 } } /***********************后面的方法都不会被调用******************************************/ public void method3(ref string ismove) { Console.WriteLine("method3"); if (ismove == "yes")//判断是否要继续向下执行 { } } public void method4(ref string ismove) { Console.WriteLine("method4"); if (ismove == "yes")//判断是否要继续向下执行 { } } }
我们对委托链的充满了信心,“肯定”在调用run()方法后可以看到会是:
“
method1
method2
”
那委托链到底表现如何?请看下图:
可以清楚的看到,此时我们已经在陷阱底部了。
仰起头向着洞口喊道“我问天,我问地,我要问问委托我为何不如我心意。”哈哈,还是问问自己吧。
掉进陷阱的原因:
程序猿的高智商。搞软件的都是些聪明人,他们接受新事物快、善于对比类推、懂得举一反三。在看到看到委托如此灵活、便捷的使用方式(附加、移除)时,各种用法都被他们类比、组合了出来。当然,掉进上例中的陷阱就太顺其自然了。
如何自救:
- 用实验来检验我们的自以为之“是”。对待新事物,既要接受又要质疑,通过这个过程深入把握它的本质,最终为我所用。(实验是各求知的利器)
- 从基础原理上了解委托的本质,以保证我们的“自以为”有科学的依据。(可参考 CLR var C#)
陷阱揭秘:
掉进陷阱的不只是初生的小牛犊。陷阱样例来源:博客园知识库中《.NET简谈委托链》的一篇文章,网址链接:click here
委托链调用原理:
- 创建委托(包含委托链)
//添加四个方法 p = new Print(method1);//第一个方法用来实例化委托第一个实例 p += method2; p += method3; p += method4;
1.1 第一行先创建了委托实例p,此时委托p包装了对method1的引用,内部的委托链_invocationList是null。
1.2 “+=”操作符实现将委托添加到委托链。最终p的委托链_invocationList中包含了分别包装着method1、method2、method3、method4的四个委托对象。
- 调用委托(包含委托链)
/// <summary> /// 开始执行委托链 /// </summary> /// <returns></returns> public string run() { string ismove = "yes"; p(ref ismove); return ismove; }
2.1 委托调用方法如p(ref ismove),即委托名(参数)。委托在调用之后会遍历委托链_invocationList依次调用委托链中委托对象所包装的方法来接受参数并执行(若委托链为空时则尝试调用委托对象包装的方法引用)。
2.2 在遍历委托链期间,任何对委托链的增删操作都是暂时无效的。要再本次遍历结束后才会生效。可以这样理解,在委托触发开始执行时,先会将当前委托链复制到一个临时委托链中,然后对临时委托链进行遍历。这是一种保证线程安全的方法。
- 委托的移除
p -= method3; //移除方法3 p -= method4;//移除方法4
3.1 “-=”操作符用于从委托链中移除一个对应的委托对象,如果委托链中有多个对象包装了相同的方法,则移除最后添加委托对象;若链表中未找到包装该方法的对象,则不做处理(无异常)。
通过对委托创建、调用、移除内部过程的简单梳理,不难看出我们曾经自以为的不是,对,问题就在过程“2.2”。
至此,陷阱1“委托链在执行期间可以(根据业务需要)动态增减其中委托实例”分析完了,在method2中移除方法3、4是挡不住委托链依次调用执行的车轮的,只会在再一次调用run时生效。当然,本文只算就事论事的讨论而已,在实际很少会出现这样的应用。因为如果要分步骤执行任务会有其他选择,如自己定义任务队列等。