views:

110

answers:

4

Saw several similar questions here, but none of them seemed to quite be my issue...

I understand (or thought I understood) the concept of closure, and understand what would cause Resharper to complain about access to a modified closure, but in the below code I don't understand how I'm breaching closure.

Because primaryApps is declared within the context of the for loop, primary isn't going to change while I'm processing primaryApps. If I had declared primaryApps outside the for loop, then absolutely, I have closure issues. But why in the code below?

var primaries = (from row in openRequestsDataSet.AppPrimaries
                 select row.User).Distinct();

    foreach (string primary in primaries) {

        // Complains because 'primary' is accessing a modified closure
        var primaryApps = openRequestsDataSet.AppPrimaries.Select(x => x.User == primary);

Is Resharper just not smart enough to figure out it's not an issue, or is there a reason closure is an issue here that I'm not seeing?

+1  A: 

As far as I know Resharper generates the warning every time you access the foreach variable even if it does not really cause closure.

Giorgi
Only if you use the variable in a lambda or other delayed-execution context
James B
+9  A: 

The problem is in the following statement

Because primaryApps is declared within the context of the for loop, primary isn't going to change while I'm processing primaryApps.

There is simply no way for Resharper to 100% verify this. The lambda which references the closure here is passed to function outside the context of this loop: The AppPrimaries.Select method. This function could itself store the resulting delegate expression, execute it later and run straight into the capture of the iteration variable issue.

Properly detecting whether or not this is possible is quite an undertaking and frankly not worth the effort. Instead ReSharper is taking the safe route and warning about the potentially dangerous capture of the iteration variable.

JaredPar
To add to that, Visual Basic's compiler gives a warning in the exact same situation. The C# team has considered adopting the VB/Resharper warning, but deferred the decision until the next version.
Jonathan Allen
@Jonathan shameless plug, here's an article explaining the VB.Net message in depth http://blogs.msdn.com/b/jaredpar/archive/2007/07/26/closures-in-vb-part-5-looping.aspx
JaredPar
So from what you and Eric are saying, the issue is that `AppPrimaries.Select()`, being static, could store the lambda in a static variable, and another call to, say, `AppPrimaries.CalculateSelectLambda()` would result in execution that was deferred until the original loop was long finished. At which point, `primary` would hold whatever value it was set to the last time through the loop.
James B
@James correct. Although this would apply equally if `Select` were an instance vs. an extension method.
JaredPar
A: 

Yes it's just warning, Look : http://devnet.jetbrains.net/thread/273042

Cihan Yakar
Yeah, I know it's just a warning... just needed to know if I could safely ignore it : ) Which the answer is apparently 'no'
James B
+8  A: 

Because primaryApps is declared within the context of the for loop, primary isn't going to change while I'm processing primaryApps. If I had declared primaryApps outside the for loop, then absolutely, I have closure issues. But why in the code below?

Jared is right; to demonstrate why your conclusion does not follow logically from your premise, let's make a program that declares primaryApps within the context for the for loop, and still suffers from a captured loop variable problem. Easy enough to do that.

static class Extensions
{
    public IEnumerable<int> Select(this IEnumerable<int> items, Func<int, bool> selector)
    {
        C.list.Add(selector);
        return System.Enumerable.Select(items, selector);
    }
}

class C
{
    public static List<Func<int, bool>> list = new List<Func<int, bool>>();
    public static void M()
    { 
        int[] primaries = { 10, 20, 30}; 
        int[] secondaries = { 11, 21, 30}; 

        foreach (int primary in primaries) 
        {
            var primaryApps = secondaries.Select(x => x == primary);
            // do something with primaryApps
        }
        C.N();
    }
    public static void N()
    {
        Console.WriteLine(C.list[0](10)); // true or false?
    }
}

Where "primaryApps" is declared is completely irrelevant. The only thing that is relevant is that the closure might survive the loop, and therefore someone might invoke it later, incorrectly expecting that the variable captured in the closure was captured by value.

Resharper has no way to know that a particular implementation of Select does not stash away the selector for later; in fact, that is exactly what all of them do. How is Resharper supposed to know that they happen to stash it away in a place that won't be accessible later?

Eric Lippert
I think you mean "Resharper", not "Reflector" ;)
Thomas Levesque
I would emphasize that the warning has to do with `primary` being declared outside the loop, rather than `primaryApps` being declared inside the loop.
Gabe