访问Modified Closure警告对字符串变量有效吗?

时间:2011-03-17 09:49:07

标签: c# linq lambda closures

我有一个警告,可以访问字符串变量的修改后的闭包。

foreach (string s in splits)
{
    regexes.Where(x => x.Pattern.Contains(s));
}

保持代码的安全性是否安全?我假设使用lambda创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”将对不同的内存进行操作。

由于

4 个答案:

答案 0 :(得分:3)

在这种情况下,它不会对您造成伤害,因为您没有在外部循环的地方使用委托。当然,你并没有真正在示例中使用任何,所以目前还不清楚这是否“代表”实际代码。

线程和延迟回调会受到影响,所以是的(在某种程度上)是有效的。如果不确定,请修复它:

foreach (string s in splits)
{ 
    var clone = s;
    regexes.Where(x => x.Pattern.Contains(clone));
}

为了澄清,当捕获的变量在foreach循环的正常流程之外使用时,此问题仅伤害。当直接在循环中使用并在同一个线程(无Parallel)上时,捕获的变量将始终处于预期值。任何破坏的东西都会看到随机数据;通常(但不总是)最后值。

请注意 这个“延期委托”包括在查询中构建Where过滤器等内容,因为您会发现它们都使用了最后一个值。以下情况会很糟糕:

var query = ...
foreach (string s in splits)
{ // BAD CODE!
    query = query.Where(x => x.Foo.Contains(s));
}

任何“更好的方式”(评论)......好吧,克隆的变量技巧具有工作的优势,但这里有一个可爱的伎俩 - 修复上面的“BAD CODE!”:

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));
}

答案 1 :(得分:2)

  

我假设使用lambda创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”将对不同的内存进行操作。

创建委托不会评估变量。这是关于闭包的全部观点:它们关闭变量。所以,你所有的lambda都将捕获相同的变量s,这可能不是你想要的。如果它影响你的代码取决于lambda被评估的时间:如果Where的调用已经“执行”了lambda(然后扔掉它),你应该没问题。如果你的regexes对象存储lambdas并在以后使用它们,那你就麻烦了。

因此,为了安全起见,首先将值复制到循环内部的局部变量。

答案 2 :(得分:1)

看起来不安全,我一般尽量避免发出警告,因为它们经常表明存在潜在问题。

谷歌搜索"access to modified closure"会带来一系列看似相关的点击,包括#2 Linq: Beware of the 'Access to modified closure' demon您肯定希望仔细查看。

答案 3 :(得分:1)

这是一个简单的例子,证明这对字符串来说是不安全的,不可变的。

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的范围内。