代码改变世界

【More Effective C#】区分早期执行和晚期执行

2010-10-20 08:19  空逸云  阅读(2164)  评论(11编辑  收藏  举报

             Lambda的出现.使我们除了可以使用"古老"的命令式代码(imperative code),还可以使用声明式代码(declarative code),可以让我们作出更为灵活的实现与工程实践.

命令式代码

顾名思义,命令式代码如同发出命令般,一步一步地给出了完成指定工作需要的各个步骤.并且步骤是不可逆的.

object answer = DoSomeThing(Method1(), Method2(), Method3());

上面的代码,在运行时,将按照如下的顺序执行,

(1)调用Method1,生成DoSomeThing所需的第一个参数.

(2)调用Method2,生成DoSomeThing所需的第二个参数.

(3)调用Method3,生成DoSomeThing所需的第三个参数.

(4)使用调用得到的三个参数调用DoSomeThing,得到结果.

我们都应该很熟悉这种风格的代码,必须先按部就班的计算出所需的参数,然后一次性传递给调用方法.该代码包含了一系列描述步骤,遵循这种步骤才能得到我们所期待的结果.

注意:命令式模型做会调用所有的三个方法.三个方法中可能进行的额外操作也会且仅会发生一次.

声明式代码

声明式代码是解释性的,它定义了将要完成什么工作.Lambda表达式所引入的延迟查询,完全改变了原始代码执行流程.例如,

object answer = DoSomeThing(() => Method1(), () => Method2(), () => Method3());

看起来和上面的例子并无多大区别,但是它们的执行顺序却全然不同.该代码的执行顺序如下

(1)调用DoSomeThing(),传入可以调用Method1,Method2,Method3的Lambda表达式.

(2)在DoSomeThing中,仅在需要Method1的执行结果时,才会调用Method1.

(3)在DoSomeThing中,仅在需要Method2的执行结果时,才会调用Method2.

(4)在DoSomeThing中,仅在需要Method3的执行结果时,才会调用Method3.

(5)Method1,Method2,Method3可能会以任意的顺序调用,并调用任意次(多次或零次)

注意:只有在需要某个方法的计算结果时,才会调用该方法.声明式模型可能会也可能不会调用某个甚至所有方法,也可能多次执行同一个方法,所以,在多次运行同一个程序时,你可能将得到不同的结果.这取决于方法的具体实现.

实现的选择

         我们已经弄清楚了什么是早期执行,什么是延迟执行,除了执行顺序的不同之外.它们还有什么区别?二者最主要的区别是,一个是准备数据,一个是准备方法.那么两者之间该如何抉择,什么时候该使用早期执行,什么时候该使用延迟执行.一个比较有效的建议是:

如果求值过程中不修改任何全局状态(如全局变量等).那么我们可以使用延迟求值,否则,每次的调用我们都可能得不到预期的结果.再者,你需要获取的数据是否不变的,如你的值是从缓存中获取,那我们没必要使用延迟求值,一句话,决定选择早期求值和延迟求值的关键在于你想要实现的语义.

         延迟求值的一个很好的例子是LINQ2SQl,若每次的求值都是早期执行,每次执行一条Lambda表达式将执行一次,服务器将多次繁忙的处理你这"一次"的请求,延迟求值直到需要所需的数据时才执行一次.大大的减轻了服务器的负担.

       更多的时候.我们编写程序时,更应该考虑数据做为参数和方法作为参数两者之间的权衡.所占用的空间不大,而传入数据将更有优势,相反,所数据输出的空间可能会非常大,并且你不需要完整的数据.那采用方法参数则更胜一筹.若难以判断与权衡.不妨试试对两者不同应用场景的性能对比从而挑选出更适合的方式.