为什么有时 IEnumerable<T> 中的元素是可变的,有时它们是不可变的?
Why sometime elements in an IEnumerable<T> are mutable and sometime they are immutable?
假设我有一个非常简单的引用类型,叫做 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<Person>
。
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<T>
中的元素是否可变的规则是什么?
这是因为每次迭代peopleEnum
枚举,
var peopleEnum = ages.Where(a => a > 1)
.Select(a => new Person
{
Name = a.ToString(),
Age = a
});
创建了一个新的 Person
。在使用数组时,它们只创建一次(并放入数组中)。
所以在你的情况下你迭代它并改变 Person.当你再次迭代它时,就会创建一个新人。
有区别——这个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();
假设我有一个非常简单的引用类型,叫做 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<Person>
。
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<T>
中的元素是否可变的规则是什么?
这是因为每次迭代peopleEnum
枚举,
var peopleEnum = ages.Where(a => a > 1)
.Select(a => new Person
{
Name = a.ToString(),
Age = a
});
创建了一个新的 Person
。在使用数组时,它们只创建一次(并放入数组中)。
所以在你的情况下你迭代它并改变 Person.当你再次迭代它时,就会创建一个新人。
有区别——这个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();