【发布时间】:2011-07-17 06:44:16
【问题描述】:
我有一个警告,可以访问字符串变量的已修改闭包。
foreach (string s in splits)
{
regexes.Where(x => x.Pattern.Contains(s));
}
按原样保留代码是否安全?我假设使用 lambda 创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”都将指向不同的内存。
谢谢
【问题讨论】:
我有一个警告,可以访问字符串变量的已修改闭包。
foreach (string s in splits)
{
regexes.Where(x => x.Pattern.Contains(s));
}
按原样保留代码是否安全?我假设使用 lambda 创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”都将指向不同的内存。
谢谢
【问题讨论】:
在这种情况下,它不会伤害您,因为您没有在循环外部的地方使用委托。当然,您在示例中并没有真正做任何任何操作,因此不清楚这是否“代表”您的实际代码。
线程和延迟回调会受到影响,所以是的,它(在某种程度上)是有效的。如果不确定,请修复它:
foreach (string s in splits)
{
var clone = s;
regexes.Where(x => x.Pattern.Contains(clone));
}
澄清一下,只有当捕获的变量在 foreach 循环的正常流程之外使用时,此问题才会痛。当直接在循环内使用时,并且在同一个线程上(没有Parallel),捕获的变量将始终处于预期值。任何会看到随机数据的中断;通常(但不总是)last 值。
注意特别是,这个“延迟委托”包括诸如在查询中构建Where 过滤器之类的东西,因为您会发现它们都使用最后一个值。以下情况会很糟糕:
var query = ...
foreach (string s in splits)
{ // BAD CODE!
query = query.Where(x => x.Foo.Contains(s));
}
任何“更好的方法”(cmets)...好吧,克隆变量技巧具有工作的优势,但这里有一个可爱的技巧 - 修复了上面的“错误代码!”:
query = splits.Aggregate(query, (tmp, s) => tmp.Where(x => x.Foo.Contains(s)));
我个人认为以下内容更容易理解:
foreach (string s in splits)
{
var tmp = s;
query = query.Where(x => x.Foo.Contains(tmp));
}
【讨论】:
我认为使用 lambda 创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”都将指向不同的内存。
创建委托不会评估变量。这就是关于闭包的全部意义:它们关闭了变量。因此,您的所有 lambda 表达式都将捕获相同的变量 s,这可能不是您想要的。它是否影响您的代码取决于何时评估 lambda:如果 Where 的调用已经“执行”了 lambda(然后将其丢弃),那么您应该没问题。如果您的 regexes 对象存储了 lambdas 并在以后使用它们,那么您就有麻烦了。
因此,为了安全起见,首先将值复制到循环内部的局部变量中。
【讨论】:
看起来不安全,我通常会尽可能避免警告,因为它们通常表明潜在的问题。
谷歌搜索 "access to modified closure" 会找到一些看似相关的点击,包括您肯定想仔细查看的 #2 Linq: Beware of the 'Access to modified closure' demon。
【讨论】:
这是一个简单的例子,证明这对于字符串是不安全的,无论是否不可变。
List<Func<int>> lambdas = new List<Func<int>>();
List<string> strings = new List<string>
{ "first", "second", "supercalifragilisticexpialidocious" };
foreach (string s in strings)
{
lambdas.Add(new Func<int>(() => s.Length));
}
Console.WriteLine(lambdas[0]()); //34
Console.WriteLine(lambdas[1]()); //34
Console.WriteLine(lambdas[2]()); //34
但是,除了被 LINQ 的延迟执行所困扰之外,我真的不认为以这种方式使用 lambdas 是一个问题,因为大多数情况下 lambda 的整个生命周期都在 foreach 的范围内。
【讨论】: