views:

200

answers:

5

I was hoping someone could explain to me what bad thing could happen in this code, which causes ReSharper to give an 'Access to modified closure' warning:

bool result = true;

foreach (string key in keys.TakeWhile(key => result))
{
    result = result && ContainsKey(key);
}

return result;

Even if the code above seems safe, what bad things could happen in other 'modified closure' instances? I often see this warning as a result of using LINQ queries, and I tend to ignore it because I don't know what could go wrong. ReSharper tries to fix the problem by making a second variable that seems pointless to me, e.g. it changes the foreach line above to:

bool result1 = result;
foreach (string key in keys.TakeWhile(key => result1))

Update: on a side note, apparently that whole chunk of code can be converted to the following statement, which causes no modified closure warnings:

return keys.Aggregate(
    true,
    (current, key) => current && ContainsKey(key)
);
+3  A: 

When you modify the result variable, the closure (the use of the variable inside the lambda expression) will pick up the change.

This frequently comes as an unexpected surprise to programmers who don't fully understand closures, so Resharper has a warning for it.

By making a separate result1 variable which is only used in the lambda expression, it will ignore any subsequent changes to the original result variable.

In your code, you're relying on the closure picking up the changes to the original variable so that it will know when to stop.

By the way, the simplest way to write your function without LINQ is like this:

foreach (string key in keys) {
    if (ContainsKey(key))
        return true;   
}
return false;

Using LINQ, you can simply call Any():

return keys.Any<string>(ContainsKey);
SLaks
So the line where I give `result` a different value is what causes the warning, since `result` is also used in the lambda in `TakeWhile`?
Sarah Vessels
Exactly​​​​​​​.
SLaks
I think your `foreach` replacement is wrong: the method should return true iff all `keys` are in the dictionary (as decided individually by `ContainsKey`). However, `return keys.All(ContainsKey);` works!
Sarah Vessels
I misunderstood your code.
SLaks
A: 

Well, the warning is because result could be changed before the closure is executed (variables are taken at execution, not defition). In your case, you are actually taking advantage of that fact. If you use the resharper reccomodation, it will actually break your code, as key => result1 always returns true.

Femaref
+5  A: 

In that particular code nothing, take the following as an example:

int myVal = 2;

var results = myDatabase.Table.Where(record => record.Value == myVal);

myVal = 3;

foreach( var myResult in results )
{
    // TODO: stuff
}

It looks like result will return all records where the Value is 2 because that's what myVal was set to when you declared the query. However, due to deferred execution it will actually be all records where the Value is 3 because the query isn't executed until you iterate over it.

In your example the value isn't being modified, but Resharper is warning you that it could be and that deferred execution could cause you problems.

Dave
+2  A: 

I'm not sure if ReSharper will give the exact same warning for this, but the following illustrates a similar situation. The iterator for a loop is used in a LINQ clause, but the clause isn't actually evaluated until after the loop has finished, by which time the iterator variable has changed. The following is a contrived example that looks like it should print all odd numbers from 1 to 100, but actually prints all numbers from 1 to 99.

var notEven = Enumerable.Range(1,100);
foreach (int i in Enumerable.Range(1, 50))
{
    notEven = notEven.Where(s => s != i * 2);
}

Console.WriteLine(notEven.Count());
Console.WriteLine(string.Join(", ", notEven.Select(s => s.ToString()).ToArray()));

This can be quite an easy mistake to make. I've heard people say that if you truly understand closures/functional programming/etc. you should never make this mistake. I've also seen professional developers who certainly do have a good grasp of those concepts make this exact mistake.

Daniel Plaisted
+3  A: 

See

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

and

http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx

for an extensive discussion of this issue and what we might do in hypothetical future versions of C# to mitigate it.

Eric Lippert