为什么我必须创建 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();
        }
    }
}

最佳答案

每次迭代 people 时,它​​都会再次执行 GetPeople() 中的代码 - 创建 Person 的新实例。调用 GetPeople 时,GetPeople() 中的代码不会运行;它仅在您调用某些内容时才开始运行:

var iterator = people.GetEnumerator();
iterator.MoveNext();

...这就是 foreach 循环所做的。

如果您调用 ToList() ,则意味着您只执行 GetPeople() 中的代码一次,并在迭代序列时存储返回的引用。在这一点上,每次迭代 List<Person> 时,您都会迭代对相同对象的引用,因此您在一个循环中所做的任何修改都将在另一个循环中看到。

如果将日志记录(或断点)放在 GetPeople() 中,您可能会发现更容易理解发生了什么。我有一个 article 进入实现细节,这也可能使事情更清楚。

关于c# - 为什么我必须创建 `IEnumerable<T>` 的具体实现才能修改其成员?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8325422/

10-11 02:02