简体   繁体   English

LINQ对对象的奇怪行为

[英]Strange behavior with LINQ to Objects

I'm seeing a strange behavior in my code, here's an analogous example using apples and persons, but the code is basically the same: 我在代码中看到一个奇怪的行为,这是一个使用苹果和人的类似示例,但是代码基本相同:

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???
}

The SelectableApple class is just a plain C# class without logic, and public getters and setters for all the properties. SelectableApple类只是一个没有逻辑的普通C#类,并且是所有属性的公共获取器和设置器。

Why does this happen? 为什么会这样?

Thanks in advance! 提前致谢!

The selectedApples is not a collection that contains objects, it's an expression that creates a collection on the fly. selectedApples不是包含对象的集合,它是一个动态创建集合的表达式。 That means that the changes that you do to the objects are discarded, and when you loop selectedApples again it will be recreated from scratch. 这意味着您对对象所做的更改将被丢弃,并且当您再次循环执行selectedApples时,将从头开始重新创建。

Make it a collection using the ToList method: 使用ToList方法使其成为一个集合:

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

There are a couple of issues here. 这里有几个问题。 The first being that the Where statement does not produce a list of objects. 第一个是Where语句不产生对象列表。 It is an expression statement. 这是一个表达式语句。

Expression statements evaluate on the fly so changes to the objects produced are discarded each time you run the statement. 表达式语句是动态评估的,因此每次运行该语句时,都会丢弃对所生成对象的更改。 Believe it or not this is a desirable result. 信不信由你,这是一个理想的结果。 This allows for you to handle complex nested for statements in a way that is more efficient and elegant. 这使您能够以更高效,更优雅的方式处理复杂的嵌套的for语句。

The best way to answer your question is by analyzing what you have written and rework some of the code to show you a better way. 回答问题的最佳方法是分析所写的内容并重新编写一些代码,以向您展示一种更好的方法。

In your code: 在您的代码中:

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???
}

So if we rework this code so that the if statement is out side of the inner loop. 因此,如果我们重新编写此代码,则if语句不在内部循环中。 Your code will do the same logical thing. 您的代码将执行相同的逻辑操作。 Mind you this does not yet fix the problem but takes you one step closer. 请注意,这还不能解决问题,但是请您更进一步。 Here is how the code will look: 代码如下所示:

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???
}

Now we have started to group things so that a simpler answer can be seen. 现在我们开始对事物进行分组,以便可以看到一个更简单的答案。 Since the if statement means that only the first person to satisfy the condition will be the person who gets all the apples. 因为if语句意味着只有第一个满足条件的人才能得到所有苹果。 So lets get rid of the outer foreach loop and condense that down to LINQ. 因此,让我们摆脱外部的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???
}

Looking at the above code, we can now see that the inner loop is just a modification on the original select. 查看上面的代码,我们现在可以看到内部循环只是对原始select的修改。 So lets look at that: 因此,让我们看一下:

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.
}

Now your code will run as desired, and you can take advantage of the lazy loading and the looping optimization provided within LINQ. 现在,您的代码将按需运行,并且您可以利用LINQ中提供的延迟加载和循环优化功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM