2011-10-12 130 views
0

我看到在我的代码一个奇怪的现象,这里是一个使用苹果和人类似的例子,但代码是基本相同的:奇怪的行为与LINQ到对象

List<Apple> apples = ... 
var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }); 

foreach (Person person in persons) 
{ 
    foreach (var unselectedApple in selectableApples.Where(aa => aa.SelectedByPerson == null)) 
    { 
     if (/*the person satisfies some conditions*/) 
     { 
      // This gets executed like 100 times: 
      unselectedApple.SelectedByPerson = person; 
     } 
    } 
} 

foreach (var selectedApple in selectableApples.Where(aa => aa.SelectedByPerson != null)) 
{ 
    Unreachable code - the collection is empty... WTF??? 
} 

SelectableApple类只是一个没有逻辑的普通C#类,以及所有属性的公共getter和setter。

为什么会发生这种情况?

在此先感谢!

+2

混合投影和突变?这条100倍线只能在事物的阴影下运作,而不是事物本身。 – bzlm

+0

你确定SelectableApple是一个“类”,而不是“结构”? –

回答

6

selectedApples不是包含对象的集合,它是一个即时创建集合的表达式。这意味着您对对象所做的更改将被丢弃,并且当您再次循环selectedApples时,它将从头开始重新创建。

让它使用ToList方法的集合:

var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }).ToList(); 
+1

是的,打败了我:-)也证实了。 –

+0

令人难以置信。谢谢你的帮助! – alf

1

有几个问题在这里。首先是Where语句不会生成对象列表。这是一个表达声明。

表达式语句即时评估,因此每次运行语句时都会丢弃对生成对象的更改。相信与否这是一个理想的结果。这允许您以更高效和优雅的方式处理复杂的嵌套for语句。

回答你的问题的最好方法是分析你写的东西,并重新编写一些代码给你展示一个更好的方法。

在您的代码:

List<Apple> apples = ... 
var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }); 

foreach (Person person in persons) 
{ 
    foreach (var unselectedApple in selectableApples.Where(aa => aa.SelectedByPerson == null)) 
    { 
     // This will ideally give all apples to the first person who 
     // meets the conditions. As such this if condition can be moved 
     // out side of the above the foreach loop. 
     if (/*the person satisfies some conditions*/) 
     { 
      // This gets executed like 100 times: 
      unselectedApple.SelectedByPerson = person; 
     } 
    } 
} 

foreach (var selectedApple in selectableApples.Where(aa => aa.SelectedByPerson != null)) 
{ 
    Unreachable code - the collection is empty... WTF??? 
} 

因此,如果我们返工此代码,以便在内部循环的if语句不在身边。你的代码将做同样的逻辑事情。请注意,这还没有解决问题,但让你更靠近一步。下面是代码的外观:

List<Apple> apples = ... 
var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }); 

foreach (Person person in persons) 
{ 
    // Now we can see that since this will all apples to the first person 
    // who satisfies the below conditions we are still doing to much. And it 
    // still does not work. 
    if (/*the person satisfies some conditions*/) 
    { 
     foreach (var unselectedApple in selectableApples.Where(aa => aa.SelectedByPerson == null)) 
     { 
      // This gets executed like 100 times: 
      unselectedApple.SelectedByPerson = person; 
     } 
    } 
} 

foreach (var selectedApple in selectableApples.Where(aa => aa.SelectedByPerson != null)) 
{ 
    Unreachable code - the collection is empty... WTF??? 
} 

现在我们已经开始组的东西,这样一个简单的答案就可以看出。由于if语句意味着只有满足条件的第一个人才是获得所有苹果的人。所以让我们摆脱外部的foreach循环,并将其凝聚到LINQ。

List<Apple> apples = ... 
var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }); 
var selectedPerson = persons.Where(p => /*the person satisfies some conditions*/).First() 

if(selectedPerson != null) 
{ 
    foreach (var unselectedApple in selectableApples.Where(aa => aa.SelectedByPerson == null)) 
    { 
     // This gets executed like 100 times: 
     unselectedApple.SelectedByPerson = person; 
    } 
} 

foreach (var selectedApple in selectableApples.Where(aa => aa.SelectedByPerson != null)) 
{ 
    Unreachable code - the collection is empty... WTF??? 
} 

看着上面的代码,我们现在可以看到的是,内环仅仅是对原选择的修改。所以让我们看看:

List<Apple> apples = ... 
var selectedPerson = persons.Where(p => /*the person satisfies some conditions*/).First() 
var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = selectedPerson, Apple = a }); 


foreach (var selectedApple in selectableApples.Where(aa => aa.SelectedByPerson != null)) 
{ 
    // This should now run provided that some person passes the condition. 
} 

现在您的代码将按需要运行,并且您可以利用LINQ中提供的延迟加载和循环优化。