Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different LINQ Answer in VS 2010 and VS 2012

The following gives answer as 1 in VS 2010 and 2 in VS 2012. I personally think it should be 2. I am not sure what's going on here.

using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System;

namespace _335ExamPreparation
{
    public class Doubts
    {
        int[] nums = { 10, 11, 12, 13, 14, 15, 16 };
        int[] divisors = { 7, 10 };

        static void Main(string[] args)
        {
            Doubts d = new Doubts();
            d.func();
        }

        public void func()
        {
            var m = Enumerable.Empty<int>();
            foreach (int d in divisors)
            {
                m = m.Concat(nums.Where(s => (s % d == 0)));
            }

            int count = m.Distinct().Count();
            Console.WriteLine(count);
        }
    }
}

Thanks.

like image 597
Varun Sharma Avatar asked Nov 10 '12 23:11

Varun Sharma


1 Answers

What you're seeing is the result of two different applications of foreach. The behavior was changed in VS 2012. See this article.

The difference between the two involves the scope and lifetime of the d variable in the foreach loop. Before VS 2012, there was only one d variable, so what this means is that you are creating two copies of a closure (s => (s % d == 0))) that both reference the same d. After the loop finishes being evaluated, d is 10. When you execute the query by calling .Distinct().Count(), both closures will see a value of 10 for d. This is why the count is 1 on VS 2010.

VS 2012 generates a different variable for each iteration1, so each closure will see a different instance of the d variable, the one that corresponds to that particular iteration.

This is roughly the code that VS 2010 generates:

int d;
for (int _index = 0; _index < divisors.Length; ++_index) {
    d = divisors[_index];
    m = m.Concat(nums.Where(s => (s % d == 0)));
}

And this is roughly what VS 2012 generates:

for (int _index = 0; _index < divisors.Length; ++_index) {
    int d = divisors[_index];
    m = m.Concat(nums.Where(s => (s % d == 0)));
}

The difference between these two should be readily apparent.

If you want to get the same behavior no matter which VS version, then always copy your iteration variable:

foreach (int d in divisors)
{
    var copy = d;
    m = m.Concat(nums.Where(s => (s % copy == 0)));
}

1 Technically, only if the iteration variable is referenced in a closure. If it's not then there is no need to make a copy as this only affects closure semantics anyway.

like image 139
cdhowie Avatar answered Sep 23 '22 03:09

cdhowie