Linq query built in foreach loop always takes parameter value from last iteration

后端 未结 3 1233
说谎
说谎 2020-11-27 20:30

I have a List containing several keywords. I foreach through them building my linq query with them like so (boiled down to remove the code noise):

List

        
相关标签:
3条回答
  • 2020-11-27 20:44

    it’s been fixed in C# 5.0, and the example above in C# 5.0 works but fails in earlier versions of C#.

    But be careful, it does not concern a for loop

      static void Main()
            {
                IEnumerable<char> query = "aaa bbb ccc";
                string lettersToRemove = "ab";
    
                Console.WriteLine("\nOK with foreach:");
                foreach (var item in lettersToRemove)
                {
                    query = query.Where(c => c != item);   
                }   
                foreach (char c in query) Console.Write(c);
    
                //OK:
                Console.WriteLine("\nOK with foreach and local temp variable:");
                query = "aaa bbb ccc";
    
                foreach (var item in lettersToRemove)
                {
                    var tmp = item;
                    query = query.Where(c => c != tmp);
                }            
                foreach (char c in query) Console.Write(c);
    
    
                /*
                 An IndexOutOfRangeException is thrown because:
                 firstly compiler iterates the for loop treating i as an outsite declared variable  
                 when the query is finnaly invoked the same variable of i is captured (lettersToRemove[i] equals 3) which generates IndexOutOfRangeException
    
                 The following program writes aaa ccc instead of writing ccc:
                 Each iteration gets the same variable="C", i (last one frome abc). 
                 */
    
                //Console.WriteLine("\nNOK with for loop and without temp variable:");
                //query = "aaa bbb ccc";
    
                //for (int i = 0; i <  lettersToRemove.Length; i++)
                //{
                //    query = query.Where(c => c != lettersToRemove[i]);
                //}
                //foreach (char c in query) Console.Write(c);
    
                /*
                 OK
                 The solution is to assign the iteration variable to a local variable scoped inside the loop
                 This causes the closure to capture a different variable on each iteration.
                */
                Console.WriteLine("\nOK with for loop and with temp variable:");
                query = "aaa bbb ccc";
    
                for (int i = 0; i < lettersToRemove.Length; i++)
                {
                    var tmp = lettersToRemove[i];
                    query = query.Where(c => c != tmp);
                }
                foreach (char c in query) Console.Write(c);
            } 
    
    0 讨论(0)
  • 2020-11-27 20:51

    You're reusing the same variable (key) in your lambda expression.

    See my article on anonymous methods for more details, and there are a number of related SO questions too:

    • LINQ to SQL bug (or very strange feature)...
    • Local variables with delegates
    • C# captured variable in a loop
    • C# gotcha answer
    • Building a LINQ query programmatically without local variables tricking me

    The simple fix is to copy the variable first:

    List<string> keys = FillKeys()
    foreach (string key in keys){
        string copy = key;
        q = q.Where(c => c.Company.Name.Contains(copy));
    }
    
    0 讨论(0)
  • Possibly a captured variable issue; try adding:

    List<string> keys = FillKeys()
    foreach (string key in keys){
        string tmp = key;
        q = q.Where(c => c.Company.Name.Contains(tmp));
    }
    
    0 讨论(0)
提交回复
热议问题