Is LINQifying my code worth accessing a foreach va

2019-02-25 07:39发布

问题:

Reminiscent of the title of a bootleg live Rolling Stones recording of yesteryear, Resharper is sharper than I'll ever be; as I had it inspect my code, it told me this regarding closures:

1) "Loop:

        foreach (var item in PlatypiIds)
        {
            var query = db.Table<Locations>().Where(l => l.PlatypusId == item).
                Where(l=> l.SentTimeUTC >= EarliestToShow).
                Where(l=> l.SentTimeUTC <= LatestToShow).
                OrderBy(l => l.SentTimeUTC);

            if (query != null)
            {
                foreach (var q in query)
                {
                    listLocs.Add(q);
                }
            }
        }

...can be converted into LINQ expression:

listLocs.AddRange(from item in PlatypiIds select db.Table<Locations>().Where(l => l.PlatypusId == item).Where(l => l.SentTimeUTC >= EarliestToShow).Where(l => l.SentTimeUTC <= LatestToShow).OrderBy(l => l.SentTimeUTC) into query 
where query != null from q in query select q);"

...but then Resharper told me later regarding the "new and improved" code: "Access to foreach variable in closure. May have different behaviour when compiled with different versions of compiler"

So what are the possibilities of compiling with a different version of the compiler? I mean, I'm not going to go backwards, version-wise, from VS2012 to VS2010, for example...???

2) On these lines:

            if (db != null)
                db.Insert(new PlatypiRequested()

...of this code:

    using (var db = new SQLiteConnection(SQLitePath))
    {
        db.CreateTable<PlatypiRequested>();

        db.RunInTransaction(() =>
        {
            if (db != null)
                db.Insert(new PlatypiRequested()
                              {
                                  PlatypusId = PlatypusId,
                                  PlatypusName = PlatypusName,
                                  InvitationSentLocal = invitationSentLocal
                              });
        });
    }

...Resharper informs me, "Access to disposed closure"

What does that mean, and what should I do about it?

回答1:

You have two different issues here, one LINQ vs foreach, and the other is a different case.

Regarding the ReSharper informing you of "Access to foreach variable in closure..." when the code is LINQified - I just never take my chances, and leave it as a foreach loop. In most cases it is also more readable and maintainable, and really, shortening the code isn't that much of a big deal.

Regarding the second case - you'll need to lose the using statement, since the db object will be disposed too soon. You should close and dispose it in the "old school fashion" INSIDE the RunInTransaction lambda expression, at the end of it.



回答2:

There's a real difference which will show up in foreach loops, as well as in LINQ queries.

It has to do with the lifetime of the closure (scope) within which a variable is defined (in a foreach loop or in a LINQ expression). In some versions the variable is redefined in each iteration of the loop, and in other occasions its lifetime spans the whole execution of the loop, keeping the old value between iterations. And this can make a big difference in the results, depending on the code.

I can't explain it better than Eric Lippert (which worked for Microsoft for 16 years, developing compilers, including C# compiler):

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

I have really seen code that behaves in a different way, depending on the traget framework (and thus on the C# version). This must be taken into account.

Most times R# is right, as in this occasion.



回答3:

You could use the Linq ForEach to remove the open loop.

db.Table<Locations>().Where(l => l.PlatypusId == item).
Where(l=> l.SentTimeUTC >= EarliestToShow).
Where(l=> l.SentTimeUTC <= LatestToShow).
OrderBy(l => l.SentTimeUTC).ToList().
ForEach(q => listLocs.Add(q));