访问修改后的闭包 - ref int

时间:2012-09-22 18:18:55

标签: c#-4.0 lambda resharper resharper-6.0

int count = itemsToValidate.Count;
foreach(var item in itemsToValidate)
{
   item.ValidateAsync += (x, y) => this.HandleValidate(ref count);
}

private void HandleValidate(ref int x)
{
  --x;
  if (x == 0)
  {
       // All items are validated.
  }
}

对于上面的代码,resharper抱怨“访问Modified Closure”。如果我将其更改为对象类型,则不会这样做。为什么这是一个闭包,即使我经过ref?

1 个答案:

答案 0 :(得分:4)

这种情况一直发生

ReSharper警告您,count被您分配为“验证完成”事件处理程序的lambda隐式捕获,并且它的值可能在创建lambda的时间(即分配时)之间发生变化事件处理程序)以及调用它的时间。如果发生这种情况,lambda将看不到人们直觉所期望的值。

一个例子:

int count = itemsToValidate.Count;
foreach(var item in itemsToValidate)
{
   item.ValidateAsync += (x, y) => this.HandleValidate(ref count);
}

// afterwards, at some point before the handlers get invoked:
count = 0;

在这种情况下,处理程序会将count的值读作0而不是itemsToValidate.Count - 这可能被称为“显而易见”,但对于许多不熟悉的开发人员来说,这是令人惊讶和反直觉的lambdas的机制。

我们通常会像这样解决它

“关闭R#up”的常用解决方案是将捕获的变量移动到内部作用域中,在该作用域中它很容易访问,并且R#可以证明在评估lambda之前不能修改它:

int count = itemsToValidate.Count;
foreach(var item in itemsToValidate)
{
   int inner = count; // this makes inner impossible to modify
   item.ValidateAsync += (x, y) => this.HandleValidate(ref inner);
}

// now this will of course not affect what the lambdas do
count = 0;

但你的情况很特别

您的特定情况是一个相对罕见的情况,您明确地想要这种行为,并且使用上述技巧实际上会使程序行为不正确(您需要捕获的引用指向相同的计数)

正确的解决方案:使用R#识别的特殊行注释禁用此警告。