为什么我必须创建IEnumerable<T>
的具体实现才能在foreach
循环中修改其成员?
This blog post (Exhibit 1)解释了这种行为,但我无法完全理解它。
我在这里有一个非常简单的代码片段来重现这个问题(C#4.0 / .NET 4.0)。
class Person
{
public int Age { get; set; }
public Person()
{
}
}
class Program
{
static void Main(string[] args)
{
//calling .ToList() on GetPeople() below will fix the issue
var people = GetPeople();
foreach (var item in people)
{
item.Age = DateTime.Now.Second;
}
foreach (var item in people)
{
Console.WriteLine("Age is {0}", item.Age);
}
Console.Read();
}
public static IEnumerable<Person> GetPeople()
{
int i = 0;
while (i < 3)
{
i++;
yield return new Person();
}
}
}
答案 0 :(得分:9)
每次迭代people
时,它都会再次执行GetPeople()
中的代码 - 创建Person
的新实例。当您致电GetPeople
时,GetPeople()
中的代码会不运行;它只在你打电话时开始运行:
var iterator = people.GetEnumerator();
iterator.MoveNext();
...这是foreach
循环的作用。
如果您致电ToList()
,这意味着您只需执行GetPeople()
一次中的代码,并存储迭代序列时返回的引用。此时,每次对List<Person>
进行迭代时,您将迭代对相同对象的引用,因此您在一个循环中进行的任何修改都将在另一个循环中看到。
如果将日志记录(或断点)放在GetPeople()
中,您可能会发现更容易理解发生了什么。我有一个article进入实现细节,可以使事情更清晰。
答案 1 :(得分:4)
GetPeople动态创建People
。您的第一个foreach (var item in people)
创建了Peoples
,您设置了年龄,之后,它们不会存储在任何地方。
您的第二个foreach (var item in people)
会创建一组新的people
,它们是新的/不同的对象。