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()
则使枚举器给出一个指针。
这是怎么回事?
区别在于一个表达式可以生成一组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(),查询将不会被求值…您的调试器可能会为您这样做,但这只是我的一个大胆的猜测。