在foreach循环中构建的Linq查询始终从最后一次迭代中获取参数值

时间:2008-11-17 13:51:21

标签: c# linq

我有一个包含多个关键字的列表。 我预先通过他们像这样构建我的linq查询(煮沸以消除代码噪音):

List<string> keys = FillKeys()
foreach (string key in keys){
    q = q.Where(c => c.Company.Name.Contains(key));
}

当我现在让我的密钥包含2个单独分别返回结果的键时,但不能一起出现(q中的每个项目都是“xyz”或“123”,从不“123”和“xyz”),我仍然得到结果。然后结果集与它到达的最后一个字符串相同。

我查看了linq查询,看起来它创建了正确的sql,但它将@ p1 AND @ p2替换为相同的(最后一个itterated)值。

我做错了什么?

3 个答案:

答案 0 :(得分:33)

您在lambda表达式中重复使用相同的变量(key)。

有关详细信息,请参阅我在anonymous methods上的文章,并且还有一些相关的SO问题:

简单的解决方法是先复制变量:

List<string> keys = FillKeys()
foreach (string key in keys){
    string copy = key;
    q = q.Where(c => c.Company.Name.Contains(copy));
}

答案 1 :(得分:14)

可能是捕获的变量问题;尝试添加:

List<string> keys = FillKeys()
foreach (string key in keys){
    string tmp = key;
    q = q.Where(c => c.Company.Name.Contains(tmp));
}

答案 2 :(得分:1)

它已在C#5.0中修复,上面的例子在C#5.0中有效,但在早期版本的C#中失败了。

但要小心,它不涉及for循环

  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);
        }