在多个链式调用中使用单个lambda表达式变量是否公平?例如:
MyList.Where(i => i.ID > 20).OrderBy(i => i.Name);
i
中使用的Where()
仍然独立于i
中使用的OrderBy()
,或者它们之间可能存在一些隐藏的副作用,所以我必须这样做每个使用不同的变量?另外,您的答案也适用于VB.NET吗?
我问这个是因为我在稍微不同的上下文中读过我不应该直接在LINQ查询中使用foreach变量,而是在循环中创建变量的本地副本。上面的代码中是否也隐藏了一些类似的效果?
答案 0 :(得分:6)
他们是完全独立的。实际上,每次声明一个lambda时,你都声明了范围变量,这些变量是这个labda表达式的局部变量。因此i
中的Where(i => i.ID > 20)
与i
中的OrderBy(i => i.Name)
完全不同。在第一种情况下,i
引用MyList
的随机元素,然后i
引用来自Where
子句的序列的随机元素, MyList
的所有元素的序列ID>20
。
答案 1 :(得分:4)
详细说明一下,这很好:
MyList.Where(i => i.ID > 20).OrderBy(i => i.Name);
这两个名为i
的变量是完全独立的,只存在于各自的lambda表达式的上下文中。但是,这不是:
int i = 0;
MyList.Where(i => i.ID > 20).OrderBy(i => i.Name);
现在,你的lambda中的i
与父范围中定义的i
冲突。
foreach
的问题比原始问题稍微微妙一些。如果你有这个:
foreach (var foo in fooList)
{
var filteredList = MyList.Where(i => i.ID > foo.Id).OrderBy(i => i.Name);
}
这里的问题是因为LINQ使用延迟执行并引用循环变量foo
所以它将创建一个包含foo
的闭包。问题是,它没有复制 foo
它实际上有一个对变量的引用。所以当你最终通过迭代执行你的lambda时,或者:
var bar = filteredList.ToList();
foo
lambda中Where
的值将是foo
现在的值,而不是声明lambda时的值。因此foo
始终是fooList
中的最后一项。复制变量修复了这个问题,因为现在它将关闭那个不同的变量(只存在于循环的那一次迭代)而不是循环变量。
foreach (var foo in fooList)
{
var copy = foo;
var filteredList = MyList.Where(i => i.ID > copy.Id).OrderBy(i => i.Name);
}
答案 2 :(得分:3)
Lambda表达式基本上是编写匿名方法的一种较短方式。因此,lambda示例中的i
与匿名方法中的参数相同。换句话说,它们彼此独立,正如不同方法的参数彼此独立一样。
为了便于阅读,将i
更改为person
或类似内容可能是值得的。例如。 MyList.Where(person => person.ID > 20).OrderBy(person => person.Name);
关于foreach
:在Eric Lippert的博客中了解此背景下的闭包:http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
摘录:
因为()=> v表示"返回变量v"的当前值,而不是"返回值v在创建委托时返回"。闭包关闭变量,而不是值。当方法运行时,显然分配给v的最后一个值是120,所以它仍然具有该值。
答案 3 :(得分:2)
请参阅:Variable Scope in Lambda Expression - MSDN
lambda表达式中引入的变量在。中不可见 外部方法。
因此,对于您的Where
子句,您声明i
仅在Where
子句中可见,因此独立于OrderBy
子句中声明的子句。