为什么 IEnumerable 变量的项在指向 IEnumerable 时无法更新,但在指向已实现的 collection 时有效

Why items of an IEnumerable variable cannot be updated when it is pointing to an IEnumerable but works when pointing to an implemented collection

我已经阅读了问题 Update object in IEnumerable<> not updating? and Updating an item property within IEnumerable but the property doesn't stay set? 中的答案,但我想 了解为什么 当我尝试更新 IEnumerable 中的项目时它不起作用。

我不明白的是:

当源 collection 指向 Ienumerable 时,我无法使用 .ElementAt() 方法更新 Ienumerable 的项目。

但是,当源 Ienumerable 指向列表时,相同的代码仍然有效。

1 谁能解释一下幕后发生的事情?

2 为什么 C# 编译器不会(或不能)对此出错?

3 另外,当我们需要更新时必须将 Ienumerables 转换为列表时,使用 Ienumerable 定义类型是否无效?

我确定我在这里遗漏了一些东西,如果有人能解释幕后发生的事情,我会很感激。

这是我的代码:

1 - Collection 未更新:

class Person
{
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] {1,2,3};

        var people = numbers.Select(n => new Person { Name = "Person " + n.ToString() });


        for (int i = 0; i < people.Count(); i++)
        {
            people.ElementAt(i).Name = "New Name";
        }

        people.ToList().ForEach(i => Console.WriteLine(i.Name));

        // OUTPUT: Person1, Person2, Person3
    }

}

2 - 当我将 .ToList() 添加到 collection 创建行时 Collection 得到更新(注意 Main 方法中的第 3 行)

class Person
{
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] {1,2,3};

        // Note the .ToList() at the end of this line.
        var people = numbers.Select(n => new Person { Name = "Person " + n.ToString() }).ToList();


        for (int i = 0; i < people.Count(); i++)
        {
            people.ElementAt(i).Name = "New Name";
        }

        people.ToList().ForEach(i => Console.WriteLine(i.Name));


        Console.ReadLine();
        // OUTPUT: New Name, New Name, New Name
    }

}

IEnumerable 仅公开一种方法:GetEnumerator(),可用于遍历列表。 ICollection 改为公开一个 Add() 方法,该方法可用于将项目添加到集合中。无法将项目添加到 ICollection,只能对其进行迭代。

"Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection."

https://msdn.microsoft.com/en-us/library/system.collections.ienumerator(v=vs.100).aspx

https://msdn.microsoft.com/en-us/en-en/library/system.collections.icollection(v=vs.110).aspx

https://msdn.microsoft.com/en-us/en-en/library/system.collections.ienumerable(v=vs.110).aspx

一如既往,您需要记住 LINQ 操作 return 查询 ,而不是执行这些查询的结果。

当您调用 select 时,returned 是一个 query,它知道如何将每个项目从源集合中投影到一个新项目中一个项目被要求的时间。每次迭代 IEnumerable 时,您都会执行一次新的查询,每次都会创建一个新的结果集(由所有新项目组成)。您可以,而且实际上 每次调用 ElementAt 时都会更新项目,这就是编译器不会阻止您的原因。除了设置一个值然后将其丢在地板上,永远不会再使用之外,您对那个项目什么都不做。当您执行该行时:

people.ElementAt(i).Name = "New Name";

您正在遍历 numbers,为每个数字创建一个新的 Person,直到您得到第 i 个值,然后它是 return 从 [=12= 编辑的],此时已设置名称,然后 无法再从您的代码访问该值,因为您尚未在任何地方保存返回的 Person。当您调用 ElementAt(0) 时,您正在从 0 创建一个人,设置名称,然后什么都不做。当您调用 ElementAt(1) 时,您正在创建两个人,return 创建第二个,设置其名称,然后忘记它。然后你调用 ElementAt(2),再创建 3 个人,return 第三个人,设置 它的 名字,然后忘记它。然后你调用 ToList,创建一个包含所有 3 个人的列表,并打印出这 3 个全新的人的值。

当您将查询具体化到一个集合中时,您可以多次访问该集合中的项目,因为您有您正在访问多个查询的结果次,并跟踪那些相同的 returned 结果,而不是 查询本身 您一遍又一遍地执行。