在foreach中列出<t> vs IEnumerable <t>

时间:2015-08-25 23:32:00

标签: c# linq foreach

我试图在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>IEnumerable<T>的行为是什么?

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

这里发生了什么?

2 个答案:

答案 0 :(得分:7)

不同之处在于,一个是可以产生一组Parent个对象的表达式,另一个是Parent个对象的列表。

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

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

答案 1 :(得分:2)

可能会添加到Guffa's answer的示例代码:

class Parent { public List<string> Children; }

可以使用&#34; Parent&#34;,创建新的&#34; Parent&#34;对象每次迭代:

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

现在第一次使用foreach进行迭代将会有10&#34; Parent&#34;创建的对象(循环的每次迭代一次)并在每次迭代结束时立即丢弃:

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时,它将被重新迭代并且新的一组&#34; Parent&#34;每次创建的对象你都会看到它,因此&#34;儿童&#34;没有排序。

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

 List<Parent> allMyParents = ...; 

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

现在result的每次迭代都会为您提供新的集合,但集合中的每个项目都只是allMyParents列表中的项目 - 所以修改&#34;儿童&#34;属性会改变allMyParents中的实例,并且更改会坚持。