闭包的副作用

  • Jonathan Allen
  • 王正

2007 年 9 月 30 日

话题:.NET语言 & 开发架构

闭包并不是新概念,在 LINQ 的使用中已经证明了它难以置信地实用。但是,在它使用时如果破坏了封装,确实会带来明显的副作用。当把两个似乎无关的功能放在一起使用,就会出现意料不到的结果。

闭包允许函数把它们的本地变量共享给定义在这些函数内部的匿名函数。这些匿名函数通常被称作 lambda 表达式,对于创建由 LINQ 暴露的强类型查询语句来说是必不可缺的。

Dustin Campbell 在对 LINQ 的实验中,发现查询语句在执行的时候能够被改变。的确如此,通过修改用在闭包代码中的本地变量,用于 while 子句的功能也就被修改了。如果这是在查询语句执行的时候改变的话,那么相应的查询语句也会有对应的变化和结果。

Dustin 使用这个技巧去创建一个仅仅返回不重复项目的查询语句。最初,where 子句是"m.Name != filter"。每次当一个条目被返回,filter 的值随着条目的值改变。在这种情况下,查询语句能够成功地创建一个不重复的列表。

然而,这个技巧也是及其脆弱的。在 Dustin 的例子中,列表必须在 where 子句调用前先排好序。如果不这样做,过滤操作会在所有条目被返回前触发,这样也就没有机会去改变查询语句的行为了。因为 where 和 order by 子句可以以任意次序先后出现,这个功能在 LINQ 中是支持的。

Dustin 没有提到的是,这个情况不会出现在所有的 LINQ Provider 中。那些要把查询语句交给像 LINQ to SQL 这样外部语法引擎的 Provider,就没有机会改变 where 子句了。如果采用并行 LINQ(Parallel LINQ),情况会更糟,因为在多个线程运行的时候,任何对 where 子句的改变都会引发竞态条件(race condition)。

当然,正确的方法应该是只调用Disctinct()就可以了。尽管这些技巧在理论上很有意思,但它们肯定会引发一些微妙的 bug,而且也很容易收到框架中变化的影响。

查看英文原文:The Dark Side of Closures

.NET语言 & 开发架构