我试图在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
时,枚举器会给出一个指针。
这里发生了什么?
答案 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
中的实例,并且更改会坚持。