LINQ to Objects 的奇怪行为

发布于 2024-12-09 11:42:02 字数 807 浏览 1 评论 0原文

我在代码中看到了奇怪的行为,这里有一个使用苹果和人的类似示例,但代码基本相同:

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以及所有属性的设置器。

为什么会发生这种情况?

提前致谢!

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.

Why does this happen?

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

碍人泪离人颜 2024-12-16 11:42:02

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

使用 ToList 方法将其设为集合:

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

The selectedApples is not a collection that contains objects, it's an expression that creates a collection on the fly. 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.

Make it a collection using the ToList method:

var selectableApples = apples.Select(a => new SelectableApple { SelectedByPerson = null, Apple = a }).ToList();
浅黛梨妆こ 2024-12-16 11:42:02

这里有几个问题。首先,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???
}

看看上面的代码,我们现在可以看到内部循环只是对原始 select 的修改。让我们看一下:

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 中提供的延迟加载和循环优化。

There are a couple of issues here. The first being that the Where statement does not produce a list of objects. 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.

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. 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. So lets get rid of the outer foreach loop and condense that down to 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. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文