Akli
Akli

Reputation: 1539

Linq strange behviour with dictionaries

I have come across through a strange behaviour of Linq : with two linq expressions that might seem identical I have different results! If I loop once I get the same result, but above it finds nothing.

Here is the code:

        Dictionary<String, String> mainDico = new Dictionary<String, String>();
        mainDico.Add("key1", "value1");
        mainDico.Add("key2", "value2");

        List<Dictionary<String, String>> ls = new List<Dictionary<String, String>>();

        Dictionary<String, String> fistDico = new Dictionary<String, String>();
        fistDico.Add("key1", "value1");
        fistDico.Add("key2", "value2");

        Dictionary<String, String> secondDico = new Dictionary<String, String>();
        secondDico.Add("key1", "other");
        secondDico.Add("key2", "other");

        ls.Add(fistDico);
        ls.Add(secondDico);


        IEnumerable<Dictionary<String, String>> failQuery = from p in ls
                                                            select p;

        IEnumerable<Dictionary<String, String>> successfulQuery = from p in ls
                                                            select p;

        String[] items = new String[] { "key1","key2" }; // with one element it works

        foreach (var item in items)
        {
            String temp = mainDico[item];
            failQuery = failQuery.Where(p => p[item] == temp);
            successfulQuery = successfulQuery.Where(p => p[item] == mainDico[item]);
        }

        Console.WriteLine(successfulQuery.SingleOrDefault() != null);//True
        Console.WriteLine(failQuery.SingleOrDefault() != null);//False

Upvotes: 4

Views: 112

Answers (1)

Servy
Servy

Reputation: 203821

The problem is that you're closing over the loop variable.

The problematic section of code is right here:

foreach (var item in items)
{
    String temp = mainDico[item];
    failQuery = failQuery.Where(p => p[item] == temp);
    successfulQuery = successfulQuery.Where(p => p[item] == mainDico[item]);
}

You're creating a lambda that closes over item in the second case (and also the first case; you should really fix that), and you're not evaluating the query until after the end of the foreach loop; that means that item will always be the last item in the foreach loop, not the current item. This can be easily resolved by creating a new local variable, which is what you do in the first case, which is why that works.

Here is a related link that discuss the matter in more detail. (You can find lots more by searching over "close over loop variable".

Note that this was changed in C# 5.0 since it's a frequent cause of confusion and bugs. (This is likely why certain people couldn't reproduce the problem.)

It's also worth noting that this has nothing to do with the dictionary. In your query item is effectively always the last item in the foreach, rather than the current, which is why it fails. Anything that you did with item that relied on it being the current value wouldn't do what you wanted.

Upvotes: 5

Related Questions