List<T> vs IEnumerable<T> in foreach

本文关键字:gt lt foreach in vs List IEnumerable | 更新日期: 2023-09-27 18:09:54

我试图在foreach循环中使用OrderBy对一些列表进行排序,但由于某种原因,它们没有在循环外维护它们的排序顺序。下面是一些简化的代码和注释,以突出显示正在发生的事情:

public class Parent
{
    // Other properties...
    public IList<Child> Children { get; set; }
}
public IEnumerable<Parent> DoStuff()
{
    var result = DoOtherStuff() // Returns IEnumerable<Parent>
        .OrderByDescending(SomePredicate) 
        .ThenBy(AnotherPredicate); // This sorting works as expected in the return value.
    foreach (Parent parent in result)
    {
        parent.Children = parent.Children.OrderBy(YetAnotherPredicate).ToList();
        // When I look at parent.Children here in the debugger, it's sorted properly.
    }
    return result;
    // When I look at the return value, the Children are not sorted.
}

然而,当我这样分配result时:

var result = DoOtherStuff()
    .OrderByDescending(SomePredicate)
    .ThenBy(AnotherPredicate)
    .ToList(); // <-- Added ToList here

则返回值将子元素在每个父元素中正确排序。

在foreach循环中List<T> vs IEnumerable<T>的行为是什么?

似乎有一些不同,因为将结果转换为List修复了foreach循环中排序的问题。感觉就像第一个代码片段创建了一个迭代器,当您使用foreach进行迭代时,它会复制每个元素(因此我的更改应用于副本而不是result中的原始对象),而使用ToList()则使枚举器给出一个指针。

这是怎么回事?

List<T> vs IEnumerable<T> in foreach

区别在于一个表达式可以生成一组Parent对象,而另一个是Parent对象列表。

每次使用表达式时,它将使用DoOtherStuff的原始结果,然后对它们进行排序。在您的例子中,这意味着它将创建一组新的Parent对象(因为它们显然不会保留以前使用的子对象)。

这意味着当您循环遍历对象并对子对象进行排序时,这些对象将被丢弃。当您再次使用表达式返回结果时,它将创建一组新的对象,其中的子对象自然按照原始顺序排列。

加到Guffa的答案中的示例代码:

class Parent { public List<string> Children; }

"Parent"的可枚举对象,每次被迭代时都会创建新的"Parent"对象:

var result = Enumerable.Range(0, 10)
      .Select(_ => new Parent { Children = new List<sting>{"b", "a"});

现在,foreach的第一次迭代将创建10个"父"对象(每次循环迭代一个),并在每次迭代结束时立即丢弃:

foreach (Parent parent in result)
{
    // sorts children of just created parent object
    parent.Children = parent.Children.OrderBy(YetAnotherPredicate).ToList();
    // parent is no longer referenced by anything - discarded and eligible for GC
}

当你再次查看result时,它将被重新迭代,并且每次你查看它时都会创建一组新的"父"对象,因此"Children"没有排序。

请注意,根据DoOtherStuff() // Returns IEnumerable<Parent>的实现方式,结果可能不同。例如,DoOtherStuff()可以从某些缓存集合中返回现有项的集合:

 List<Parent> allMyParents = ...; 
 IEnumerable<Parent> DoOtherStuff()
 {
      return allMyParents.Take(7);
 }

现在result的每次迭代都会给你新的集合,但是集合中的每个项目将只是allMyParents列表中的一个项目-所以修改"Children"属性会改变allMyParents中的实例,并且更改将保持不变。

评论

ToList(IEnumerable)方法强制立即执行查询求值,并返回包含查询的List结果。您可以将此方法附加到查询中,以获得查询结果的缓存副本。

从https://msdn.microsoft.com/en-us/library/bb342261 (v = vs.110) . aspx

如果省略了ToList(),查询将不会被求值…您的调试器可能会为您这样做,但这只是我的一个大胆的猜测。