【问题标题】:Is Access to Modified Closure warnings valid for string variable?访问修改的闭包警告对字符串变量有效吗?
【发布时间】:2011-07-17 06:44:16
【问题描述】:

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

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

按原样保留代码是否安全?我假设使用 lambda 创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”都将指向不同的内存。

谢谢

【问题讨论】:

标签: c# linq lambda closures


【解决方案1】:

在这种情况下,它不会伤害您,因为您没有在循环外部的地方使用委托。当然,您在示例中并没有真正做任何任何操作,因此不清楚这是否“代表”您的实际代码。

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

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

【讨论】:

  • 谢谢,这实际上是我想做的。有没有更好的方法来构建 where 子句?
【解决方案2】:

我认为使用 lambda 创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”都将指向不同的内存。

创建委托不会评估变量。这就是关于闭包的全部意义:它们关闭了变量。因此,您的所有 lambda 表达式都将捕获相同的变量 s,这可能不是您想要的。它是否影响您的代码取决于何时评估 lambda:如果 Where 的调用已经“执行”了 lambda(然后将其丢弃),那么您应该没问题。如果您的 regexes 对象存储了 lambdas 并在以后使用它们,那么您就有麻烦了。

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

【讨论】:

  • 谢谢。如果我做对了,这意味着委托接收到变量的指针,而不是它自己的变量值。
  • @marek:简而言之,是的。编译器实际上做了什么来创建一个帮助类,将你的变量和委托移动到帮助类中,并与你的代码共享该类的一个实例。这样,您的代码和委托都有一个指向同一个变量的“指针”。这里有详细解释:blogs.msdn.com/b/oldnewthing/archive/2006/08/02/686456.aspx
【解决方案3】:

看起来不安全,我通常会尽可能避免警告,因为它们通常表明潜在的问题。

谷歌搜索 "access to modified closure" 会找到一些看似相关的点击,包括您肯定想仔细查看的 #2 Linq: Beware of the 'Access to modified closure' demon

【讨论】:

    【解决方案4】:

    这是一个简单的例子,证明这对于字符串是不安全的,无论是否不可变。

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

    【讨论】:

    • 你用什么 C# 版本试过这个? VS2010 / C# 4 打印 5、6、34。在玩了一些之后,似乎他们已经改变了 foreach-loops 的行为,现在它“按预期”工作了。请注意,问题仍然存在于 for 循环中。
    • 我刚刚用 VS2010/C#4 试过了,还是得到 34、34、34。你确定你没有安装 C#5 测试版,因为这将在C# 5.
    猜你喜欢
    • 2010-12-13
    • 1970-01-01
    • 2012-09-14
    • 2022-10-04
    • 2023-03-21
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    相关资源
    最近更新 更多