为啥有时 IEnumerable<T> 中的元素是可变的,而有时它们是不可变的? [复制]

Posted

技术标签:

【中文标题】为啥有时 IEnumerable<T> 中的元素是可变的,而有时它们是不可变的? [复制]【英文标题】:Why sometime elements in an IEnumerable<T> are mutable and sometime they are immutable? [duplicate]为什么有时 IEnumerable<T> 中的元素是可变的,而有时它们是不可变的? [复制] 【发布时间】:2020-11-25 14:23:41 【问题描述】:

假设我有一个非常简单的引用类型,称为Person

class Person

    public string Name  get; set; 
    public int Age  get; set; 

我使用 LINQ 创建一个 IEnumerable 调用 peopleEnum,代码如下。

var ages = new int[]  1, 2, 3, 4, 5 ;

var peopleEnum = ages.Where(a => a > 1)
    .Select(a => new Person
    
        Name = a.ToString(),
        Age = a
    );

我的理解是因为Person是引用类型,所以peopleEnum里面的元素应该是可变的。但我发现他们不是。

var person = peopleEnum.Single(p => p.Age == 4);
person.Name = "Four";

foreach (var p in peopleEnum)

    Console.WriteLine($"p.Name, p.Age");

预期输出。

2, 2
3, 3
Four, 4
5, 5

实际输出。

2, 2
3, 3
4, 4
5, 5

但是,如果我以不同的方式创建 IEnumerable&lt;Person&gt;

var people = new Person[]

    new Person Name = "1", Age = 1 ,
    new Person Name = "2", Age = 2 ,
    new Person Name = "3", Age = 3 ,
    new Person Name = "4", Age = 4 ,
    new Person Name = "5", Age = 5 ,
;

var peopleEnum2 = people.Where(p => p.Age > 1);

那么peopleEnum2 中的元素将是可变的。

var person = peopleEnum2.Single(p => p.Age == 4);
person.Name = "Four";

foreach (var p in peopleEnum2)

    Console.WriteLine($"p.Name, p.Age");

输出

2, 2
3, 3
Four, 4
5, 5

哪些规则确定IEnumerable&lt;T&gt; 中的元素是否可变?

【问题讨论】:

在您的示例中,您枚举了 IEnumerable&lt;T&gt; 两次。当IEnumerable&lt;T&gt; 是一个具体的列表或数组时,多次枚举每次都会产生相同的元素。在您的第一个示例中,每次枚举时都会“动态”和“按需”创建元素,这就是为什么每次都会获得不同的元素。 当您执行Single 时,它会运行创建人员对象的代码。然后,当您运行 foreach 时,它会再次运行该代码,创建 4 个新对象。这称为延迟执行。 【参考方案1】:

有区别 - 这个IEnumerable 基于ages 数组,每次枚举peopleEnum 时都会执行。这意味着每次将枚举放入 foreach 语句时都会获得新的引用。

var ages = new int[]  1, 2, 3, 4, 5 ;

var peopleEnum = ages.Where(a => a > 1)
    .Select(a => new Person
    
        Name = a.ToString(),
        Age = a
    );

为了使元素在第一种情况下“可变”,您必须将结果存储到某个静态结构中。例如,使用.ToList()。这有助于确保您只执行一次 Select 语句并保留由它生成的引用

var peopleEnum = ages.Where(a => a > 1)
        .Select(a => new Person
        
            Name = a.ToString(),
            Age = a
        ).ToList();

【讨论】:

【参考方案2】:

这是因为每次迭代peopleEnum枚举,

var peopleEnum = ages.Where(a => a > 1)
    .Select(a => new Person
    
        Name = a.ToString(),
        Age = a
    );

创建了一个新的Person。使用数组时,它们被创建一次(并放入数组中)。 因此,在您的情况下,您对其进行迭代并更改人员。当你再次迭代它时,就会创建一个新人。

【讨论】:

以上是关于为啥有时 IEnumerable<T> 中的元素是可变的,而有时它们是不可变的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Enumerable 不实现 IEnumerable?

将 IEnumerable<Ienumerable<T>> 转换为 Dictionary<key,IEnumerable<T>>

为啥 HashSet<T> 没有实现 ICollection?

为啥我不能将 List<List<Foo>> 传递给 IEnumerable<IEnumerable<Foo>>

如何将两个 IEnumerable<T> 连接成一个新的 IEnumerable<T>?

无法将类型“IEnumerable<T>”隐式转换为“ActionResult<IEnumerable<T>>”