LINQ对对象的奇怪行为
本文关键字:对象 LINQ | 更新日期: 2023-09-27 18:11:09
我在我的代码中看到一个奇怪的行为,这里有一个类似的例子,使用苹果和人,但代码基本上是相同的:
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。
为什么会发生这种情况?
提前感谢!
selectedApples
不是包含对象的集合,它是动态创建集合的表达式。这意味着您对对象所做的更改将被丢弃,并且当您再次循环selectedApples
时,它将从头开始重新创建。
使用ToList
方法使它成为一个集合:
var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }).ToList();
这里有几个问题。第一个是Where语句不生成对象列表。它是一个表达式语句。
Expression语句动态求值,因此每次运行该语句时都会丢弃对所生成对象的更改。信不信由你,这是一个理想的结果。这允许你以一种更高效、更优雅的方式处理复杂的嵌套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中提供的延迟加载和循环优化。